lib/ViDNS.pm
changeset 6 271dfe27e1d3
equal deleted inserted replaced
5:70ecc1882968 6:271dfe27e1d3
       
     1 use 5.010;
       
     2 use strict;
       
     3 use warnings;
       
     4 use if $ENV{DEBUG}//''  eq 'vidns' => 'Smart::Comments';
       
     5 use Digest::SHA qw(sha512_hex);
       
     6 
       
     7 use base 'Exporter';
       
     8 
       
     9 our @EXPORT = qw(ttl2h h2ttl parse delta);
       
    10 our @EXPORT_OK = ();
       
    11 
       
    12 sub parse {
       
    13     my $data = join '', @_;
       
    14     my @lines = split /\n/, $data;
       
    15 
       
    16     my @zone;
       
    17     my ($origin, $ttl, $last_label, $soa_seen);
       
    18 
       
    19     foreach (@lines) {
       
    20         s{;.*$}{};
       
    21         given ($_) {
       
    22             when (m{^\s*$})                { next }
       
    23             when (m{^\s*\$ORIGIN\s+(\S+)}) { $origin = $1 }
       
    24             when (m{^\s*\$TTL\s+(\S+)})    { $ttl = $1 }
       
    25             when (
       
    26                 m{^(?<label>\S+)?
       
    27 		    \s+(?<ttl>\d[\dwdmhs]*(?=\s+))?
       
    28 		    \s+(?:(?:IN|ANY)\s+)?(?<rrtype>[a-z]\S*(?=\s+))
       
    29 		    \s+(?<data>.*)
       
    30 		  }ix
       
    31               )
       
    32             {
       
    33                 my %rrset = (
       
    34                     label => $last_label =
       
    35                       defined $+{label}
       
    36                     ? $+{label} eq '@'
       
    37                           ? $origin
       
    38                           : $+{label}
       
    39                     : $last_label,
       
    40                       ttl => h2ttl($+{ttl} // $ttl),
       
    41                       rrtype => uc $+{rrtype},
       
    42                       data   => $+{data},
       
    43                 );
       
    44 
       
    45                 if ($rrset{rrtype} eq 'SOA') {
       
    46                     next if $soa_seen;
       
    47                     $soa_seen = 1;
       
    48                 }
       
    49 
       
    50                 # label ergänzen, wenn nicht FQDN
       
    51                 $rrset{label} .= ".$origin"
       
    52                   unless substr($rrset{label}, -1) eq '.';
       
    53 
       
    54                 given ($rrset{rrtype}) {
       
    55 
       
    56                     # origin steht im SOA
       
    57                     when ('SOA') {
       
    58                         $origin = $rrset{label};
       
    59 
       
    60                         # fix the nameserver name
       
    61                         $rrset{data} =~ s{^(\S+[^.])(?=\s)}{$1.$origin};
       
    62 
       
    63                         # fix the hostmaster address
       
    64                         $rrset{data} =~ s{^\S+\s+\K(\S+[^.])(?=\s)}{$1.$origin};
       
    65                     }
       
    66 
       
    67                     # bei einigen RRs müssen wir die Daten korrigieren
       
    68                     when ([qw/MX NS PTR/]) {
       
    69                         $rrset{data} .= ".$origin"
       
    70                           unless substr($rrset{data}, -1) eq '.';
       
    71                     }
       
    72                 }
       
    73                 my $id = sha512_hex(sort %rrset);
       
    74                 push @zone, { id => $id, rrset => \%rrset };
       
    75             }
       
    76         }
       
    77     }
       
    78 
       
    79     # list of {
       
    80     #	id => $id,
       
    81     #	rrset => { label => …, ttl => …, rrtype => …, data => … }
       
    82     # }
       
    83     ### @zone
       
    84     return @zone;
       
    85 }
       
    86 
       
    87 sub ttl2h {
       
    88     my $seconds = shift;
       
    89     my @out;
       
    90     my @units = ([w => 604800], [d => 86400], [h => 3600], [m => 60], [s => 1]);
       
    91 
       
    92     foreach (@units) {
       
    93         my $x = int($seconds / $_->[1]);
       
    94         push @out, "$x$_->[0]" if $x;
       
    95         $seconds %= $_->[1] or last;
       
    96     }
       
    97 
       
    98     return join '', @out;
       
    99 }
       
   100 
       
   101 sub h2ttl {
       
   102     my $ttl = shift;
       
   103     my $out;
       
   104     my %factor = (
       
   105         w => 604800,
       
   106         d => 86400,
       
   107         h => 3600,
       
   108         m => 60,
       
   109         s => 1,
       
   110     );
       
   111 
       
   112     while ($ttl =~ m{(\d+)([wdhms])}g) {
       
   113         $out += $1 * $factor{$2};
       
   114     }
       
   115 
       
   116     return $out // $ttl;
       
   117 }
       
   118 
       
   119 sub nice {
       
   120 
       
   121     # get a list of { id => $id, rrset => \%rrset }
       
   122     my @zone =
       
   123       sort {
       
   124         length $a->{label} <=> length $b->{label}
       
   125           or $a->{label}
       
   126           cmp $b->{label}
       
   127       } map { $_->{rrset} } @_;
       
   128 
       
   129     my @out;
       
   130     my $origin = (grep { $_->{rrtype} eq 'SOA' } @zone)[0]->{label};
       
   131     my $ttl    = (grep { $_->{rrtype} eq 'SOA' } @zone)[0]->{ttl};
       
   132     my $l1 = (sort map { index $_->{label}, '.' } @zone)[-1];
       
   133     my $l2 = (sort map { length $_->{rrtype} } @zone)[-1];
       
   134     push @out, "\$ORIGIN $origin", "\$TTL " . ttl2h($ttl);
       
   135 
       
   136     my $print = sub {
       
   137         my %r = %{ +shift };
       
   138         state $last_label;
       
   139 
       
   140         $r{label} = '@' if $r{label} eq $origin;
       
   141         $r{label} =~ s{\.\Q$origin\E$}{};
       
   142         $r{data} =~ s{\.\Q$origin\E$}{} if $r{rrtype} ~~ [qw(MX SOA PTR)];
       
   143         $r{ttl} = $r{ttl} == $ttl ? '' : ttl2h($r{ttl});
       
   144         $r{label} = do {
       
   145             if (defined $last_label and $r{label} eq $last_label) { '' }
       
   146             else { $last_label = $r{label} }
       
   147         };
       
   148 
       
   149         return sprintf '%-*s %6s %-*s    %s',
       
   150           $l1 => $r{label},
       
   151           $r{ttl},
       
   152           $l2 => $r{rrtype},
       
   153           $r{data};
       
   154     };
       
   155     push @out, $print->($_) foreach @zone;
       
   156     return join "\n", @out;
       
   157 }
       
   158 
       
   159 sub delta {
       
   160     my ($zone1, $zone2) = @_;
       
   161     my %zone1 = map { $_->{id}, $_->{rrset} } @$zone1;
       
   162     my %zone2 = map { $_->{id}, $_->{rrset} } @$zone2;
       
   163     my @keys1 = keys %zone1;
       
   164     my @keys2 = keys %zone2;
       
   165     delete @zone1{@keys2};
       
   166     delete @zone2{@keys1};
       
   167 
       
   168     my (@add, @del);
       
   169     push @add, "@{$_}{qw/label ttl rrtype data/}" foreach values %zone2;
       
   170     push @del, "@{$_}{qw/label ttl rrtype data/}" foreach values %zone1;
       
   171 
       
   172     return (\@add, \@del);
       
   173 }
       
   174 
       
   175 1;