Changeset 234


Ignore:
Timestamp:
02/16/12 16:14:36 (12 years ago)
Author:
Kris Deugau
Message:

/trunk

Validation for addRec() now functional with %validators hash
Several more tweaks and cleanups in A, AAAA, PTR, and A/AAAA+PTR records
dns.cgi now reports WARN conditions on add record
Minor typos in reclist.tmpl and record.tmpl fixed

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/DNSDB.pm

    r233 r234  
    205205  return 0 if $parnet->addr =~ /:/ && $$val =~ /\./;
    206206
    207   if ($$addr && $$val =~ /^\d[\d:]+\d$/) {
     207  if ($$addr && $$val =~ /^[\da-fA-F][\da-fA-F:]+[\da-fA-F]$/) {
    208208    # the only case where NetAddr::IP's acceptance of legitimate IPs is "correct" is for a proper IPv6 address.
    209209    # the rest we have to restructure before fiddling.  *sigh*
     
    260260
    261261  # Check IP is well-formed, and that it's a v4 address
     262  # Fail on "compact" IPv4 variants, because they are not consistent and predictable.
     263  return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv4 address")
     264        unless ${$args{val}} =~ /^\d+\.\d+\.\d+\.\d+$/;
    262265  return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv4 address")
    263266        unless $args{addr} && !$args{addr}->{isv6};
    264267  # coerce IP/value to normalized form for storage
    265268  ${$args{val}} = $args{addr}->addr;
    266 
    267   # Add the necessary fields.
    268   ${$args{fields}} = 'domain_id,';
    269   push @{$args{vallist}}, $args{id};
    270269
    271270  return ('OK','OK');
     
    337336      ${$args{val}} = $args{addr}->addr;
    338337    } else {
    339       ${$args{val}} =~ s/^([:.]*)/ZONE$1/;
     338      if (${$args{val}} =~ /\./) {
     339        # looks like a v4 or fragment
     340        if (${$args{val}} =~ /^\d+\.\d+\.\d+\.\d+$/) {
     341          # woo!  a complete IP!  validate it and normalize, or fail.
     342          $args{addr} = NetAddr::IP->new(${$args{val}})
     343                or return ('FAIL', "IP/value looks like IPv4 but isn't valid");
     344          ${$args{val}} = $args{addr}->addr;
     345        } else {
     346          ${$args{val}} =~ s/^\.*/ZONE./;
     347        }
     348      } elsif (${$args{val}} =~ /[a-f:]/) {
     349        # looks like a v6 or fragment
     350        ${$args{val}} =~ s/^:*/ZONE::/ if !$args{addr};
     351        if ($args{addr}) {
     352          if ($args{addr}->addr =~ /^0/) {
     353            ${$args{val}} =~ s/^:*/ZONE::/;
     354          } else {
     355            ${$args{val}} = $args{addr}->addr;
     356          }
     357        }
     358      } else {
     359        # bare number (probably).  These could be v4 or v6, so we'll
     360        # expand on these on creation of a reverse zone.
     361        ${$args{val}} = "ZONE,${$args{val}}";
     362      }
     363      ${$args{host}} =~ s/\.*$/\.$config{domain}/ if ${$args{host}} !~ /$config{domain}$/;
    340364    }
    341 
    342     ${$args{fields}} = _recparent($args{defrec},$args{revrec}).",";
    343     push @{$args{vallist}}, $args{id};
    344365
    345366# Multiple PTR records do NOT generally do what most people believe they do,
     
    415436  ${$args{val}} = $args{addr}->addr;
    416437
    417   # Add the necessary fields.
    418   ${$args{fields}} = 'domain_id,';
    419   push @{$args{vallist}}, $args{id};
    420 
    421438  return ('OK','OK');
    422439} # done AAAA record
     
    482499        return ('WARN', $msg);
    483500      }
    484       ${$args{fields}} .= "domain_id,";
    485       push @{$args{vallist}}, ${$args{domid}};
    486501
    487502    } else {
     
    522537        return ('FAIL',"A+PTR record must be a valid IPv4 address or fragment")
    523538                if ${$args{val}} =~ /:/;
    524         ${$args{val}} =~ s/^ZONE,/ZONE./;       # Clean up after uncertain IP-fragment-type from _validate_12
     539        ${$args{val}} =~ s/^ZONE,/ZONE./;       # Clean up after uncertain IP-fragment-type from _validate_12
    525540      } elsif (${$args{rectype}} == 65281) {
    526541        return ('FAIL',"AAAA+PTR record must be a valid IPv6 address or fragment")
    527542                if ${$args{val}} =~ /\./;
    528         ${$args{val}} =~ s/^ZONE,/ZONE::/;      # Clean up after uncertain IP-fragment-type from _validate_12
     543        ${$args{val}} =~ s/^ZONE,/ZONE::/;      # Clean up after uncertain IP-fragment-type from _validate_12
    529544      }
    530545    } else {
     
    533548      # sanely, and you'd end up with guaranteed over-replicated PTR records that would
    534549      # confuse the hell out of pretty much anything that uses them.
     550##fixme: make this a config flag?
    535551      return ('FAIL', "$typemap{${$args{rectype}}} records not allowed in default domains");
    536552    }
     
    18221838# and weight/port for SRV
    18231839# Returns a status code and detail message in case of error
     1840##fixme:  pass a hash with the record data, not a series of separate values
    18241841sub addRec {
    18251842  $errstr = '';
     
    18311848
    18321849  my $host = shift;
    1833   my $rectype = shift;
     1850  my $rectype = shift;  # reference so we can coerce it if "+"-types can't find both zones
    18341851  my $val = shift;
    18351852  my $ttl = shift;
     
    18481865  return ('FAIL', "TTL must be numeric") unless $ttl =~ /^\d+$/;
    18491866
    1850 
    1851 ## possible contents for record types
    1852 # (A/AAAA+)PTR: IP + (FQDN or bare hostname to have ADMINDOMAIN appended?)
    1853 # (A/AAAA+)PTR template: IP or netblock + (fully-qualified hostname pattern or bare hostname pattern to have
    1854 # ADMINDOMAIN appended?)
    1855 # A/AAAA: append parent domain if not included, validate IP
    1856 # NS,MX,CNAME,SRV,TXT: append parent domain if not included
    1857 
    1858 # ickypoo.  can't see a handy way to really avoid hardcoding these here...  otoh, these aren't
    1859 # really mutable, it's just handy to have them in a DB table for reordering
    1860 # 65280 | A+PTR
    1861 # 65281 | AAAA+PTR
    1862 # 65282 | PTR template
    1863 # 65283 | A+PTR template
    1864 # 65284 | AAAA+PTR template
    1865 
    1866   # can only validate parenthood on IPs in live zones;  group default records are likely to contain "ZONE"
    1867   if ($revrec eq 'y' && $defrec eq 'n') {
    1868     if ($rectype == $reverse_typemap{PTR} || $rectype == 65280 || $rectype == 65281) {
    1869       return ('FAIL', "IP or IP fragment $val is not within ".revName($dbh, $id))
    1870         unless _ipparent($dbh, $defrec, $revrec, $val, $id, \$addr);
    1871       $revid = $id;
    1872     }
    1873     if ($rectype == 65280 || $rectype == 65281) {
    1874         # check host to see if it's managed here.  coerce down to PTR if not.
    1875         # Split $host and work our way up the hierarchy until we run out of parts to add, or we find a match
    1876         # Note we do not do '$checkdom = shift @hostbits' right away, since we want to be able to support
    1877         # private TLDs.
    1878       my @hostbits = reverse(split(/\./, $host));
    1879       my $checkdom = '';
    1880       my $sth = $dbh->prepare("SELECT count(*),domain_id FROM domains WHERE domain = ? GROUP BY domain_id");
    1881       foreach (@hostbits) {
    1882         $checkdom = "$_.$checkdom";
    1883         $checkdom =~ s/\.$//;
    1884         $sth->execute($checkdom);
    1885         my ($found, $parid) = $sth->fetchrow_array;
    1886         if ($found) {
    1887           $domid = $parid;
    1888           last;
    1889         }
    1890       }
    1891       if (!$domid) {
    1892         # no domain found;  set the return code and message, then coerce type down to PTR
    1893         $retcode = 'WARN';
    1894         $retmsg = "Record added as PTR instead of $typemap{$rectype};  domain not found for $host";
    1895         $rectype = $reverse_typemap{PTR};
    1896       }
    1897     }
    1898     # types 65282, 65283, 65284 left
    1899   } elsif ($revrec eq 'n' && $defrec eq 'n') {
    1900     # Forward zone.  Validate IPs where we know they *MUST* be correct,
    1901     # check to see if we manage the reverse zone on A(AAA)+PTR,
    1902     # append the domain on hostnames without it.
    1903     if ($rectype == $reverse_typemap{A} || $rectype == 65280) {
    1904       return ('FAIL',$typemap{$rectype}." record must be a valid IPv4 address")
    1905         unless $addr && !$addr->{isv6};
    1906       $val = $addr->addr;
    1907     }
    1908     if ($rectype == $reverse_typemap{AAAA} || $rectype == 65281) {
    1909       return ('FAIL',$typemap{$rectype}." record must be a valid IPv6 address")
    1910         unless $addr && $addr->{isv6};
    1911       $val = $addr->addr;
    1912     }
    1913     if ($rectype == 65280 || $rectype == 65281) {
    1914       # The ORDER BY here makes sure we pick the *smallest* revzone parent.  Just In Case.
    1915       ($revid) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?".
    1916         " ORDER BY masklen(revnet) DESC", undef, ($val));
    1917       if (!$revid) {
    1918         $retcode = 'WARN';
    1919         $retmsg = "Record added as ".($rectype == 65280 ? 'A' : 'AAAA')." instead of $typemap{$rectype}; ".
    1920                 "reverse zone not found for $val";
    1921         $rectype = $reverse_typemap{A} if $rectype == 65280;
    1922         $rectype = $reverse_typemap{AAAA} if $rectype == 65281;
    1923         $revid = 0;     # Just In Case
    1924       }
    1925     }
    1926     my $parstr = domainName($dbh,$id);
    1927     $host .= ".$parstr" if $host !~ /$parstr$/;
    1928   }
    1929 
    1930 # Validate IPs in MX, NS, SRV records?
    1931 # hmm..  this might work.  except possibly for something pointing to "deadbeef.ca".  <g>
    1932 #  if ($type == $reverse_typemap{NS} || $type == $reverse_typemap{MX} || $type == $reverse_typemap{SRV}) {
    1933 #    if ($val =~ /^\s*[\da-f:.]+\s*$/) {
    1934 #      return ('FAIL',"$val is not a valid IP address") if !$addr;
    1935 #    }
    1936 #  }
    1937 
    1938 # basic fields:  immediate parent ID, host, type, val, ttl
    1939   my $fields = _recparent($defrec,$revrec).",host,type,val,ttl";
    1940   my $vallen = "?,?,?,?,?";
    1941   my @vallist = ($id,$host,$rectype,$val,$ttl);
    1942 
    1943   if ($defrec eq 'n' && ($rectype == 65280 || $rectype == 65281)) {
    1944     $fields .= ",".($revrec eq 'n' ? 'rdns_id' : 'domain_id');
    1945     $vallen .= ",?";
    1946     push @vallist, ($revrec eq 'n' ? $revid : $domid);
    1947   }
    1948 
    1949 # MX and SRV specials
    1950   my $dist;
    1951   if ($rectype == $reverse_typemap{MX} or $rectype == $reverse_typemap{SRV}) {
    1952     $dist = shift;
    1953     return ('FAIL',"Distance is required for $typemap{$rectype} records") unless defined($dist);
    1954     $dist =~ s/\s*//g;
    1955     return ('FAIL',"Distance is required, and must be numeric") unless $dist =~ /^\d+$/;
    1956     $fields .= ",distance";
    1957     $vallen .= ",?";
    1958     push @vallist, $dist;
    1959   }
    1960   my $weight;
    1961   my $port;
    1962   if ($rectype == $reverse_typemap{SRV}) {
    1963     # check for _service._protocol.  NB:  RFC2782 does not say "MUST"...  nor "SHOULD"...
    1964     # it just says (paraphrased) "... is prepended with _ to prevent DNS collisions"
    1965     return ('FAIL',"SRV records must begin with _service._protocol [$host]")
    1966         unless $host =~ /^_[A-Za-z]+\._[A-Za-z]+\.[a-zA-Z0-9-]+/;
    1967     $weight = shift;
    1968     $port = shift;
    1969     return ('FAIL',"Port and weight are required for SRV records") unless defined($weight) && defined($port);
    1970     $weight =~ s/\s*//g;
    1971     $port =~ s/\s*//g;
    1972     return ('FAIL',"Port and weight are required, and must be numeric")
    1973         unless $weight =~ /^\d+$/ && $port =~ /^\d+$/;
    1974     $fields .= ",weight,port";
    1975     $vallen .= ",?,?";
    1976     push @vallist, ($weight,$port);
    1977   }
     1867  # Quick check on hostname parts.  Note the regex is more forgiving than the error message;
     1868  # domain names technically are case-insensitive, and we use printf-like % codes for a couple
     1869  # of types.  Other things may also be added to validate default records of several flavours.
     1870  return ('FAIL', "Hostnames may not contain anything other than (0-9 a-z . _)")
     1871        if $defrec eq 'n' && $host !~ /^[0-9a-z_%.]+$/i;
     1872
     1873  # Collect these even if we're only doing a simple A record so we can call *any* validation sub
     1874  my $dist = shift;
     1875  my $port = shift;
     1876  my $weight = shift;
     1877
     1878  my $fields;
     1879  my @vallist;
     1880
     1881  # Call the validation sub for the type requested.
     1882  ($retcode,$retmsg) = $validators{$$rectype}($dbh, (defrec => $defrec, revrec => $revrec, id => $id,
     1883        host => \$host, rectype => $rectype, val => \$val, addr => $addr,
     1884        dist => \$dist, port => \$port, weight => \$weight,
     1885        fields => \$fields, vallist => \@vallist) );
     1886
     1887  return ($retcode,$retmsg) if $retcode eq 'FAIL';
     1888
     1889  # Set up database fields and bind parameters
     1890  $fields .= "host,type,val,ttl,"._recparent($defrec,$revrec);
     1891  push @vallist, ($host,$$rectype,$val,$ttl,$id);
     1892  my $vallen = '?'.(',?'x$#vallist);
    19781893
    19791894  # Allow transactions, and raise an exception on errors so we can catch it later.
     
    19831898
    19841899  eval {
    1985     $dbh->do("INSERT INTO ".($defrec eq 'y' ? 'default_' : '')."records ($fields) VALUES ($vallen)",
     1900    $dbh->do("INSERT INTO "._rectable($defrec, $revrec)."records ($fields) VALUES ($vallen)",
    19861901        undef, @vallist);
    19871902    $dbh->commit;
  • trunk/dns.cgi

    r227 r234  
    486486      $session->clear('resultmsg');
    487487    }
     488    if ($session->param('warnmsg')) {
     489      $page->param(warnmsg => $session->param('warnmsg'));
     490      $session->clear('warnmsg');
     491    }
    488492    if ($session->param('errmsg')) {
    489493      $page->param(errmsg => $session->param('errmsg'));
     
    531535        unless ($permissions{admin} || $permissions{record_create});
    532536
    533 ##fixme: this should probably go in DNSDB::addRec(), need to ponder what to do about PTR and friends
    534     # prevent out-of-domain records from getting added by appending the domain, or DOMAIN for default records
    535 #    my $pname = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid}));
    536 #    $webvar{name} =~ s/\.*$/\.$pname/ if $webvar{name} !~ /$pname$/;
    537 
    538537    my @recargs = ($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid},
    539         $webvar{name},$webvar{type},$webvar{address},$webvar{ttl});
     538        $webvar{name},\$webvar{type},$webvar{address},$webvar{ttl});
    540539    if ($webvar{type} == $reverse_typemap{MX} or $webvar{type} == $reverse_typemap{SRV}) {
    541540      push @recargs, $webvar{distance};
     
    548547    my ($code,$msg) = addRec(@recargs);
    549548
    550     if ($code eq 'OK') {
     549    if ($code eq 'OK' || $code eq 'WARN') {
     550      my $restr;
    551551      if ($webvar{defrec} eq 'y') {
    552         my $restr = "Added default record '$webvar{name} $typemap{$webvar{type}}";
     552        $restr = "Added default record '$webvar{name} $typemap{$webvar{type}}";
    553553        $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX';
    554554        $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]"
     
    556556        $restr .= " $webvar{address}', TTL $webvar{ttl}";
    557557        logaction(0, $session->param("username"), $webvar{parentid}, $restr);
    558         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
    559558      } else {
    560         my $restr = "Added record '$webvar{name} $typemap{$webvar{type}}";
     559        $restr = "Added record '$webvar{name} $typemap{$webvar{type}}";
    561560        $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX';
    562561        $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]"
     
    564563        $restr .= " $webvar{address}', TTL $webvar{ttl}";
    565564        logaction($webvar{parentid}, $session->param("username"), parentID($webvar{parentid}, 'dom', 'group'), $restr);
    566         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
    567       }
     565      }
     566      my %pageparams = (page => "reclist", id => $webvar{parentid},
     567        defrec => $webvar{defrec}, revrec => $webvar{revrec});
     568      $pageparams{warnmsg} = $msg."<br><br>\n".$restr if $code eq 'WARN';
     569      $pageparams{resultmsg} = $restr if $code eq 'OK';
     570      changepage(%pageparams);
    568571    } else {
    569572      $page->param(failed       => 1);
     
    574577      $page->param(parentid     => $webvar{parentid});
    575578      $page->param(defrec       => $webvar{defrec});
     579      $page->param(revrec       => $webvar{revrec});
    576580      $page->param(id           => $webvar{id});
     581      $page->param(fwdzone      => $webvar{revrec} eq 'n');
    577582      fill_recdata();   # populate the form... er, mostly.
    578583      $page->param(name => $webvar{name});
  • trunk/templates/reclist.tmpl

    r224 r234  
    88<div class="result"><TMPL_VAR NAME=resultmsg></div>
    99</TMPL_IF>
     10<TMPL_IF warnmsg>
     11<div class="warn"><TMPL_VAR NAME=warnmsg></div>
     12</TMPL_IF>
    1013<TMPL_IF errmsg>
    11 <div class='errmsg'><TMPL_VAR NAME=errmsg></div>
     14<div class="errmsg"><TMPL_VAR NAME=errmsg></div>
    1215</TMPL_IF>
    1316
  • trunk/templates/record.tmpl

    r226 r234  
    2424
    2525    <table border="0" cellspacing="2" cellpadding="2" width="100%">
    26 <TMPL_IF failed>        <tr><td class="errhead" colspan="2">Error <TMPL_VAR NAME=wastrying> record: <TMPL_VARNAME=errmsg></td></tr></TMPL_IF>
     26<TMPL_IF failed>        <tr><td class="errhead" colspan="2">Error <TMPL_VAR NAME=wastrying> record: <TMPL_VAR NAME=errmsg></td></tr></TMPL_IF>
    2727        <tr class="tableheader"><td align="center" colspan="2"><TMPL_VAR NAME=todo>: <TMPL_VAR NAME=dohere></td></tr>
    2828        <tr class="datalinelight">
Note: See TracChangeset for help on using the changeset viewer.