Changeset 544 for branches/stable


Ignore:
Timestamp:
12/10/13 17:15:56 (11 years ago)
Author:
Kris Deugau
Message:

/branches/stable

Merge reverse DNS work; 1 of mumble

  • from branch creation through r261

Minor conflicts in dns.cgi and DNSDB.pm

Location:
branches/stable
Files:
11 edited
3 copied

Legend:

Unmodified
Added
Removed
  • branches/stable

  • branches/stable/DNSDB.pm

    r438 r544  
    2828use Crypt::PasswdMD5;
    2929use Net::SMTP;
    30 use NetAddr::IP;
     30use NetAddr::IP qw(:lower);
    3131use POSIX;
    3232use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
     
    3939        &changeGroup
    4040        &loadConfig &connectDB &finish
    41         &addDomain &delDomain &domainName &domainID
     41        &addDomain &delDomain &domainName &revName &domainID &addRDNS
     42        &getZoneCount &getZoneList
    4243        &addGroup &delGroup &getChildren &groupName
    4344        &addUser &updateUser &delUser &userFullName &userStatus &getUserData
    4445        &getSOA &getRecLine &getDomRecs &getRecCount
    4546        &addRec &updateRec &delRec
    46         &getParents
     47        &getTypelist
     48        &parentID
    4749        &isParent
    4850        &domStatus &importAXFR
     
    5961                &changeGroup
    6062                &loadConfig &connectDB &finish
    61                 &addDomain &delDomain &domainName &domainID
     63                &addDomain &delDomain &domainName &revName &domainID &addRDNS
     64                &getZoneCount &getZoneList
    6265                &addGroup &delGroup &getChildren &groupName
    6366                &addUser &updateUser &delUser &userFullName &userStatus &getUserData
    6467                &getSOA &getRecLine &getDomRecs &getRecCount
    6568                &addRec &updateRec &delRec
    66                 &getParents
     69                &getTypelist
     70                &parentID
    6771                &isParent
    6872                &domStatus &importAXFR
     
    139143                perpage         => 15,
    140144        );
     145
     146## (Semi)private variables
     147# Hash of functions for validating record types.  Filled in initGlobals() since
     148# it relies on visibility flags from the rectypes table in the DB
     149my %validators;
     150
     151
     152##
     153## utility functions
     154# _rectable()
     155# Takes default+rdns flags, returns appropriate table name
     156sub _rectable {
     157  my $def = shift;
     158  my $rev = shift;
     159
     160  return 'records' if $def ne 'y';
     161  return 'default_records' if $rev ne 'y';
     162  return 'default_rev_records';
     163} # end _rectable()
     164
     165# _recparent()
     166# Takes default+rdns flags, returns appropriate parent-id column name
     167sub _recparent {
     168  my $def = shift;
     169  my $rev = shift;
     170
     171  return 'group_id' if $def eq 'y';
     172  return 'rdns_id' if $rev eq 'y';
     173  return 'domain_id';
     174} # end _recparent()
     175
     176# Check an IP to be added in a reverse zone to see if it's really in the requested parent.
     177# Takes a database handle, default and reverse flags, IP (fragment) to check, parent zone ID,
     178# and a reference to a NetAddr::IP object (also used to pass back a fully-reconstructed IP for
     179# database insertion)
     180sub _ipparent {
     181  my $dbh = shift;
     182  my $defrec = shift;
     183  my $revrec = shift;
     184  my $val = shift;
     185  my $id = shift;
     186  my $addr = shift;
     187
     188  return if $revrec ne 'y';     # this sub not useful in forward zones
     189
     190  $$addr = NetAddr::IP->new($$val);      #necessary?
     191
     192  # subsub to split, reverse, and overlay an IP fragment on a netblock
     193  sub __rev_overlay {
     194    my $splitme = shift;        # ':' or '.', m'lud?
     195    my $parnet = shift;
     196    my $val = shift;
     197    my $addr = shift;
     198
     199    my $joinme = $splitme;
     200    $splitme = '\.' if $splitme eq '.';
     201    my @working = reverse(split($splitme, $parnet->addr));
     202    my @parts = reverse(split($splitme, $$val));
     203    for (my $i = 0; $i <= $#parts; $i++) {
     204      $working[$i] = $parts[$i];
     205    }
     206    my $checkme = NetAddr::IP->new(join($joinme, reverse(@working))) or return 0;
     207    return 0 unless $checkme->within($parnet);
     208    $$addr = $checkme;  # force "correct" IP to be recorded.
     209    return 1;
     210  }
     211
     212  my ($parstr) = $dbh->selectrow_array("SELECT revnet FROM revzones WHERE rdns_id = ?", undef, ($id));
     213  my $parnet = NetAddr::IP->new($parstr);
     214
     215  # Fail early on v6-in-v4 or v4-in-v6.  We're not accepting these ATM.
     216  return 0 if $parnet->addr =~ /\./ && $$val =~ /:/;
     217  return 0 if $parnet->addr =~ /:/ && $$val =~ /\./;
     218
     219  if ($$addr && $$val =~ /^[\da-fA-F][\da-fA-F:]+[\da-fA-F]$/) {
     220    # the only case where NetAddr::IP's acceptance of legitimate IPs is "correct" is for a proper IPv6 address.
     221    # the rest we have to restructure before fiddling.  *sigh*
     222    return 1 if $$addr->within($parnet);
     223  } else {
     224    # We don't have a complete IP in $$val (yet)
     225    if ($parnet->addr =~ /:/) {
     226      $$val =~ s/^:+//;  # gotta strip'em all...
     227      return __rev_overlay(':', $parnet, $val, $addr);
     228    }
     229    if ($parnet->addr =~ /\./) {
     230      $$val =~ s/^\.+//;
     231      return __rev_overlay('.', $parnet, $val, $addr);
     232    }
     233    # should be impossible to get here...
     234  }
     235  # ... and here.
     236  # can't do nuttin' in forward zones
     237} # end _ipparent()
     238
     239# A little different than _ipparent above;  this tries to *find* the parent zone of a hostname
     240sub _hostparent {
     241  my $dbh = shift;
     242  my $hname = shift;
     243 
     244  my @hostbits = split /\./, $hname;
     245  my $sth = $dbh->prepare("SELECT count(*),domain_id FROM domains WHERE domain = ? GROUP BY domain_id");
     246  foreach (@hostbits) {
     247    $sth->execute($hname);
     248    my ($found, $parid) = $sth->fetchrow_array;
     249    if ($found) {
     250      return $parid;
     251    }
     252    $hname =~ s/^$_\.//;
     253  }
     254} # end _hostparent()
     255
     256##
     257## Record validation subs.
     258##
     259
     260# A record
     261sub _validate_1 {
     262  my $dbh = shift;
     263
     264  my %args = @_;
     265
     266  return ('FAIL', 'Reverse zones cannot contain A records') if $args{revrec} eq 'y';
     267
     268  # Coerce all hostnames to end in ".DOMAIN" for group/default records,
     269  # or the intended parent domain for live records.
     270  my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id}));
     271  ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/;
     272
     273  # Check IP is well-formed, and that it's a v4 address
     274  # Fail on "compact" IPv4 variants, because they are not consistent and predictable.
     275  return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv4 address")
     276        unless ${$args{val}} =~ /^\d+\.\d+\.\d+\.\d+$/;
     277  return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv4 address")
     278        unless $args{addr} && !$args{addr}->{isv6};
     279  # coerce IP/value to normalized form for storage
     280  ${$args{val}} = $args{addr}->addr;
     281
     282  return ('OK','OK');
     283} # done A record
     284
     285# NS record
     286sub _validate_2 {
     287  my $dbh = shift;
     288
     289  my %args = @_;
     290
     291  # Coerce the hostname to "DOMAIN" for forward default records, "ZONE" for reverse default records,
     292  # or the intended parent zone for live records.
     293##fixme:  allow for delegating <subdomain>.DOMAIN?
     294  if ($args{revrec} eq 'y') {
     295    my $pname = ($args{defrec} eq 'y' ? 'ZONE' : revName($dbh,$args{id}));
     296    ${$args{host}} = $pname if ${$args{host}} ne $pname;
     297  } else {
     298    my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id}));
     299    ${$args{host}} = $pname if ${$args{host}} ne $pname;
     300  }
     301
     302# Let this lie for now.  Needs more magic.
     303#  # Check IP is well-formed, and that it's a v4 address
     304#  return ('FAIL',"A record must be a valid IPv4 address")
     305#       unless $addr && !$addr->{isv6};
     306#  # coerce IP/value to normalized form for storage
     307#  $$val = $addr->addr;
     308
     309  return ('OK','OK');
     310} # done NS record
     311
     312# CNAME record
     313sub _validate_5 {
     314  my $dbh = shift;
     315
     316  my %args = @_;
     317
     318# Not really true, but these are only useful for delegating smaller-than-/24 IP blocks.
     319# This is fundamentally a messy operation and should really just be taken care of by the
     320# export process, not manual maintenance of the necessary records.
     321  return ('FAIL', 'Reverse zones cannot contain CNAME records') if $args{revrec} eq 'y';
     322
     323  # Coerce all hostnames to end in ".DOMAIN" for group/default records,
     324  # or the intended parent domain for live records.
     325  my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id}));
     326  ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/;
     327
     328  return ('OK','OK');
     329} # done CNAME record
     330
     331# SOA record
     332sub _validate_6 {
     333  # Smart monkeys won't stick their fingers in here;  we have
     334  # separate dedicated routines to deal with SOA records.
     335  return ('OK','OK');
     336} # done SOA record
     337
     338# PTR record
     339sub _validate_12 {
     340  my $dbh = shift;
     341
     342  my %args = @_;
     343
     344  if ($args{revrec} eq 'y') {
     345    if ($args{defrec} eq 'n') {
     346      return ('FAIL', "IP or IP fragment ${$args{val}} is not within ".revName($dbh, $args{id}))
     347        unless _ipparent($dbh, $args{defrec}, $args{revrec}, $args{val}, $args{id}, \$args{addr});
     348      ${$args{val}} = $args{addr}->addr;
     349    } else {
     350      if (${$args{val}} =~ /\./) {
     351        # looks like a v4 or fragment
     352        if (${$args{val}} =~ /^\d+\.\d+\.\d+\.\d+$/) {
     353          # woo!  a complete IP!  validate it and normalize, or fail.
     354          $args{addr} = NetAddr::IP->new(${$args{val}})
     355                or return ('FAIL', "IP/value looks like IPv4 but isn't valid");
     356          ${$args{val}} = $args{addr}->addr;
     357        } else {
     358          ${$args{val}} =~ s/^\.*/ZONE./ unless ${$args{val}} =~ /^ZONE/;
     359        }
     360      } elsif (${$args{val}} =~ /[a-f:]/) {
     361        # looks like a v6 or fragment
     362        ${$args{val}} =~ s/^:*/ZONE::/ if !$args{addr} && ${$args{val}} !~ /^ZONE/;
     363        if ($args{addr}) {
     364          if ($args{addr}->addr =~ /^0/) {
     365            ${$args{val}} =~ s/^:*/ZONE::/ unless ${$args{val}} =~ /^ZONE/;
     366          } else {
     367            ${$args{val}} = $args{addr}->addr;
     368          }
     369        }
     370      } else {
     371        # bare number (probably).  These could be v4 or v6, so we'll
     372        # expand on these on creation of a reverse zone.
     373        ${$args{val}} = "ZONE,${$args{val}}" unless ${$args{val}} =~ /^ZONE/;
     374      }
     375      ${$args{host}} =~ s/\.*$/\.$config{domain}/ if ${$args{host}} !~ /(?:$config{domain}|ADMINDOMAIN)$/;
     376    }
     377
     378# Multiple PTR records do NOT generally do what most people believe they do,
     379# and tend to fail in the most awkward way possible.  Check and warn.
     380# We use $val instead of $addr->addr since we may be in a defrec, and may have eg "ZONE::42" or "ZONE.12"
     381
     382    my @checkvals = (${$args{val}});
     383    if (${$args{val}} =~ /,/) {
     384      # push . and :: variants into checkvals if val has ,
     385      my $tmp;
     386      ($tmp = ${$args{val}}) =~ s/,/./;
     387      push @checkvals, $tmp;
     388      ($tmp = ${$args{val}}) =~ s/,/::/;
     389      push @checkvals, $tmp;
     390    }
     391    my $pcsth = $dbh->prepare("SELECT count(*) FROM "._rectable($args{defrec},$args{revrec})." WHERE val = ?");
     392    foreach my $checkme (@checkvals) {
     393      my $ptrcount;
     394      ($ptrcount) = $dbh->selectrow_array("SELECT count(*) FROM "._rectable($args{defrec},$args{revrec}).
     395        " WHERE val = ?", undef, ($checkme));
     396      return ('WARN', "PTR record for $checkme already exists;  adding another will probably not do what you want")
     397        if $ptrcount;
     398    }
     399  } else {
     400    # Not absolutely true but only useful if you hack things up for sub-/24 v4 reverse delegations
     401    # Simpler to just create the reverse zone and grant access for the customer to edit it, and create direct
     402    # PTR records on export
     403    return ('FAIL',"Forward zones cannot contain PTR records");
     404  }
     405
     406  return ('OK','OK');
     407} # done PTR record
     408
     409# MX record
     410sub _validate_15 {
     411  my $dbh = shift;
     412
     413  my %args = @_;
     414
     415# Not absolutely true but WTF use is an MX record for a reverse zone?
     416  return ('FAIL', 'Reverse zones cannot contain MX records') if $args{revrec} eq 'y';
     417
     418  return ('FAIL', "Distance is required for MX records") unless defined(${$args{dist}});
     419  ${$args{dist}} =~ s/\s*//g;
     420  return ('FAIL',"Distance is required, and must be numeric") unless ${$args{dist}} =~ /^\d+$/;
     421
     422  ${$args{fields}} = "distance,";
     423  push @{$args{vallist}}, ${$args{dist}};
     424
     425  # Coerce all hostnames to end in ".DOMAIN" for group/default records,
     426  # or the intended parent domain for live records.
     427  my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id}));
     428  ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/;
     429
     430  return ('OK','OK');
     431} # done MX record
     432
     433# TXT record
     434sub _validate_16 {
     435  # Could arguably put a WARN return here on very long (>512) records
     436  return ('OK','OK');
     437} # done TXT record
     438
     439# RP record
     440sub _validate_17 {
     441  # Probably have to validate these some day
     442  return ('OK','OK');
     443} # done RP record
     444
     445# AAAA record
     446sub _validate_28 {
     447  my $dbh = shift;
     448
     449  my %args = @_;
     450
     451  return ('FAIL', 'Reverse zones cannot contain AAAA records') if $args{revrec} eq 'y';
     452
     453  # Coerce all hostnames to end in ".DOMAIN" for group/default records,
     454  # or the intended parent domain for live records.
     455  my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id}));
     456  ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/;
     457
     458  # Check IP is well-formed, and that it's a v6 address
     459  return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv6 address")
     460        unless $args{addr} && $args{addr}->{isv6};
     461  # coerce IP/value to normalized form for storage
     462  ${$args{val}} = $args{addr}->addr;
     463
     464  return ('OK','OK');
     465} # done AAAA record
     466
     467# SRV record
     468sub _validate_33 {
     469  my $dbh = shift;
     470
     471  my %args = @_;
     472
     473# Not absolutely true but WTF use is an SRV record for a reverse zone?
     474  return ('FAIL', 'Reverse zones cannot contain SRV records') if $args{revrec} eq 'y';
     475
     476  return ('FAIL', "Distance is required for SRV records") unless defined(${$args{dist}});
     477  ${$args{dist}} =~ s/\s*//g;
     478  return ('FAIL',"Distance is required, and must be numeric") unless ${$args{dist}} =~ /^\d+$/;
     479
     480  return ('FAIL',"SRV records must begin with _service._protocol [${$args{host}}]")
     481        unless ${$args{host}} =~ /^_[A-Za-z]+\._[A-Za-z]+\.[a-zA-Z0-9-]+/;
     482  return ('FAIL',"Port and weight are required for SRV records")
     483        unless defined(${$args{weight}}) && defined(${$args{port}});
     484  ${$args{weight}} =~ s/\s*//g;
     485  ${$args{port}} =~ s/\s*//g;
     486
     487  return ('FAIL',"Port and weight are required, and must be numeric")
     488        unless ${$args{weight}} =~ /^\d+$/ && ${$args{port}} =~ /^\d+$/;
     489
     490  ${$args{fields}} = "distance,weight,port,";
     491  push @{$args{vallist}}, (${$args{dist}}, ${$args{weight}}, ${$args{port}});
     492
     493  # Coerce all hostnames to end in ".DOMAIN" for group/default records,
     494  # or the intended parent domain for live records.
     495  my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id}));
     496  ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/;
     497
     498  return ('OK','OK');
     499} # done SRV record
     500
     501# Now the custom types
     502
     503# A+PTR record.  With a very little bit of magic we can also use this sub to validate AAAA+PTR.  Whee!
     504sub _validate_65280 {
     505  my $dbh = shift;
     506
     507  my %args = @_;
     508
     509  my $code = 'OK';
     510  my $msg = 'OK';
     511
     512  if ($args{defrec} eq 'n') {
     513    # live record;  revrec determines whether we validate the PTR or A component first.
     514
     515    if ($args{revrec} eq 'y') {
     516      ($code,$msg) = _validate_12($dbh, %args);
     517      return ($code,$msg) if $code eq 'FAIL';
     518
     519      # Check if the reqested domain exists.  If not, coerce the type down to PTR and warn.
     520      if (!(${$args{domid}} = _hostparent($dbh, ${$args{host}}))) {
     521        my $addmsg = "Record added as PTR instead of $typemap{${$args{rectype}}};  domain not found for ${$args{host}}";
     522        $msg .= "\n$addmsg" if $code eq 'WARN';
     523        $msg = $addmsg if $code eq 'OK';
     524        ${$args{rectype}} = $reverse_typemap{PTR};
     525        return ('WARN', $msg);
     526      }
     527
     528      # Add domain ID to field list and values
     529      ${$args{fields}} .= "domain_id,";
     530      push @{$args{vallist}}, ${$args{domid}};
     531
     532    } else {
     533      ($code,$msg) = _validate_1($dbh, %args) if ${$args{rectype}} == 65280;
     534      ($code,$msg) = _validate_28($dbh, %args) if ${$args{rectype}} == 65281;
     535      return ($code,$msg) if $code eq 'FAIL';
     536
     537      # Check if the requested reverse zone exists - note, an IP fragment won't
     538      # work here since we don't *know* which parent to put it in.
     539      # ${$args{val}} has been validated as a valid IP by now, in one of the above calls.
     540      my ($revid) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?".
     541        " ORDER BY masklen(revnet) DESC", undef, (${$args{val}}));
     542      if (!$revid) {
     543        $msg = "Record added as ".(${$args{rectype}} == 65280 ? 'A' : 'AAAA').
     544                " instead of $typemap{${$args{rectype}}};  reverse zone not found for ${$args{val}}";
     545        ${$args{rectype}} = (${$args{rectype}} == 65280 ? $reverse_typemap{A} : $reverse_typemap{AAAA});
     546        return ('WARN', $msg);
     547      }
     548
     549      # Check for duplicate PTRs.  Note we don't have to play games with $code and $msg, because
     550      # by definition there can't be duplicate PTRs if the reverse zone isn't managed here.
     551      my ($ptrcount) = $dbh->selectrow_array("SELECT count(*) FROM "._rectable($args{defrec},$args{revrec}).
     552        " WHERE val = ?", undef, ${$args{val}});
     553      if ($ptrcount) {
     554        $msg = "PTR record for ${$args{val}} already exists;  adding another will probably not do what you want";
     555        $code = 'WARN';
     556      }
     557
     558      ${$args{fields}} .= "rdns_id,";
     559      push @{$args{vallist}}, $revid;
     560    }
     561
     562  } else {      # defrec eq 'y'
     563    if ($args{revrec} eq 'y') {
     564      ($code,$msg) = _validate_12($dbh, %args);
     565      return ($code,$msg) if $code eq 'FAIL';
     566      if (${$args{rectype}} == 65280) {
     567        return ('FAIL',"A+PTR record must be a valid IPv4 address or fragment")
     568                if ${$args{val}} =~ /:/;
     569        ${$args{val}} =~ s/^ZONE,/ZONE./;       # Clean up after uncertain IP-fragment-type from _validate_12
     570      } elsif (${$args{rectype}} == 65281) {
     571        return ('FAIL',"AAAA+PTR record must be a valid IPv6 address or fragment")
     572                if ${$args{val}} =~ /\./;
     573        ${$args{val}} =~ s/^ZONE,/ZONE::/;      # Clean up after uncertain IP-fragment-type from _validate_12
     574      }
     575    } else {
     576      # This is easy.  I also can't see a real use-case for A/AAAA+PTR in *all* forward
     577      # domains, since you wouldn't be able to substitute both domain and reverse zone
     578      # sanely, and you'd end up with guaranteed over-replicated PTR records that would
     579      # confuse the hell out of pretty much anything that uses them.
     580##fixme: make this a config flag?
     581      return ('FAIL', "$typemap{${$args{rectype}}} records not allowed in default domains");
     582    }
     583  }
     584
     585  return ($code, $msg);
     586} # done A+PTR record
     587
     588# AAAA+PTR record
     589# A+PTR above has been magicked to handle AAAA+PTR as well.
     590sub _validate_65281 {
     591  return _validate_65280(@_);
     592} # done AAAA+PTR record
     593
     594# PTR template record
     595sub _validate_65282 {
     596  return ('OK','OK');
     597} # done PTR template record
     598
     599# A+PTR template record
     600sub _validate_65283 {
     601  return ('OK','OK');
     602} # done AAAA+PTR template record
     603
     604# AAAA+PTR template record
     605sub _validate_65284 {
     606  return ('OK','OK');
     607} # done AAAA+PTR template record
     608
    141609
    142610
     
    327795
    328796# load record types from database
    329   my $sth = $dbh->prepare("select val,name from rectypes");
     797  my $sth = $dbh->prepare("SELECT val,name,stdflag FROM rectypes");
    330798  $sth->execute;
    331   while (my ($recval,$recname) = $sth->fetchrow_array()) {
     799  while (my ($recval,$recname,$stdflag) = $sth->fetchrow_array()) {
    332800    $typemap{$recval} = $recname;
    333801    $reverse_typemap{$recname} = $recval;
     802    # now we fill the record validation function hash
     803    if ($stdflag < 5) {
     804      my $fn = "_validate_$recval";
     805      $validators{$recval} = \&$fn;
     806    } else {
     807      my $fn = "sub { return ('FAIL','Type $recval ($recname) not supported'); }";
     808      $validators{$recval} = eval $fn;
     809    }
    334810  }
    335811} # end initGlobals
     
    5341010# Log an action
    5351011# Internal sub
    536 # Takes a database handle, domain_id, user_id, group_id, email, name and log entry
     1012# Takes a database handle and log entry hash containing at least:
     1013# user_id, group_id, log entry
     1014# and optionally one or more of:
     1015# username/email, user full name, domain_id, rdns_id
    5371016##fixme:  convert to trailing hash for user info
    5381017# User info must contain a (user ID OR username)+fullname
    5391018sub _log {
    5401019  my $dbh = shift;
    541   my ($domain_id,$user_id,$group_id,$username,$name,$entry) = @_;
     1020
     1021  my %args = @_;
     1022
     1023  $args{rdns_id} = 0 if !$args{rdns_id};
     1024  $args{domain_id} = 0 if !$args{domain_id};
    5421025
    5431026##fixme:  need better way(s?) to snag userinfo for log entries.  don't want to have
    5441027# to pass around yet *another* constant (already passing $dbh, shouldn't need to)
    5451028  my $fullname;
    546   if (!$user_id) {
    547     ($user_id, $fullname) = $dbh->selectrow_array("SELECT user_id, firstname || ' ' || lastname FROM users".
    548         " WHERE username=?", undef, ($username));
    549   } elsif (!$username) {
    550     ($username, $fullname) = $dbh->selectrow_array("SELECT username, firstname || ' ' || lastname FROM users".
    551         " WHERE user_id=?", undef, ($user_id));
    552   } else {
     1029  if (!$args{user_id}) {
     1030    ($args{user_id}, $fullname) = $dbh->selectrow_array("SELECT user_id, firstname || ' ' || lastname FROM users".
     1031        " WHERE username=?", undef, ($args{username}));
     1032  }
     1033  if (!$args{username}) {
     1034    ($args{username}, $fullname) = $dbh->selectrow_array("SELECT username, firstname || ' ' || lastname FROM users".
     1035        " WHERE user_id=?", undef, ($args{user_id}));
     1036  }
     1037  if (!$args{fullname}) {
    5531038    ($fullname) = $dbh->selectrow_array("SELECT firstname || ' ' || lastname FROM users".
    554         " WHERE user_id=?", undef, ($user_id));
    555   }
    556 
    557   $name = $fullname if !$name;
     1039        " WHERE user_id=?", undef, ($args{user_id}));
     1040  }
     1041
     1042  $args{name} = $fullname if !$args{name};
    5581043
    5591044##fixme:  farm out the actual logging to different subs for file, syslog, internal, etc based on config
    560   $dbh->do("INSERT INTO log (domain_id,user_id,group_id,email,name,entry) VALUES (?,?,?,?,?,?)", undef,
    561         ($domain_id,$user_id,$group_id,$username,$name,$entry));
    562 #            123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
    563 #                     1         2         3         4         5         6         7
     1045  $dbh->do("INSERT INTO log (domain_id,rdns_id,user_id,group_id,email,name,entry) VALUES (?,?,?,?,?,?,?)",
     1046        undef,
     1047        ($args{domain_id},$args{rdns_id},$args{user_id},$args{group_id},$args{username},$args{name},$args{entry}));
     1048
    5641049} # end _log
    5651050
     
    6191104    ($dom_id) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE domain=?", undef, ($domain));
    6201105
    621     _log($dbh, $dom_id, $userinfo{id}, $group, $userinfo{name}, $userinfo{fullname},
    622         "Added ".($state ? 'active' : 'inactive')." domain $domain");
     1106    _log($dbh, (domain_id => $dom_id, user_id => $userinfo{id}, group_id => $group, username => $userinfo{username},
     1107        entry => "Added ".($state ? 'active' : 'inactive')." domain $domain"));
    6231108
    6241109    # ... and now we construct the standard records from the default set.  NB:  group should be variable.
     
    6341119        my @tmp1 = split /:/, $host;
    6351120        my @tmp2 = split /:/, $val;
    636         _log($dbh, $dom_id, $userinfo{id}, $group, $userinfo{name}, $userinfo{fullname},
     1121        _log($dbh, (domain_id => $dom_id, user_id => $userinfo{id}, group_id => $group,
     1122                username => $userinfo{username}, entry =>
    6371123                "[new $domain] Added SOA record [contact $tmp1[0]] [master $tmp1[1]] ".
    638                 "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl");
     1124                "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl"));
    6391125      } else {
    6401126        my $logentry = "[new $domain] Added record '$host $typemap{$type}";
    6411127        $logentry .= " [distance $dist]" if $typemap{$type} eq 'MX';
    6421128        $logentry .= " [priority $dist] [weight $weight] [port $port]" if $typemap{$type} eq 'SRV';
    643         _log($dbh, $dom_id, $userinfo{id}, $group, $userinfo{name}, $userinfo{fullname},
    644                 $logentry." $val', TTL $ttl");
     1129        _log($dbh, (domain_id => $dom_id, user_id => $userinfo{id}, group_id => $group,
     1130                username => $userinfo{username}, entry =>
     1131                $logentry." $val', TTL $ttl"));
    6451132      }
    6461133    }
     
    7131200
    7141201
     1202## DNSDB::revName()
     1203# Return the reverse zone name based on an rDNS ID
     1204# Takes a database handle and the rDNS ID
     1205# Returns the reverse zone name or undef on failure
     1206sub revName {
     1207  $errstr = '';
     1208  my $dbh = shift;
     1209  my $revid = shift;
     1210  my ($revname) = $dbh->selectrow_array("SELECT revnet FROM revzones WHERE rdns_id=?", undef, ($revid) );
     1211  $errstr = $DBI::errstr if !$revname;
     1212  return $revname if $revname;
     1213} # end revName()
     1214
     1215
    7151216## DNSDB::domainID()
    7161217# Takes a database handle and domain name
     
    7241225  return $domid if $domid;
    7251226} # end domainID()
     1227
     1228
     1229## DNSDB::addRDNS
     1230# Adds a reverse DNS zone
     1231# Takes a database handle, CIDR block, numeric group, boolean(ish) state (active/inactive),
     1232# and user info hash (for logging).
     1233# Returns a status code and message
     1234sub addRDNS {
     1235  my $dbh = shift;
     1236  my $zone = NetAddr::IP->new(shift);
     1237  return ('FAIL',"Zone name must be a valid CIDR netblock") unless ($zone && $zone->addr !~ /^0/);
     1238  my $revpatt = shift;
     1239  my $group = shift;
     1240  my $state = shift;
     1241
     1242  my %userinfo = @_;    # remaining bits.
     1243# user ID, username, user full name
     1244
     1245  $state = 1 if $state =~ /^active$/;
     1246  $state = 1 if $state =~ /^on$/;
     1247  $state = 0 if $state =~ /^inactive$/;
     1248  $state = 0 if $state =~ /^off$/;
     1249
     1250  return ('FAIL',"Invalid zone status") if $state !~ /^\d+$/;
     1251
     1252# quick check to start to see if we've already got one
     1253  my ($rdns_id) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revzone=?", undef, ("$zone"));
     1254
     1255  return ('FAIL', "Zone already exists") if $rdns_id;
     1256
     1257  # Allow transactions, and raise an exception on errors so we can catch it later.
     1258  # Use local to make sure these get "reset" properly on exiting this block
     1259  local $dbh->{AutoCommit} = 0;
     1260  local $dbh->{RaiseError} = 1;
     1261
     1262#$dbh->selectrow_array("SELECT currval('users_user_id_seq')");
     1263  # Wrap all the SQL in a transaction
     1264  eval {
     1265    # insert the domain...
     1266    $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,?,?)", undef, ($zone, $group, $state));
     1267
     1268    # get the ID...
     1269    ($rdns_id) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
     1270
     1271    _log($dbh, (rdns_id => $rdns_id, user_id => $userinfo{id}, group_id => $group, username => $userinfo{name},
     1272        entry => "Added ".($state ? 'active' : 'inactive')." reverse zone $zone"));
     1273
     1274    # ... and now we construct the standard records from the default set.  NB:  group should be variable.
     1275    my $sth = $dbh->prepare("SELECT host,type,val,ttl FROM default_rev_records WHERE group_id=?");
     1276    my $sth_in = $dbh->prepare("INSERT INTO records (rdns_id,host,type,val,ttl)".
     1277        " VALUES ($rdns_id,?,?,?,?)");
     1278    $sth->execute($group);
     1279    while (my ($host,$type,$val,$ttl) = $sth->fetchrow_array()) {
     1280      $host =~ s/ADMINDOMAIN/$config{domain}/g;
     1281##work
     1282# - replace ZONE in $val
     1283# - skip records not appropriate for the zone (skip A+PTR on v6 zones, and AAAA+PTR on v4 zones)
     1284#      $val =~ s/DOMAIN/$domain/g;
     1285      $sth_in->execute($host,$type,$val,$ttl);
     1286      if ($typemap{$type} eq 'SOA') {
     1287        my @tmp1 = split /:/, $host;
     1288        my @tmp2 = split /:/, $val;
     1289        _log($dbh, (rdns_id => $rdns_id, user_id => $userinfo{id}, group_id => $group,
     1290                username => $userinfo{name}, entry =>
     1291                "[new $zone] Added SOA record [contact $tmp1[0]] [master $tmp1[1]] ".
     1292                "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl"));
     1293      } else {
     1294        my $logentry = "[new $zone] Added record '$host $typemap{$type}";
     1295#       $logentry .= " [distance $dist]" if $typemap{$type} eq 'MX';
     1296#       $logentry .= " [priority $dist] [weight $weight] [port $port]" if $typemap{$type} eq 'SRV';
     1297        _log($dbh, (rdns_id => $rdns_id, user_id => $userinfo{id}, group_id => $group,
     1298                username => $userinfo{name}, entry =>
     1299                $logentry." $val', TTL $ttl"));
     1300      }
     1301    }
     1302
     1303    # once we get here, we should have suceeded.
     1304    $dbh->commit;
     1305  }; # end eval
     1306
     1307  if ($@) {
     1308    my $msg = $@;
     1309    eval { $dbh->rollback; };
     1310    return ('FAIL',$msg);
     1311  } else {
     1312    return ('OK',$rdns_id);
     1313  }
     1314
     1315} # end addRDNS()
     1316
     1317
     1318## DNSDB::getZoneCount
     1319# Get count of zones in group or groups
     1320# Takes a database handle and hash containing:
     1321#  - the "current" group
     1322#  - an array of "acceptable" groups
     1323#  - a flag for forward/reverse zones
     1324#  - Optionally accept a "starts with" and/or "contains" filter argument
     1325# Returns an integer count of the resulting zone list.
     1326sub getZoneCount {
     1327  my $dbh = shift;
     1328
     1329  my %args = @_;
     1330
     1331  my @filterargs;
     1332  $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/;
     1333  push @filterargs, "^$args{startwith}" if $args{startwith};
     1334  $args{filter} =~ s/\./\[\.\]/g if $args{filter};      # only match literal dots, usually in reverse zones
     1335  push @filterargs, $args{filter} if $args{filter};
     1336
     1337  my $sql;
     1338  # Not as compact, and fix-me-twice if the common bits get wrong, but much easier to read
     1339  if ($args{revrec} eq 'n') {
     1340    $sql = "SELECT count(*) FROM domains".
     1341        " WHERE group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")".
     1342        ($args{startwith} ? " AND domain ~* ?" : '').
     1343        ($args{filter} ? " AND domain ~* ?" : '');
     1344  } else {
     1345    $sql = "SELECT count(*) FROM revzones".
     1346        " WHERE group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")".
     1347        ($args{startwith} ? " AND CAST(revnet AS VARCHAR) ~* ?" : '').
     1348        ($args{filter} ? " AND CAST(revnet AS VARCHAR) ~* ?" : '');
     1349  }
     1350  my ($count) = $dbh->selectrow_array($sql, undef, @filterargs);
     1351  return $count;
     1352} # end getZoneCount()
     1353
     1354
     1355## DNSDB::getZoneList()
     1356# Get a list of zones in the specified group(s)
     1357# Takes the same arguments as getZoneCount() above
     1358# Returns a reference to an array of hashrefs suitable for feeding to HTML::Template
     1359sub getZoneList {
     1360  my $dbh = shift;
     1361
     1362  my %args = @_;
     1363
     1364  my @zonelist;
     1365
     1366  $args{sortorder} = 'ASC' if !grep $args{sortorder}, ('ASC','DESC');
     1367  $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/;
     1368
     1369  my @filterargs;
     1370  $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/;
     1371  push @filterargs, "^$args{startwith}" if $args{startwith};
     1372  $args{filter} =~ s/\./\[\.\]/g if $args{filter};      # only match literal dots, usually in reverse zones
     1373  push @filterargs, $args{filter} if $args{filter};
     1374
     1375  my $sql;
     1376  # Not as compact, and fix-me-twice if the common bits get wrong, but much easier to read
     1377  if ($args{revrec} eq 'n') {
     1378    $args{sortby} = 'domain' if !grep $args{sortby}, ('revnet','group','status');
     1379    $sql = "SELECT domain_id,domain,status,groups.group_name AS group FROM domains".
     1380        " INNER JOIN groups ON domains.group_id=groups.group_id".
     1381        " WHERE domains.group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")".
     1382        ($args{startwith} ? " AND domain ~* ?" : '').
     1383        ($args{filter} ? " AND domain ~* ?" : '');
     1384  } else {
     1385##fixme:  arguably startwith here is irrelevant.  depends on the UI though.
     1386    $args{sortby} = 'revnet' if !grep $args{sortby}, ('domain','group','status');
     1387    $sql = "SELECT rdns_id,revnet,status,groups.group_name AS group FROM revzones".
     1388        " INNER JOIN groups ON revzones.group_id=groups.group_id".
     1389        " WHERE revzones.group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")".
     1390        ($args{startwith} ? " AND CAST(revnet AS VARCHAR) ~* ?" : '').
     1391        ($args{filter} ? " AND CAST(revnet AS VARCHAR) ~* ?" : '');
     1392  }
     1393  # A common tail.
     1394  $sql .= " ORDER BY ".($args{sortby} eq 'group' ? 'groups.group_name' : $args{sortby})." $args{sortorder} ".
     1395        ($args{offset} eq 'all' ? '' : " LIMIT $config{perpage}".
     1396        " OFFSET ".$args{offset}*$config{perpage});
     1397  my $sth = $dbh->prepare($sql);
     1398  $sth->execute(@filterargs);
     1399  my $rownum = 0;
     1400
     1401  while (my @data = $sth->fetchrow_array) {
     1402    my %row;
     1403    $row{domainid} = $data[0];
     1404    $row{domain} = $data[1];
     1405    $row{status} = $data[2];
     1406    $row{group} = $data[3];
     1407    push @zonelist, \%row;
     1408  }
     1409
     1410  return \@zonelist;
     1411} # end getZoneList()
    7261412
    7271413
     
    7621448    $sth->execute($pargroup,$groupname);
    7631449
    764     $sth = $dbh->prepare("SELECT group_id FROM groups WHERE group_name=?");
    765     $sth->execute($groupname);
    766     my ($groupid) = $sth->fetchrow_array();
     1450    my ($groupid) = $dbh->selectrow_array("SELECT group_id FROM groups WHERE group_name=?", undef, ($groupname));
    7671451
    7681452# Permissions
     
    7891473
    7901474# Default records
    791     $sth = $dbh->prepare("INSERT INTO default_records (group_id,host,type,val,distance,weight,port,ttl) ".
     1475    my $sthf = $dbh->prepare("INSERT INTO default_records (group_id,host,type,val,distance,weight,port,ttl) ".
    7921476        "VALUES ($groupid,?,?,?,?,?,?,?)");
     1477    my $sthr = $dbh->prepare("INSERT INTO default_rev_records (group_id,host,type,val,ttl) ".
     1478        "VALUES ($groupid,?,?,?,?)");
    7931479    if ($inherit) {
    7941480      # Duplicate records from parent.  Actually relying on inherited records feels
     
    7971483      $sth2->execute($pargroup);
    7981484      while (my @clonedata = $sth2->fetchrow_array) {
    799         $sth->execute(@clonedata);
     1485        $sthf->execute(@clonedata);
     1486      }
     1487      # And now the reverse records
     1488      $sth2 = $dbh->prepare("SELECT group_id,host,type,val,ttl FROM default_rev_records WHERE group_id=?");
     1489      $sth2->execute($pargroup);
     1490      while (my @clonedata = $sth2->fetchrow_array) {
     1491        $sthr->execute(@clonedata);
    8001492      }
    8011493    } else {
     
    8031495      # reasonable basic defaults for SOA, MX, NS, and minimal hosting
    8041496      # could load from a config file, but somewhere along the line we need hardcoded bits.
    805       $sth->execute('ns1.example.com:hostmaster.example.com', 6, '10800:3600:604800:10800', 0, 0, 0, 86400);
    806       $sth->execute('DOMAIN', 1, '192.168.4.2', 0, 0, 0, 7200);
    807       $sth->execute('DOMAIN', 15, 'mx.example.com', 10, 0, 0, 7200);
    808       $sth->execute('DOMAIN', 2, 'ns1.example.com', 0, 0, 0, 7200);
    809       $sth->execute('DOMAIN', 2, 'ns2.example.com', 0, 0, 0, 7200);
    810       $sth->execute('www.DOMAIN', 5, 'DOMAIN', 0, 0, 0, 7200);
     1497      $sthf->execute('ns1.example.com:hostmaster.example.com', 6, '10800:3600:604800:10800', 0, 0, 0, 86400);
     1498      $sthf->execute('DOMAIN', 1, '192.168.4.2', 0, 0, 0, 7200);
     1499      $sthf->execute('DOMAIN', 15, 'mx.example.com', 10, 0, 0, 7200);
     1500      $sthf->execute('DOMAIN', 2, 'ns1.example.com', 0, 0, 0, 7200);
     1501      $sthf->execute('DOMAIN', 2, 'ns2.example.com', 0, 0, 0, 7200);
     1502      $sthf->execute('www.DOMAIN', 5, 'DOMAIN', 0, 0, 0, 7200);
     1503      # reasonable basic defaults for generic reverse zone.  Same as initial SQL tabledef.
     1504      $sthr->execute('hostmaster.ADMINDOMAIN:ns1.ADMINDOMAIN', 6, '10800:3600:604800:10800', 86400);
     1505      $sthr->execute('unused-%r.ADMINDOMAIN', 65283, 'ZONE', 3600);
    8111506    }
    8121507
     
    12221917## DNSDB::getSOA()
    12231918# Return all suitable fields from an SOA record in separate elements of a hash
    1224 # Takes a database handle, default/live flag, and group (default) or domain (live) ID
     1919# Takes a database handle, default/live flag, domain/reverse flag, and parent ID
    12251920sub getSOA {
    12261921  $errstr = '';
    12271922  my $dbh = shift;
    12281923  my $def = shift;
     1924  my $rev = shift;
    12291925  my $id = shift;
    12301926  my %ret;
    12311927
    1232   # (ab)use distance and weight columns to store SOA data
    1233 
    1234   my $sql = "SELECT record_id,host,val,ttl,distance from";
    1235   if ($def eq 'def' or $def eq 'y') {
    1236     $sql .= " default_records WHERE group_id=? AND type=$reverse_typemap{SOA}";
    1237   } else {
    1238     # we're editing a live SOA record;  find based on domain
    1239     $sql .= " records WHERE domain_id=? AND type=$reverse_typemap{SOA}";
    1240   }
     1928  # (ab)use distance and weight columns to store SOA data?  can't for default_rev_records...
     1929  # - should really attach serial to the zone parent somewhere
     1930
     1931  my $sql = "SELECT record_id,host,val,ttl from "._rectable($def,$rev).
     1932        " WHERE "._recparent($def,$rev)." = ? AND type=$reverse_typemap{SOA}";
     1933
    12411934  my $sth = $dbh->prepare($sql);
    12421935  $sth->execute($id);
    1243 
    1244   my ($recid,$host,$val,$ttl,$serial) = $sth->fetchrow_array() or return;
     1936##fixme:  stick a flag somewhere if the record doesn't exist.  by the API, this is an impossible case, but...
     1937
     1938  my ($recid,$host,$val,$ttl) = $sth->fetchrow_array() or return;
    12451939  my ($contact,$prins) = split /:/, $host;
    12461940  my ($refresh,$retry,$expire,$minttl) = split /:/, $val;
     
    12481942  $ret{recid}   = $recid;
    12491943  $ret{ttl}     = $ttl;
    1250   $ret{serial}  = $serial;
     1944#  $ret{serial} = $serial;      # ca't use distance for serial with default_rev_records
    12511945  $ret{prins}   = $prins;
    12521946  $ret{contact} = $contact;
     
    12601954
    12611955
     1956## DNSDB::updateSOA()
     1957# Update the specified SOA record
     1958# Takes a database handle, default/live flag, forward/reverse flag, and SOA data hash
     1959sub updateSOA {
     1960  my $dbh = shift;
     1961  my $defrec = shift;
     1962  my $revrec = shift;
     1963
     1964  my %soa = @_;
     1965
     1966##fixme: data validation: make sure {recid} is really the SOA for {parent}
     1967  my $sql = "UPDATE "._rectable($defrec, $revrec)." SET host=?, val=?, ttl=? WHERE record_id=? AND type=6";
     1968  $dbh->do($sql, undef, ("$soa{contact}:$soa{prins}", "$soa{refresh}:$soa{retry}:$soa{expire}:$soa{minttl}",
     1969        $soa{ttl}, $soa{recid}));
     1970
     1971} # end updateSOA()
     1972
     1973
    12621974## DNSDB::getRecLine()
    12631975# Return all data fields for a zone record in separate elements of a hash
    1264 # Takes a database handle, default/live flag, and record ID
     1976# Takes a database handle, default/live flag, forward/reverse flag, and record ID
    12651977sub getRecLine {
    12661978  $errstr = '';
    12671979  my $dbh = shift;
    1268   my $def = shift;
     1980  my $defrec = shift;
     1981  my $revrec = shift;
    12691982  my $id = shift;
    12701983
    1271   my $sql = "SELECT record_id,host,type,val,distance,weight,port,ttl".
    1272         (($def eq 'def' or $def eq 'y') ? ',group_id FROM default_' : ',domain_id FROM ').
    1273         "records WHERE record_id=?";
     1984  my $sql = "SELECT record_id,host,type,val,ttl".($revrec eq 'n' ? ',distance,weight,port' : '').
     1985        (($defrec eq 'y') ? ',group_id FROM ' : ',domain_id,rdns_id FROM ').
     1986        _rectable($defrec,$revrec)." WHERE record_id=?";
    12741987  my $ret = $dbh->selectrow_hashref($sql, undef, ($id) );
    12751988
     
    12841997  }
    12851998
    1286   $ret->{parid} = (($def eq 'def' or $def eq 'y') ? $ret->{group_id} : $ret->{domain_id});
     1999  # explicitly set a parent id
     2000  if ($defrec eq 'y') {
     2001    $ret->{parid} = $ret->{group_id};
     2002  } else {
     2003    $ret->{parid} = (($revrec eq 'n') ? $ret->{domain_id} : $ret->{rdns_id});
     2004    # and a secondary if we have a custom type that lives in both a forward and reverse zone
     2005    $ret->{secid} = (($revrec eq 'y') ? $ret->{domain_id} : $ret->{rdns_id}) if $ret->{type} > 65279;
     2006  }
    12872007
    12882008  return $ret;
     
    12992019  $errstr = '';
    13002020  my $dbh = shift;
    1301   my $type = shift;
     2021  my $def = shift;
     2022  my $rev = shift;
    13022023  my $id = shift;
    13032024  my $nrecs = shift || 'all';
     
    13102031  my $filter = shift || '';
    13112032
    1312   $type = 'y' if $type eq 'def';
    1313 
    1314   my $sql = "SELECT r.record_id,r.host,r.type,r.val,r.distance,r.weight,r.port,r.ttl FROM ";
    1315   $sql .= "default_" if $type eq 'y';
    1316   $sql .= "records r ";
     2033  my $sql = "SELECT r.record_id,r.host,r.type,r.val,r.ttl";
     2034  $sql .= ",r.distance,r.weight,r.port" if $rev eq 'n';
     2035  $sql .= " FROM "._rectable($def,$rev)." r ";
    13172036
    13182037  # whee!  multisort means just passing comma-separated fields in sortby!
     
    13262045
    13272046  $sql .= "INNER JOIN rectypes t ON r.type=t.val ";     # for sorting by type alphabetically
    1328   if ($type eq 'y') {
    1329     $sql .= "WHERE r.group_id=?";
    1330   } else {
    1331     $sql .= "WHERE r.domain_id=?";
    1332   }
     2047  $sql .= "WHERE "._recparent($def,$rev)." = ?";
    13332048  $sql .= " AND NOT r.type=$reverse_typemap{SOA}";
    13342049  $sql .= " AND host ~* ?" if $filter;
    13352050  # use alphaorder column for "correct" ordering of sort-by-type instead of DNS RR type number
    13362051  $sql .= " ORDER BY $newsort $direction";
    1337   $sql .= " LIMIT $nrecs OFFSET ".($nstart*$nrecs) if $nstart ne 'all';
    13382052
    13392053  my @bindvars = ($id);
    13402054  push @bindvars, $filter if $filter;
     2055
     2056  # just to be ultraparanoid about SQL injection vectors
     2057  if ($nstart ne 'all') {
     2058    $sql .= " LIMIT ? OFFSET ?";
     2059    push @bindvars, $nrecs;
     2060    push @bindvars, ($nstart*$nrecs);
     2061  }
    13412062  my $sth = $dbh->prepare($sql) or warn $dbh->errstr;
    13422063  $sth->execute(@bindvars) or warn "$sql: ".$sth->errstr;
     
    13532074
    13542075## DNSDB::getRecCount()
    1355 # Return count of non-SOA records in domain (or default records in a group)
    1356 # Takes a database handle, default/live flag, group/domain ID, and optional filtering modifier
     2076# Return count of non-SOA records in zone (or default records in a group)
     2077# Takes a database handle, default/live flag, reverse/forward flag, group/domain ID,
     2078# and optional filtering modifier
    13572079# Returns the count
    13582080sub getRecCount {
    13592081  my $dbh = shift;
    13602082  my $defrec = shift;
     2083  my $revrec = shift;
    13612084  my $id = shift;
    13622085  my $filter = shift || '';
     
    13682091  my @bindvars = ($id);
    13692092  push @bindvars, $filter if $filter;
    1370   my ($count) = $dbh->selectrow_array("SELECT count(*) FROM ".
    1371         ($defrec eq 'y' ? 'default_' : '')."records ".
    1372         "WHERE ".($defrec eq 'y' ? 'group' : 'domain')."_id=? ".
    1373         "AND NOT type=$reverse_typemap{SOA}".
    1374         ($filter ? " AND host ~* ?" : ''),
    1375         undef, (@bindvars) );
     2093  my $sql = "SELECT count(*) FROM ".
     2094        _rectable($defrec,$revrec).
     2095        " WHERE "._recparent($defrec,$revrec)."=? ".
     2096        "AND NOT type=$reverse_typemap{SOA}".
     2097        ($filter ? " AND host ~* ?" : '');
     2098  my ($count) = $dbh->selectrow_array($sql, undef, (@bindvars) );
    13762099
    13772100  return $count;
     
    13872110# and weight/port for SRV
    13882111# Returns a status code and detail message in case of error
     2112##fixme:  pass a hash with the record data, not a series of separate values
    13892113sub addRec {
    13902114  $errstr = '';
    13912115  my $dbh = shift;
    13922116  my $defrec = shift;
    1393   my $id = shift;
     2117  my $revrec = shift;
     2118  my $id = shift;       # parent (group_id for defrecs, rdns_id for reverse records,
     2119                        # domain_id for domain records)
    13942120
    13952121  my $host = shift;
    1396   my $rectype = shift;
     2122  my $rectype = shift;  # reference so we can coerce it if "+"-types can't find both zones
    13972123  my $val = shift;
    13982124  my $ttl = shift;
     
    14182144  }
    14192145
     2146  my $domid = 0;
     2147  my $revid = 0;
     2148
     2149  my $retcode = 'OK';   # assume everything will go OK
     2150  my $retmsg = '';
     2151
     2152  # do simple validation first
    14202153  return ('FAIL', "TTL must be numeric") unless $ttl =~ /^\d+$/;
    14212154
    1422   my $fields = ($defrec eq 'y' ? 'group_id' : 'domain_id').",host,type,val,ttl";
    1423   my $vallen = "?,?,?,?,?";
    1424   my @vallist = ($id,$host,$rectype,$val,$ttl);
    1425 
    1426   my $dist;
    1427   if ($rectype == $reverse_typemap{MX} or $rectype == $reverse_typemap{SRV}) {
    1428     $dist = shift;
    1429     return ('FAIL',"Distance is required for $typemap{$rectype} records") unless defined($dist);
    1430     $dist =~ s/\s*//g;
    1431     return ('FAIL',"Distance is required, and must be numeric") unless $dist =~ /^\d+$/;
    1432     $fields .= ",distance";
    1433     $vallen .= ",?";
    1434     push @vallist, $dist;
    1435   }
    1436   my $weight;
    1437   my $port;
    1438   if ($rectype == $reverse_typemap{SRV}) {
    1439     # check for _service._protocol.  NB:  RFC2782 does not say "MUST"...  nor "SHOULD"...
    1440     # it just says (paraphrased) "... is prepended with _ to prevent DNS collisions"
    1441     return ('FAIL',"SRV records must begin with _service._protocol [$host]")
    1442         unless $host =~ /^_[A-Za-z]+\._[A-Za-z]+\.[a-zA-Z0-9-]+/;
    1443     $weight = shift;
    1444     $port = shift;
    1445     return ('FAIL',"Port and weight are required for SRV records") unless defined($weight) && defined($port);
    1446     $weight =~ s/\s*//g;
    1447     $port =~ s/\s*//g;
    1448     return ('FAIL',"Port and weight are required, and must be numeric")
    1449         unless $weight =~ /^\d+$/ && $port =~ /^\d+$/;
    1450     $fields .= ",weight,port";
    1451     $vallen .= ",?,?";
    1452     push @vallist, ($weight,$port);
    1453   }
     2155  # Quick check on hostname parts.  Note the regex is more forgiving than the error message;
     2156  # domain names technically are case-insensitive, and we use printf-like % codes for a couple
     2157  # of types.  Other things may also be added to validate default records of several flavours.
     2158  return ('FAIL', "Hostnames may not contain anything other than (0-9 a-z . _)")
     2159        if $defrec eq 'n' && $$host !~ /^[0-9a-z_%.]+$/i;
     2160
     2161  # Collect these even if we're only doing a simple A record so we can call *any* validation sub
     2162  my $dist = shift;
     2163  my $port = shift;
     2164  my $weight = shift;
     2165
     2166  my $fields;
     2167  my @vallist;
     2168
     2169  # Call the validation sub for the type requested.
     2170  ($retcode,$retmsg) = $validators{$$rectype}($dbh, (defrec => $defrec, revrec => $revrec, id => $id,
     2171        host => $host, rectype => $rectype, val => $val, addr => $addr,
     2172        dist => \$dist, port => \$port, weight => \$weight,
     2173        fields => \$fields, vallist => \@vallist) );
     2174
     2175  return ($retcode,$retmsg) if $retcode eq 'FAIL';
     2176
     2177  # Set up database fields and bind parameters
     2178  $fields .= "host,type,val,ttl,"._recparent($defrec,$revrec);
     2179  push @vallist, ($$host,$$rectype,$$val,$ttl,$id);
     2180  my $vallen = '?'.(',?'x$#vallist);
    14542181
    14552182  # Allow transactions, and raise an exception on errors so we can catch it later.
     
    14592186
    14602187  eval {
    1461     $dbh->do("INSERT INTO ".($defrec eq 'y' ? 'default_' : '')."records ($fields) VALUES ($vallen)",
     2188    $dbh->do("INSERT INTO "._rectable($defrec, $revrec)." ($fields) VALUES ($vallen)",
    14622189        undef, @vallist);
    14632190    $dbh->commit;
     
    14692196  }
    14702197
    1471   return ('OK','OK');
     2198  return ($retcode, $retmsg);
    14722199
    14732200} # end addRec()
     
    15652292  my $dbh = shift;
    15662293  my $defrec = shift;
     2294  my $revrec = shift;
    15672295  my $id = shift;
    15682296
    1569   my $sth = $dbh->prepare("DELETE FROM ".($defrec eq 'y' ? 'default_' : '')."records WHERE record_id=?");
     2297  my $sth = $dbh->prepare("DELETE FROM "._rectable($defrec,$revrec)." WHERE record_id=?");
    15702298  $sth->execute($id);
    15712299
     
    15772305
    15782306  # Reference hashes.
    1579   my %par_tbl = (
     2307my %par_tbl = (
    15802308                group   => 'groups',
    15812309                user    => 'users',
    15822310                defrec  => 'default_records',
     2311                defrevrec       => 'default_rev_records',
    15832312                domain  => 'domains',
     2313                revzone => 'revzones',
    15842314                record  => 'records'
    15852315        );
    1586   my %id_col = (
     2316my %id_col = (
    15872317                group   => 'group_id',
    15882318                user    => 'user_id',
    15892319                defrec  => 'record_id',
     2320                defrevrec       => 'record_id',
    15902321                domain  => 'domain_id',
     2322                revzone => 'rdns_id',
    15912323                record  => 'record_id'
    15922324        );
    1593   my %par_col = (
     2325my %par_col = (
    15942326                group   => 'parent_group_id',
    15952327                user    => 'group_id',
    15962328                defrec  => 'group_id',
     2329                defrevrec       => 'group_id',
    15972330                domain  => 'group_id',
     2331                revzone => 'group_id',
    15982332                record  => 'domain_id'
    15992333        );
    1600   my %par_type = (
     2334my %par_type = (
    16012335                group   => 'group',
    16022336                user    => 'group',
    16032337                defrec  => 'group',
     2338                defrevrec       => 'group',
    16042339                domain  => 'group',
     2340                revzone => 'group',
    16052341                record  => 'domain'
    16062342        );
    16072343
    1608 ## DNSDB::getParents()
    1609 # Find out which entities are parent to the requested id
    1610 # Returns arrayref containing hash pairs of id/type
    1611 sub getParents {
    1612   my $dbh = shift;
    1613   my $id = shift;
    1614   my $type = shift;
    1615   my $depth = shift || 'all';   # valid values:  'all', 'immed', <int> (stop at this group ID)
    1616 
    1617   my @parlist;
    1618 
    1619   while (1) {
    1620     my $result = $dbh->selectrow_hashref("SELECT $par_col{$type} FROM $par_tbl{$type} WHERE $id_col{$type} = ?",
    1621         undef, ($id) );
    1622     my %tmp = ($result->{$par_col{$type}} => $par_type{$type});
    1623     unshift @parlist, \%tmp;
    1624     last if $result->{$par_col{$type}} == 1;    # group 1 is its own parent
    1625     $id = $result->{$par_col{$type}};
    1626     $type = $par_type{$type};
    1627   }
    1628 
    1629   return \@parlist;
    1630 
    1631 } # end getParents()
     2344
     2345## DNSDB::getTypelist()
     2346# Get a list of record types for various UI dropdowns
     2347# Takes database handle, forward/reverse/lookup flag, and optional "tag as selected" indicator (defaults to A)
     2348# Returns an arrayref to list of hashrefs perfect for HTML::Template
     2349sub getTypelist {
     2350  my $dbh = shift;
     2351  my $recgroup = shift;
     2352  my $type = shift || $reverse_typemap{A};
     2353
     2354  # also accepting $webvar{revrec}!
     2355  $recgroup = 'f' if $recgroup eq 'n';
     2356  $recgroup = 'r' if $recgroup eq 'y';
     2357
     2358  my $sql = "SELECT val,name FROM rectypes WHERE ";
     2359  if ($recgroup eq 'r') {
     2360    # reverse zone types
     2361    $sql .= "stdflag=2 OR stdflag=3";
     2362  } elsif ($recgroup eq 'l') {
     2363    # DNS lookup types.  Note we avoid our custom types >= 65280, since those are entirely internal.
     2364    $sql .= "(stdflag=1 OR stdflag=2 OR stdflag=3) AND val < 65280";
     2365  } else {
     2366    # default;  forward zone types.  technically $type eq 'f' but not worth the error message.
     2367    $sql .= "stdflag=1 OR stdflag=2";
     2368  }
     2369  $sql .= " ORDER BY listorder";
     2370
     2371  my $sth = $dbh->prepare($sql);
     2372  $sth->execute;
     2373  my @typelist;
     2374  while (my ($rval,$rname) = $sth->fetchrow_array()) {
     2375    my %row = ( recval => $rval, recname => $rname );
     2376    $row{tselect} = 1 if $rval == $type;
     2377    push @typelist, \%row;
     2378  }
     2379
     2380  # Add SOA on lookups since it's not listed in other dropdowns.
     2381  if ($recgroup eq 'l') {
     2382    my %row = ( recval => $reverse_typemap{SOA}, recname => 'SOA' );
     2383    $row{tselect} = 1 if $reverse_typemap{SOA} == $type;
     2384    push @typelist, \%row;
     2385  }
     2386
     2387  return \@typelist;
     2388} # end getTypelist()
     2389
     2390
     2391## DNSDB::parentID()
     2392# Get ID of entity that is nearest parent to requested id
     2393# Takes a database handle and a hash of entity ID, entity type, optional parent type flag
     2394# (domain/reverse zone or group), and optional default/live and forward/reverse flags
     2395# Returns the ID or undef on failure
     2396sub parentID {
     2397  my $dbh = shift;
     2398
     2399  my %args = @_;
     2400
     2401  # clean up the parent-type.  Set it to group if not set;  coerce revzone to domain for simpler logic
     2402  $args{partype} = 'group' if !$args{partype};
     2403  $args{partype} = 'domain' if $args{partype} eq 'revzone';
     2404
     2405  # clean up defrec and revrec.  default to live record, forward zone
     2406  $args{defrec} = 'n' if !$args{defrec};
     2407  $args{revrec} = 'n' if !$args{revrec};
     2408
     2409  if ($par_type{$args{partype}} eq 'domain') {
     2410    # only live records can have a domain/zone parent
     2411    return unless ($args{type} eq 'record' && $args{defrec} eq 'n');
     2412    my $result = $dbh->selectrow_hashref("SELECT ".($args{revrec} eq 'n' ? 'domain_id' : 'rdns_id').
     2413        " FROM records WHERE record_id = ?",
     2414        undef, ($args{id}) ) or return;
     2415    return $result;
     2416  } else {
     2417    # snag some arguments that will either fall through or be overwritten to save some code duplication
     2418    my $tmpid = $args{id};
     2419    my $type = $args{type};
     2420    if ($type eq 'record' && $args{defrec} eq 'n') {
     2421      # Live records go through the records table first.
     2422      ($tmpid) = $dbh->selectrow_array("SELECT ".($args{revrec} eq 'n' ? 'domain_id' : 'rdns_id').
     2423        " FROM records WHERE record_id = ?",
     2424        undef, ($args{id}) ) or return;
     2425      $type = ($args{revrec} eq 'n' ? 'domain' : 'revzone');
     2426    }
     2427    my ($result) = $dbh->selectrow_array("SELECT $par_col{$type} FROM $par_tbl{$type} WHERE $id_col{$type} = ?",
     2428        undef, ($tmpid) );
     2429    return $result;
     2430  }
     2431# should be impossible to get here with even remotely sane arguments
     2432  return;
     2433} # end parentID()
    16322434
    16332435
     
    16432445
    16442446  # Return false on invalid types
    1645   return 0 if !grep /^$type1$/, ('record','defrec','user','domain','group');
    1646   return 0 if !grep /^$type2$/, ('record','defrec','user','domain','group');
     2447  return 0 if !grep /^$type1$/, ('record','defrec','defrevrec','user','domain','revzone','group');
     2448  return 0 if !grep /^$type2$/, ('record','defrec','defrevrec','user','domain','revzone','group');
    16472449
    16482450  # Return false on impossible relations
    16492451  return 0 if $type1 eq 'record';       # nothing may be a child of a record
    16502452  return 0 if $type1 eq 'defrec';       # nothing may be a child of a record
     2453  return 0 if $type1 eq 'defrevrec';    # nothing may be a child of a record
    16512454  return 0 if $type1 eq 'user';         # nothing may be child of a user
    16522455  return 0 if $type1 eq 'domain' && $type2 ne 'record'; # domain may not be a parent of anything other than a record
     2456  return 0 if $type1 eq 'revzone' && $type2 ne 'record';# reverse zone may not be a parent of anything other than a record
    16532457
    16542458  # ennnhhhh....  if we're passed an id of 0, it will never be found.  usual
    16552459  # case would be the UI creating a new <thing>, and so we don't have an ID for
    16562460  # <thing> to look up yet.  in that case the UI should check the parent as well.
    1657   # argument for returning 1 is
    16582461  return 0 if $id1 == 0;        # nothing can have a parent id of 0
    16592462  return 1 if $id2 == 0;        # anything could have a child id of 0 (or "unknown")
     
    16652468  return 1 if $type1 eq 'group' && $type2 eq 'group' && $id1 == $id2;
    16662469
    1667 # almost the same loop as getParents() above
    16682470  my $id = $id2;
    16692471  my $type = $type2;
    16702472  my $foundparent = 0;
    16712473
     2474  # Records are the only entity with two possible parents.  We need to split the parent checks on
     2475  # domain/rdns.
     2476  if ($type eq 'record') {
     2477    my ($dom,$rdns) = $dbh->selectrow_array("SELECT domain_id,rdns_id FROM records WHERE record_id=?",
     2478        undef, ($id));
     2479    # check immediate parent against request
     2480    return 1 if $type1 eq 'domain' && $id1 == $dom;
     2481    return 1 if $type1 eq 'revzone' && $id1 == $rdns;
     2482    # if request is group, check *both* parents.  Only check if the parent is nonzero though.
     2483    return 1 if $dom && isParent($dbh, $id1, $type1, $dom, 'domain');
     2484    return 1 if $rdns && isParent($dbh, $id1, $type1, $rdns, 'revzone');
     2485    # exit here since we've executed the loop below by proxy in the above recursive calls.
     2486    return 0;
     2487  }
     2488
     2489# almost the same loop as getParents() above
    16722490  my $limiter = 0;
    16732491  while (1) {
     
    16772495    if (!$result) {
    16782496      $limiter++;
    1679 ##fixme:  how often will this happen on a live site?
     2497##fixme:  how often will this happen on a live site?  fail at max limiter <n>?
    16802498      warn "no results looking for $sql with id $id (depth $limiter)\n";
    16812499      last;
     
    16862504    } else {
    16872505##fixme: do we care about trying to return a "no such record/domain/user/group" error?
     2506# should be impossible to create an inconsistent DB just with API calls.
    16882507      warn $dbh->errstr." $sql, $id" if $dbh->errstr;
    16892508    }
  • branches/stable/dns.cgi

    r438 r544  
    6262
    6363# shut up some warnings, in case we arrive somewhere we forgot to set this
    64 $webvar{defrec} = 'n' if !$webvar{defrec};
     64$webvar{defrec} = 'n' if !$webvar{defrec};      # non-default records
     65$webvar{revrec} = 'n' if !$webvar{revrec};      # non-reverse (domain) records
    6566
    6667# load some local system defaults (mainly DB connect info)
     
    8788  $session->param('domlistsortby','domain');
    8889  $session->param('domlistorder','ASC');
     90  $session->param('revzonessortby','revnet');
     91  $session->param('revzonesorder','ASC');
    8992  $session->param('useradminsortby','user');
    9093  $session->param('useradminorder','ASC');
     
    204207my $page;
    205208eval {
    206   $page = HTML::Template->new(filename => "$templatedir/$webvar{page}.tmpl");
     209  # sigh.  can't set loop_context_vars or global_vars once instantiated.
     210  $page = HTML::Template->new(filename => "$templatedir/$webvar{page}.tmpl",
     211        loop_context_vars => 1, global_vars => 1);
    207212};
    208213if ($@) {
    209   warn "Bad page $webvar{page} requested";
     214  my $msg = $@;
    210215  $page = HTML::Template->new(filename => "$templatedir/badpage.tmpl");
    211   $page->param(badpage => $q->escapeHTML($webvar{page}));
     216  if (-e "$templatedir/$webvar{page}.tmpl") {
     217    $page->param(badtemplate => $q->escapeHTML($msg));
     218  } else {
     219    warn "Bad page $webvar{page} requested";
     220    $page->param(badpage => $q->escapeHTML($webvar{page}));
     221  }
    212222  $webvar{page} = 'badpage';
    213223}
     
    279289
    280290    # I hate special cases.
     291##fixme: probably need to handle webvar{revrec}=='y' too
    281292    if ($webvar{page} eq 'reclist' && $webvar{defrec} eq 'y') {
    282       my %args = (page => $webvar{page}, id => $curgroup, defrec => $webvar{defrec});
     293      my %args = (page => $webvar{page}, id => $curgroup, defrec => $webvar{defrec}, revrec => $webvar{revrec});
    283294      $args{errmsg} = $errmsg if $errmsg;
    284295      changepage(%args);
     
    300311
    301312} elsif ($webvar{page} eq 'domlist' or $webvar{page} eq 'index') {
     313
     314  $page->param(domlist => 1);
    302315
    303316# hmm.  seeing problems in some possibly-not-so-corner cases.
     
    312325      my $stat = domStatus($dbh,$webvar{id},$webvar{domstatus});
    313326##fixme  switch to more consise "Enabled <domain"/"Disabled <domain>" as with users?
    314       logaction($webvar{id}, $session->param("username"), parentID($webvar{id}, 'dom', 'group'),
     327      logaction($webvar{id}, $session->param("username"),
     328        parentID($dbh, (id => $webvar{id}, type => 'domain', revrec => $webvar{revrec})),
    315329        "Changed ".domainName($dbh, $webvar{id})." state to ".($stat ? 'active' : 'inactive'));
    316330      $page->param(resultmsg => "Changed ".domainName($dbh, $webvar{id})." state to ".
     
    366380
    367381  my ($code,$msg) = addDomain($dbh,$webvar{domain},$webvar{group},($webvar{makeactive} eq 'on' ? 1 : 0),
    368         (name => $session->param("username"), id => $session->param("uid")));
     382        (username => $session->param("username"), id => $session->param("uid")));
    369383
    370384  if ($code eq 'OK') {
     
    401415
    402416  } elsif ($webvar{del} eq 'ok') {
    403     my $pargroup = parentID($webvar{id}, 'dom', 'group');
     417    my $pargroup = parentID($dbh, (id => $webvar{id}, type => 'domain', revrec => $webvar{revrec}));
    404418    my $dom = domainName($dbh, $webvar{id});
    405419    my ($code,$msg) = delDomain($dbh, $webvar{id});
     
    418432  }
    419433
     434} elsif ($webvar{page} eq 'revzones') {
     435
     436  $webvar{revrec} = 'y';
     437  $page->param(curpage => $webvar{page});
     438  listzones();
     439
     440} elsif ($webvar{page} eq 'newrevzone') {
     441
     442## scope/access check - use domain settings?  invent new (bleh)
     443  changepage(page => "revzones", errmsg => "You are not permitted to add reverse zones")
     444       unless ($permissions{admin} || $permissions{domain_create});
     445
     446  fill_grouplist("grouplist");
     447
     448  if ($webvar{add_failed}) {
     449    $page->param(add_failed => 1);
     450    $page->param(errmsg => $webvar{errmsg});
     451    $page->param(revzone => $webvar{revzone});
     452    $page->param(revpatt => $webvar{revpatt});
     453  }
     454
     455} elsif ($webvar{page} eq 'addrevzone') {
     456
     457  changepage(page => "revzones", errmsg => "You are not permitted to add reverse zones")
     458       unless ($permissions{admin} || $permissions{domain_create});
     459
     460  # security check - does the user have permission to access this entity?
     461  if (!check_scope(id => $webvar{group}, type => 'group')) {
     462    changepage(page => "newrevzone", add_failed => 1, revzone => $webvar{revzone}, revpatt => $webvar{revpatt},
     463       errmsg => "You do not have permission to add a reverse zone to the requested group");
     464  }
     465
     466  my ($code,$msg) = addRDNS($dbh, $webvar{revzone}, $webvar{revpatt}, $webvar{group},
     467        ($webvar{makeactive} eq 'on' ? 1 : 0),
     468        (username => $session->param("username"), id => $session->param("uid")) );
     469
     470  if ($code eq 'OK') {
     471    logaction(0, $session->param("username"), $webvar{group}, "Added reverse zone $webvar{revzone}", $msg);
     472    changepage(page => "reclist", id => $msg, revrec => 'y');
     473  } else {
     474    logaction(0, $session->param("username"), $webvar{group}, "Failed adding reverse zone $webvar{revzone} ($msg)");
     475    changepage(page => "newrevzone", add_failed => 1, revzone => $webvar{revzone}, revpatt => $webvar{revpatt},
     476       errmsg => $msg);
     477  }
     478
     479#} elsif ($webvar{page} eq 'delrevzone') {
     480
    420481} elsif ($webvar{page} eq 'reclist') {
    421482
    422483  # security check - does the user have permission to view this entity?
    423   if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) {
     484  if (!check_scope(id => $webvar{id}, type =>
     485        ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) {
    424486    $page->param(errmsg => "You are not permitted to view or change the requested ".
    425         ($webvar{defrec} eq 'y' ? "group's default records" : "domain's records"));
     487        ($webvar{defrec} eq 'y' ? "group's default records" :
     488                ($webvar{revrec} eq 'y' ? "reverse zone's records" : "domain's records")));
    426489    $page->param(perm_err => 1);        # this causes the template to skip the record listing output.
    427490    goto DONERECLIST;   # and now we skip filling in the content which is not printed due to perm_err above
     
    446509
    447510    $page->param(defrec => $webvar{defrec});
     511    $page->param(revrec => $webvar{revrec});
    448512    $page->param(id => $webvar{id});
    449513    $page->param(curpage => $webvar{page});
    450514
    451     my $count = getRecCount($dbh, $webvar{defrec}, $webvar{id}, $filter);
     515    my $count = getRecCount($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id}, $filter);
    452516
    453517    $sortby = 'host';
     
    460524
    461525# set up the headers
    462     my @cols = ('host', 'type', 'val', 'distance', 'weight', 'port', 'ttl');
    463     my %colheads = (host => 'Name', type => 'Type', val => 'Address',
     526    my @cols;
     527    my %colheads;
     528    if ($webvar{revrec} eq 'n') {
     529      @cols = ('host', 'type', 'val', 'distance', 'weight', 'port', 'ttl');
     530      %colheads = (host => 'Name', type => 'Type', val => 'Address',
    464531        distance => 'Distance', weight => 'Weight', port => 'Port', ttl => 'TTL');
    465     my %custom = (id => $webvar{id}, defrec => $webvar{defrec});
     532    } else {
     533      @cols = ('host', 'type', 'val', 'ttl');
     534      %colheads = (host => 'IP Address', type => 'Type', val => 'Hostname', ttl => 'TTL');
     535    }
     536    my %custom = (id => $webvar{id}, defrec => $webvar{defrec}, revrec => $webvar{revrec});
    466537    fill_colheads($sortby, $sortorder, \@cols, \%colheads, \%custom);
    467538
    468539# fill the page-count and first-previous-next-last-all details
    469540    fill_pgcount($count,"records",
    470         ($webvar{defrec} eq 'y' ? "group ".groupName($dbh,$webvar{id}) : domainName($dbh,$webvar{id})));
     541        ($webvar{defrec} eq 'y' ? "group ".groupName($dbh,$webvar{id}) :
     542                ($webvar{revrec} eq 'y' ? revName($dbh,$webvar{id}) : domainName($dbh,$webvar{id}))
     543        ));
    471544    fill_fpnla($count);  # should put some params on this sub...
    472545
    473546    $page->param(defrec => $webvar{defrec});
    474     if ($webvar{defrec} eq 'y') {
    475       showdomain('y',$curgroup);
    476     } else {
    477       showdomain('n',$webvar{id});
     547    showzone($webvar{defrec}, $webvar{revrec}, $webvar{id});
     548    if ($webvar{defrec} eq 'n') {
     549#      showzone('n',$webvar{id});
    478550##fixme:  permission for viewing logs?
    479       $page->param(logdom => 1);
     551##fixme:  determine which slice of the log we view (group, domain, revzone)
     552      if ($webvar{revrec} eq 'n') {
     553        $page->param(logdom => 1);
     554      } else {
     555        $page->param(logrdns => 1);
     556      }
    480557    }
    481558
     
    484561      $session->clear('resultmsg');
    485562    }
     563    if ($session->param('warnmsg')) {
     564      $page->param(warnmsg => $session->param('warnmsg'));
     565      $session->clear('warnmsg');
     566    }
    486567    if ($session->param('errmsg')) {
    487568      $page->param(errmsg => $session->param('errmsg'));
     
    497578
    498579  # security check - does the user have permission to access this entity?
    499   if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'defrec' : 'record'))) {
     580  if (!check_scope(id => $webvar{id}, type =>
     581        ($webvar{defrec} eq 'y' ? ($webvar{revrec} eq 'y' ? 'defrevrec' : 'defrec') : 'record'))) {
    500582    $page->param(perm_err => "You are not permitted to edit the requested record");
    501583    goto DONEREC;
    502584  }
    503585  # round 2, check the parent.
    504   if (!check_scope(id => $webvar{parentid}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) {
     586  if (!check_scope(id => $webvar{parentid}, type =>
     587        ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) {
    505588    my $msg = ($webvar{defrec} eq 'y' ?
    506589        "You are not permitted to add or edit default records in the requested group" :
    507         "You are not permitted to add or edit records in the requested domain");
     590        "You are not permitted to add or edit records in the requested domain/zone");
    508591    $page->param(perm_err => $msg);
    509592    goto DONEREC;
    510593  }
     594
     595  $page->param(defrec => $webvar{defrec});
     596  $page->param(revrec => $webvar{revrec});
     597  $page->param(fwdzone => $webvar{revrec} eq 'n');
    511598
    512599  if ($webvar{recact} eq 'new') {
     
    518605    $page->param(recact => "add");
    519606    $page->param(parentid => $webvar{parentid});
    520     $page->param(defrec => $webvar{defrec});
    521607
    522608    fill_recdata();
     
    527613        unless ($permissions{admin} || $permissions{record_create});
    528614
    529 ##fixme: this should probably go in DNSDB::addRec(), need to ponder what to do about PTR and friends
    530     # prevent out-of-domain records from getting added by appending the domain, or DOMAIN for default records
    531     my $pname = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid}));
    532     $webvar{name} =~ s/\.*$/\.$pname/ if $webvar{name} !~ /$pname$/;
    533 
    534     my @recargs = ($dbh,$webvar{defrec},$webvar{parentid},$webvar{name},$webvar{type},$webvar{address},$webvar{ttl});
     615    my @recargs = ($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid},
     616        \$webvar{name},\$webvar{type},\$webvar{address},$webvar{ttl});
    535617    if ($webvar{type} == $reverse_typemap{MX} or $webvar{type} == $reverse_typemap{SRV}) {
    536618      push @recargs, $webvar{distance};
     
    543625    my ($code,$msg) = addRec(@recargs);
    544626
    545     if ($code eq 'OK') {
     627    if ($code eq 'OK' || $code eq 'WARN') {
     628      my $restr;
    546629      if ($webvar{defrec} eq 'y') {
    547         my $restr = "Added default record '$webvar{name} $typemap{$webvar{type}}";
     630        $restr = "Added default record '$webvar{name} $typemap{$webvar{type}}";
    548631        $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX';
    549632        $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]"
     
    551634        $restr .= " $webvar{address}', TTL $webvar{ttl}";
    552635        logaction(0, $session->param("username"), $webvar{parentid}, $restr);
    553         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
    554636      } else {
    555         my $restr = "Added record '$webvar{name} $typemap{$webvar{type}}";
     637        $restr = "Added record '$webvar{name} $typemap{$webvar{type}}";
    556638        $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX';
    557639        $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]"
    558640                if $typemap{$webvar{type}} eq 'SRV';
    559641        $restr .= " $webvar{address}', TTL $webvar{ttl}";
    560         logaction($webvar{parentid}, $session->param("username"), parentID($webvar{parentid}, 'dom', 'group'), $restr);
    561         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
    562       }
     642        logaction($webvar{parentid}, $session->param("username"),
     643                parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})), $restr);
     644      }
     645      my %pageparams = (page => "reclist", id => $webvar{parentid},
     646        defrec => $webvar{defrec}, revrec => $webvar{revrec});
     647      $pageparams{warnmsg} = $msg."<br><br>\n".$restr if $code eq 'WARN';
     648      $pageparams{resultmsg} = $restr if $code eq 'OK';
     649      changepage(%pageparams);
    563650    } else {
    564651      $page->param(failed       => 1);
     
    568655      $page->param(recact       => "add");
    569656      $page->param(parentid     => $webvar{parentid});
    570       $page->param(defrec       => $webvar{defrec});
    571657      $page->param(id           => $webvar{id});
    572658      fill_recdata();   # populate the form... er, mostly.
    573       $page->param(name => $webvar{name});
    574659      if ($config{log_failures}) {
    575660        if ($webvar{defrec} eq 'y') {
     
    577662                "Failed adding default record '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl} ($msg)");
    578663        } else {
    579           logaction($webvar{parentid}, $session->param("username"), parentID($webvar{parentid}, 'dom', 'group'),
     664          logaction($webvar{parentid}, $session->param("username"),
     665                parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})),
    580666                "Failed adding record '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl} ($msg)");
    581667        }
     
    592678    $page->param(parentid       => $webvar{parentid});
    593679    $page->param(id             => $webvar{id});
    594     $page->param(defrec         => $webvar{defrec});
    595     my $recdata = getRecLine($dbh, $webvar{defrec}, $webvar{id});
     680    my $recdata = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
    596681    $page->param(name           => $recdata->{host});
    597682    $page->param(address        => $recdata->{val});
     
    600685    $page->param(port           => $recdata->{port});
    601686    $page->param(ttl            => $recdata->{ttl});
    602     fill_rectypes($recdata->{type});
     687    $page->param(typelist       => getTypelist($dbh, $webvar{revrec}, $webvar{type}));
    603688
    604689  } elsif ($webvar{recact} eq 'update') {
     
    613698
    614699    # get current/previous record info so we can log "updated 'foo A 1.2.3.4' to 'foo A 2.3.4.5'"
    615     my $oldrec = getRecLine($dbh, $webvar{defrec}, $webvar{id});
     700    my $oldrec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
    616701
    617702    my ($code,$msg) = updateRec($dbh,$webvar{defrec},$webvar{id},
     
    629714        my $restr = "Updated record from '$oldrec->{host} $typemap{$oldrec->{type}} $oldrec->{val}', TTL $oldrec->{ttl}\n".
    630715                "to '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl}";
    631         logaction($webvar{parentid}, $session->param("username"), parentID($webvar{id}, 'rec', 'group'), $restr);
     716        logaction($webvar{parentid}, $session->param("username"),
     717                parentID($dbh, (id => $webvar{id}, type => 'record', defrec => $webvar{defrec},
     718                        revrec => $webvar{revrec}, partype => 'group')),
     719                $restr);
    632720        changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
    633721      }
     
    639727      $page->param(recact       => "update");
    640728      $page->param(parentid     => $webvar{parentid});
    641       $page->param(defrec       => $webvar{defrec});
    642729      $page->param(id           => $webvar{id});
    643730      fill_recdata();
     
    647734                "Failed updating default record '$typemap{$webvar{type}} $webvar{name} $webvar{address}', TTL $webvar{ttl} ($msg)");
    648735        } else {
    649           logaction($webvar{parentid}, $session->param("username"), parentID($webvar{parentid}, 'dom', 'group'),
     736          logaction($webvar{parentid}, $session->param("username"),
     737                parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})),
    650738                "Failed updating record '$typemap{$webvar{type}} $webvar{name} $webvar{address}', TTL $webvar{ttl} ($msg)");
    651739        }
     
    658746  } else {
    659747    $page->param(parentid => $webvar{parentid});
    660     $page->param(dohere => domainName($dbh,$webvar{parentid}));
     748    $page->param(dohere => domainName($dbh,$webvar{parentid})) if $webvar{revrec} eq 'n';
     749    $page->param(dohere => revName($dbh,$webvar{parentid})) if $webvar{revrec} eq 'y';
    661750  }
    662751
     
    668757  # This is a complete separate segment since it uses a different template from add/edit records above
    669758
    670   changepage(page => "reclist", errmsg => "You are not permitted to delete records", id => $webvar{parentid})
     759  changepage(page => "reclist", errmsg => "You are not permitted to delete records", id => $webvar{parentid},
     760                defrec => $webvar{defrec}, revrec => $webvar{revrec})
    671761        unless ($permissions{admin} || $permissions{record_delete});
    672762
    673763  if (!check_scope(id => $webvar{id}, type =>
    674764        ($webvar{defrec} eq 'y' ? ($webvar{revrec} eq 'y' ? 'defrevrec' : 'defrec') : 'record'))) {
    675     changepage(page => 'domlist', errmsg => "You do not have permission to delete records in the requested ".
     765    # redirect to domlist because we don't have permission for the entity requested
     766    changepage(page => 'domlist', revrec => $webvar{revrec},
     767        errmsg => "You do not have permission to delete records in the requested ".
    676768        ($webvar{defrec} eq 'y' ? 'group' : 'domain'));
    677769  }
     
    679771  $page->param(id => $webvar{id});
    680772  $page->param(defrec => $webvar{defrec});
     773  $page->param(revrec => $webvar{revrec});
    681774  $page->param(parentid => $webvar{parentid});
    682775  # first pass = confirm y/n (sorta)
    683776  if (!defined($webvar{del})) {
    684777    $page->param(del_getconf => 1);
    685     my $rec = getRecLine($dbh,$webvar{defrec},$webvar{id});
     778    my $rec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
    686779    $page->param(host => $rec->{host});
    687780    $page->param(ftype => $typemap{$rec->{type}});
     
    689782  } elsif ($webvar{del} eq 'ok') {
    690783# get rec data before we try to delete it
    691     my $rec = getRecLine($dbh,$webvar{defrec},$webvar{id});
    692     my ($code,$msg) = delRec($dbh,$webvar{defrec},$webvar{id});
     784    my $rec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
     785    my ($code,$msg) = delRec($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
    693786    if ($code eq 'OK') {
    694787      if ($webvar{defrec} eq 'y') {
     788        my $recclass = ($webvar{revrec} eq 'n' ? 'default record' : 'default reverse record');
    695789##fixme:  log distance for MX;  log port/weight/distance for SRV
    696         my $restr = "Deleted default record '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}";
     790        my $restr = "Deleted $recclass '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}";
    697791        logaction(0, $session->param("username"), $rec->{parid}, $restr);
    698         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
     792        changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec},
     793                revrec => $webvar{revrec}, resultmsg => $restr);
    699794      } else {
    700         my $restr = "Deleted record '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}";
    701         logaction($rec->{parid}, $session->param("username"), parentID($rec->{parid}, 'dom', 'group'), $restr);
    702         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
     795        my $recclass = ($webvar{revrec} eq 'n' ? 'record' : 'reverse record');
     796        my $restr = "Deleted $recclass '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}";
     797        logaction($rec->{parid}, $session->param("username"),
     798                parentID($dbh, (id => $rec->{parid}, type => 'domain', revrec => $webvar{revrec})),
     799                $restr);
     800        changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec},
     801                revrec => $webvar{revrec}, resultmsg => $restr);
    703802      }
    704803    } else {
     
    710809                " TTL $rec->{ttl} ($msg)");
    711810        } else {
    712           logaction($rec->{parid}, $session->param("username"), parentID($rec->{parid}, 'dom', 'group'),
     811          logaction($rec->{parid}, $session->param("username"),
     812                parentID($dbh, (id => $rec->{parid}, type => 'domain', revrec => $webvar{revrec})),
    713813                "Failed deleting record '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl} ($msg)");
    714814        }
    715815      }
    716816      changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec},
    717                 errmsg => "Error deleting record: $msg");
     817                revrec => $webvar{revrec}, errmsg => "Error deleting record: $msg");
    718818    }
    719819  } else {
    720     changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec});
     820    changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, revrec => $webvar{revrec});
    721821  }
    722822
     
    724824
    725825  # security check - does the user have permission to view this entity?
    726   if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) {
     826  # id is domain/revzone/group id
     827  if (!check_scope(id => $webvar{id}, type =>
     828        ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) {
    727829    changepage(page => 'domlist', errmsg => "You do not have permission to edit the ".
    728830        ($webvar{defrec} eq 'y' ? 'default ' : '')."SOA record for the requested ".
     
    744846  # security check - does the user have permission to view this entity?
    745847  # pass 1, record ID
    746   if (!check_scope(id => $webvar{recid}, type => ($webvar{defrec} eq 'y' ? 'defrec' : 'record'))) {
     848  if (!check_scope(id => $webvar{recid}, type =>
     849        ($webvar{defrec} eq 'y' ? ($webvar{revrec} eq 'y' ? 'defrevrec' : 'defrec') : 'record'))) {
    747850    changepage(page => 'domlist', errmsg => "You do not have permission to edit the requested SOA record");
    748851  }
    749852  # pass 2, parent (group or domain) ID
    750   if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) {
     853  if (!check_scope(id => $webvar{id}, type =>
     854        ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) {
    751855    changepage(page => 'domlist', errmsg => "You do not have permission to edit the ".
    752856        ($webvar{defrec} eq 'y' ? 'default ' : '')."SOA record for the requested ".
     
    787891      $logdomain = 0;
    788892    } else {
    789       $loggroup = parentID($logdomain, 'dom', 'group', $webvar{defrec});
     893      $loggroup = parentID($dbh, (id => $logdomain, type => 'domain', revrec => $webvar{revrec}));
    790894    }
    791895
     
    8981002  } elsif ($webvar{del} eq 'ok') {
    8991003    my $deleteme = groupName($dbh,$webvar{id}); # get this before we delete it...
    900     my $delparent = parentID($webvar{id}, 'group','group');
     1004    my $delparent = parentID($dbh, (id => $webvar{id}, type => 'group'));
    9011005    my ($code,$msg) = delGroup($dbh, $webvar{id});
    9021006    if ($code eq 'OK') {
     
    10411145      my ($code, $msg) = changeGroup($dbh, 'domain', $webvar{$_}, $webvar{destgroup});
    10421146      if ($code eq 'OK') {
    1043         logaction($webvar{$_}, $session->param("username"), parentID($webvar{$_}, 'dom', 'group'),
     1147        logaction($webvar{$_}, $session->param("username"),
     1148                parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})),
    10441149                "Moved domain ".domainName($dbh, $webvar{$_})." to group $newgname");
    10451150        $row{domok} = ($code eq 'OK');
    10461151      } else {
    1047         logaction($webvar{$_}, $session->param("username"), parentID($webvar{$_}, 'dom', 'group'),
     1152        logaction($webvar{$_}, $session->param("username"),
     1153                parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})),
    10481154                "Failed to move domain ".domainName($dbh, $webvar{$_})." to group $newgname: $msg")
    10491155                if $config{log_failures};
     
    10721178##fixme:  error handling on status change
    10731179      my $stat = domStatus($dbh,$webvar{$_},($webvar{bulkaction} eq 'activate' ? 'domon' : 'domoff'));
    1074       logaction($webvar{$_}, $session->param("username"), parentID($webvar{$_}, 'dom', 'group'),
    1075                 "Changed domain ".domainName($dbh, $webvar{$_})." state to ".($stat ? 'active' : 'inactive'));
     1180      logaction($webvar{$_}, $session->param("username"),
     1181        parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})),
     1182        "Changed domain ".domainName($dbh, $webvar{$_})." state to ".($stat ? 'active' : 'inactive'));
    10761183      $row{domok} = 1;
    10771184#      $row{domok} = ($code eq 'OK');
     
    10971204      }
    10981205      $row{domain} = domainName($dbh,$webvar{$_});
    1099       my $pargroup = parentID($webvar{$_}, 'dom', 'group');
     1206      my $pargroup = parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec}));
    11001207      my $dom = domainName($dbh, $webvar{$_});
    11011208      my ($code, $msg) = delDomain($dbh, $webvar{$_});
     
    11301237    if ($flag && ($permissions{admin} || $permissions{user_edit})) {
    11311238      my $stat = userStatus($dbh,$webvar{id},$webvar{userstatus});
    1132       logaction(0, $session->param("username"), parentID($webvar{id}, 'user', 'group'),
     1239      logaction(0, $session->param("username"), parentID($dbh, (id => $webvar{id}, type => 'user')),
    11331240        ($stat ? 'Enabled' : 'Disabled')." ".userFullName($dbh, $webvar{id}, '%u'));
    11341241      $page->param(resultmsg => ($stat ? 'Enabled' : 'Disabled')." ".userFullName($dbh, $webvar{id}, '%u'));
     
    13881495
    13891496  $page->param(qfor => $webvar{qfor}) if $webvar{qfor};
    1390   fill_rectypes($webvar{type} ? $webvar{type} : '', 1);
     1497  $page->param(typelist => getTypelist($dbh, 'l', ($webvar{type} ? $webvar{type} : undef)));
    13911498  $page->param(nrecurse => $webvar{nrecurse}) if $webvar{nrecurse};
    13921499  $page->param(resolver => $webvar{resolver}) if $webvar{resolver};
     
    15631670    }
    15641671    $page->param(logfor => 'domain '.domainName($dbh,$id));
     1672  } elsif ($webvar{ltype} && $webvar{ltype} eq 'rdns') {
     1673    $sql .= "rdns_id=?";
     1674    $id = $webvar{id};
     1675    if (!check_scope(id => $id, type => 'revzone')) {
     1676      $page->param(errmsg => "You are not permitted to view log entries for the requested reverse zone");
     1677      goto DONELOG;
     1678    }
     1679    $page->param(logfor => 'reverse zone '.revName($dbh,$id));
    15651680  } else {
    15661681    # Default to listing curgroup log
     
    15701685    # group log is always for the "current" group
    15711686  }
     1687##fixme:
     1688# - filtering
     1689# - show reverse zone column?
     1690# - pagination/limiting number of records - put newest-first so user
     1691#   doesn't always need to go to the last page for recent activity?
    15721692  my $sth = $dbh->prepare($sql);
    15731693  $sth->execute($id);
     
    16011721  $page->param(logingrp => groupName($dbh,$logingroup));
    16021722  $page->param(logingrp_num => $logingroup);
     1723
     1724##fixme
     1725  $page->param(mayrdns => 1);
    16031726
    16041727  $page->param(maydefrec => $permissions{admin});
     
    16961819  # handle user check
    16971820  my $newurl = "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}?sid=$sid";
    1698   foreach (keys %params) {
     1821  foreach (sort keys %params) {
    16991822    $newurl .= "&$_=".$q->url_encode($params{$_});
    17001823  }
     
    17431866}
    17441867
    1745 sub showdomain {
     1868sub showzone {
    17461869  my $def = shift;
     1870  my $rev = shift;
    17471871  my $id = shift;
    17481872
    17491873  # get the SOA first
    1750   my %soa = getSOA($dbh,$def,$id);
     1874  my %soa = getSOA($dbh,$def,$rev,$id);
    17511875
    17521876  $page->param(contact  => $soa{contact});
     
    17581882  $page->param(ttl      => $soa{ttl});
    17591883
    1760   my $foo2 = getDomRecs($dbh,$def,$id,$perpage,$webvar{offset},$sortby,$sortorder,$filter);
     1884  my $foo2 = getDomRecs($dbh,$def,$rev,$id,$perpage,$webvar{offset},$sortby,$sortorder,$filter);
    17611885
    17621886  my $row = 0;
     
    17651889    $rec->{row} = $row % 2;
    17661890    $rec->{defrec} = $def;
     1891    $rec->{revrec} = $rev;
    17671892    $rec->{sid} = $webvar{sid};
    17681893    $rec->{id} = $id;
     1894    $rec->{fwdzone} = $rev eq 'n';
    17691895    $rec->{distance} = 'n/a' unless ($rec->{type} eq 'MX' || $rec->{type} eq 'SRV');
    17701896    $rec->{weight} = 'n/a' unless ($rec->{type} eq 'SRV');
     
    17781904}
    17791905
    1780 # fill in record type list on add/update/edit record template
    1781 sub fill_rectypes {
    1782   my $type = shift || $reverse_typemap{A};
    1783   my $soaflag = shift || 0;
    1784 
    1785   my $sth = $dbh->prepare("SELECT val,name FROM rectypes WHERE stdflag=1 ORDER BY listorder");
    1786   $sth->execute;
    1787   my @typelist;
    1788   while (my ($rval,$rname) = $sth->fetchrow_array()) {
    1789     my %row = ( recval => $rval, recname => $rname );
    1790     $row{tselect} = 1 if $rval == $type;
    1791     push @typelist, \%row;
    1792   }
    1793   if ($soaflag) {
    1794     my %row = ( recval => $reverse_typemap{SOA}, recname => 'SOA' );
    1795     $row{tselect} = 1 if $reverse_typemap{SOA} == $type;
    1796     push @typelist, \%row;
    1797   }
    1798   $page->param(typelist => \@typelist);
    1799 } # fill_rectypes
    1800 
    18011906sub fill_recdata {
    1802   fill_rectypes($webvar{type});
     1907  $page->param(typelist => getTypelist($dbh, $webvar{revrec}, $webvar{type}));
    18031908
    18041909# le sigh.  we may get called with many empty %webvar keys
     
    18071912##todo:  allow BIND-style bare names, ASS-U-ME that the name is within the domain?
    18081913# prefill <domain> or DOMAIN in "Host" space for new records
    1809   my $domroot = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid}));
    1810   $page->param(name     => $domroot);
    1811   $page->param(address  => $webvar{address});
    1812   $page->param(distance => $webvar{distance})
     1914  if ($webvar{revrec} eq 'n') {
     1915    my $domroot = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid}));
     1916    $page->param(name   => $domroot);
     1917    $page->param(address        => $webvar{address});
     1918    $page->param(distance       => $webvar{distance})
    18131919        if ($webvar{type} == $reverse_typemap{MX} or $webvar{type} == $reverse_typemap{SRV});
    1814   $page->param(weight   => $webvar{weight}) if $webvar{type} == $reverse_typemap{SRV};
    1815   $page->param(port     => $webvar{port}) if $webvar{type} == $reverse_typemap{SRV};
     1920    $page->param(weight => $webvar{weight}) if $webvar{type} == $reverse_typemap{SRV};
     1921    $page->param(port   => $webvar{port}) if $webvar{type} == $reverse_typemap{SRV};
     1922  } else {
     1923    my $domroot = ($webvar{defrec} eq 'y' ? 'ADMINDOMAIN' : ".$config{domain}");
     1924    $page->param(name   => ($webvar{name} ? $webvar{name} : $domroot));
     1925    my $zname = ($webvar{defrec} eq 'y' ? 'ZONE' : revName($dbh,$webvar{parentid}));
     1926    $zname =~ s|\d*/\d+$||;
     1927    $page->param(address        => ($webvar{address} ? $webvar{address} : $zname));
     1928  }
    18161929# retrieve the right ttl instead of falling (way) back to the hardcoded system default
    1817   my %soa = getSOA($dbh,$webvar{defrec},$webvar{parentid});
     1930  my %soa = getSOA($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid});
    18181931  $page->param(ttl      => ($webvar{ttl} ? $webvar{ttl} : $soa{minttl}));
    18191932}
     
    18932006  # on a page showing nothing.
    18942007  # For bonus points, this reverts to the original offset on clicking the "All" link (mostly)
    1895   if ($offset ne 'all') { 
     2008  if ($offset ne 'all') {
    18962009    $offset-- while ($offset * $perpage) >= $pgcount;
    18972010  }
     
    19072020} # end fill_pgcount()
    19082021
    1909 sub listdomains {
    1910 
     2022
     2023sub listdomains { listzones(); }        # temp
     2024
     2025sub listzones {
    19112026# ACLs
    19122027  $page->param(domain_create    => ($permissions{admin} || $permissions{domain_create}) );
     
    19182033  my $childlist = join(',',@childgroups);
    19192034
    1920   my $sql = "SELECT count(*) FROM domains WHERE group_id IN ($curgroup".($childlist ? ",$childlist" : '').")".
    1921         ($startwith ? " AND domain ~* ?" : '').
    1922         ($filter ? " AND domain ~* ?" : '');
    1923   my $sth = $dbh->prepare($sql);
    1924   $sth->execute(@filterargs);
    1925   my ($count) = $sth->fetchrow_array;
     2035  my $count = getZoneCount($dbh, (childlist => $childlist, curgroup => $curgroup, revrec => $webvar{revrec},
     2036        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef) ) );
    19262037
    19272038# fill page count and first-previous-next-last-all bits
    1928   fill_pgcount($count,"domains",groupName($dbh,$curgroup));
     2039  fill_pgcount($count,($webvar{revrec} eq 'n' ? 'domains' : 'revzones'),groupName($dbh,$curgroup));
    19292040  fill_fpnla($count);
    19302041
     
    19372048
    19382049# set up the headers
    1939   my @cols = ('domain', 'status', 'group');
    1940   my %colheads = (domain => 'Domain', status => 'Status', group => 'Group');
     2050  my @cols = (($webvar{revrec} eq 'n' ? 'domain' : 'revnet'), 'status', 'group');
     2051  my %colheads = (domain => 'Domain', revnet => 'Reverse Zone', status => 'Status', group => 'Group');
    19412052  fill_colheads($sortby, $sortorder, \@cols, \%colheads);
    19422053
     
    19462057
    19472058# waffle, waffle - keep state on these as well as sortby, sortorder?
     2059##fixme:  put this higher so the count doesn't get munched?
    19482060  $page->param("start$startwith" => 1) if $startwith && $startwith =~ /^(?:[a-z]|0-9)$/;
    19492061
     
    19512063  $page->param(searchsubs => $searchsubs) if $searchsubs;
    19522064
    1953 ##fixme
    1954 ##fixme  push the SQL and direct database fiddling off into a sub in DNSDB.pm
    1955 ##fixme
    1956 
    19572065  $page->param(group => $curgroup);
    1958   my @domlist;
    1959   $sql = "SELECT domain_id,domain,status,groups.group_name AS group FROM domains".
    1960         " INNER JOIN groups ON domains.group_id=groups.group_id".
    1961         " WHERE domains.group_id IN ($curgroup".($childlist ? ",$childlist" : '').")".
    1962         ($startwith ? " AND domain ~* ?" : '').
    1963         ($filter ? " AND domain ~* ?" : '').
    1964         " ORDER BY ".($sortby eq 'group' ? 'groups.group_name' : $sortby).
    1965         " $sortorder ".($offset eq 'all' ? '' : " LIMIT $perpage OFFSET ".$offset*$perpage);
    1966   $sth = $dbh->prepare($sql);
    1967   $sth->execute(@filterargs);
    1968   my $rownum = 0;
    1969   while (my @data = $sth->fetchrow_array) {
    1970     my %row;
    1971     $row{domainid} = $data[0];
    1972     $row{domain} = $data[1];
    1973     $row{status} = ($data[2] ? 'Active' : 'Inactive');
    1974     $row{group} = $data[3];
    1975     $row{bg} = ($rownum++)%2;
    1976     $row{mkactive} = !$data[2];
    1977     $row{sid} = $sid;
    1978     $row{offset} = $offset;
    1979 # ACLs
    1980     $row{domain_edit} = ($permissions{admin} || $permissions{domain_edit});
    1981     $row{domain_delete} = ($permissions{admin} || $permissions{domain_delete});
    1982     push @domlist, \%row;
    1983   }
    1984   $page->param(domtable => \@domlist);
     2066
     2067  my $zonelist = getZoneList($dbh, (childlist => $childlist, curgroup => $curgroup,
     2068        revrec => $webvar{revrec},
     2069        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef),
     2070        offset => $webvar{offset}, sortby => $sortby, sortorder => $sortorder
     2071        ) );
     2072# probably don't need this, keeping for reference for now
     2073#  foreach (@$zonelist) {
     2074#  }
     2075  $page->param(domtable => $zonelist);
    19852076} # end listdomains()
    19862077
     
    22212312  my $groupid = shift;
    22222313  my $entry = shift;
     2314  my $revid = shift || 0;
    22232315
    22242316##fixme: push SQL into DNSDB.pm
     
    22282320  my ($user_id, $fullname) = $sth->fetchrow_array;
    22292321
    2230   $sth = $dbh->prepare("INSERT INTO log (domain_id,user_id,group_id,email,name,entry) ".
    2231         "VALUES (?,?,?,?,?,?)") or warn $dbh->errstr;
    2232   $sth->execute($domid,$user_id,$groupid,$username,$fullname,$entry) or warn $sth->errstr;
     2322  $sth = $dbh->prepare("INSERT INTO log (domain_id,user_id,group_id,email,name,entry,rdns_id) ".
     2323        "VALUES (?,?,?,?,?,?,?)") or warn $dbh->errstr;
     2324  $sth->execute($domid,$user_id,$groupid,$username,$fullname,$entry,$revid) or warn $sth->errstr;
    22332325} # end logaction()
    2234 
    2235 
    2236 ##fixme:  generalize to return appropriate id on all cases (ie, use $partype)
    2237 sub parentID {
    2238   my $id = shift;
    2239   my $idtype = shift;
    2240   my $partype = shift;
    2241   my $defrec = shift || '';
    2242 
    2243   my $sql = '';
    2244 
    2245   if ($idtype eq 'dom') {
    2246     return $id if $defrec eq 'y';  # "domain" + default records, we're really looking at a group.
    2247     $sql = "SELECT group_id FROM domains WHERE domain_id=?";
    2248   } elsif ($idtype eq 'rec') {
    2249     if ($defrec eq 'y') {
    2250       $sql = "SELECT group_id FROM default_records WHERE record_id=?";
    2251     } else {
    2252       $sql = "SELECT d.group_id FROM domains d".
    2253         " INNER JOIN records r ON d.domain_id=r.domain_id".
    2254         " WHERE r.record_id=?";
    2255     }
    2256   } elsif ($idtype eq 'group') {
    2257     $sql = "SELECT parent_group_id FROM groups WHERE group_id=?";
    2258   } elsif ($idtype eq 'user') {
    2259     $sql = "SELECT group_id FROM users WHERE user_id=?";
    2260   } else {
    2261     return "FOO", "BAR";  # can't get here.... we think.
    2262   }
    2263   my $sth = $dbh->prepare($sql);
    2264   $sth->execute($id);
    2265   my ($retid) = $sth->fetchrow_array;
    2266   return $retid if $retid;
    2267   # ahh! fall of the edge of the world if things went sideways
    2268   ##fixme:  really need to do a little more error handling, I think
    2269 } # end parentID()
    22702326
    22712327
  • branches/stable/dns.sql

    r215 r544  
    4141\.
    4242
     43CREATE TABLE default_rev_records (
     44    record_id serial NOT NULL,
     45    group_id integer DEFAULT 1 NOT NULL,
     46    host text DEFAULT '' NOT NULL,
     47    "type" integer DEFAULT 1 NOT NULL,
     48    val text DEFAULT '' NOT NULL,
     49    ttl integer DEFAULT 86400 NOT NULL,
     50    description text
     51);
     52
     53COPY default_rev_records (record_id, group_id, host, "type", val, ttl, description) FROM stdin;
     541       1       hostmaster.ADMINDOMAIN:ns1.ADMINDOMAIN  6       3600:900:1048576:2560   3600   
     552       1       unused-%r.ADMINDOMAIN   65283   ZONE    3600   
     56\.
     57
    4358CREATE TABLE domains (
    4459    domain_id serial NOT NULL,
    4560    "domain" character varying(80) NOT NULL,
     61    group_id integer DEFAULT 1 NOT NULL,
     62    description character varying(255) DEFAULT ''::character varying NOT NULL,
     63    status integer DEFAULT 1 NOT NULL,
     64    zserial integer,
     65    sertype character(1) DEFAULT 'D'::bpchar
     66);
     67
     68CREATE TABLE revzones (
     69    rdns_id serial NOT NULL,
     70    revnet cidr NOT NULL,
    4671    group_id integer DEFAULT 1 NOT NULL,
    4772    description character varying(255) DEFAULT ''::character varying NOT NULL,
     
    6994    log_id serial NOT NULL,
    7095    domain_id integer,
     96    rdns_id integer,
    7197    user_id integer,
    7298    group_id integer,
     
    103129\.
    104130
    105 -- fixme:  need to handle looooong records (eg, SPF)
     131-- rdns_id defaults to 0 since many records will not have an associated rDNS entry.
    106132CREATE TABLE records (
    107     domain_id integer NOT NULL,
     133    domain_id integer NOT NULL DEFAULT 0,
     134    rdns_id integer NOT NULL DEFAULT 0,
    108135    record_id serial NOT NULL,
    109136    host text DEFAULT '' NOT NULL,
     
    119146CREATE TABLE rectypes (
    120147    val integer NOT NULL,
    121     name character varying(12) NOT NULL,
     148    name character varying(20) NOT NULL,
    122149    stdflag integer DEFAULT 1 NOT NULL,
    123150    listorder integer DEFAULT 255 NOT NULL,
     
    129156COPY rectypes (val, name, stdflag, listorder, alphaorder) FROM stdin;
    1301571       A       1       1       1
    131 2       NS      1       2       37
    132 3       MD      2       255     29
    133 4       MF      2       255     30
    134 5       CNAME   1       6       9
    135 6       SOA     0       8       53
    136 7       MB      3       255     28
    137 8       MG      3       255     31
    138 9       MR      3       255     33
    139 10      NULL    3       255     43
    140 11      WKS     3       255     64
    141 12      PTR     2       4       46
    142 13      HINFO   3       255     18
    143 14      MINFO   3       255     32
    144 15      MX      1       3       34
    145 16      TXT     1       5       60
    146 17      RP      2       255     48
    147 18      AFSDB   3       255     4
    148 19      X25     3       255     65
    149 20      ISDN    3       255     21
    150 21      RT      3       255     50
    151 22      NSAP    3       255     38
    152 23      NSAP-PTR        3       255     39
    153 24      SIG     3       255     51
    154 25      KEY     3       255     23
    155 26      PX      3       255     47
    156 27      GPOS    3       255     17
    157 28      AAAA    1       2       3
    158 29      LOC     3       255     25
    159 30      NXT     3       255     44
    160 31      EID     3       255     15
    161 32      NIMLOC  3       255     36
    162 33      SRV     1       7       55
    163 34      ATMA    3       255     6
    164 35      NAPTR   3       255     35
    165 36      KX      3       255     24
    166 37      CERT    3       255     8
    167 38      A6      3       3       2
    168 39      DNAME   3       255     12
    169 40      SINK    3       255     52
    170 41      OPT     3       255     45
    171 42      APL     3       255     5
    172 43      DS      3       255     14
    173 44      SSHFP   3       255     56
    174 45      IPSECKEY        3       255     20
    175 46      RRSIG   3       255     49
    176 47      NSEC    3       255     40
    177 48      DNSKEY  3       255     13
    178 49      DHCID   3       255     10
    179 50      NSEC3   3       255     41
    180 51      NSEC3PARAM      3       255     42
    181 55      HIP     3       255     19
    182 99      SPF     3       255     54
    183 100     UINFO   3       255     62
    184 101     UID     3       255     61
    185 102     GID     3       255     16
    186 103     UNSPEC  3       255     63
    187 249     TKEY    3       255     58
    188 250     TSIG    3       255     59
    189 251     IXFR    3       255     22
    190 252     AXFR    3       255     7
    191 253     MAILB   3       255     27
    192 254     MAILA   3       255     26
    193 32768   TA      3       255     57
    194 32769   DLV     3       255     11
     1582       NS      1       5       37
     1593       MD      5       255     29
     1604       MF      5       255     30
     1615       CNAME   1       7       9
     1626       SOA     0       0       53
     1637       MB      5       255     28
     1648       MG      5       255     31
     1659       MR      5       255     33
     16610      NULL    5       255     43
     16711      WKS     5       255     64
     16812      PTR     3       10      46
     16913      HINFO   5       255     18
     17014      MINFO   5       255     32
     17115      MX      1       6       34
     17216      TXT     1       8       60
     17317      RP      4       255     48
     17418      AFSDB   5       255     4
     17519      X25     5       255     65
     17620      ISDN    5       255     21
     17721      RT      5       255     50
     17822      NSAP    5       255     38
     17923      NSAP-PTR        5       255     39
     18024      SIG     5       255     51
     18125      KEY     5       255     23
     18226      PX      5       255     47
     18327      GPOS    5       255     17
     18428      AAAA    1       3       3
     18529      LOC     5       255     25
     18630      NXT     5       255     44
     18731      EID     5       255     15
     18832      NIMLOC  5       255     36
     18933      SRV     1       9       55
     19034      ATMA    5       255     6
     19135      NAPTR   5       255     35
     19236      KX      5       255     24
     19337      CERT    5       255     8
     19438      A6      5       3       2
     19539      DNAME   5       255     12
     19640      SINK    5       255     52
     19741      OPT     5       255     45
     19842      APL     5       255     5
     19943      DS      5       255     14
     20044      SSHFP   5       255     56
     20145      IPSECKEY        5       255     20
     20246      RRSIG   5       255     49
     20347      NSEC    5       255     40
     20448      DNSKEY  5       255     13
     20549      DHCID   5       255     10
     20650      NSEC3   5       255     41
     20751      NSEC3PARAM      5       255     42
     20855      HIP     5       255     19
     20999      SPF     5       255     54
     210100     UINFO   5       255     62
     211101     UID     5       255     61
     212102     GID     5       255     16
     213103     UNSPEC  5       255     63
     214249     TKEY    5       255     58
     215250     TSIG    5       255     59
     216251     IXFR    5       255     22
     217252     AXFR    5       255     7
     218253     MAILB   5       255     27
     219254     MAILA   5       255     26
     22032768   TA      5       255     57
     22132769   DLV     5       255     11
     222\.
     223
     224-- Custom types (ab)using the "Private use" range from 65280 to 65534
     225COPY rectypes (val, name, stdflag, listorder, alphaorder) FROM stdin;
     22665280   A+PTR   2       2       2
     22765281   AAAA+PTR        2       4       4
     22865282   PTR template    3       11      2
     22965283   A+PTR template  3       12      2
     23065284   AAAA+PTR template       3       13      2
    195231\.
    196232
     
    254290    ADD CONSTRAINT "$1" FOREIGN KEY (group_id) REFERENCES groups(group_id);
    255291
    256 ALTER TABLE ONLY records
    257     ADD CONSTRAINT "$1" FOREIGN KEY (domain_id) REFERENCES domains(domain_id);
    258 
    259292ALTER TABLE ONLY users
    260293    ADD CONSTRAINT "$1" FOREIGN KEY (group_id) REFERENCES groups(group_id);
     
    264297
    265298-- set starting sequence numbers, since we've inserted data before they're active
    266 SELECT pg_catalog.setval('misc_misc_id_seq', 1, true);
    267 SELECT pg_catalog.setval('default_records_record_id_seq', 8, true);
     299SELECT pg_catalog.setval('misc_misc_id_seq', 2, false);
     300SELECT pg_catalog.setval('default_records_record_id_seq', 8, false);
     301SELECT pg_catalog.setval('default_rev_records_record_id_seq', 3, false);
    268302SELECT pg_catalog.setval('domains_domain_id_seq', 1, false);
    269 SELECT pg_catalog.setval('groups_group_id_seq', 1, true);
    270 SELECT pg_catalog.setval('permissions_permission_id_seq', 2, true);
     303SELECT pg_catalog.setval('groups_group_id_seq', 2, false);
     304SELECT pg_catalog.setval('permissions_permission_id_seq', 3, false);
    271305SELECT pg_catalog.setval('records_record_id_seq', 1, false);
    272306SELECT pg_catalog.setval('users_user_id_seq', 2, false);
  • branches/stable/templates/badpage.tmpl

    r173 r544  
    11<!-- <TMPL_VAR NAME=sid> -->
    22<div id="badpage">
     3<TMPL_IF badpage>
    34Bad page requested:
    45<div class="errmsg">
     
    67</div>
    78Press the 'Back' button on your browser to continue.
     9</TMPL_IF>
     10<TMPL_IF badtemplate>
     11Template error:
     12<div class="warnmsg">
     13<TMPL_VAR NAME=badtemplate>
    814</div>
     15</TMPL_IF>
     16</div>
  • branches/stable/templates/delrec.tmpl

    r100 r544  
    66<h3>Are you really sure you want to delete record:<br />
    77<TMPL_VAR NAME=host> <TMPL_VAR NAME=ftype> <TMPL_VAR NAME=recval></h3>
    8 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=delrec&amp;del=cancel&amp;id=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;parentid=<TMPL_VAR NAME=parentid>">cancel</a> &nbsp; | &nbsp; <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=delrec&amp;del=ok&amp;id=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;parentid=<TMPL_VAR NAME=parentid>">confirm</a>
     8<a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=delrec&amp;del=cancel&amp;id=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;revrec=<TMPL_VAR NAME=revrec>&amp;parentid=<TMPL_VAR NAME=parentid>">cancel</a>
     9 &nbsp; | &nbsp;
     10<a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=delrec&amp;del=ok&amp;id=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;revrec=<TMPL_VAR NAME=revrec>&amp;parentid=<TMPL_VAR NAME=parentid>">confirm</a>
    911</td></tr></table>
    1012
  • branches/stable/templates/domlist.tmpl

    r147 r544  
    99</TMPL_IF>
    1010<TMPL_IF errmsg>
    11 <div class='errmsg'><TMPL_VAR NAME=errmsg></div>
     11<div class="errmsg"><TMPL_VAR NAME=errmsg></div>
    1212</TMPL_IF>
    1313
    1414<table width="98%">
    15 <tr><th colspan="3"><div class="center maintitle">Domain list</div></th></tr>
     15<tr><th colspan="3"><div class="center maintitle"><TMPL_IF domlist>Domain<TMPL_ELSE>Reverse zone</TMPL_IF> list</div></th></tr>
    1616<tr>
    1717<td class="leftthird"><TMPL_INCLUDE NAME="pgcount.tmpl"></td>
     
    1919<td class="rightthird"><TMPL_INCLUDE NAME="sbox.tmpl"></td>
    2020</tr>
    21 <tr><td colspan="3" align="center"><TMPL_INCLUDE NAME="lettsearch.tmpl"></td></tr>
    22 <tr><td colspan="3" align="right"><TMPL_IF domain_create><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=newdomain">New Domain</a></TMPL_IF></td></tr>
     21<TMPL_IF domlist><tr><td colspan="3" align="center"><TMPL_INCLUDE NAME="lettsearch.tmpl"></td></tr></TMPL_IF>
     22<tr><td colspan="3" align="right">
     23<TMPL_IF domain_create>
     24<TMPL_IF domlist>
     25<a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=newdomain">New Domain</a>
     26<TMPL_ELSE>
     27<a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=newrevzone">New Reverse Zone</a>
     28</TMPL_IF>
     29</TMPL_IF>
     30</td></tr>
    2331</table>
    2432
     
    3745<TMPL_IF name=domtable>
    3846<TMPL_LOOP name=domtable>
    39 <tr class="row<TMPL_VAR name=bg>">
    40         <td align="left"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=reclist&amp;id=<TMPL_VAR NAME=domainid>&amp;defrec=n"><TMPL_VAR NAME=domain></a></td>
    41         <td><TMPL_VAR name=status></td>
     47<tr class="row<TMPL_IF __odd__>1<TMPL_ELSE>0</TMPL_IF>">
     48        <td align="left"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=reclist&amp;id=<TMPL_VAR NAME=domainid>&amp;defrec=n<TMPL_UNLESS domlist>&amp;revrec=y</TMPL_UNLESS>"><TMPL_VAR NAME=domain></a></td>
     49        <td><TMPL_IF status>Active<TMPL_ELSE>Inactive</TMPL_IF></td>
    4250        <td><TMPL_VAR name=group></td>
    43 <TMPL_IF domain_edit>   <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=domlist<TMPL_IF NAME=offset>&amp;offset=<TMPL_VAR NAME=offset></TMPL_IF>&amp;id=<TMPL_VAR NAME=domainid>&amp;domstatus=<TMPL_IF NAME=mkactive>domon<TMPL_ELSE>domoff</TMPL_IF>"><TMPL_IF NAME=mkactive>activate<TMPL_ELSE>deactivate</TMPL_IF></a></td></TMPL_IF>
    44 <TMPL_IF domain_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=deldom&amp;id=<TMPL_VAR NAME=domainid>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF>
     51<TMPL_IF domain_edit>   <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage><TMPL_IF NAME=offset>&amp;offset=<TMPL_VAR NAME=offset></TMPL_IF>&amp;id=<TMPL_VAR NAME=domainid>&amp;domstatus=<TMPL_IF status>domoff<TMPL_ELSE>domon</TMPL_IF>"><TMPL_IF status>deactivate<TMPL_ELSE>activate</TMPL_IF></a></td></TMPL_IF>
     52<TMPL_IF domain_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_IF domlist>deldom<TMPL_ELSE>delrevzone</TMPL_IF>&amp;id=<TMPL_VAR NAME=domainid>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF>
    4553</tr>
    4654</TMPL_LOOP>
    4755<TMPL_ELSE>
    48 <tr><td colspan="5" align="center">No domains found</td></tr>
     56<tr><td colspan="5" align="center">No <TMPL_IF domlist>domains<TMPL_ELSE>reverse zones</TMPL_IF> found</td></tr>
    4957</TMPL_IF>
    5058</table>
  • branches/stable/templates/fpnla.tmpl

    r87 r544  
    1 <TMPL_IF navfirst><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=0<TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF>"><img src="images/frev.png" alt="[ First ]" />First</a><TMPL_ELSE><img src="images/frev.png" alt="[ First ]" />First</TMPL_IF>&nbsp;
    2 <TMPL_IF navprev><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=<TMPL_VAR NAME=prevoffs><TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF>"><img src="images/rev.png" alt="[ Previous ]" />Previous</a><TMPL_ELSE><img src="images/rev.png" alt="[ Previous ]" />Previous</TMPL_IF>&nbsp;
    3 <TMPL_IF navnext><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=<TMPL_VAR NAME=nextoffs><TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF>">Next<img src="images/fwd.png" alt="[ Next ]" /></a><TMPL_ELSE>Next<img src="images/fwd.png" alt="[ Next ]" /></TMPL_IF>&nbsp;
    4 <TMPL_IF navlast><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=<TMPL_VAR NAME=lastoffs><TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF>">Last<img src="images/ffwd.png" alt="[ Last ]" /></a><TMPL_ELSE>Last<img src="images/ffwd.png" alt="[ Last ]" /></TMPL_IF>&nbsp;
    5 <TMPL_IF navall><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=all<TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF>">All</a><TMPL_ELSE><TMPL_UNLESS onepage><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=0<TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF>"><TMPL_VAR NAME=perpage> per page</a></TMPL_UNLESS></TMPL_IF>
     1<TMPL_IF navfirst><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=0<TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&amp;revrec=<TMPL_VAR NAME=revrec></TMPL_IF>"><img src="images/frev.png" alt="[ First ]" />First</a><TMPL_ELSE><img src="images/frev.png" alt="[ First ]" />First</TMPL_IF>&nbsp;
     2<TMPL_IF navprev><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=<TMPL_VAR NAME=prevoffs><TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&amp;revrec=<TMPL_VAR NAME=revrec></TMPL_IF>"><img src="images/rev.png" alt="[ Previous ]" />Previous</a><TMPL_ELSE><img src="images/rev.png" alt="[ Previous ]" />Previous</TMPL_IF>&nbsp;
     3<TMPL_IF navnext><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=<TMPL_VAR NAME=nextoffs><TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&amp;revrec=<TMPL_VAR NAME=revrec></TMPL_IF>">Next<img src="images/fwd.png" alt="[ Next ]" /></a><TMPL_ELSE>Next<img src="images/fwd.png" alt="[ Next ]" /></TMPL_IF>&nbsp;
     4<TMPL_IF navlast><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=<TMPL_VAR NAME=lastoffs><TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&amp;revrec=<TMPL_VAR NAME=revrec></TMPL_IF>">Last<img src="images/ffwd.png" alt="[ Last ]" /></a><TMPL_ELSE>Last<img src="images/ffwd.png" alt="[ Last ]" /></TMPL_IF>&nbsp;
     5<TMPL_IF navall><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=all<TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&amp;revrec=<TMPL_VAR NAME=revrec></TMPL_IF>">All</a><TMPL_ELSE><TMPL_UNLESS onepage><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=<TMPL_VAR NAME=curpage>&amp;offset=0<TMPL_IF id>&amp;id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&amp;defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&amp;revrec=<TMPL_VARNAME=revrec></TMPL_IF>"><TMPL_VAR NAME=perpage> per page</a></TMPL_UNLESS></TMPL_IF>
  • branches/stable/templates/menu.tmpl

    r214 r544  
    44<hr />
    55<a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=domlist">Domains</a><br />
     6<TMPL_IF mayrdns><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=revzones">Reverse Zones</a><br /></TMPL_IF>
    67<a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=useradmin">Users</a><br />
    78<a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=log">Log</a><br />
    8 <TMPL_IF maydefrec><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=reclist&amp;id=<TMPL_VAR NAME=group>&amp;defrec=y">Default Records</a><br /></TMPL_IF>
     9<TMPL_IF maydefrec><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=reclist&amp;id=<TMPL_VAR NAME=group>&amp;defrec=y">Default Records</a><br />
     10<TMPL_IF mayrdns><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=reclist&amp;id=<TMPL_VAR NAME=group>&amp;defrec=y&amp;revrec=y">Default Reverse Records</a><br /></TMPL_IF></TMPL_IF>
    911<TMPL_IF mayimport><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=axfr">AXFR Import</a><br /></TMPL_IF>
    1012<TMPL_IF maybulk><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=bulkdomain">Bulk Domain Operations</a><br /></TMPL_IF>
  • branches/stable/templates/reclist.tmpl

    r422 r544  
    88<div class="result"><TMPL_VAR NAME=resultmsg></div>
    99</TMPL_IF>
     10<TMPL_IF warnmsg>
     11<div class="warning"><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
     
    3740                <input type="hidden" name="id" value="<TMPL_VAR NAME=id>" />
    3841                <input type="hidden" name="defrec" value="<TMPL_VAR NAME=defrec>" />
     42                <input type="hidden" name="revrec" value="<TMPL_VAR NAME=revrec>" />
    3943                <input name="filter"<TMPL_IF filter> value="<TMPL_VAR NAME=filter>"</TMPL_IF> />
    4044                <input type="submit" value="Filter" />
     
    4650        <td colspan="3">Records</td>
    4751        <td align="center"><a href="textrecs.cgi?sid=<TMPL_VAR NAME=sid>&amp;id=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>">Plain text</a></td>
    48 <TMPL_IF record_create> <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=record&amp;parentid=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;recact=new">Add record</a></td></TMPL_IF>
    49         <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=log&amp;id=<TMPL_VAR NAME=id><TMPL_IF logdom>&amp;ltype=dom</TMPL_IF>">View log</a></td>
     52<TMPL_IF record_create> <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=record&amp;parentid=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;revrec=<TMPL_VAR NAME=revrec>&amp;recact=new">Add record</a></td></TMPL_IF>
     53        <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=log&amp;id=<TMPL_VAR NAME=id><TMPL_IF logdom>&amp;ltype=dom</TMPL_IF><TMPL_IF logrdns>&amp;ltype=rdns</TMPL_IF>">View log</a></td>
    5054</tr>
    5155
     
    5963 NAME=offset>&amp;offset=<TMPL_VAR NAME=offset></TMPL_IF>&amp;sortby=<TMPL_VAR
    6064 NAME=sortby>&amp;order=<TMPL_VAR NAME=order>&amp;id=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR
    61  NAME=defrec>"><TMPL_VAR NAME=colname></a><TMPL_IF NAME=sortorder>&nbsp;<img alt="<TMPL_VAR
    62  NAME=sortorder>" src="images/<TMPL_VAR NAME=sortorder>.png" /></TMPL_IF></td></TMPL_LOOP>
     65 NAME=defrec>&amp;revrec=<TMPL_VAR NAME=revrec>"><TMPL_VAR NAME=colname></a><TMPL_IF
     66 NAME=sortorder>&nbsp;<img alt="<TMPL_VAR NAME=sortorder>" src="images/<TMPL_VAR NAME=sortorder>.png"
     67 /></TMPL_IF></td></TMPL_LOOP>
    6368<TMPL_IF record_delete> <td>Delete</td></TMPL_IF>
    6469</tr>
    6570<TMPL_LOOP NAME=reclist>
    6671<tr class="row<TMPL_VAR NAME=row>">
    67         <td><TMPL_IF record_edit><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=record&amp;parentid=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;recact=edit&amp;id=<TMPL_VAR NAME=record_id>"><TMPL_VAR NAME=host></a><TMPL_ELSE><TMPL_VAR NAME=host></TMPL_IF></td>
     72<TMPL_IF fwdzone>
     73        <td><TMPL_IF record_edit><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=record&amp;parentid=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;revrec=<TMPL_VAR NAME=revrec>&amp;recact=edit&amp;id=<TMPL_VAR NAME=record_id>"><TMPL_VAR NAME=host></a><TMPL_ELSE><TMPL_VAR NAME=host></TMPL_IF></td>
    6874        <td><TMPL_VAR NAME=type></td>
    6975        <td><TMPL_VAR NAME=val></td>
     
    7177        <td><TMPL_VAR NAME=weight></td>
    7278        <td><TMPL_VAR NAME=port></td>
     79<TMPL_ELSE>
     80        <td><TMPL_IF record_edit><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=record&amp;parentid=<TMPL_VAR NAME=id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;revrec=<TMPL_VAR NAME=revrec>&amp;recact=edit&amp;id=<TMPL_VAR NAME=record_id>"><TMPL_VAR NAME=val></a><TMPL_ELSE><TMPL_VAR NAME=val></TMPL_IF></td>
     81        <td><TMPL_VAR NAME=type></td>
     82        <td><TMPL_VAR NAME=host></td>
     83</TMPL_IF>
    7384        <td><TMPL_VAR NAME=ttl></td>
    74 <TMPL_IF record_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=delrec&amp;id=<TMPL_VAR NAME=record_id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;parentid=<TMPL_VAR NAME=id>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF>
     85<TMPL_IF record_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&amp;page=delrec&amp;id=<TMPL_VAR NAME=record_id>&amp;defrec=<TMPL_VAR NAME=defrec>&amp;revrec=<TMPL_VAR NAME=revrec>&amp;parentid=<TMPL_VAR NAME=id>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF>
    7586</tr>
    7687</TMPL_LOOP>
  • branches/stable/templates/record.tmpl

    r155 r544  
    1414<input type="hidden" name="page" value="record" />
    1515<input type="hidden" name="defrec" value="<TMPL_VAR NAME=defrec>" />
     16<input type="hidden" name="revrec" value="<TMPL_VAR NAME=revrec>" />
    1617<input type="hidden" name="sid" value="<TMPL_VAR NAME=sid>" />
    1718<input type="hidden" name="parentid" value="<TMPL_VAR NAME=parentid>" />
     
    2324
    2425    <table border="0" cellspacing="2" cellpadding="2" width="100%">
    25 <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>
    2627        <tr class="tableheader"><td align="center" colspan="2"><TMPL_VAR NAME=todo>: <TMPL_VAR NAME=dohere></td></tr>
    2728        <tr class="datalinelight">
     29<TMPL_IF fwdzone>
    2830                <td>Hostname</td>
    2931                <td><input type="text" name="name" value="<TMPL_VAR NAME=name>" /></td>
     32<TMPL_ELSE>
     33                <td>IP Address</td>
     34                <td><input type="text" name="address" value="<TMPL_VAR ESCAPE=HTML NAME=address>" /></td>
     35</TMPL_IF>
    3036        </tr>
    3137        <tr class="datalinelight">
     
    3844        </tr>
    3945        <tr class="datalinelight">
     46<TMPL_IF fwdzone>
    4047                <td>Address</td>
    4148                <td><input type="text" name="address" value="<TMPL_VAR ESCAPE=HTML NAME=address>" /></td>
     49<TMPL_ELSE>
     50                <td>Hostname</td>
     51                <td><input type="text" name="name" value="<TMPL_VAR NAME=name>" /></td>
     52</TMPL_IF>
    4253        </tr>
     54<TMPL_IF fwdzone>
    4355        <tr class="datalinelight">
    4456                <td>Distance (MX and SRV only)</td>
     
    5365                <td><input type="text" name="port" value="<TMPL_VAR NAME=port>" size="5" maxlength="10" /></td>
    5466        </tr>
     67</TMPL_IF>
    5568        <tr class="datalinelight">
    5669                <td>TTL</td>
Note: See TracChangeset for help on using the changeset viewer.