X-Git-Url: http://gitweb.michael.orlitzky.com/?p=valtz.git;a=blobdiff_plain;f=valtz;h=1b69a73dae233c2173f1e2d2273b62ac587e7c54;hp=c68c120e9a96b324a5de4619f3857b418d551e75;hb=HEAD;hpb=422cc33cf0da52d10c271a75cda271d5963da4eb diff --git a/valtz b/valtz old mode 100644 new mode 100755 index c68c120..1b69a73 --- a/valtz +++ b/valtz @@ -1,10 +1,8 @@ #!/usr/bin/perl # -# $Id: valtz,v 0.7 2003/07/10 16:39:30 magnus Exp $ -# # # -# Copyright (c) 2003, Magnus Bodin, , http://x42.com +# Copyright (c) 2020, the valtz authors # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -18,9 +16,9 @@ # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # -# Neither the name of Magnus Bodin, x42.com nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. +# Neither the name of valtz nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT @@ -43,26 +41,25 @@ use File::Temp qw/ tempfile /; use File::Copy qw/ move /; -my $VERSION = $1 if '$Revision: 0.7 $' =~ /(\d+\.\d+)/; -my $COPYRIGHT = '; (C) 2003 Magnus Bodin, http://x42.com/software/'; +my $VERSION = '0.8'; + $| = 1; my %opt; -getopts('?fFhHiIqrRstT:x', \%opt); - +getopts('?fFhHiIqrRtT:', \%opt); -my $FILESUFFIXREGEXP = '('.join('|', qw/ - ,v ~ .bak .log .old .swp .tmp - /).')$'; +# Validation errors my $verrs_total = 0; + +# "Permission" errors with respect to what record types are allowed my $perrs_total = 0; ## # global location registry # (reset for every zone file) -my %loreg; +my %loreg; # NOTE : DO NOT CHANGE the id numbers my %validation_msg = ( @@ -76,6 +73,7 @@ my %validation_msg = ( 1008 => 'integer out of bounds', 1009 => 'must have at least three labels to be valid as mail address', 1010 => 'must not be 2(NS), 5(CNAME), 6(SOA), 12(PTR), 15(MX) or 252(AXFR)', + 1011 => 'IP address found where hostname expected' ); # NOTE : ONLY translate the right-hand part @@ -100,6 +98,9 @@ my %token_name = ( 'min' => 'Minimum time', 'n' => 'Record type number', 'rdata' => 'Resource data', + 'port' => 'Port', + 'priority' => 'Priority', + 'weight' => 'Weight' ); my %record_type = ( @@ -114,6 +115,7 @@ my %record_type = ( "'" => 'TXT', '^' => 'PTR', 'C' => 'CNAME', + 'S' => 'SRV', 'Z' => 'SOA', ':' => 'GENERIC' ); @@ -131,6 +133,8 @@ my %line_type = ( "'" => [ 'TXT', 'fqdn:s:ttl:timestamp:lo', 'fqdn:s' ], '^' => [ 'PTR', 'fqdn:p:ttl:timestamp:lo', 'fqdn:p' ], 'C' => [ 'CNAME', 'fqdn:p:ttl:timestamp:lo', 'fqdn:p' ], + 'S' => [ 'SRV', 'fqdn:ip:x:port:weight:priority:ttl:timestamp:lo', + 'fqdn:x:port' ], 'Z' => [ 'SOA', 'fqdn:mname:rname:ser:ref:ret:exp:min:ttl:timestamp:lo', 'fqdn:mname:rname' ], ':' => [ 'GENERIC', 'fqdn:n:rdata:ttl:timestamp:lo', 'fqdn:n:rdata' ] @@ -146,7 +150,7 @@ sub validate_integer { my $i = $1; - $result = 1008 if $boundary && ($i >= $boundary); + $result = 1008 if $boundary && ($i >= $boundary); } else { @@ -179,11 +183,11 @@ my %token_validator = ( if ($s =~ /^(\d+)(\.(\d+)(\.(\d+)(\.(\d+))?)?)?$/) { my ($a, $b, $c, $d) = ($1, $3, $5, $7); - $a ||= 0; - $b ||= 0; - $c ||= 0; - $d ||= 0; - if (($a > 255) || ($b > 255) || ($c > 255) || ($d > 255)) + $a ||= 0; + $b ||= 0; + $c ||= 0; + $d ||= 0; + if (($a > 255) || ($b > 255) || ($c > 255) || ($d > 255)) { $result = 1003; } @@ -202,7 +206,7 @@ my %token_validator = ( # check all parts for my $hostpart (split /\./, $s) { - return 1005 unless $hostpart =~ /^[-a-z0-9]+$/i; + return 1005 unless $hostpart =~ /^_?[-a-z0-9]+$/i; return 1006 if $hostpart =~ /^-/; return 1007 if $hostpart =~ /-$/; } @@ -214,11 +218,11 @@ my %token_validator = ( if ($s =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\.?$/) { my ($a, $b, $c, $d) = ($1, $3, $5, $7); - $a ||= 0; - $b ||= 0; - $c ||= 0; - $d ||= 0; - if (($a > 255) || ($b > 255) || ($c > 255) || ($d > 255)) + $a ||= 0; + $b ||= 0; + $c ||= 0; + $d ||= 0; + if (($a > 255) || ($b > 255) || ($c > 255) || ($d > 255)) { $result = 1003 } @@ -232,6 +236,13 @@ my %token_validator = ( 'x' => [ 5, sub { my ($type, $s) = @_; my $result = 0; + + # Check to see if someone put an IP address in a hostname + # field. The motivation for this was MX records where many + # people expect an IP address to be a valid response, but I + # see no harm in enforcing it elsewhere. + return 1011 if $s =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\.?$/; + # check all parts for (split /\./, $s) { @@ -259,7 +270,7 @@ my %token_validator = ( 's' => [ 10, sub { my ($type, $s) = @_; my $result = 0; - # TODO : Validation needed? + # TODO : Validation needed? return $result; }], 'p' => [ 11, sub { @@ -268,13 +279,13 @@ my %token_validator = ( # check all parts for (split /\./, $s) { - return 1005 unless /^[-[a-z0-9]+$/i; + return 1005 unless /^_?[-[a-z0-9]+$/i; return 1006 if /^-/; return 1007 if /-$/; } return $result; }], - 'mname' => [ 12, sub { + 'mname' => [ 12, sub { my ($type, $s) = @_; my $result = 0; # check all parts @@ -289,7 +300,7 @@ my %token_validator = ( 'rname' => [ 13, sub { my ($type, $s) = @_; my $result = 0; - + # check all parts my @parts = split /\./, $s; return 1009 if @parts < 3; @@ -330,16 +341,31 @@ my %token_validator = ( 'n' => [ 19, sub { my ($type, $s) = @_; my $result = validate_integer($s, 65535); - + return 1010 if ($s==2)||($s==5)||($s==6)||($s==12)||($s==15)||($s==252); return $result; }], 'rdata' => [ 20, sub { my ($type, $s) = @_; - # TODO : Validation needed? + # TODO : Validation needed? my $result = 0; return $result; + }], + 'port' => [ 21, sub { + my ($type, $s) = @_; + my $result = validate_integer($s, 65536); + return $result; + }], + 'priority' => [ 22, sub { + my ($type, $s) = @_; + my $result = validate_integer($s, 65536); + return $result; + }], + 'weight' => [ 23, sub { + my ($type, $s) = @_; + my $result = validate_integer($s, 65536); + return $result; }], @@ -402,7 +428,7 @@ sub validate_line ($) { $ip = $token; } - + # if (length($ip) && ($mask[$c] eq 'x')) { @@ -410,14 +436,14 @@ sub validate_line ($) $tmp =~ s/\.$//; push @{$$result[3]}, $tmp; } - + # perform validation - + my $tv = &{$$validator[1]}($type, $token); if ($tv) { $$result[0] ^= (2 ** $$validator[0]); - $$result[1] .= + $$result[1] .= "\npos $c; $mask[$c]; $validation_msg{$tv}"; } } @@ -432,7 +458,7 @@ sub validate_line ($) if ($mand) { - $$result[0] ^= (2 ** $$validator[0]); + $$result[0] ^= (2 ** $$validator[0]); $$result[1] .= "\npos $c; $mask[$c]; ". $token_name{$mask[$c]}.' is mandatory'; } @@ -449,7 +475,7 @@ sub validate_line ($) $c++; } } - + if ($$result[0]) { $$result[1] = "expected: ".$line_type{$type}->[1]."\n". @@ -457,7 +483,7 @@ sub validate_line ($) } } - else + else { $result = [ 1, sprintf("unknown record type: #%02x", ord($type)) ]; @@ -467,7 +493,7 @@ sub validate_line ($) $$result[1] =~ s/^\n+//; $$result[1] =~ s/\n+/\n/g; - # result is now [ iErrno, sErrtxt, sRecordType, [ sFQDN ] ] + # result is now [ iErrno, sErrtxt, sRecordType, [ sFQDN ] ] return $result; } @@ -539,7 +565,7 @@ sub read_filter ($$) chomp; s/^\s+//; s/\s+$//; - + if (/^(\w+)\s+(.+)$/) { my ($key, $value) = ($1, $2); @@ -668,7 +694,7 @@ sub do_filterfile ($$) $$f{allowtype} = (keys %{$$f{allowtype}})[0]; $$f{allowtype} .= $opt{T}; - + my $allowtyperegex = make_char_regexp($$f{allowtype}); if ($$f{extralog}) @@ -697,11 +723,6 @@ sub do_filterfile ($$) } for my $zonefile (@zonefiles) { - unless ($opt{s}) - { - next if $zonefile =~ /$FILESUFFIXREGEXP/i; - } - my $info = 0; my $filehandle = \*STDIN; my $fopen = 1; @@ -713,7 +734,7 @@ sub do_filterfile ($$) { my $temp = ($zonefile eq '-') ? '' : $zonefile; p $output, "File $temp"; - + %loreg = (); my $errs = 0; my $lno = 0; @@ -759,12 +780,11 @@ sub do_filterfile ($$) # Check $$v[3] against allowed fqdn:s:wq! if (keys %{$$f{deny}}) { - # my $patterns = regexped_patterns($$f{deny}); # Default ALLOW ALL $ok = $fqdnok = 1; $reason = 'default allow ^.*$'; - + for my $pat (@{$patterns}) { for (@{$$v[3]}) @@ -796,9 +816,9 @@ sub do_filterfile ($$) } } } # if deny/allow - } # if fqdn + } # if fqdn } # if recordtype ok - } # + } if ($ok && length($line)) { @@ -810,13 +830,13 @@ sub do_filterfile ($$) { $errs++; $perrs_total++; - p $output, " line $lno; err -2; $line"; + p $output, " line $lno; err -2; $line"; p $output, " use of fqdn denied; $reason"; if ($opt{I}) { - print STDOUT "# line $lno; err -2; $line\n"; + print STDOUT "# line $lno; err -2; $line\n"; print STDOUT "# use of fqdn denied; $reason\n"; - } + } } } } @@ -832,8 +852,8 @@ sub do_filterfile ($$) } } - - # Close all extra logfiles + + # Close all extra logfiles for my $el (@extralogs) { if (close($$el[0])) @@ -864,151 +884,48 @@ sub do_filterfile ($$) my $files = funiq(@ARGV); +sub usage { + print <<"--EOT"; +valtz $VERSION - validate tinydns-data files -if ($opt{h} || $opt{H} || $opt{'?'}) -{ - print <<"--EOT"; -valtz $VERSION, $COPYRIGHT -validates tinydns-data zone files Usage: - $0 [-hfFqrRiItTx] + $0 [-r] [-R] [-i] tinydns-file1 [tinydns-file2...] - -h shows this help. + $0 [-HiIqrRt] [-T types] -f tinydns-file1 [tinydns-file2 ...] + $0 valtz [-fHiIqrRt] [-T types] -F filter-file1 [filter-file2 ...] - -f filter (don't just validate) file and output accepted lines to STDOUT. - +Flags: + -h print usage information + -f filter invalid lines (filter mode) + -F filter using configuration files (advanced filter mode) + -r allow "fqdn" fields to be empty + -R allow "mname" and "p" fields to be empty + -i allow "ip" fields to be empty + -I include rejected lines as comments (filtering only) + -q don't print valid lines to standard out (filtering only) + -t don't ignore comment lines (filtering only) + -T allow additional record types (advanced filtering only) - -F treat files as filter configuration files for more advanced filtering. - These filterfiles one or several of the following filter directives: - - zonefile - zonefile file: - Defines the file(s) to be filtered. Can be a globbed value, like - /var/zones/external/* - - extralog - Defines an extra logfile that the STDERR output will be copied for - this specific filterfile. Useful if you have a lot of filterfiles - and want to separate the logs. - - deny - deny file: - Defines a zonepattern to explicitly DENY after implicitly allowing all. - (cannot be combined with allow) - - allow - allow file: - Defines a zonepattern to explicitly ALLOW after implicitly denying all. - - allowtype - Explicitly sets the allowed recordtypes. Note that even comments - has to be allowed (but these will not result in errors unless -t) - to be copied to the output. - - Multiple zonefile, allow- and deny-lines are allowed, but also the - alternative file:-line that points to a textfile containing one - value per line. - - - -r allows fqdn to be empty thus denoting the root. - This is also allowed per default when doing implict allow - see deny, - or when specifying 'allow .', i.e. explictly allowing root as such. - (cannot be combined with deny) - - - -R relaxes the validation and allows empty mname and p-fields.xi - This is probably not very useful. - - - -i allows the ip-fields to be empty as well. These will then not generate any - records. - - - -I Include rejected lines as comments in output (valid when filtering). - - - -q Do not echo valid lines to STDOUT. - - -s DO NOT ignore files ending with ,v ~ .bak .log .old .swp .tmp - which is done per default. - - - -t Give error even on #comment-lines when they are not allowed. - (These errors are silently ignored per default) - - - -T - A commandline way to explicitly set the allowed recordtypes. - This is _concatenated_ to the allowtype-allowed recordtypes. - - -x Exit with non-null exit code on errors; i.e. make errors detectable by - e.g. shell scripts; 1 = validation error, 2 = permission error, - 3 = combination of 1 and 2. - - - -All errors in the zonefiles are sent to STDERR. - - Example; simple use: - valtz zone-bodin-org - - Example; simple filter-use; - valtz -f /etc/zones/zone-* \ - >/etc/tinydns/data.filtered \ - 2>/var/log/tinydns/valtz.log - - Example; filterfile use; - valtz -F /etc/zones/filter/zones-otto \ - >/etc/tinydns/data.otto \ - 2>/var/log/tinydns/valtz.log - - - Example filterfile for using as import from primary (as above): - zonefile /var/zones/external/otto/zone-* - deny bodin.org - deny x42.com - extralog /var/log/tinydns/external-otto.log - - Example #2, strict filter for a certain user editing just A-records - - zonefile /home/felix/zones/zone-fl3x-net - allow fl3x.net - allowtype + - extralog /var/log/tinydns/fl3x-net.log - - Example #3, export filter to secondary - - zonefile /var/zones/primary/zone-* - # just allow OUR zones to be exported, not to annoy secondary partner - allow file:/var/zones/primary-zones.txt - # don't allow any other types than this; e.g. comments won't be exported - allowtype Z + @ . C - extralog /var/log/tinydns/primary-export.log +Errors are generally printed to standard error, and the exit code +shall reflect the presense of both usage and validation errors. See +the man page for details. --EOT - exit 0; } -elsif (@{$files} == 0) -{ - print <<"--EOT"; -valtz $VERSION, $COPYRIGHT -validates tinydns-data zone files -Usage: - Simple validation: - $0 [-qrRix] - Simple filtering: - $0 -f[qrRiItTx] - Extensive filtering: - $0 -F[qrRiItTx] - More help and information about options: - $0 -h +if ($opt{h} || $opt{H} || $opt{'?'}) { + usage(); ---EOT + # If they asked for help, ignore whatever else they may have done + # wrong. exit 0; } +if (@{$files} == 0) { + usage(); + exit 4; +} if ($opt{F}) { @@ -1027,11 +944,6 @@ else for my $zonefile (sort @{$files}) { - unless ($opt{s}) - { - next if $zonefile =~ /$FILESUFFIXREGEXP/i; - } - my $filehandle = \*STDIN; my $fopen = 1; if ($zonefile ne '-') @@ -1063,7 +975,7 @@ else { print STDOUT "# line $lno; err $$v[0] $line print STDOUT "# $$v[1]; \n"; - } + } } else { @@ -1084,7 +996,7 @@ else } } -if ($opt{x} && ($verrs_total + $perrs_total)) +if ($verrs_total + $perrs_total) { my $exitcode = $verrs_total > 0 ? 1 : 0; $exitcode += $perrs_total > 0 ? 2 : 0;