Changeset 725 for branches/stable


Ignore:
Timestamp:
06/20/16 13:18:07 (9 years ago)
Author:
Kris Deugau
Message:

/branches/stable

Merge /trunk through r721; minor tweaks and updates required for RPC
from IPDB

Location:
branches/stable
Files:
9 edited
1 copied

Legend:

Unmodified
Added
Removed
  • branches/stable

  • branches/stable/DNSDB.pm

    r696 r725  
    222222                template_skip_0 => 0,   # publish .0 by default
    223223                template_skip_255       => 0,   # publish .255 by default
     224                # allow TXT records to be dealt with mostly automatically by DNS server?
     225                autotxt         => 1,
    224226        );
    225227
     
    252254  # Several settings are booleans.  Handle multiple possible ways of setting them.
    253255  for my $boolopt ('log_failures', 'force_refresh', 'lowercase', 'usecache',
    254         'template_skip_0', 'template_skip_255') {
     256        'template_skip_0', 'template_skip_255', 'autotxt') {
    255257    if ($self->{$boolopt} ne '1' && $self->{$boolopt} ne '0') {
    256258      # true/false, on/off, yes/no all valid.
     
    990992    # Not strictly true, but SRV records not following this convention won't be found.
    991993    return ('FAIL',"SRV records must begin with _service._protocol [${$args{host}}]")
    992         unless ${$args{host}} =~ /^_[A-Za-z]+\._[A-Za-z]+\.[a-zA-Z0-9-]+/;
     994        unless ${$args{host}} =~ /^_[A-Za-z-]+\._[A-Za-z]+\.[a-zA-Z0-9-]+/;
    993995
    994996    # SRV target check - IP addresses not allowed.  Must be a more or less well-formed hostname.
     
    18521854      $cfg->{template_skip_0}   = $1 if /^template_skip_0\s*=\s*([a-z01]+)/i;
    18531855      $cfg->{template_skip_255} = $1 if /^template_skip_255\s*=\s*([a-z01]+)/i;
     1856      $cfg->{autotxt}           = $1 if /^autotxt\s*=\s*([a-z01]+)/i;
    18541857# not supported in dns.cgi yet
    18551858#      $cfg->{templatedir}      = $1 if m{^templatedir\s*=\s*([a-z0-9/_.-]+)}i;
     
    23162319# Add a domain
    23172320# Takes a database handle, domain name, numeric group, boolean(ish) state (active/inactive),
    2318 # and user info hash (for logging).
     2321# and a default location indicator
    23192322# Returns a status code and message
    23202323sub addDomain {
     
    23412344  return ('FAIL', "Invalid characters in domain") if $domain !~ /^[a-zA-Z0-9_.-]+$/;
    23422345
    2343   my $sth = $dbh->prepare("SELECT domain_id FROM domains WHERE lower(domain) = lower(?)");
     2346  my $sth = $dbh->prepare("SELECT domain_id FROM domains WHERE lower(domain) = lower(?) AND default_location = ?");
    23442347  my $dom_id;
    23452348
    23462349# quick check to start to see if we've already got one
    2347   $sth->execute($domain);
     2350  $sth->execute($domain, $defloc);
    23482351  ($dom_id) = $sth->fetchrow_array;
    23492352
     
    23622365
    23632366    # get the ID...
    2364     ($dom_id) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE lower(domain) = lower(?)",
    2365         undef, ($domain));
     2367    ($dom_id) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE lower(domain) = lower(?) AND default_location = ?",
     2368        undef, ($domain, $defloc));
    23662369
    23672370    $self->_log(domain_id => $dom_id, group_id => $group,
     
    25372540  my $dbh = $self->{dbh};
    25382541  my $domain = shift;
    2539   my ($domid) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE lower(domain) = lower(?)",
    2540         undef, ($domain) );
     2542  my $location = shift;
     2543
     2544  # Note that location may be *empty*, but it may not be *undefined*
     2545  if (!defined($location)) {
     2546    $errstr = "Missing location";
     2547    return;
     2548  }
     2549
     2550  my ($domid) = $dbh->selectrow_array(
     2551        "SELECT domain_id FROM domains WHERE lower(domain) = lower(?) AND default_location = ?",
     2552        undef, ($domain, $location) );
    25412553  if (!$domid) {
    25422554    if ($dbh->err) {
     
    25582570  my $dbh = $self->{dbh};
    25592571  my $revzone = shift;
    2560   my ($revid) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet=?", undef, ($revzone) );
     2572  my $location  = shift;
     2573
     2574  # Note that location may be *empty*, but it may not be *undefined*
     2575  if (!defined($location)) {
     2576    $errstr = "Missing location";
     2577    return;
     2578  }
     2579
     2580  my ($revid) = $dbh->selectrow_array(
     2581        "SELECT rdns_id FROM revzones WHERE revnet = ? AND default_location = ?",
     2582        undef, ($revzone, $location) );
    25612583  if (!$revid) {
    25622584    if ($dbh->err) {
     
    26022624
    26032625# quick check to start to see if we've already got one
    2604   my ($rdns_id) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet=?", undef, ("$zone"));
     2626  my ($rdns_id) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet = ? AND default_location = ?",
     2627        undef, ("$zone", $defloc));
    26052628
    26062629  return ('FAIL', "Zone already exists") if $rdns_id;
     
    28432866  if ($args{revrec} eq 'n') {
    28442867    $args{sortby} = 'domain' if !$args{sortby} || !grep /^$args{sortby}$/, ('domain','group','status');
    2845     $sql = "SELECT domain_id AS zoneid,domain AS zone,status,groups.group_name AS group FROM domains".
    2846         " INNER JOIN groups ON domains.group_id=groups.group_id".
    2847         " WHERE domains.group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")".
     2868    $sql = q(SELECT
     2869                domain_id AS zoneid,
     2870                domain AS zone,
     2871                status,
     2872                groups.group_name AS group,
     2873                l.description AS location
     2874        FROM domains
     2875        LEFT JOIN locations l ON domains.default_location=l.location
     2876        INNER JOIN groups ON domains.group_id=groups.group_id ).
     2877        "WHERE domains.group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")".
    28482878        ($args{startwith} ? " AND domain ~* ?" : '').
    28492879        ($args{filter} ? " AND domain ~* ?" : '');
     
    28512881##fixme:  arguably startwith here is irrelevant.  depends on the UI though.
    28522882    $args{sortby} = 'revnet' if !$args{sortby} || !grep /^$args{sortby}$/, ('revnet','group','status');
    2853     $sql = "SELECT rdns_id AS zoneid,revnet AS zone,status,groups.group_name AS group FROM revzones".
    2854         " INNER JOIN groups ON revzones.group_id=groups.group_id".
     2883    $sql = q(SELECT
     2884                rdns_id AS zoneid,
     2885                revnet AS zone,
     2886                status,
     2887                groups.group_name AS group,
     2888                l.description AS location
     2889        FROM revzones
     2890        LEFT JOIN locations l ON revzones.default_location=l.location
     2891        INNER JOIN groups ON revzones.group_id=groups.group_id ).
    28552892        " WHERE revzones.group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")".
    28562893        ($args{startwith} ? " AND CAST(revnet AS VARCHAR) ~* ?" : '');
     
    34723509  my $sel = shift || 0;
    34733510
    3474   my $sth = $dbh->prepare("SELECT username,user_id FROM users WHERE group_id=?");
     3511  my $sth = $dbh->prepare("SELECT username,user_id FROM users WHERE group_id=? AND password <> 'RPC'");
    34753512  $sth->execute($grp);
    34763513
     
    36893726  my $self = shift;
    36903727  my $dbh = $self->{dbh};
    3691   my $grp = shift;
    3692   my $shdesc = shift;
    3693   my $comments = shift;
    3694   my $iplist = shift;
     3728  my %args = @_;
     3729
     3730  my $grp = $args{group};
     3731  my $shdesc = $args{desc};
     3732  my $comments = $args{comments};
     3733  my $iplist = $args{iplist};
    36953734
    36963735  # $shdesc gets set to the generated location ID if possible, but these can be de-undefined here.
     
    36983737  $iplist = '' if !$iplist;
    36993738
    3700   my $loc;
     3739  # allow requesting a specific location entry.
     3740  my $loc = $args{loc};
    37013741
    37023742  # Generate a location ID.  This is, by spec, a two-character widget.  We'll use [a-z][a-z]
    37033743  # for now;  676 locations should satisfy all but the largest of the huge networks.
    3704   # Not sure whether these are case-sensitive, or what other rules might apply - in any case
    3705   # the absolute maximum is 16K (256*256) since it's parsed by tinydns as a two-character field.
     3744
     3745  # just to be as clear as possible;  as per http://cr.yp.to/djbdns/tinydns-data.html:
     3746
     3747#For versions 1.04 and above: You may include a client location on each line. The line is ignored for clients
     3748#outside that location. Client locations are specified by % lines:
     3749#
     3750#     %lo:ipprefix
     3751#
     3752#means that IP addresses starting with ipprefix are in location lo. lo is a sequence of one or two ASCII letters.
     3753
     3754  # this has been confirmed by experiment;  locations "lo", "Lo", and "lO" are all distinct.
    37063755
    37073756# add just after "my $origloc = $loc;":
     
    37253774  eval {
    37263775    # Get the "last" location.  Note this is the only use for loc_id, because selecting on location Does Funky Things
    3727     ($loc) = $dbh->selectrow_array("SELECT location FROM locations ORDER BY loc_id DESC LIMIT 1");
    3728     ($loc) = ($loc =~ /^(..)/) if $loc;
    3729     my $origloc = $loc;
    3730     $loc = 'aa' if !$loc;       
     3776    my ($newloc) = $dbh->selectrow_array("SELECT location FROM locations ORDER BY loc_id DESC LIMIT 1");
     3777
     3778    no warnings qw(uninitialized);
     3779
     3780    my $ecnt = $dbh->prepare("SELECT count(*) FROM locations WHERE location LIKE ?");
     3781
     3782    if ($loc) {
     3783      $ecnt->execute($loc);
     3784      if (($ecnt->fetchrow_array())[0]) {
     3785        # too bad, so sad, requested location is unavailable.
     3786##fixme:  known failure case:  caller requests a location ID that is not two characters.
     3787        die "Requested location is already defined\n" if $args{reqonly};
     3788        # fall back to autoincrement
     3789      }
     3790      $newloc = $loc;
     3791    }
     3792
     3793    # Either the requested location ID is unavailable and the caller isn't too attached
     3794    # to it, OR, the caller hasn't specified a location ID.  (The second case should be
     3795    # far more common.)  Find the "next available" location identifier.
     3796
     3797    ($newloc) = ($newloc =~ /^(..)/) if $newloc;
     3798    my $origloc = $newloc;
     3799    $newloc = 'aa' if !$newloc;
    37313800    # Make a change...
    3732     $loc++;
    37333801    # ... and keep changing if it exists
    3734     while ($dbh->selectrow_array("SELECT count(*) FROM locations WHERE location LIKE ?", undef, ($loc.'%'))) {
    3735       $loc++;
    3736       ($loc) = ($loc =~ /^(..)/);
    3737       die "too many locations in use, can't add another one\n" if $loc eq $origloc;
     3802    while ($dbh->selectrow_array("SELECT count(*) FROM locations WHERE location LIKE ?", undef, ($newloc.'%'))) {
     3803      $newloc++;
     3804      ($newloc) = ($newloc =~ /^(..)/);
     3805      die "too many locations in use, can't add another one\n" if $newloc eq $origloc;
    37383806##fixme: really need to handle this case faster somehow
    37393807#if $loc eq $origloc die "<thwap> bad admin:  all locations used, your network is too fragmented";
    37403808    }
    3741     # And now we should have a unique location.  tinydns fundamentally limits the
    3742     # number of these but there's no doc on what characters are valid.
    3743     $shdesc = $loc if !$shdesc;
     3809    # And now we should have a unique location.
     3810    $shdesc = $newloc if !$shdesc;
    37443811    $dbh->do("INSERT INTO locations (location, group_id, iplist, description, comments) VALUES (?,?,?,?,?)",
    3745         undef, ($loc, $grp, $iplist, $shdesc, $comments) );
     3812        undef, ($newloc, $grp, $iplist, $shdesc, $comments) );
    37463813    $self->_log(entry => "Added location ($shdesc, '$iplist')");
     3814    $loc = $newloc;
    37473815    $dbh->commit;
    37483816  };
     
    41404208  $args{defrec} = 'n' if !$args{defrec};
    41414209
     4210  # RPC callers generally want the "true" IP.  Flag argument for those to bypass showrev_arpa
     4211##fixme:  this will still blow up if some idiot has actually stored .arpa names in the DB.
     4212# ... do we care?
     4213  $args{rpc} = 0 if !$args{rpc};
     4214
    41424215  # protection against bad or missing arguments
    41434216  $args{sortorder} = 'ASC' if !$args{sortorder} || !grep /^$args{sortorder}$/, ('ASC','DESC');
     
    41504223  $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 
    41514224  my $perpage = ($args{nrecs} ? $args{nrecs} : $self->{perpage});
    4152 
    41534225
    41544226##fixme:  do we need a knob to twist to switch from unix epoch to postgres time string?
     
    41814253
    41824254  # Filtering on other fields
    4183   foreach (qw(type distance weight port ttl description)) {
     4255  foreach (qw(type distance weight port ttl description location)) {
    41844256    if ($args{$_}) {
    4185       $sql .= " AND $_ ~* ?";
     4257      $sql .= " AND r.$_ ~* ?";
    41864258      push @bindvars, $args{$_};
    41874259    }
     
    42124284  $recsth->execute(@bindvars);
    42134285  while (my $rec = $recsth->fetchrow_hashref) {
    4214     if ($args{revrec} eq 'y' && $args{defrec} eq 'n' &&
     4286    if (!$args{rpc} && $args{revrec} eq 'y' && $args{defrec} eq 'n' &&
    42154287        ($self->{showrev_arpa} eq 'record' || $self->{showrev_arpa} eq 'all') &&
    42164288        $rec->{val} !~ /\.arpa$/ ) {
     
    48184890  my $dbh = $self->{dbh};
    48194891  my $cidr = shift;
    4820   my $group = shift || 1;       # just in case
     4892  my %args = @_;
     4893  $args{group} = 1 if !$args{group};    # just in case
     4894  $args{location} = '' if !$args{location};
    48214895
    48224896  # for speed!  Casting and comparing even ~7K records takes ~2.5s, so narrow it down to one revzone first.
     
    48264900##fixme?  may need to narrow things down more by octet-chopping and doing text comparisons before casting.
    48274901  my ($revpatt) = $dbh->selectrow_array("SELECT host FROM records ".
    4828         "WHERE (type in (12,65280,65281,65282,65283,65284)) AND rdns_id = ? AND inetlazy(val) >>= ? ".
    4829         "ORDER BY inetlazy(val) DESC LIMIT 1", undef, ($revid, $cidr) );
     4902        "WHERE (type in (12,65280,65281,65282,65283,65284)) AND rdns_id = ? ".
     4903        "AND location = ? AND inetlazy(val) >>= ? ".
     4904        "ORDER BY inetlazy(val) DESC LIMIT 1",
     4905        undef, ($revid, $args{location}, $cidr) );
     4906
    48304907  return $revpatt;
    48314908} # end getRevPattern()
     
    48394916  my $dbh = $self->{dbh};
    48404917  my $cidr = shift;
    4841   my $group = shift || 1;       # just in case
     4918  my %args = @_;
     4919  $args{group} = 1 if !$args{group};    # just in case
     4920  $args{location} = '' if !$args{location};
    48424921
    48434922  # for speed!  Casting and comparing even ~7K records takes ~2.5s, so narrow it down to one revzone first.
     
    48554934
    48564935  my $sth = $dbh->prepare("SELECT val, host FROM records ".
    4857         "WHERE (type in (12,65280,65281,65282,65283,65284)) AND rdns_id = ? AND inetlazy(val) = ?");
     4936        "WHERE (type in (12,65280,65281,65282,65283,65284)) AND rdns_id = ? AND location = ? AND inetlazy(val) = ?");
    48584937
    48594938  my @ret;
    48604939  foreach my $ip (@{$cidr->splitref()}) {
    4861     $sth->execute($revid, $ip);
     4940    $sth->execute($revid, $args{location}, $ip);
    48624941    my @data = $sth->fetchrow_array();
    48634942    my %row;
     
    51275206## DNSDB::getZonesByCIDR()
    51285207# Get a list of zone names and IDs that records for a passed CIDR block are within.
     5208# Optionally restrict to a specific location/view
     5209# Optionally leave off the default_location field
    51295210sub getZonesByCIDR {
    51305211  my $self = shift;
    51315212  my $dbh = $self->{dbh};
    51325213  my %args = @_;
    5133 
    5134   my $result = $dbh->selectall_arrayref("SELECT rdns_id,revnet FROM revzones WHERE revnet >>= ? OR revnet <<= ?",
    5135         { Slice => {} }, ($args{cidr}, $args{cidr}) );
     5214  $args{return_location} = 1 if !defined($args{return_location});
     5215
     5216  my $sql = "SELECT rdns_id,revnet".($args{return_location} ? ',default_location' : '').
     5217        " FROM revzones WHERE (revnet >>= ? OR revnet <<= ?)".
     5218        (defined($args{location}) ? " AND default_location = ?" : '');
     5219  my @svals = ($args{cidr}, $args{cidr});
     5220  push @svals, $args{location} if defined $args{location};
     5221
     5222  my $result = $dbh->selectall_arrayref($sql, { Slice => {} }, @svals );
    51365223  return $result;
    51375224} # end getZonesByCIDR()
     
    52955382##fixme:  serial
    52965383      $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,?,?)", undef,
    5297         ($zone, $group, $args{status}) );
     5384        ($zone, $group, $args{status}) ) or die $dbh->errstr;
    52985385      # get domain id so we can do the records
    52995386      ($zone_id) = $dbh->selectrow_array("SELECT currval('domains_domain_id_seq')");
     
    62226309
    62236310    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    6224 ##fixme:  split v-e-r-y long TXT strings?  will need to do so for BIND export, at least
    6225     $val =~ s/:/\\072/g;        # may need to replace other symbols
    6226     print $datafile "'$host:$val:$ttl:$stamp:$loc\n" or die $!;
     6311# le sigh.  Some idiot DNS implementations don't seem to like tinydns autosplitting
     6312# long TXT records at 127 characters instead of 255.  Hand-crafting a record seems
     6313# to paper over the remote stupid.  We will NOT try to split on whitespace;  the
     6314# contents of a TXT record are opaque and clients who can't deal are even more broken
     6315# than the ones that don't like them split at 127 characters...  because BIND tries
     6316# to "intelligently" split TXT data, and abso-by-damn-lutely generates chunks <255
     6317# characters, and anything that can't interpret BIND's DNS responses has no business
     6318# trying to interpret DNS data at all.
     6319
     6320    if ($self->{autotxt}) {
     6321      # let tinydns deal with splitting the record.  note tinydns autosplits at 127
     6322      # characters, not 255.  Because Reasons.
     6323      $val =~ s/:/\\072/g;      # may need to replace other symbols
     6324      print $datafile "'$host:$val:$ttl:$stamp:$loc\n" or die $!;
     6325    } else {
     6326      print $datafile ":$host:16:";
     6327      my @txtsegs = $val =~ /.{1,255}/g;
     6328      foreach (@txtsegs) {
     6329        my $len = length($_);
     6330        s/:/\\072/g;
     6331        printf $datafile "\\%0.3o%s", $len, $_;
     6332      }
     6333      print $datafile ":$ttl:$stamp:$loc\n";
     6334    }
    62276335
    62286336# by-hand TXT
  • branches/stable/Makefile

    r696 r725  
    33
    44PKGNAME=dnsadmin
    5 VERSION=1.2.5p2
     5VERSION=1.2.6
    66RELEASE=1
    77
     
    4646        INSTALL COPYING TODO Makefile dnsadmin.spec \
    4747        \
    48         dns.sql dns-1.0-1.2.sql dns-1.2.3-1.2.4.sql \
     48        dns.sql dns-1.0-1.2.sql dns-1.2.3-1.2.4.sql dns-upd-1.2.6.sql\
    4949        \
    5050        $(SCRIPTS) $(MODULES) \
  • branches/stable/dns-rpc.cgi

    r690 r725  
    137137##
    138138
     139##
     140## Internal utility subs
     141##
     142
    139143# Check RPC ACL
    140144sub _aclcheck {
     
    174178}
    175179
    176 # set ttl to zone defailt minttl if none is specified
     180# set ttl to zone default minttl if none is specified
    177181sub _ttlcheck {
    178182  my $argref = shift;
     
    182186  }
    183187}
     188
     189# Check if the hashrefs passed in refer to identical record data, so we can skip
     190# the actual update if nothing has actually changed.  This is mainly useful for
     191# reducing log noise due to chained calls orginating with updateRevSet() since
     192# "many" records could be sent for update but only one or two have actually changed.
     193sub _checkRecMod {
     194  my $oldrec = shift;
     195  my $newrec = shift;
     196
     197  # Because we don't know which fields we've even been passed
     198  no warnings qw(uninitialized);
     199
     200  my $modflag = 0;
     201  # order by most common change.  host should be first, due to rDNS RPC calls
     202  for my $field qw(host type val) {
     203    return 1 if (
     204        defined($newrec->{$field}) &&
     205        $oldrec->{$field} ne $newrec->{$field} );
     206  }
     207
     208  return 0;
     209} # _checRecMod
     210
     211
     212##
     213## Shims for DNSDB core subs
     214##
    184215
    185216#sub connectDB {
     
    215246    ($code,$msg) = $dnsdb->delZone($args{zone}, $args{revrec});
    216247  } else {
     248    die "Need zone location\n" if !defined($args{location});
    217249    my $zoneid;
    218     $zoneid = $dnsdb->domainID($args{zone}) if $args{revrec} eq 'n';
    219     $zoneid = $dnsdb->revID($args{zone}) if $args{revrec} eq 'y';
     250    $zoneid = $dnsdb->domainID($args{zone}, $args{location}) if $args{revrec} eq 'n';
     251    $zoneid = $dnsdb->revID($args{zone}, $args{location}) if $args{revrec} eq 'y';
    220252    die "Can't find zone: ".$dnsdb->errstr."\n" if !$zoneid;
    221253    ($code,$msg) = $dnsdb->delZone($zoneid, $args{revrec});
     
    233265  _commoncheck(\%args, 'y');
    234266
    235   my $domid = $dnsdb->domainID($args{domain});
     267  my $domid = $dnsdb->domainID($args{domain}, $args{location});
    236268  die $dnsdb->errstr."\n" if !$domid;
    237269  return $domid;
     
    419451  die "Missing zone ID\n" if !$args{id};
    420452
     453  # caller may not know about zone IDs.  accept the zone name, but require a location if so
     454  if ($args{id} !~ /^\d+$/) {
     455    die "Location required to use the zone name\n" if !defined($args{location});
     456  }
     457
    421458  # set some optional args
    422459  $args{offset} = 0 if !$args{offset};
     
    430467  if ($args{defrec} eq 'n') {
    431468    if ($args{revrec} eq 'n') {
    432       $args{id} = $dnsdb->domainID($args{id}) if $args{id} !~ /^\d+$/;
     469      $args{id} = $dnsdb->domainID($args{id}, $args{location}) if $args{id} !~ /^\d+$/;
    433470    } else {
    434       $args{id} = $dnsdb->revID($args{id}) if $args{id} !~ /^\d+$/
     471      $args{id} = $dnsdb->revID($args{id}, $args{location}) if $args{id} !~ /^\d+$/
    435472    }
    436473  }
     
    454491
    455492  _reccheck(\%args);
     493
     494  # caller may not know about zone IDs.  accept the zone name, but require a location if so
     495  if ($args{id} !~ /^\d+$/) {
     496    die "Location required to use the zone name\n" if !defined($args{location});
     497  }
    456498
    457499  # set some optional args
     
    462504  $args{direction} = 'ASC' if !$args{direction};
    463505
     506  # convert zone name to zone ID, if needed
     507  if ($args{defrec} eq 'n') {
     508    if ($args{revrec} eq 'n') {
     509      $args{id} = $dnsdb->domainID($args{id}, $args{location}) if $args{id} !~ /^\d+$/;
     510    } else {
     511      $args{id} = $dnsdb->revID($args{id}, $args{location}) if $args{id} !~ /^\d+$/
     512    }
     513  }
     514
     515  # fail if we *still* don't have a valid zone ID
     516  die $dnsdb->errstr."\n" if !$args{id};
     517
    464518  my $ret = $dnsdb->getRecCount(defrec => $args{defrec}, revrec => $args{revrec},
    465519        id => $args{id}, filter => $args{filter});
     
    468522
    469523  return $ret;
    470 }
     524} # getRecCount()
    471525
    472526# The core sub uses references for some arguments to allow limited modification for
     
    534588} # rpc_updateRec
    535589
     590
    536591# Takes a passed CIDR block and DNS pattern;  adds a new record or updates the record(s) affected
    537592sub addOrUpdateRevRec {
     
    541596  my $cidr = new NetAddr::IP $args{cidr};
    542597
    543 ##fixme:  Minor edge case; if we receive calls one after the other to update
    544 # to the same thing, we bulk out the log with useless notices.  Leaving this
    545 # for future development since this should be rare in practice.
     598  # Location required so we don't turn up unrelated zones in getZonesByCIDR().
     599  # Caller should generally have some knowledge of this.
     600  die "Need location\n" if !defined($args{location});
    546601
    547602  my $zonelist = $dnsdb->getZonesByCIDR(%args);
     
    552607    my $zone = new NetAddr::IP $zonelist->[0]->{revnet};
    553608    if ($zone->contains($cidr)) {
    554       # We need to strip the CIDR mask on IPv4 /32 assignments, or we just add a new record all the time.
    555       my $filt = ($cidr->{isv6} || $cidr->masklen != 32 ? "$cidr" : $cidr->addr);
    556       my $reclist = $dnsdb->getRecList(defrec => 'n', revrec => 'y',
     609      # We need to strip the CIDR mask on IPv4 /32 or v6 /128 assignments, or we just add a new record all the time.
     610      my $filt = ( $cidr->{isv6} ? ($cidr->masklen != 128 ? "$cidr" : $cidr->addr) :
     611                   ($cidr->masklen != 32 ? "$cidr" : $cidr->addr) );
     612      my $reclist = $dnsdb->getRecList(rpc => 1, defrec => 'n', revrec => 'y',
    557613        id => $zonelist->[0]->{rdns_id}, filter => $filt);
    558614##fixme: Figure some new magic to automerge new incoming A(AAA)+PTR requests
     
    570626                || $rec->{type} == 65282 || $rec->{type} == 65283 || $rec->{type} == 65284;
    571627          next unless $rec->{val} eq $filt;     # make sure we really update the record we want to update.
     628          # canonicalize the IP values so funny IPv6 short forms don't
     629          # cause non-updates by not being literally string-equal
     630          $rec->{val} = new NetAddr::IP $rec->{val};
     631          my $tmpcidr = new NetAddr::IP $args{cidr};
     632          my %newrec = (host => $args{name}, val => $tmpcidr, type => $args{type});
    572633          rpc_updateRec(defrec =>'n', revrec => 'y', id => $rec->{record_id},
    573             parent_id => $zonelist->[0]->{rdns_id}, address => "$cidr", %args);
     634                parent_id => $zonelist->[0]->{rdns_id}, address => "$cidr", %args)
     635                if _checkRecMod($rec, \%newrec);        # and only do the update if there really is something to change
    574636          $flag = 1;
    575637          last; # only do one record.
     
    590652    # that spans multiple reverse zones (eg, /23 CIDR -> 2 /24 rzones)
    591653    foreach my $zdata (@$zonelist) {
    592       my $reclist = $dnsdb->getRecList(defrec => 'n', revrec => 'y',
     654      my $reclist = $dnsdb->getRecList(rpc => 1, defrec => 'n', revrec => 'y',
    593655        id => $zdata->{rdns_id}, filter => $zdata->{revnet});
    594656      if (scalar(@$reclist) == 0) {
     
    597659          address => "$args{cidr}", %args);
    598660      } else {
     661        my $updflag = 0;
    599662        foreach my $rec (@$reclist) {
    600663          # only the composite and/or template types;  pure PTR or nontemplate composite
    601664          # types are nominally impossible here.
    602665          next unless $rec->{type} == 65282 || $rec->{type} == 65283 || $rec->{type} == 65284;
     666          my %newrec = (host => $args{name}, val => $zdata->{revnet}, type => $args{type});
    603667          rpc_updateRec(defrec => 'n', revrec => 'y', id => $rec->{record_id},
    604             parent_id => $zdata->{rdns_id}, %args);
     668            parent_id => $zdata->{rdns_id}, %args)
     669            if _checkRecMod($rec, \%newrec);    # and only do the update if there really is something to change
     670          $updflag = 1;
    605671          last; # only do one record.
    606672        }
    607       }
     673        # catch the case of "oops, no zone-sized template record and need to add a new one",
     674        # because the SOA and NS records will be returned from the getRecList() call above
     675        unless ($updflag) {
     676          my $type = ($cidr->{isv6} ? 65284 : 65283);
     677          rpc_addRec(defrec => 'n', revrec => 'y', parent_id => $zdata->{rdns_id}, type => $type,
     678            address => $zdata->{revnet}, %args);
     679        }
     680      } # scalar(@$reclist) != 0
    608681    } # iterate zones within $cidr
    609682  } # done $cidr-contains-zones
     
    623696    next unless $key =~ m{^host_((?:[\d.]+|[\da-f:]+)(?:/\d+)?)$};
    624697    my $ip = $1;
    625     push @ret, addOrUpdateRevRec(cidr => $ip, name => $args{$key}, %args);
    626   }
     698    push @ret, addOrUpdateRevRec(%args, cidr => $ip, name => $args{$key});
     699  }
     700
     701  # now we check the parts of the block that didn't get passed to see if they should be deleted
     702  my $block = new NetAddr::IP $args{cidr};
     703  if (!$block->{isv6}) {
     704    foreach my $ip (@{$block->splitref(32)}) {
     705      my $bare = $ip->addr;
     706      next if $args{"host_$bare"};
     707      delByCIDR(delforward => 1, delsubs => 0, cidr => $bare, location => $args{location},
     708        rpcuser => $args{rpcuser}, rpcsystem => $args{rpcsystem});
     709    }
     710  }
     711
    627712##fixme:  what about errors?  what about warnings?
    628713  return \@ret;
     
    637722
    638723  my $cidr = new NetAddr::IP $args{cidr};
     724
     725  # Location required so we don't turn up unrelated zones in getZonesByCIDR().
     726  # Caller should generally have some knowledge of this.
     727  die "Need location\n" if !defined($args{location});
    639728
    640729  my $zonelist = $dnsdb->getZonesByCIDR(%args);
     
    647736    if ($zone->contains($cidr)) {
    648737      # Find the first record in the reverse zone that matches the CIDR we're splitting...
    649       my $reclist = $dnsdb->getRecList(defrec => 'n', revrec => 'y',
     738      my $reclist = $dnsdb->getRecList(rpc => 1, defrec => 'n', revrec => 'y',
    650739        id => $zonelist->[0]->{rdns_id}, filter => $cidr, sortby => 'val', sortorder => 'DESC');
    651740      my $oldrec;
     
    706795
    707796  my $up_res;
     797
     798  # Location required so we don't turn up unrelated zones in getZonesByCIDR().
     799  # Caller should generally have some knowledge of this.
     800  die "Need location\n" if !defined($args{location});
    708801
    709802  my $zonelist = $dnsdb->getZonesByCIDR(%args);
     
    769862  my @retlist;
    770863
    771   my $zsth = $dnsdb->{dbh}->prepare("SELECT rdns_id,group_id FROM revzones WHERE revnet >>= ?");
     864  # Location required so we don't turn up unrelated zones
     865  die "Need location\n" if !defined($args{location});
     866
     867  my $zsth = $dnsdb->{dbh}->prepare("SELECT rdns_id,group_id FROM revzones WHERE revnet >>= ? AND location = ?");
    772868  # Going to assume template records with no expiry
    773869  # Also note IPv6 template records don't expand sanely the way v4 records do
     
    792888  eval {
    793889    foreach my $template (@{$args{templates}}) {
    794       $zsth->execute($template);
     890      $zsth->execute($template, $args{location});
    795891      my ($zid,$zgrp) = $zsth->fetchrow_array;
    796892      if (!$zid) {
     
    851947  # Caller may pass 'n' in delsubs.  Assume it should be false/undefined
    852948  # unless the caller explicitly requested 'yes'
    853   $args{delsubs} = 0 if $args{delsubs} ne 'y';
     949  $args{delsubs} = 0 if !$args{delsubs} || $args{delsubs} ne 'y';
    854950
    855951  # Don't delete the A component of an A+PTR by default
    856952  $args{delforward} = 0 if !$args{delforward};
     953
     954  # Location required so we don't turn up unrelated zones in getZonesByCIDR().
     955  die "Need location\n" if !defined($args{location});
    857956
    858957  # much like addOrUpdateRevRec()
     
    869968      if ($args{delsubs}) {
    870969        # Delete ALL EVARYTHING!!one11!! in $args{cidr}
    871         my $reclist = $dnsdb->getRecList(defrec => 'n', revrec => 'y', id => $zonelist->[0]->{rdns_id});
     970        my $reclist = $dnsdb->getRecList(rpc => 1, defrec => 'n', revrec => 'y', id => $zonelist->[0]->{rdns_id});
    872971        foreach my $rec (@$reclist) {
    873972          my $reccidr = new NetAddr::IP $rec->{val};
     
    895994        # Selectively delete only exact matches on $args{cidr}
    896995        # We need to strip the CIDR mask on IPv4 /32 assignments, or we can't find single-IP records
    897         my $filt = ($cidr->{isv6} || $cidr->masklen != 32 ? "$cidr" : $cidr->addr);
    898         my $reclist = $dnsdb->getRecList(defrec => 'n', revrec => 'y',
     996        my $filt = ( $cidr->{isv6} ? ($cidr->masklen != 128 ? "$cidr" : $cidr->addr) :
     997                     ($cidr->masklen != 32 ? "$cidr" : $cidr->addr) );
     998        my $reclist = $dnsdb->getRecList(rpc => 1, defrec => 'n', revrec => 'y', location => $args{location},
    899999          id => $zonelist->[0]->{rdns_id}, filter => $filt, sortby => 'val', sortorder => 'DESC');
    9001000        foreach my $rec (@$reclist) {
     
    9241024    # that spans multiple reverse zones (eg, /23 CIDR -> 2 /24 rzones)
    9251025    foreach my $zdata (@$zonelist) {
    926       my $reclist = $dnsdb->getRecList(defrec => 'n', revrec => 'y', id => $zdata->{rdns_id});
     1026      my $reclist = $dnsdb->getRecList(rpc => 1, defrec => 'n', revrec => 'y', id => $zdata->{rdns_id});
    9271027      if (scalar(@$reclist) == 0) {
    9281028# nothing to do?  or do we (re)add a record based on the parent?
     
    9811081  _commoncheck(\%args, 'y');
    9821082
    983   return $dnsdb->getRevPattern($args{cidr}, $args{group});
     1083  return $dnsdb->getRevPattern($args{cidr}, location => $args{location}, group => $args{group});
    9841084}
    9851085
     
    9891089  _commoncheck(\%args, 'y');
    9901090
    991   return $dnsdb->getRevSet($args{cidr}, $args{group});
     1091  return $dnsdb->getRevSet($args{cidr}, location => $args{location}, group => $args{group});
    9921092}
    9931093
  • branches/stable/dns-upd-1.2.6.sql

    r711 r725  
    22
    33-- Allow zones to be duplicated, so long as each version is in a unique location
    4 ALTER TABLE domains DROP CONSTRAINT domains_pkey;
    5 ALTER TABLE domains ADD PRIMARY KEY (domain,default_location);
     4ALTER TABLE ONLY domains
     5    DROP CONSTRAINT domains_pkey;
     6ALTER TABLE ONLY domains
     7    ADD PRIMARY KEY (domain,default_location);
    68
    7 ALTER TABLE revzones DROP CONSTRAINT revzones_pkey;
    8 ALTER TABLE revzones ADD PRIMARY KEY (revnet,default_location);
     9ALTER TABLE ONLY revzones
     10    DROP CONSTRAINT revzones_pkey;
     11ALTER TABLE ONLY revzones
     12    ADD PRIMARY KEY (revnet,default_location);
     13
     14-- MIA unique constraint to match domains table.  Arguably not strictly necessary.
     15ALTER TABLE ONLY revzones
     16    ADD CONSTRAINT revzones_rdns_id_key UNIQUE (rdns_id);
     17
     18-- Update dbversion
     19UPDATE misc SET value='1.2.6' WHERE key='dbversion';
  • branches/stable/dns.cgi

    r690 r725  
    2525use CGI::Simple;
    2626use HTML::Template;
    27 use CGI::Session;
     27use CGI::Session '-ip_match';
    2828use Net::DNS;
    2929use DBI;
     
    158158}
    159159if (defined($webvar{filter})) {
     160  $session->param($webvar{page}.'filter', '') if !$session->param($webvar{page}.'filter');
    160161  if ($webvar{filter} ne $session->param($webvar{page}.'filter')) {
    161162    $uri_self =~ s/\&amp;offset=[^&]//;
     
    528529
    529530  fill_grouplist("grouplist");
    530   my $loclist = $dnsdb->getLocDropdown($curgroup);
    531   $page->param(loclist => $loclist);
     531  fill_loclist($curgroup, $webvar{defloc} ? $webvar{defloc} : '');
    532532
    533533  # prepopulate revpatt with the matching default record
     
    15251525        unless ($permissions{admin} || $permissions{location_create});
    15261526
    1527     my ($code,$msg) = $dnsdb->addLoc($curgroup, $webvar{locname}, $webvar{comments}, $webvar{iplist});
     1527    my ($code,$msg) = $dnsdb->addLoc(group => $curgroup, desc => $webvar{locname},
     1528        comments => $webvar{comments}, iplist => $webvar{iplist});
    15281529
    15291530    if ($code eq 'OK' || $code eq 'WARN') {
     
    16211622} elsif ($webvar{page} eq 'dnsq') {
    16221623
    1623   $page->param(qfor => $webvar{qfor}) if $webvar{qfor};
     1624  if ($webvar{qfor}) {
     1625    $webvar{qfor} =~ s/^\s*//;
     1626    $webvar{qfor} =~ s/\s*$//;
     1627    $page->param(qfor => $webvar{qfor});
     1628  }
     1629  if ($webvar{resolver}) {
     1630    $webvar{resolver} =~ s/^\s*//;
     1631    $webvar{resolver} =~ s/\s*$//;
     1632    $page->param(resolver => $webvar{resolver});
     1633  }
    16241634  $page->param(typelist => $dnsdb->getTypelist('l', ($webvar{type} ? $webvar{type} : undef)));
    16251635  $page->param(nrecurse => $webvar{nrecurse}) if $webvar{nrecurse};
    1626   $page->param(resolver => $webvar{resolver}) if $webvar{resolver};
    16271636
    16281637  if ($webvar{qfor}) {
  • branches/stable/dns.sql

    r649 r725  
    3131
    3232COPY misc (misc_id, key, value) FROM stdin;
    33 1       dbversion       1.2.4
     331       dbversion       1.2.6
    3434\.
    3535
     
    8686CREATE TABLE domains (
    8787    domain_id serial NOT NULL,
    88     "domain" character varying(80) NOT NULL PRIMARY KEY,
     88    "domain" character varying(80) NOT NULL,
    8989    group_id integer DEFAULT 1 NOT NULL,
    9090    description character varying(255) DEFAULT ''::character varying NOT NULL,
     
    101101CREATE TABLE revzones (
    102102    rdns_id serial NOT NULL,
    103     revnet cidr NOT NULL PRIMARY KEY,
     103    revnet cidr NOT NULL,
    104104    group_id integer DEFAULT 1 NOT NULL,
    105105    description character varying(255) DEFAULT ''::character varying NOT NULL,
     
    108108    sertype character(1) DEFAULT 'D'::bpchar,
    109109    changed boolean DEFAULT true NOT NULL,
    110     default_location character varying (4) DEFAULT '' NOT NULL
     110    default_location character varying(4) DEFAULT ''::character varying NOT NULL
    111111);
    112112CREATE INDEX rev_status_index ON revzones USING btree (status);
     
    313313    ADD CONSTRAINT domains_domain_id_key UNIQUE (domain_id);
    314314
     315ALTER TABLE ONLY domains
     316    ADD CONSTRAINT domains_pkey PRIMARY KEY ("domain", default_location);
     317
    315318ALTER TABLE ONLY default_records
    316319    ADD CONSTRAINT default_records_pkey PRIMARY KEY (record_id);
     
    321324ALTER TABLE ONLY rectypes
    322325    ADD CONSTRAINT rectypes_pkey PRIMARY KEY (val, name);
     326
     327ALTER TABLE ONLY revzones
     328    ADD CONSTRAINT revzones_rdns_id_key UNIQUE (rdns_id);
     329
     330ALTER TABLE ONLY revzones
     331    ADD CONSTRAINT revzones_pkey PRIMARY KEY (revnet, default_location);
    323332
    324333ALTER TABLE ONLY users
  • branches/stable/dnsdb.conf

    r690 r725  
    4949#showrev_arpa = 0
    5050
     51# Let DNS server autosplit long TXT records however it pleases, or hand-generate the split points?
     52#autosplit = 1
    5153
    5254## General RPC options
  • branches/stable/notes

    r545 r725  
    327327-> would solve the conundrum of what to do with the unsightly CNAME
    328328   records presented in the UI to indicate sub-octet zone delegation
     329
     330BIND reference for views/locations/split-horizon
     331https://kb.isc.org/article/AA-00851/0/Understanding-views-in-BIND-9-by-example.html
  • branches/stable/templates/domlist.tmpl

    r649 r725  
    4242<TMPL_LOOP name=domtable>
    4343<tr class="row<TMPL_IF __odd__>0<TMPL_ELSE>1</TMPL_IF>">
    44         <td align="left"><a href="<TMPL_VAR NAME=script_self>&amp;page=reclist&amp;id=<TMPL_VAR NAME=zoneid>&amp;defrec=n<TMPL_UNLESS domlist>&amp;revrec=y</TMPL_UNLESS>"><TMPL_VAR NAME=zone></a></td>
     44        <td align="left"><a href="<TMPL_VAR NAME=script_self>&amp;page=reclist&amp;id=<TMPL_VAR NAME=zoneid>&amp;defrec=n<TMPL_UNLESS domlist>&amp;revrec=y</TMPL_UNLESS>"><TMPL_VAR NAME=zone></a><TMPL_IF location> (<TMPL_VAR NAME=location>)</TMPL_IF></td>
    4545        <td><TMPL_IF status>Active<TMPL_ELSE>Inactive</TMPL_IF></td>
    4646        <td><TMPL_VAR name=group></td>
Note: See TracChangeset for help on using the changeset viewer.