File Coverage

blib/lib/NoZone/Zone.pm
Criterion Covered Total %
statement 371 384 96.6
branch 115 170 67.6
condition 7 13 53.8
subroutine 37 37 100.0
pod 20 20 100.0
total 550 624 88.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #
3             # NoZone::Zone - record information for a bind zone
4             #
5             # Copyright (C) 2013-2021 Daniel P. Berrange
6             #
7             # This program is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 3 of the License, or
10             # (at your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20              
21             package NoZone::Zone;
22              
23 2     2   13 use strict;
  2         4  
  2         62  
24 2     2   10 use warnings;
  2         4  
  2         60  
25              
26 2     2   1010 use POSIX qw(strftime);
  2         13937  
  2         10  
27              
28             =head1 NAME
29              
30             Nozone::Zone - record information for a bind zone
31              
32             =head1 SYNOPSIS
33              
34             use Nozone::Zone;
35              
36             my $nozone = Nozone::Zone->new(
37             domains => [
38             "nozone.org",
39             "nozone.com",
40             ],
41             hostmaster => "hostmaster",
42             lifetimes => {
43             refresh => "1H",
44             retry => "15M",
45             expire => "1W"
46             negative => "1H",
47             ttl => "1H",
48             },
49             machines => {
50             platinum => {
51             ipv4 => "12.32.56.1"
52             ipv6 => "2001:1234:6789::1"
53             },
54             gold => {
55             ipv4 => "12.32.56.2"
56             ipv6 => "2001:1234:6789::2"
57             },
58             silver => {
59             ipv4 => "12.32.56.3"
60             ipv6 => "2001:1234:6789::3"
61             },
62             },
63             default => "platinum",
64             spf => {
65             policy => "reject",
66             machines => [
67             "gold",
68             "silver"
69             ]
70             },
71             dkim => {
72             "default" => {
73             version => "DKIM1",
74             keytype => "rsa",
75             pubkey => "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1TaNgLlSyQMNWVLNLvyY/neDgaL2oqQE8T5illKqCgDtFHc8eHVAU+nlcaGmrKmDMw9dbgiGk1ocgZ56NR4ycfUHwQhvQPMUZw0cveel/8EAGoi/UyPmqfcPibytH81NFtTMAxUeM4Op8A6iHkvAMj5qLf4YRNsTkKAV"
76             },
77             },
78             dmarc => {
79             version => "DMARC1",
80             policy => "none",
81             subdomain_policy => "none",
82             percent => "20",
83             forensic_report => "mailto:dmarcfail@example.com",
84             aggregate_report => "mailto:dmarcagg@example.com",
85             },
86             mail => {
87             mx0 => {
88             priority => 10,
89             machine => "gold"
90             },
91             mx1 => {
92             priority => 30,
93             machine => "silver"
94             },
95             },
96             dns => {
97             ns0 => "gold",
98             ns1 => "silver",
99             },
100             names => {
101             www => "platinum",
102             },
103             aliases => {
104             db => "gold",
105             backup => "silver",
106             },
107             txt => {
108             challenge1 => "9e428dae-b677-49b6-9eb9-a5754cbbfc2c",
109             },
110             wildcard => "platinum",
111             inherits => $parentzone,
112             );
113              
114             foreach my $domain ($zone->get_domains()) {
115             my $conffile = "/etc/named/$domain.conf";
116             my $datafile = "/var/named/data/$domain.data";
117              
118             my $conffh = IO::File->new($conffile, ">");
119             $zone->generate_conffile($conffh, $domain, $datafile);
120             $conffh->close();
121              
122             my $datafh = IO::File->new($datafile, ">");
123             $zone->generate_datafile($datafh, $domain);
124             $datafh->close();
125             }
126              
127             =head1 DESCRIPTION
128              
129             The C class records the information for a single
130             DNS zone. A DNS zone can be associated with zero or more domain
131             names. A zone without any associated domain names can serve as
132             an abstract base from which other zones inherit data. Inheritance
133             of zones allows admins to minimize the duplication of data across
134             zones.
135              
136             A zone contains a number of parameters, which are usually provided
137             when the object is initialized.
138              
139             =over 4
140              
141             =item domains
142              
143             The C parameter is an array reference providing the list
144             of domain names associated with the DNS zone.
145              
146             domains => [
147             "nozone.org",
148             "nozone.com",
149             ]
150              
151             =item hostmaster
152              
153             The C parameter is the local part of the email address
154             of the person who manages the domain. This will be combined with
155             the domain name to form the complete email address
156              
157             hostmaster => "hostmaster"
158              
159             =item lifetimes
160              
161             The C parameter specifies various times for DNS zone
162             records. These are use to populate the SOA records in the zone.
163              
164             lifetimes => {
165             refresh => "1H",
166             retry => "15M",
167             expire => "1W"
168             negative => "1H",
169             ttl => "1H",
170             }
171              
172             =item machines
173              
174             The C parameter is a hash reference whose keys are the
175             names of physical machines. The values are further hash references
176             specifying the IPv4 and IPv6 addresses for the names.
177              
178             machines => {
179             platinum => {
180             ipv4 => "12.32.56.1"
181             ipv6 => "2001:1234:6789::1"
182             },
183             gold => {
184             ipv4 => "12.32.56.2"
185             ipv6 => "2001:1234:6789::2"
186             },
187             silver => {
188             ipv4 => "12.32.56.3"
189             ipv6 => "2001:1234:6789::3"
190             },
191             }
192              
193             =item default
194              
195             The C parameter is used to specify the name of the
196             machine which will be use as the default when resolving the
197             base domain name
198              
199             default => "platinum"
200              
201             =item mail
202              
203             The C parameter is a hash reference whose keys are the
204             names to setup as mail servers. The values are an further has
205             reference whose elements specify the priority of the mail
206             server and the name of the machine defined in the C
207             parameter.
208              
209             mail => {
210             mx0 => {
211             priority => 10,
212             machine => "gold"
213             },
214             mx1 => {
215             priority => 30,
216             machine => "silver"
217             },
218             }
219              
220             =item dns
221              
222             The C parameter is a hash reference whose keys are the
223             names to setup as DNS servers. The values are the names of
224             machines defined in the C parameter which are to
225             used to define the corresponding IP addresses
226              
227             dns => [
228             ns0 => "gold",
229             ns1 => "silver",
230             ]
231              
232             =item names
233              
234             The C parameter is a hash reference whose keys reflect
235             additional names to be defined as A/AAAA records for the zone.
236             The values refer to keys in the C parameter and are
237             used to define the corresponding IP addresses
238              
239             names => {
240             www => "platinum",
241             }
242              
243             =item aliases
244              
245             The C parameter is a hash reference whose keys reflect
246             additional names to be defiend as CNAME records for the zone.
247             The values refer to keys in the C or C
248             parameter and are used to the define the CNAME target.
249              
250             aliases => {
251             db => "gold",
252             backup => "silver",
253             }
254              
255             =item wildcard
256              
257             The C parameter is a string refering to a name
258             defined in the C parameter. If set this parameter
259             is used to defined a wildcard DNS entry in the zone.
260              
261             wildcard => "platinum"
262              
263             =item spf
264              
265             The C parameter is a hash reference setting up the
266             SPF records. The C key takes one of the values
267             B, B, or B, to specify what happens
268             when an IP doesn't match the SPF. The C key
269             is an array reference that specifies the list of machine
270             names that are permitted to send email.
271              
272             =item dkim
273              
274             The C parameter is a hash of hash references setting
275             up the DKIM records. The key for the first level hash is
276             the DKIM selector. The second level hashes contain the
277             following keys.
278              
279             The C key must always be C. The C
280             key must be a public key algorithm name, typically 'rsa'.
281             The C key is a string restricting the usage.
282             The C key is the public key.
283              
284             =item dmarc
285              
286             The C parameter is a hash reference setting up the
287             DMARC records.
288              
289             The C key must always be C. The C
290             key is one of C, C or C. The
291             C key takes the same values. The
292             C key indicates how often to filter messages.
293             The C and C keys
294             give a URI for sending reports.
295              
296             =item txt
297              
298             The C parameter is a has of arbitrary key and value
299             strings, which will be added as TXT records.
300              
301             =back
302              
303             =head1 METHODS
304              
305             =over 4
306              
307             =item my $zone = NoZone::Zone->new(%params);
308              
309             Creates a new L object to hold information
310             about a DNS zone. The C<%params> has keys are allowed to
311             be any of the parameters documented earlier in this
312             document. In addition the C parameter is valid
313             and can refer to another instance of the L
314             class.
315              
316             =cut
317              
318              
319             sub new {
320 6     6 1 13 my $proto = shift;
321 6   33     43 my $class = ref($proto) || $proto;
322 6         10 my $self = {};
323 6         44 my %params = @_;
324              
325 6 50       20 $self->{domains} = $params{domains} ? $params{domains} : [];
326 6 100       15 $self->{hostmaster} = $params{hostmaster} ? $params{hostmaster} : "hostmaster";
327 6 100       16 $self->{lifetimes} = $params{lifetimes} ? $params{lifetimes} : undef;
328 6 50       13 $self->{machines} = $params{machines} ? $params{machines} : {};
329 6 100       12 $self->{default} = $params{default} ? $params{default} : undef;
330 6 50       15 $self->{mail} = $params{mail} ? $params{mail} : {};
331 6 50       31 $self->{dns} = $params{dns} ? $params{dns} : [];
332 6 50       19 $self->{names} = $params{names} ? $params{names} : {};
333 6 50       15 $self->{aliases} = $params{aliases} ? $params{aliases} : {};
334 6 50       12 $self->{txt} = $params{txt} ? $params{txt} : {};
335 6 100       14 $self->{wildcard} = $params{wildcard} ? $params{wildcard} : undef;
336 6 100       11 $self->{spf} = $params{spf} ? $params{spf} : undef;
337 6 50       15 $self->{dkim} = $params{dkim} ? $params{dkim} : {};
338 6 100       13 $self->{dmarc} = $params{dmarc} ? $params{dmarc} : undef;
339 6 50       13 $self->{inherits} = $params{inherits} ? $params{inherits} : undef;
340              
341 6         12 bless $self, $class;
342              
343 6         29 return $self;
344             }
345              
346              
347             =item $zone->set_inherits($parentzone);
348              
349             Sets the zone from which this zone will inherit data
350             parameters. The C<$parentzone> method should be an
351             instance of the C class.
352              
353             =cut
354              
355              
356             sub set_inherits {
357 4     4 1 6 my $self = shift;
358 4         6 my $zone = shift;
359              
360 4         16 $self->{inherits} = $zone;
361             }
362              
363              
364             =item my @domains = $zone->get_domains();
365              
366             Returns the array of domain names associated directly
367             with this zone.
368              
369             =cut
370              
371             sub get_domains {
372 6     6 1 11 my $self = shift;
373              
374 6         10 return @{$self->{domains}};
  6         33  
375             }
376              
377              
378             =item my $name = $zone->get_hostmaster();
379              
380             Returns the hostmaster setting associated with this
381             zone, if any. If no hostmaster is set against this zone,
382             then the hostmaster from any parent zone will be returned.
383             If no parent zone is present, an undefined value will
384             be returned.
385              
386             =cut
387              
388             sub get_hostmaster {
389 3     3 1 5 my $self = shift;
390              
391 3 50       11 if (defined $self->{hostmaster}) {
392 3         10 return $self->{hostmaster};
393             }
394              
395 0 0       0 if (defined $self->{inherits}) {
396 0         0 return $self->{inherits}->get_hostmaster();
397             }
398              
399 0         0 return "hostmaster";
400             }
401              
402              
403             =item my %lifetimes = $zone->get_lifetimes();
404              
405             Return a hash containing the lifetimes defined against
406             this zone. If no data is defined for this zone, then
407             the data from any parent zone is returned. If not
408             parent zone is set, then some sensible default times
409             are returned.
410              
411             =cut
412              
413             sub get_lifetimes {
414 6     6 1 41 my $self = shift;
415              
416 6 100       34 if (defined $self->{lifetimes}) {
417 3         5 return %{$self->{lifetimes}};
  3         33  
418             }
419              
420 3 50       10 if ($self->{inherits}) {
421 3         12 return $self->{inherits}->get_lifetimes();
422             }
423              
424             return (
425 0         0 refresh => "1H",
426             retry => "15M",
427             expire => "1W",
428             negative => "1H",
429             ttl => "1H",
430             );
431             }
432              
433              
434             =item my %machines = $zone->get_machines();
435              
436             Return hash containing the union of all the machines
437             defined in this zone and its parent(s) recursively.
438              
439             =cut
440              
441             sub get_machines {
442 102     102 1 126 my $self = shift;
443              
444 102         140 my %machines;
445              
446 102 100       206 if ($self->{inherits}) {
447 51         91 %machines = $self->{inherits}->get_machines();
448             }
449              
450 102         116 foreach my $name (keys %{$self->{machines}}) {
  102         257  
451 153         235 $machines{$name} = $self->{machines}->{$name};
452             }
453              
454 102         293 return %machines;
455             }
456              
457              
458             =item $machine = $zone->get_machine($name);
459              
460             Return a hash reference containing the IP addresses
461             associated with the machine named C<$name>.
462              
463             =cut
464              
465             sub get_machine {
466 48     48 1 71 my $self = shift;
467 48         65 my $name = shift;
468              
469 48         89 my %machines = $self->get_machines();
470              
471 48 50       137 return exists $machines{$name} ? $machines{$name} : undef;
472             }
473              
474              
475             =item my $name = $zone->get_default();
476              
477             Returns the name of the machine to be used as the
478             default for the zone. If no default is defined
479             for this zone, then the default from any parent
480             zone is defined. If no parent zone is defined,
481             then return an undefined value
482              
483             =cut
484              
485             sub get_default {
486 6     6 1 9 my $self = shift;
487              
488 6 100       16 if (defined $self->{default}) {
489 3         8 return $self->{default};
490             }
491              
492 3 50       7 if (defined $self->{inherits}) {
493 3         8 return $self->{inherits}->get_default();
494             }
495              
496 0         0 return undef;
497             }
498              
499              
500             =item my %dns = $zone->get_dns();
501              
502             Return a hash containing the union of all the machines
503             defined as dns servers in this zone and its parent(s)
504             recursively.
505              
506             =cut
507              
508             sub get_dns {
509 6     6 1 9 my $self = shift;
510              
511 6         9 my %dns;
512              
513 6 100       18 if ($self->{inherits}) {
514 3         10 %dns = $self->{inherits}->get_dns();
515             }
516              
517 6         11 foreach my $name (keys %{$self->{dns}}) {
  6         21  
518 6         17 $dns{$name} = $self->{dns}->{$name};
519             }
520              
521 6         36 return %dns;
522             }
523              
524              
525             =item my %mail = $zone->get_mail();
526              
527             Return a hash containing the union of all the machines
528             defined as mail servers in this zone and its parent(s)
529             recursively.
530              
531             =cut
532              
533             sub get_mail {
534 6     6 1 9 my $self = shift;
535              
536 6         10 my %mail;
537              
538 6 100       15 if ($self->{inherits}) {
539 3         9 %mail = $self->{inherits}->get_mail();
540             }
541              
542 6         10 foreach my $name (keys %{$self->{mail}}) {
  6         21  
543 6         13 $mail{$name} = $self->{mail}->{$name};
544             }
545              
546 6         20 return %mail;
547             }
548              
549              
550             =item %names = $zone->get_names();
551              
552             Return a hash containing the union of all the machine
553             hostnames defined in this zone and its parent(s)
554             recursively.
555              
556             =cut
557              
558             sub get_names {
559 6     6 1 10 my $self = shift;
560              
561 6         10 my %names;
562              
563 6 100       23 if ($self->{inherits}) {
564 3         17 %names = $self->{inherits}->get_names();
565             }
566              
567 6         10 foreach my $name (keys %{$self->{names}}) {
  6         19  
568 3         11 $names{$name} = $self->{names}->{$name};
569             }
570              
571 6         20 return %names;
572             }
573              
574              
575             =item %names = $zone->get_aliases();
576              
577             Return a hash containing the union of all the machine
578             aliases defined in this zone and its parent(s)
579             recursively.
580              
581             =cut
582              
583             sub get_aliases {
584 6     6 1 11 my $self = shift;
585              
586 6         10 my %aliases;
587              
588 6 100       16 if ($self->{inherits}) {
589 3         18 %aliases = $self->{inherits}->get_aliases();
590             }
591              
592 6         11 foreach my $name (keys %{$self->{aliases}}) {
  6         19  
593 6         15 $aliases{$name} = $self->{aliases}->{$name};
594             }
595              
596 6         24 return %aliases;
597             }
598              
599              
600             =item %names = $zone->get_txt();
601              
602             Return a hash containing the union of all the TXT
603             records defined in this zone and its parent(s)
604             recursively.
605              
606             =cut
607              
608             sub get_txt {
609 6     6 1 10 my $self = shift;
610              
611 6         8 my %txt;
612              
613 6 100       14 if ($self->{inherits}) {
614 3         10 %txt = $self->{inherits}->get_txt();
615             }
616              
617 6         11 foreach my $name (keys %{$self->{txt}}) {
  6         43  
618 4         14 $txt{$name} = $self->{txt}->{$name};
619             }
620              
621 6         19 return %txt;
622             }
623              
624              
625             =item %selectors = $zone->get_dkim_selectors();
626              
627             Return a hash containing the union of all the dkim
628             records defined in this zone and its parent(s)
629             recursively.
630              
631             =cut
632              
633             sub get_dkim_selectors {
634 6     6 1 8 my $self = shift;
635              
636 6         9 my %selectors;
637              
638 6 100       29 if ($self->{inherits}) {
639             %selectors = $self->{inherits}->get_dkim_selectors()
640 3         10 }
641              
642 6         9 foreach my $selector (keys %{$self->{dkim}}) {
  6         28  
643 4         10 $selectors{$selector} = $self->{dkim}->{$selector};
644             }
645              
646 6         20 return %selectors;
647             }
648              
649              
650             =item my $name = $zone->get_wildcard();
651              
652             Return the name of the machine which will handle
653             wildcard name lookups. If no wildcard is defined
654             against the zone, returns the wildcard of the
655             parent zone. If there is no parent zone, an
656             undefined value is returned, indicating that no
657             wildcard DNS entry will be created.
658              
659             =cut
660              
661             sub get_wildcard {
662 6     6 1 10 my $self = shift;
663              
664 6 100       17 if (defined $self->{wildcard}) {
665 3         11 return $self->{wildcard};
666             }
667              
668 3 50       9 if ($self->{inherits}) {
669 3         9 return $self->{inherits}->get_wildcard();
670             }
671              
672 0         0 return undef;
673             }
674              
675             =item my $policy = $zone->get_spf_policy();
676              
677             Returns the policy for SPF records for the domain.
678             The policy is one of the string B, B
679             or B. If no SPF policy is defined gainst the
680             zone, returns the SPF policy of the parent zone.
681             if there is no parent zone an undefined value is
682             returned indicating that no SPF entry will be
683             created.
684              
685             =cut
686              
687             sub get_spf_policy {
688 40     40 1 49 my $self = shift;
689              
690 40 100       79 if (defined $self->{spf}) {
691 20         52 return $self->{spf}->{policy};
692             }
693              
694 20 100       38 if ($self->{inherits}) {
695 10         19 return $self->{inherits}->get_spf_policy();
696             }
697              
698 10         19 return undef;
699             }
700              
701              
702             =item my @machines = $zone->get_spf_machines();
703              
704             Returns the list of machines that are permitted to
705             send mail to record as SPF records. If no machines
706             are defined against the zone, returns the machines
707             of teh parent zone. If there is no parent zone an
708             empty list if returned
709              
710             =cut
711              
712             sub get_spf_machines {
713 20     20 1 27 my $self = shift;
714              
715 20 50       39 if (defined $self->{spf}) {
716 20         26 return @{$self->{spf}->{machines}};
  20         55  
717             }
718              
719 0 0       0 if ($self->{inherits}) {
720 0         0 return $self->{inherits}->get_spf_machines();
721             }
722              
723 0         0 return ();
724             }
725              
726              
727             =item my $config = $zone->get_dmarc_config();
728              
729             Returns the config for the DMARC records for the domain.
730             If no DMARC config is defined gainst the
731             zone, returns the DMAWRC config of the parent zone.
732             if there is no parent zone undefined values are
733             returned indicating that no DMARC entry will be
734             created.
735              
736             =cut
737              
738             sub get_dmarc_config {
739 4     4 1 8 my $self = shift;
740              
741 4 100       12 if (defined $self->{dmarc}) {
742 2         6 return $self->{dmarc};
743             }
744              
745 2 100       7 if ($self->{inherits}) {
746 1         5 return $self->{inherits}->get_dmarc_config();
747             }
748              
749 1         3 return undef;
750             }
751              
752              
753              
754             =item $zone->generate_conffile($fh, $domain, $datafile, \@masters, $verbose=0);
755              
756             Generate a Bind zone conf file for the domain C<$domain>
757             writing the data to the file handle C<$fh>. C<$fh>
758             should be an instance of L. The optional C<$verbose>
759             parameter can be set to a true value to print progress on
760             stdout. If C<@masters> is a non-empty list, then a slave
761             config will be created, otherwise a master config will be
762             created. The C<$datafile> parameter should specify the
763             path to the corresponding zone data file, usually kept
764             in C.
765              
766             =cut
767              
768             sub generate_conffile {
769 6     6 1 12 my $self = shift;
770 6         11 my $fh = shift;
771 6         9 my $domain = shift;
772 6         13 my $datafile = shift;
773 6         7 my $masters = shift;
774 6   50     23 my $verbose = shift || 0;
775              
776 6 100       8 if (int(@{$masters})) {
  6         20  
777 3         7 my $masterlist = join (" ; ", @{$masters});
  3         8  
778              
779 3         23 print $fh <
780             zone "$domain" in {
781             type slave;
782             file "$datafile";
783             masters { $masterlist ; };
784             };
785             EOF
786             } else {
787 3         33 print $fh <
788             zone "$domain" in {
789             type master;
790             file "$datafile";
791             };
792             EOF
793             }
794             }
795              
796              
797             =item $zone->generate_datafile($fh, $domain, $verbose=0);
798              
799             Generate a Bind zone data file for the domain C<$domain>
800             writing the data to the file handle C<$fh>. C<$fh>
801             should be an instance of L. The optional C<$verbose>
802             parameter can be set to a true value to print progress on
803             stdout.
804              
805             =cut
806              
807             sub generate_datafile {
808 3     3 1 8 my $self = shift;
809 3         4 my $fh = shift;
810 3         5 my $domain = shift;
811 3   50     7 my $verbose = shift || 0;
812              
813 3         13 $self->_generate_soa($fh, $domain, $verbose);
814 3         11 $self->_generate_default($fh, $verbose);
815 3         10 $self->_generate_dns($fh, $verbose);
816 3         10 $self->_generate_mail($fh, $verbose);
817 3         10 $self->_generate_machines($fh, $verbose);
818 3         11 $self->_generate_names($fh, $verbose);
819 3         8 $self->_generate_aliases($fh, $verbose);
820 3         10 $self->_generate_dkim($fh, $verbose);
821 3         9 $self->_generate_dmarc($fh, $verbose);
822 3         9 $self->_generate_txt($fh, $verbose);
823 3         9 $self->_generate_wildcard($fh, $verbose);
824             }
825              
826              
827             sub _generate_soa {
828 3     3   6 my $self = shift;
829 3         5 my $fh = shift;
830 3         5 my $domain = shift;
831 3         3 my $verbose = shift;
832              
833 3 50       27 print " - Generate soa $domain\n" if $verbose;
834              
835 3         16 my $hostmaster = $self->get_hostmaster();
836              
837 3         8 my $now = time;
838 3         564 my $time = strftime("%Y/%m/%d %H:%M:%S", gmtime(time));
839              
840 3         17 my %lifetimes = $self->get_lifetimes();
841 3         8 my $refresh = $lifetimes{refresh};
842 3         4 my $retry = $lifetimes{retry};
843 3         6 my $expire = $lifetimes{expire};
844 3         5 my $negative = $lifetimes{negative};
845 3         4 my $ttl = $lifetimes{ttl};
846              
847 3         51 print $fh <
848             \$ORIGIN $domain.
849             \$TTL $ttl ; queries are cached for this long
850             @ IN SOA ns1 $hostmaster (
851             $now ; Date $time
852             $refresh ; slave queries for refresh this often
853             $retry ; slave retries refresh this often after failure
854             $expire ; slave expires after this long if not refreshed
855             $negative ; errors are cached for this long
856             )
857              
858             EOF
859              
860             }
861              
862              
863             sub _generate_record {
864 108     108   134 my $self = shift;
865 108         124 my $fh = shift;
866 108         141 my $name = shift;
867 108         121 my $type = shift;
868 108         131 my $detail = shift;
869 108         131 my $value = shift;
870 108         135 my $comment = shift;
871              
872 108         125 my $suffix = "";
873 108 100       174 if (defined $comment) {
874 62         81 $suffix = " ; " . $comment;
875             }
876              
877 108         535 printf $fh "%-20s IN %-8s %-6s %s%s\n", $name, $type, $detail, $value, $suffix;
878             }
879              
880              
881             sub _generate_spf {
882 30     30   39 my $self = shift;
883 30         35 my $fh = shift;
884 30         40 my $name = shift;
885 30         38 my $machine = shift;
886 30         38 my $verbose = shift;
887              
888 30         53 my $policy = $self->get_spf_policy();
889 30 100       65 return unless defined $policy;
890              
891 20         24 my $sentinel;
892 20 50       55 if ($policy eq "accept") {
    50          
    50          
893 0         0 $sentinel = "+all";
894             } elsif ($policy eq "reject") {
895 0         0 $sentinel = "-all";
896             } elsif ($policy eq "mark") {
897 20         32 $sentinel = "~all";
898             } else {
899 0         0 $sentinel = "?all";
900             }
901              
902 20         31 my $spf;
903             my $comment;
904 20 100 100     71 if ($name eq "\@" || $name eq "*") {
905 4         9 my @machines = $self->get_spf_machines();
906              
907 4         13 my @ips;
908 4         11 foreach my $machine (@machines) {
909 8         16 my $addrs = $self->get_machine($machine);
910 8 50       15 die "cannot find machine $machine" unless defined $addrs;
911 8 50       17 if (exists $addrs->{ipv4}) {
912 8         22 push @ips, "ip4:" . $addrs->{ipv4};
913             }
914 8 50       22 if (exists $addrs->{ipv6}) {
915 8         20 push @ips, "ip6:" . $addrs->{ipv6};
916             }
917             }
918              
919 4         11 $comment = "Machine(s) " . join(", ", @machines);
920 4         14 $spf = "v=spf1 " . join(" ", @ips, $sentinel);
921             } else {
922 16         33 my @machines = $self->get_spf_machines();
923              
924 16         29 $comment = "Machine $machine";
925 16 100       35 if (grep { $_ eq $machine } @machines) {
  32         77  
926 10         22 my $addrs = $self->get_machine($machine);
927 10 50       24 die "cannot find machine $machine" unless defined $addrs;
928 10         14 my @ips;
929 10 50       19 if (exists $addrs->{ipv4}) {
930 10         36 push @ips, "ip4:" . $addrs->{ipv4};
931             }
932 10 50       19 if (exists $addrs->{ipv6}) {
933 10         20 push @ips, "ip6:" . $addrs->{ipv6};
934             }
935 10         28 $spf = "v=spf1 " . join(" ", @ips, $sentinel);
936             } else {
937 6         12 $spf = "v=spf1 " . $sentinel;
938             }
939             }
940 20         56 $self->_generate_record($fh, $name, "TXT", "", '"' . $spf . '"', $comment);
941             }
942              
943             sub _generate_machine {
944 30     30   44 my $self = shift;
945 30         34 my $fh = shift;
946 30         36 my $name = shift;
947 30         40 my $machine = shift;
948 30         51 my $verbose = shift;
949              
950 30 100       588 print " - Generate [$name] for [$machine]\n" if $verbose;
951              
952 30         116 my $addrs = $self->get_machine($machine);
953              
954 30 50       60 die "cannot find machine $machine" unless defined $addrs;
955              
956 30         37 my $comment;
957 30 100       57 if ($name ne $machine) {
958 21         34 $comment = "Machine $machine";
959             }
960              
961 30 50       107 $self->_generate_record($fh, $name, "A", "", $addrs->{ipv4}, $comment) if exists $addrs->{ipv4};
962 30 50       108 $self->_generate_record($fh, $name, "AAAA", "", $addrs->{ipv6}, $comment) if exists $addrs->{ipv6};
963 30 50 33     87 if (exists $addrs->{ipv4} || exists $addrs->{ipv6}) {
964 30         61 $self->_generate_spf($fh, $name, $machine, $verbose);
965             }
966             }
967              
968              
969             sub _generate_default {
970 3     3   6 my $self = shift;
971 3         4 my $fh = shift;
972 3         5 my $cfg = shift;
973 3         4 my $domain = shift;
974 3         4 my $verbose = shift;
975              
976 3 50       13 print " - Generate default\n" if $verbose;
977              
978 3         11 my $default = $self->get_default();
979              
980 3 50       7 if (defined $default) {
981 3         6 print $fh "; Primary name records for unqualfied domain\n";
982 3         9 $self->_generate_machine($fh, "\@", $default, $verbose);
983 3         7 print $fh "\n";
984             }
985             }
986              
987              
988             sub _generate_dns {
989 3     3   5 my $self = shift;
990 3         5 my $fh = shift;
991 3         5 my $verbose = shift;
992              
993 3 50       109 print " - Generate dns\n" if $verbose;
994              
995 3         23 my %dns = $self->get_dns();
996              
997 3         8 print $fh "; DNS server records\n";
998              
999 3         17 my @dns = sort { $a cmp $b } keys %dns;
  3         12  
1000 3         7 foreach my $name (@dns) {
1001 6         18 $self->_generate_record($fh, "\@", "NS", "", $name);
1002             }
1003              
1004 3         6 foreach my $name (@dns) {
1005 6         16 $self->_generate_machine($fh, $name, $dns{$name}, $verbose);
1006             }
1007 3         10 print $fh "\n";
1008             }
1009              
1010              
1011             sub _generate_mail {
1012 3     3   9 my $self = shift;
1013 3         5 my $fh = shift;
1014 3         5 my $verbose = shift;
1015              
1016 3 50       84 print " - Generate mail\n" if $verbose;
1017              
1018 3         17 my %mail = $self->get_mail();
1019              
1020 3         8 print $fh "; E-Mail server records\n";
1021              
1022 3         14 my @mail = sort { $a cmp $b } keys %mail;
  3         14  
1023 3         16 foreach my $name (@mail) {
1024 6         16 my $prio = $mail{$name}->{'priority'};
1025 6         16 $self->_generate_record($fh, "\@", "MX", $prio, $name);
1026             }
1027              
1028 3         7 foreach my $name (@mail) {
1029 6         14 my $machine = $mail{$name}->{'machine'};
1030 6         16 $self->_generate_machine($fh, $name, $machine, $verbose);
1031             }
1032 3         10 print $fh "\n";
1033             }
1034              
1035              
1036             sub _generate_machines {
1037 3     3   17 my $self = shift;
1038 3         4 my $fh = shift;
1039 3         7 my $verbose = shift;
1040              
1041 3 50       92 print " - Generate machines\n" if $verbose;
1042              
1043 3         15 my %names = $self->get_machines();
1044              
1045 3 50       7 if (%names) {
1046 3         8 print $fh "; Primary names\n";
1047              
1048 3         13 foreach my $name (sort { $a cmp $b } keys %names) {
  9         19  
1049 9         21 $self->_generate_machine($fh, $name, $name, $verbose);
1050             }
1051 3         9 print $fh "\n";
1052             }
1053             }
1054              
1055              
1056             sub _generate_names {
1057 3     3   4 my $self = shift;
1058 3         6 my $fh = shift;
1059 3         6 my $verbose = shift;
1060              
1061 3 50       94 print " - Generate names\n" if $verbose;
1062              
1063 3         18 my %names = $self->get_names();
1064              
1065 3 50       9 if (%names) {
1066 3         8 print $fh "; Extra names\n";
1067              
1068 3         11 foreach my $name (sort { $a cmp $b } keys %names) {
  0         0  
1069 3         10 $self->_generate_machine($fh, $name, $names{$name}, $verbose);
1070             }
1071 3         11 print $fh "\n";
1072             }
1073             }
1074              
1075              
1076             sub _generate_aliases {
1077 3     3   5 my $self = shift;
1078 3         5 my $fh = shift;
1079 3         5 my $verbose = shift;
1080              
1081 3 50       92 print " - Generate aliases\n" if $verbose;
1082              
1083 3         17 my %aliases = $self->get_aliases();
1084              
1085 3 50       9 if (%aliases) {
1086 3         8 print $fh "; Aliased names\n";
1087              
1088 3         14 foreach my $alias (sort { $a cmp $b } keys %aliases) {
  3         11  
1089 6         16 $self->_generate_record($fh, $alias, "CNAME", "", $aliases{$alias});
1090             }
1091 3         9 print $fh "\n";
1092             }
1093             }
1094              
1095              
1096             sub _generate_txt {
1097 3     3   4 my $self = shift;
1098 3         5 my $fh = shift;
1099 3         5 my $verbose = shift;
1100              
1101 3 50       52 print " - Generate txt\n" if $verbose;
1102              
1103 3         13 my %txt = $self->get_txt();
1104              
1105 3 50       8 if (%txt) {
1106 3         8 print $fh "; Extra TXT\n";
1107              
1108 3         9 foreach my $alias (sort { $a cmp $b } keys %txt) {
  1         5  
1109 4         15 $self->_generate_record($fh, $alias, "TXT", "", $txt{$alias});
1110             }
1111 3         8 print $fh "\n";
1112             }
1113             }
1114              
1115              
1116             sub _generate_dkim {
1117 3     3   4 my $self = shift;
1118 3         4 my $fh = shift;
1119 3         4 my $verbose = shift;
1120              
1121 3 50       71 print " - Generate dkim\n" if $verbose;
1122              
1123 3         17 my %selectors = $self->get_dkim_selectors();
1124              
1125 3 100       9 if (%selectors) {
1126 2         5 print $fh "; DKIM selectors\n";
1127              
1128 2         8 foreach my $selector (sort { $a cmp $b } keys %selectors) {
  2         6  
1129 4         8 my $dkim = $selectors{$selector};
1130 4 100       14 my $version = exists $dkim->{"version"} ? $dkim->{"version"} : "DKIM1";
1131 4 100       9 my $keytype = exists $dkim->{"keytype"} ? $dkim->{"keytype"} : "rsa";
1132 4         27 my $service = $dkim->{"service"};
1133 4         7 my $pubkey = $dkim->{"pubkey"};
1134 4         12 my $value = "v=$version; k=$keytype;";
1135 4 100       7 if (defined $service) {
1136 2         6 $value .= " s=$service;";
1137             }
1138 4         15 $value .= " p=$pubkey";
1139 4         15 $self->_generate_record($fh, $selector . "._domainkey", "TXT", "", '"' . $value . '"');
1140             }
1141 2         7 print $fh "\n";
1142             }
1143             }
1144              
1145              
1146             sub _generate_dmarc {
1147 3     3   6 my $self = shift;
1148 3         4 my $fh = shift;
1149 3         6 my $verbose = shift;
1150              
1151 3 50       72 print " - Generate dmarc\n" if $verbose;
1152              
1153 3         15 my $config = $self->get_dmarc_config();
1154              
1155 3 100       9 return unless defined $config;
1156              
1157 2 50       7 my $version = exists $config->{version} ? $config->{version} : "DMARC1";
1158 2 50       6 my $policy = exists $config->{policy} ? $config->{policy} : "none";
1159 2         4 my $subpolicy = $config->{subdomain_policy};
1160 2         4 my $percent = $config->{percent};
1161 2         3 my $rua = $config->{aggregate_report};
1162 2         4 my $ruf = $config->{forensic_report};
1163              
1164 2         7 my $value = "v=$version; p=$policy;";
1165 2 50       4 if (defined $subpolicy) {
1166 2         14 $value .= " sp=$subpolicy;";
1167             }
1168 2 50       5 if (defined $percent) {
1169 2         5 $value .= " pct=$percent;";
1170             }
1171 2 50       4 if (defined $rua) {
1172 2         5 $value .= " rua=$rua;";
1173             }
1174 2 50       5 if (defined $ruf) {
1175 2         4 $value .= " ruf=$ruf;";
1176             }
1177 2         5 print $fh "; DMARC policy\n";
1178 2         9 $self->_generate_record($fh, "_dmarc", "TXT", "", '"' . $value . '"');
1179 2         7 print $fh "\n";
1180             }
1181              
1182              
1183             sub _generate_wildcard {
1184 3     3   6 my $self = shift;
1185 3         4 my $fh = shift;
1186 3         5 my $verbose = shift;
1187              
1188 3 50       65 print " - Generate wildcard\n" if $verbose;
1189 3         16 my $wildcard = $self->get_wildcard();
1190              
1191 3 50       57 if (defined $wildcard) {
1192 3         37 print $fh "; Wildcard\n";
1193 3         13 $self->_generate_machine($fh, "*", $wildcard, $verbose);
1194 3         15 print $fh "\n";
1195             }
1196             }
1197              
1198              
1199             1;
1200              
1201             =back
1202              
1203             =head1 AUTHORS
1204              
1205             C was written by Daniel P. Berrange
1206              
1207             =head1 LICENSE
1208              
1209             C is distributed under the terms of the GNU GPL version 3
1210             or any later version. You should have received a copy of the GNU
1211             General Public License along with this program. If not, see
1212             C.
1213              
1214             =head1 SEE ALSO
1215              
1216             L, C