]> gitweb.michael.orlitzky.com - valtz.git/blobdiff - valtz
Show only a usage summary when -h is passed.
[valtz.git] / valtz
diff --git a/valtz b/valtz
old mode 100644 (file)
new mode 100755 (executable)
index c68c120..12d22c9
--- a/valtz
+++ b/valtz
@@ -43,26 +43,29 @@ 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('?fFhHiIqrRstT:', \%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 +79,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 +104,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 +121,7 @@ my %record_type = (
     "'" => 'TXT',
     '^' => 'PTR',
     'C' => 'CNAME',
+    'S' => 'SRV',
     'Z' => 'SOA',
     ':' => 'GENERIC'
 );
@@ -131,6 +139,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 +156,7 @@ sub validate_integer
     {
         my $i = $1;
 
-        $result = 1008 if $boundary && ($i >= $boundary); 
+        $result = 1008 if $boundary && ($i >= $boundary);
     }
     else
     {
@@ -179,11 +189,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 +212,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 +224,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 +242,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 +276,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 +285,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 +306,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 +347,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 +434,7 @@ sub validate_line ($)
                             {
                                 $ip = $token;
                             }
-              
+
                             #
                             if (length($ip) && ($mask[$c] eq 'x'))
                             {
@@ -410,14 +442,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 +464,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 +481,7 @@ sub validate_line ($)
                     $c++;
                 }
             }
-            
+
             if ($$result[0])
             {
                 $$result[1] = "expected: ".$line_type{$type}->[1]."\n".
@@ -457,7 +489,7 @@ sub validate_line ($)
             }
 
         }
-        else 
+        else
         {
             $result = [ 1, sprintf("unknown record type: #%02x",
                 ord($type)) ];
@@ -467,7 +499,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 +571,7 @@ sub read_filter ($$)
             chomp;
             s/^\s+//;
             s/\s+$//;
-            
+
             if (/^(\w+)\s+(.+)$/)
             {
                 my ($key, $value) = ($1, $2);
@@ -668,7 +700,7 @@ sub do_filterfile ($$)
 
     $$f{allowtype} = (keys %{$$f{allowtype}})[0];
     $$f{allowtype} .= $opt{T};
-    
+
     my $allowtyperegex = make_char_regexp($$f{allowtype});
 
     if ($$f{extralog})
@@ -701,7 +733,7 @@ sub do_filterfile ($$)
         {
             next if $zonefile =~ /$FILESUFFIXREGEXP/i;
         }
+
         my $info = 0;
     my $filehandle = \*STDIN;
     my $fopen = 1;
@@ -713,7 +745,7 @@ sub do_filterfile ($$)
         {
             my $temp = ($zonefile eq '-') ? '<STDIN>' : $zonefile;
             p $output, "File $temp";
-    
+
             %loreg = ();
             my $errs = 0;
             my $lno = 0;
@@ -759,12 +791,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 +827,9 @@ sub do_filterfile ($$)
                                             }
                                         }
                                     } # if deny/allow
-                                } # if fqdn 
+                                } # if fqdn
                             } # if recordtype ok
-                        } # 
+                        }
 
                         if ($ok && length($line))
                         {
@@ -810,13 +841,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 +863,8 @@ sub do_filterfile ($$)
         }
     }
 
-    
-      # Close all extra logfiles
+
+    # Close all extra logfiles
     for my $el (@extralogs)
     {
         if (close($$el[0]))
@@ -864,151 +895,49 @@ 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] <file(s)>
-
-  -h shows this help.
+ $0 [-r] [-R] [-i] tinydns-file1 [tinydns-file2...]
 
+ $0 [-HiIqrRst] [-T types] -f tinydns-file1 [tinydns-file2 ...]
 
-  -f filter (don't just validate) file and output accepted lines to STDOUT.
-     
+ $0 valtz [-fHiIqrRst] [-T types] -F filter-file1 [filter-file2 ...]
 
-  -F treat files as filter configuration files for more advanced filtering.
-     These filterfiles one or several of the following filter directives:
+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)
+ -s           don't skip temporary and backup files
+ -t           don't ignore comment lines (filtering only)
+ -T <types>   allow additional record types (advanced filtering only)
 
-     zonefile <zonefilepath>
-     zonefile file:<path to textfile including zonefilepaths>
-        Defines the file(s) to be filtered. Can be a globbed value, like
-        /var/zones/external/*
-
-     extralog <logfile>
-        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 <zonepattern>
-     deny file:<path to <zonepatternfile>
-        Defines a zonepattern to explicitly DENY after implicitly allowing all.
-        (cannot be combined with allow)
-
-     allow <zonepattern>
-     allow file:<path to <zonepatternfile>
-        Defines a zonepattern to explicitly ALLOW after implicitly denying all.
-
-     allowtype <recordtype character(s)>
-        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<types>
-     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] <zonefiles>
-  Simple filtering:
-    $0 -f[qrRiItTx] <zonefiles>
-  Extensive filtering:
-    $0 -F[qrRiItTx] <zonefiles>
 
-  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})
 {
@@ -1031,7 +960,7 @@ else
         {
             next if $zonefile =~ /$FILESUFFIXREGEXP/i;
         }
-    
+
     my $filehandle = \*STDIN;
     my $fopen = 1;
     if ($zonefile ne '-')
@@ -1063,7 +992,7 @@ else
                         {
                              print STDOUT "# line $lno; err $$v[0] $line
                              print STDOUT "# $$v[1]; \n";
-                        }    
+                        }
                     }
                     else
                     {
@@ -1084,7 +1013,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;