Ignore:
Timestamp:
12/10/13 17:47:44 (10 years ago)
Author:
Kris Deugau
Message:

/branches/stable

Merge reverse DNS and location work; 2 of mumble

Numerous conflicts due to hand-copy or partial merges

Location:
branches/stable
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • branches/stable

  • branches/stable/dns.cgi

    r544 r545  
    33##
    44# $Id$
    5 # Copyright 2008-2011 Kris Deugau <kdeugau@deepnet.cx>
     5# Copyright 2008-2012 Kris Deugau <kdeugau@deepnet.cx>
    66#
    77#    This program is free software: you can redistribute it and/or modify
     
    9696  $session->param('reclistsortby','host');
    9797  $session->param('reclistorder','ASC');
     98  $session->param('loclistsortby','description');
     99  $session->param('loclistorder','ASC');
     100  $session->param('logsortby','stamp');
     101  $session->param('logorder','DESC');
    98102}
    99103
     
    122126$webvar{startwith} =~ s/^(0-9|[a-z]).*/$1/ if $webvar{startwith};
    123127# not much call for chars not allowed in domain names
    124 $webvar{filter} =~ s/[^a-zA-Z0-9_.:@-]//g if $webvar{filter};
     128$webvar{filter} =~ s/[^a-zA-Z0-9_.:\@-]//g if $webvar{filter};
    125129## only set 'y' if box is checked, no other values legal
    126130## however, see https://secure.deepnet.cx/trac/dnsadmin/ticket/31
     
    227231  if ($webvar{action} eq 'login') {
    228232    # Snag ACL/permissions here too
    229     my $sth = $dbh->prepare("SELECT user_id,group_id,password,firstname,lastname,status FROM users WHERE username=?");
    230     $sth->execute($webvar{username});
    231 
    232     if (my ($uid,$gid,$pass,$fname,$lname,$status) = $sth->fetchrow_array) {
    233       $webvar{password} = '' if !$webvar{password};
    234       if (!$status) {
    235         $webvar{loginfailed} = 1;
    236       } elsif ($pass =~ m|^\$1\$([A-Za-z0-9/.]+)\$|) {
    237         # native passwords (crypt-md5)
    238         $webvar{loginfailed} = 1 if $pass ne unix_md5_crypt($webvar{password},$1);
    239       } elsif ($pass =~ /^[0-9a-f]{32}$/) {
    240         # VegaDNS import (hex-coded MD5)
    241         $webvar{loginfailed} = 1 if $pass ne md5_hex($webvar{password});
    242       } else {
    243         # plaintext (convenient now and then)
    244         $webvar{loginfailed} = 1 if $pass ne $webvar{password};
    245       }
     233
     234    my $userdata = login($dbh, $webvar{username}, $webvar{password});
     235
     236    if ($userdata) {
    246237
    247238      # set session bits
    248       $session->param('logingroup',$gid);
    249       $session->param('curgroup',$gid);
    250       $session->param('uid',$uid);
     239      $session->param('logingroup',$userdata->{group_id});
     240      $session->param('curgroup',$userdata->{group_id});
     241      $session->param('uid',$userdata->{user_id});
    251242      $session->param('username',$webvar{username});
    252243
    253       changepage(page => "domlist") if !defined($webvar{loginfailed});
     244      changepage(page => "domlist");
    254245
    255246    } else {
     
    299290} # handle global webvar{action}s
    300291
    301 initPermissions($dbh,$session->param('uid'));
     292# finally check if the user was disabled.  we could just leave this for logout/session expiry,
     293# but if they keep the session active they'll continue to have access long after being disabled.  :/
     294# Treat it as a session expiry.
     295if ($session->param('uid') && !userStatus($dbh, $session->param('uid')) ) {
     296  $sid = '';
     297  $session->delete;     # force expiry of the session Right Away
     298  $session->flush;      # make sure it hits storage
     299  changepage(page=> "login", sessexpired => 1);
     300}
     301
     302# Misc Things To Do on most pages
     303initPermissions($dbh, $session->param('uid'));
     304initActionLog($dbh, $session->param('uid'));
    302305
    303306$page->param(sid => $sid) unless $webvar{page} eq 'login';      # no session ID on the login page
     
    307310  $page->param(loginfailed => 1) if $webvar{loginfailed};
    308311  $page->param(sessexpired => 1) if $webvar{sessexpired};
    309 #  $page->param(orgname => $config{orgname}) if $config{orgname} ne 'Example Corp';
    310312  $page->param(version => $DNSDB::VERSION);
    311313
     
    316318# hmm.  seeing problems in some possibly-not-so-corner cases.
    317319# this currently only handles "domain on", "domain off"
    318   if (defined($webvar{domstatus})) {
     320  if (defined($webvar{zonestatus})) {
    319321    # security check - does the user have permission to access this entity?
    320322    my $flag = 0;
     
    323325    }
    324326    if ($flag && ($permissions{admin} || $permissions{domain_edit})) {
    325       my $stat = domStatus($dbh,$webvar{id},$webvar{domstatus});
    326 ##fixme  switch to more consise "Enabled <domain"/"Disabled <domain>" as with users?
    327       logaction($webvar{id}, $session->param("username"),
    328         parentID($dbh, (id => $webvar{id}, type => 'domain', revrec => $webvar{revrec})),
    329         "Changed ".domainName($dbh, $webvar{id})." state to ".($stat ? 'active' : 'inactive'));
    330       $page->param(resultmsg => "Changed ".domainName($dbh, $webvar{id})." state to ".
    331         ($stat ? 'active' : 'inactive'));
     327      my $stat = zoneStatus($dbh,$webvar{id},'n',$webvar{zonestatus});
     328      $page->param(resultmsg => $DNSDB::resultstr);
    332329    } else {
    333330      $page->param(errmsg => "You are not permitted to view or change the requested domain");
    334331    }
    335     $uri_self =~ s/\&amp;domstatus=[^&]*//g;    # clean up URL for stuffing into templates
    336   }
    337 
    338   if ($session->param('resultmsg')) {
    339     $page->param(resultmsg => $session->param('resultmsg'));
    340     $session->clear('resultmsg');
    341   }
    342   if ($session->param('errmsg')) {
    343     $page->param(errmsg => $session->param('errmsg'));
    344     $session->clear('errmsg');
    345   }
     332    $uri_self =~ s/\&amp;zonestatus=[^&]*//g;   # clean up URL for stuffing into templates
     333  }
     334
     335  show_msgs();
    346336
    347337  $page->param(curpage => $webvar{page});
     
    354344        unless ($permissions{admin} || $permissions{domain_create});
    355345
    356   fill_grouplist("grouplist");
     346  $webvar{group} = $curgroup if !$webvar{group};
     347  fill_grouplist("grouplist", $webvar{group});
     348  fill_loclist();
    357349
    358350  if ($session->param('add_failed')) {
     
    362354    $session->clear('errmsg');
    363355    $page->param(domain => $webvar{domain});
     356    $page->param(addinactive => $webvar{makeactive} eq 'n');
    364357  }
    365358
     
    379372  $webvar{makeactive} = 0 if !defined($webvar{makeactive});
    380373
    381   my ($code,$msg) = addDomain($dbh,$webvar{domain},$webvar{group},($webvar{makeactive} eq 'on' ? 1 : 0),
    382         (username => $session->param("username"), id => $session->param("uid")));
     374  my ($code,$msg) = addDomain($dbh,$webvar{domain},$webvar{group},($webvar{makeactive} eq 'on' ? 1 : 0));
    383375
    384376  if ($code eq 'OK') {
     
    388380    changepage(page => "reclist", id => $msg);
    389381  } else {
    390     logaction(0, $session->param("username"), $webvar{group}, "Failed adding domain $webvar{domain} ($msg)")
    391         if $config{log_failures};
    392382    $session->param('add_failed', 1);
    393383##fixme:  domain a security risk for XSS?
    394 ##fixme:  keep active/inactive state, group selection
    395     changepage(page => "newdomain", domain => $webvar{domain}, errmsg => $msg);
     384    changepage(page => "newdomain", domain => $webvar{domain}, errmsg => $msg,
     385        makeactive => ($webvar{makeactive} ? 'y' : 'n'), group => $webvar{group});
    396386  }
    397387
     
    416406  } elsif ($webvar{del} eq 'ok') {
    417407    my $pargroup = parentID($dbh, (id => $webvar{id}, type => 'domain', revrec => $webvar{revrec}));
    418     my $dom = domainName($dbh, $webvar{id});
    419     my ($code,$msg) = delDomain($dbh, $webvar{id});
     408    my ($code,$msg) = delZone($dbh, $webvar{id}, $webvar{revrec});
    420409    if ($code eq 'OK') {
    421       logaction($webvar{id}, $session->param("username"), $pargroup, "Deleted domain $dom");
    422       changepage(page => "domlist", resultmsg => "Deleted domain $dom");
     410      changepage(page => "domlist", resultmsg => $msg);
    423411    } else {
    424       logaction($webvar{id}, $session->param("username"), $pargroup, "Failed to delete domain $dom ($msg)")
    425         if $config{log_failures};
    426       changepage(page => "domlist", errmsg => "Error deleting domain $dom: $msg");
     412      changepage(page => "domlist", errmsg => $msg);
    427413    }
    428414
     
    435421
    436422  $webvar{revrec} = 'y';
     423
     424  if (defined($webvar{zonestatus})) {
     425    # security check - does the user have permission to access this entity?
     426    my $flag = 0;
     427    foreach (@viewablegroups) {
     428      $flag = 1 if isParent($dbh, $_, 'group', $webvar{id}, 'revzone');
     429    }
     430    if ($flag && ($permissions{admin} || $permissions{domain_edit})) {
     431      my $stat = zoneStatus($dbh,$webvar{id},'y',$webvar{zonestatus});
     432      $page->param(resultmsg => $DNSDB::resultstr);
     433    } else {
     434      $page->param(errmsg => "You are not permitted to view or change the requested reverse zone");
     435    }
     436    $uri_self =~ s/\&amp;zonestatus=[^&]*//g;   # clean up URL for stuffing into templates
     437  }
     438
     439  show_msgs();
     440
    437441  $page->param(curpage => $webvar{page});
    438442  listzones();
     
    446450  fill_grouplist("grouplist");
    447451
    448   if ($webvar{add_failed}) {
    449     $page->param(add_failed => 1);
    450     $page->param(errmsg => $webvar{errmsg});
     452  # prepopulate revpatt with the matching default record
     453# getRecByName($dbh, (revrec => $webvar{revrec}, defrec => $webvar{defrec}, host => 'string'));
     454
     455  if ($session->param('add_failed')) {
     456    $session->clear('add_failed');
     457    $page->param(errmsg => $session->param('errmsg'));
     458    $session->clear('errmsg');
    451459    $page->param(revzone => $webvar{revzone});
    452460    $page->param(revpatt => $webvar{revpatt});
     
    465473
    466474  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")) );
     475        ($webvar{makeactive} eq 'on' ? 1 : 0));
    469476
    470477  if ($code eq 'OK') {
    471     logaction(0, $session->param("username"), $webvar{group}, "Added reverse zone $webvar{revzone}", $msg);
    472478    changepage(page => "reclist", id => $msg, revrec => 'y');
     479  } elsif ($code eq 'WARN') {
     480    changepage(page => "reclist", id => $msg, revrec => 'y', warnmsg => $DNSDB::resultstr);
    473481  } 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') {
     482    $session->param('add_failed', 1);
     483    changepage(page => "newrevzone", revzone => $webvar{revzone}, revpatt => $webvar{revpatt}, errmsg => $msg);
     484  }
     485
     486} elsif ($webvar{page} eq 'delrevzone') {
     487
     488  changepage(page => "revzones", errmsg => "You are not permitted to delete reverse zones")
     489        unless ($permissions{admin} || $permissions{domain_delete});
     490
     491  # security check - does the user have permission to access this entity?
     492  if (!check_scope(id => $webvar{id}, type => 'revzone')) {
     493    changepage(page => "revzones", errmsg => "You do not have permission to delete the requested reverse zone");
     494  }
     495
     496  $page->param(id => $webvar{id});
     497
     498  # first pass = confirm y/n (sorta)
     499  if (!defined($webvar{del})) {
     500
     501    $page->param(del_getconf => 1);
     502    $page->param(revzone => revName($dbh,$webvar{id}));
     503
     504  } elsif ($webvar{del} eq 'ok') {
     505    my $pargroup = parentID($dbh, (id => $webvar{id}, type => 'revzone', revrec => $webvar{revrec}));
     506    my $zone = revName($dbh, $webvar{id});
     507    my ($code,$msg) = delZone($dbh, $webvar{id}, 'y');
     508    if ($code eq 'OK') {
     509      changepage(page => "revzones", resultmsg => $msg);
     510    } else {
     511      changepage(page => "revzones", errmsg => $msg);
     512    }
     513
     514  } else {
     515    # cancelled.  whee!
     516    changepage(page => "revzones");
     517  }
    480518
    481519} elsif ($webvar{page} eq 'reclist') {
     
    531569        distance => 'Distance', weight => 'Weight', port => 'Port', ttl => 'TTL');
    532570    } else {
    533       @cols = ('host', 'type', 'val', 'ttl');
    534       %colheads = (host => 'IP Address', type => 'Type', val => 'Hostname', ttl => 'TTL');
     571      @cols = ('val', 'type', 'host', 'ttl');
     572      %colheads = (val => 'IP Address', type => 'Type', host => 'Hostname', ttl => 'TTL');
    535573    }
    536574    my %custom = (id => $webvar{id}, defrec => $webvar{defrec}, revrec => $webvar{revrec});
     
    547585    showzone($webvar{defrec}, $webvar{revrec}, $webvar{id});
    548586    if ($webvar{defrec} eq 'n') {
    549 #      showzone('n',$webvar{id});
    550 ##fixme:  permission for viewing logs?
    551 ##fixme:  determine which slice of the log we view (group, domain, revzone)
    552587      if ($webvar{revrec} eq 'n') {
    553588        $page->param(logdom => 1);
     
    557592    }
    558593
    559     if ($session->param('resultmsg')) {
    560       $page->param(resultmsg => $session->param('resultmsg'));
    561       $session->clear('resultmsg');
    562     }
    563     if ($session->param('warnmsg')) {
    564       $page->param(warnmsg => $session->param('warnmsg'));
    565       $session->clear('warnmsg');
    566     }
    567     if ($session->param('errmsg')) {
    568       $page->param(errmsg => $session->param('errmsg'));
    569       $session->clear('errmsg');
    570     }
     594    show_msgs();
    571595
    572596  } # close "you can't edit default records" check
     
    608632    fill_recdata();
    609633
     634    if ($webvar{defrec} eq 'n') {
     635      my $defloc = getZoneLocation($dbh, $webvar{revrec}, $webvar{parentid});
     636      fill_loclist($curgroup, $defloc);
     637    }
     638
    610639  } elsif ($webvar{recact} eq 'add') {
    611640
     
    613642        unless ($permissions{admin} || $permissions{record_create});
    614643
     644    # location check - if user does not have record_locchg, set $webvar{location} to default location for zone
     645    my $parloc = getZoneLocation($dbh, $webvar{revrec}, $webvar{parentid});
     646    $webvar{location} = $parloc unless ($permissions{admin} || $permissions{record_locchg});
     647
    615648    my @recargs = ($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid},
    616         \$webvar{name},\$webvar{type},\$webvar{address},$webvar{ttl});
     649        \$webvar{name},\$webvar{type},\$webvar{address},$webvar{ttl},$webvar{location});
    617650    if ($webvar{type} == $reverse_typemap{MX} or $webvar{type} == $reverse_typemap{SRV}) {
    618651      push @recargs, $webvar{distance};
     
    626659
    627660    if ($code eq 'OK' || $code eq 'WARN') {
    628       my $restr;
    629       if ($webvar{defrec} eq 'y') {
    630         $restr = "Added default record '$webvar{name} $typemap{$webvar{type}}";
    631         $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX';
    632         $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]"
    633                 if $typemap{$webvar{type}} eq 'SRV';
    634         $restr .= " $webvar{address}', TTL $webvar{ttl}";
    635         logaction(0, $session->param("username"), $webvar{parentid}, $restr);
    636       } else {
    637         $restr = "Added record '$webvar{name} $typemap{$webvar{type}}";
    638         $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX';
    639         $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]"
    640                 if $typemap{$webvar{type}} eq 'SRV';
    641         $restr .= " $webvar{address}', TTL $webvar{ttl}";
    642         logaction($webvar{parentid}, $session->param("username"),
    643                 parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})), $restr);
    644       }
    645661      my %pageparams = (page => "reclist", id => $webvar{parentid},
    646662        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';
     663      $pageparams{warnmsg} = $msg."<br><br>\n".$DNSDB::resultstr if $code eq 'WARN';
     664      $pageparams{resultmsg} = $DNSDB::resultstr if $code eq 'OK';
    649665      changepage(%pageparams);
    650666    } else {
     
    657673      $page->param(id           => $webvar{id});
    658674      fill_recdata();   # populate the form... er, mostly.
    659       if ($config{log_failures}) {
    660         if ($webvar{defrec} eq 'y') {
    661           logaction(0, $session->param("username"), $webvar{parentid},
    662                 "Failed adding default record '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl} ($msg)");
    663         } else {
    664           logaction($webvar{parentid}, $session->param("username"),
    665                 parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})),
    666                 "Failed adding record '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl} ($msg)");
    667         }
     675      if ($webvar{defrec} eq 'n') {
     676        fill_loclist($curgroup, $webvar{location});
    668677      }
    669678    }
     
    685694    $page->param(port           => $recdata->{port});
    686695    $page->param(ttl            => $recdata->{ttl});
    687     $page->param(typelist       => getTypelist($dbh, $webvar{revrec}, $webvar{type}));
     696    $page->param(typelist       => getTypelist($dbh, $webvar{revrec}, $recdata->{type}));
     697
     698    if ($webvar{defrec} eq 'n') {
     699      fill_loclist($curgroup, $recdata->{location});
     700    }
    688701
    689702  } elsif ($webvar{recact} eq 'update') {
     
    692705        unless ($permissions{admin} || $permissions{record_edit});
    693706
    694     # prevent out-of-domain records from getting added by appending the domain, or DOMAIN for default records
    695     my $pname = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid}));
    696     $webvar{name} =~ s/\s+$//;
    697     $webvar{name} =~ s/\.*$/\.$pname/ if $webvar{name} !~ /$pname$/;
    698 
    699     # get current/previous record info so we can log "updated 'foo A 1.2.3.4' to 'foo A 2.3.4.5'"
     707    # retain old location if user doesn't have permission to fiddle locations
    700708    my $oldrec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
    701 
    702     my ($code,$msg) = updateRec($dbh,$webvar{defrec},$webvar{id},
    703         $webvar{name},$webvar{type},$webvar{address},$webvar{ttl},
     709    $webvar{location} = $oldrec->{location} unless ($permissions{admin} || $permissions{record_locchg});
     710
     711    my ($code,$msg) = updateRec($dbh,$webvar{defrec},$webvar{revrec},$webvar{id},$webvar{parentid},
     712        \$webvar{name},\$webvar{type},\$webvar{address},$webvar{ttl},$webvar{location},
    704713        $webvar{distance},$webvar{weight},$webvar{port});
    705714
    706     if ($code eq 'OK') {
    707 ##fixme: retrieve old record info for full logging of change
    708       if ($webvar{defrec} eq 'y') {
    709         my $restr = "Updated default record from '$oldrec->{host} $typemap{$oldrec->{type}} $oldrec->{val}', TTL $oldrec->{ttl}\n".
    710                 "to '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl}";
    711         logaction(0, $session->param("username"), $webvar{parentid}, $restr);
    712         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
    713       } else {
    714         my $restr = "Updated record from '$oldrec->{host} $typemap{$oldrec->{type}} $oldrec->{val}', TTL $oldrec->{ttl}\n".
    715                 "to '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl}";
    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);
    720         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);
    721       }
     715    if ($code eq 'OK' || $code eq 'WARN') {
     716      my %pageparams = (page => "reclist", id => $webvar{parentid},
     717        defrec => $webvar{defrec}, revrec => $webvar{revrec});
     718      $pageparams{warnmsg} = $msg."<br><br>\n".$DNSDB::resultstr if $code eq 'WARN';
     719      $pageparams{resultmsg} = $DNSDB::resultstr if $code eq 'OK';
     720      changepage(%pageparams);
    722721    } else {
    723722      $page->param(failed       => 1);
     
    729728      $page->param(id           => $webvar{id});
    730729      fill_recdata();
    731       if ($config{log_failures}) {
    732         if ($webvar{defrec} eq 'y') {
    733           logaction(0, $session->param("username"), $webvar{parentid},
    734                 "Failed updating default record '$typemap{$webvar{type}} $webvar{name} $webvar{address}', TTL $webvar{ttl} ($msg)");
    735         } else {
    736           logaction($webvar{parentid}, $session->param("username"),
    737                 parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})),
    738                 "Failed updating record '$typemap{$webvar{type}} $webvar{name} $webvar{address}', TTL $webvar{ttl} ($msg)");
    739         }
    740       }
    741730    }
    742731  }
     
    745734    $page->param(dohere => "default records in group ".groupName($dbh,$webvar{parentid}));
    746735  } else {
    747     $page->param(parentid => $webvar{parentid});
    748736    $page->param(dohere => domainName($dbh,$webvar{parentid})) if $webvar{revrec} eq 'n';
    749737    $page->param(dohere => revName($dbh,$webvar{parentid})) if $webvar{revrec} eq 'y';
     
    781769    $page->param(recval => $rec->{val});
    782770  } elsif ($webvar{del} eq 'ok') {
    783 # get rec data before we try to delete it
    784     my $rec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
    785771    my ($code,$msg) = delRec($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id});
    786772    if ($code eq 'OK') {
    787       if ($webvar{defrec} eq 'y') {
    788         my $recclass = ($webvar{revrec} eq 'n' ? 'default record' : 'default reverse record');
    789 ##fixme:  log distance for MX;  log port/weight/distance for SRV
    790         my $restr = "Deleted $recclass '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}";
    791         logaction(0, $session->param("username"), $rec->{parid}, $restr);
    792         changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec},
    793                 revrec => $webvar{revrec}, resultmsg => $restr);
    794       } else {
    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);
    802       }
     773      changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec},
     774                revrec => $webvar{revrec}, resultmsg => $msg);
    803775    } else {
    804776## need to find failure mode
    805       if ($config{log_failures}) {
    806         if ($webvar{defrec} eq 'y') {
    807           logaction(0, $session->param("username"), $rec->{parid},
    808                 "Failed deleting default record '$rec->{host} $typemap{$rec->{type}} $rec->{val}',".
    809                 " TTL $rec->{ttl} ($msg)");
    810         } else {
    811           logaction($rec->{parid}, $session->param("username"),
    812                 parentID($dbh, (id => $rec->{parid}, type => 'domain', revrec => $webvar{revrec})),
    813                 "Failed deleting record '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl} ($msg)");
    814         }
    815       }
    816777      changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec},
    817                 revrec => $webvar{revrec}, errmsg => "Error deleting record: $msg");
     778                revrec => $webvar{revrec}, errmsg => $msg);
    818779    }
    819780  } else {
     
    840801  }
    841802
    842   fillsoa($webvar{defrec},$webvar{id});
     803  fillsoa($webvar{defrec},$webvar{revrec},$webvar{id});
    843804
    844805} elsif ($webvar{page} eq 'updatesoa') {
     
    848809  if (!check_scope(id => $webvar{recid}, type =>
    849810        ($webvar{defrec} eq 'y' ? ($webvar{revrec} eq 'y' ? 'defrevrec' : 'defrec') : 'record'))) {
     811##fixme:  should we redirect to the requested record list page instead of the domain list?
    850812    changepage(page => 'domlist', errmsg => "You do not have permission to edit the requested SOA record");
    851813  }
     
    853815  if (!check_scope(id => $webvar{id}, type =>
    854816        ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) {
    855     changepage(page => 'domlist', errmsg => "You do not have permission to edit the ".
     817    changepage(page => ($webvar{revrec} eq 'y' ? 'revzones' : 'domlist'),
     818        errmsg => "You do not have permission to edit the ".
    856819        ($webvar{defrec} eq 'y' ? 'default ' : '')."SOA record for the requested ".
    857         ($webvar{defrec} eq 'y' ? 'group' : 'domain'));
     820        ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'reverse zone' : 'domain')) );
    858821  }
    859822
     
    861824        unless ($permissions{admin} || $permissions{domain_edit});
    862825
    863   # get old SOA for log
    864   my %soa = getSOA($dbh,$webvar{defrec},$webvar{id});
    865 
    866   my $sth;
    867 ##fixme:  push SQL into DNSDB.pm
    868 ##fixme: data validation: make sure {recid} is really the SOA for {id}
    869   # no domain ID, so we're editing the default SOA for a group (we don't care which one here)
    870   # plus a bit of magic to update the appropriate table
    871   my $sql = "UPDATE ".($webvar{defrec} eq 'y' ? "default_records" : "records").
    872         " SET host=?, val=?, ttl=? WHERE record_id=?";
    873   $sth = $dbh->prepare($sql);
    874   $sth->execute("$webvar{contact}:$webvar{prins}",
    875         "$webvar{refresh}:$webvar{retry}:$webvar{expire}:$webvar{minttl}",
    876         $webvar{ttl},
    877         $webvar{recid});
    878 
    879   if ($sth->err) {
     826  my ($code, $msg) = updateSOA($dbh, $webvar{defrec}, $webvar{revrec},
     827        (contact => $webvar{contact}, prins => $webvar{prins}, refresh => $webvar{refresh},
     828        retry => $webvar{retry}, expire => $webvar{expire}, minttl => $webvar{minttl},
     829        ttl => $webvar{ttl}, id => $webvar{id}) );
     830  if ($code eq 'OK') {
     831    changepage(page => "reclist", id => $webvar{id}, defrec => $webvar{defrec}, revrec => $webvar{revrec},
     832        resultmsg => "SOA record updated");
     833  } else {
    880834    $page->param(update_failed => 1);
    881     $page->param(msg => $DBI::errstr);
    882     fillsoa($webvar{defrec},$webvar{id});
    883 ##fixme: faillog
    884   } else {
    885 
    886     # do this in the order of "default to most common case"
    887     my $loggroup;
    888     my $logdomain = $webvar{id};
    889     if ($webvar{defrec} eq 'y') {
    890       $loggroup = $webvar{id};
    891       $logdomain = 0;
    892     } else {
    893       $loggroup = parentID($dbh, (id => $logdomain, type => 'domain', revrec => $webvar{revrec}));
    894     }
    895 
    896     logaction($logdomain, $session->param("username"), $loggroup,
    897         "Updated ".($webvar{defrec} eq 'y' ? 'default ' : '')."SOA for ".
    898         ($webvar{defrec} eq 'y' ? groupName($dbh, $webvar{id}) : domainName($dbh, $webvar{id}) ).
    899         ": (ns $soa{prins}, contact $soa{contact}, refresh $soa{refresh},".
    900         " retry $soa{retry}, expire $soa{expire}, minTTL $soa{minttl}, TTL $soa{ttl}) to ".
    901         "(ns $webvar{prins}, contact $webvar{contact}, refresh $webvar{refresh},".
    902         " retry $webvar{retry}, expire $webvar{expire}, minTTL $webvar{minttl}, TTL $webvar{ttl})");
    903     changepage(page => "reclist", id => $webvar{id}, defrec => $webvar{defrec},
    904         resultmsg => "SOA record updated");
     835    $page->param(msg => $msg);
     836    fillsoa($webvar{defrec}, $webvar{revrec}, $webvar{id}, 'w');
    905837  }
    906838
     
    914846  $page->param(delgrp => $permissions{admin} || $permissions{group_delete});
    915847
    916   if ($session->param('resultmsg')) {
    917     $page->param(resultmsg => $session->param('resultmsg'));
    918     $session->clear('resultmsg');
    919   }
    920   if ($session->param('warnmsg')) {
    921     $page->param(warnmsg => $session->param('warnmsg'));
    922     $session->clear('warnmsg');
    923   }
    924   if ($session->param('errmsg')) {
    925     $page->param(errmsg => $session->param('errmsg'));
    926     $session->clear('errmsg');
    927   }
     848  show_msgs();
    928849  $page->param(curpage => $webvar{page});
    929850
     
    951872      }
    952873    }
    953     # force inheritance of parent group's default records with inherit flag,
    954     # otherwise we end up with the hardcoded defaults from DNSDB.pm.  See
    955     # https://secure.deepnet.cx/trac/dnsadmin/ticket/8 for the UI enhancement
    956     # that will make this variable.
    957     my ($code,$msg) = addGroup($dbh, $webvar{newgroup}, $webvar{pargroup}, \%newperms, 1);
     874    # "Chained" permissions.  Some permissions imply others;  make sure they get set.
     875    foreach (keys %permchains) {
     876      if ($newperms{$_} && !$newperms{$permchains{$_}}) {
     877        $newperms{$permchains{$_}} = 1;
     878      }
     879    }
     880    # not gonna provide the 4th param: template-or-clone flag, just yet
     881    my ($code,$msg) = addGroup($dbh, $webvar{newgroup}, $webvar{pargroup}, \%newperms);
    958882    if ($code eq 'OK') {
    959       logaction(0, $session->param("username"), $webvar{pargroup}, "Added group $webvar{newgroup}");
    960883      if ($alterperms) {
    961884        changepage(page => "grpman", warnmsg =>
     
    965888      }
    966889    } # fallthrough else
    967     logaction(0, $session->param("username"), $webvar{pargroup}, "Failed to add group $webvar{newgroup}: $msg")
    968         if $config{log_failures};
    969890    # no point in doing extra work
    970891    fill_permissions($page, \%newperms);
     
    1001922
    1002923  } elsif ($webvar{del} eq 'ok') {
    1003     my $deleteme = groupName($dbh,$webvar{id}); # get this before we delete it...
    1004     my $delparent = parentID($dbh, (id => $webvar{id}, type => 'group'));
    1005924    my ($code,$msg) = delGroup($dbh, $webvar{id});
    1006925    if ($code eq 'OK') {
    1007926##fixme: need to clean up log when deleting a major container
    1008       logaction(0, $session->param("username"), $delparent, "Deleted group $deleteme");
    1009       changepage(page => "grpman", resultmsg => "Deleted group $deleteme");
     927      changepage(page => "grpman", resultmsg => $msg);
    1010928    } else {
    1011929# need to find failure mode
    1012       logaction(0, $session->param("username"), $delparent, "Failed to delete group $deleteme: $msg")
    1013         if $config{log_failures};
    1014       changepage(page => "grpman", errmsg => "Error deleting group $deleteme: $msg");
     930      changepage(page => "grpman", errmsg => $msg);
    1015931    }
    1016932  } else {
     
    1030946  }
    1031947
    1032   if ($webvar{grpaction} eq 'updperms') {
     948  if ($webvar{grpaction} && $webvar{grpaction} eq 'updperms') {
    1033949    # extra safety check;  make sure user can't construct a URL to bypass ACLs
    1034950    my %curperms;
     
    1046962      }
    1047963    }
     964    # "Chained" permissions.  Some permissions imply others;  make sure they get set.
     965    foreach (keys %permchains) {
     966      if ($chperms{$_} && !$chperms{$permchains{$_}}) {
     967        $chperms{$permchains{$_}} = 1;
     968      }
     969    }
    1048970    my ($code,$msg) = changePermissions($dbh, 'group', $webvar{gid}, \%chperms);
    1049971    if ($code eq 'OK') {
    1050       logaction(0, $session->param("username"), $webvar{gid},
    1051         "Updated default permissions in group $webvar{gid} (".groupName($dbh, $webvar{gid}).")");
    1052972      if ($alterperms) {
    1053973        changepage(page => "grpman", warnmsg =>
     
    1055975                groupName($dbh, $webvar{gid})." updated with reduced access");
    1056976      } else {
    1057         changepage(page => "grpman", resultmsg =>
    1058                 "Updated default permissions in group ".groupName($dbh, $webvar{gid}));
     977        changepage(page => "grpman", resultmsg => $msg);
    1059978      }
    1060979    } # fallthrough else
    1061     logaction(0, $session->param("username"), $webvar{gid}, "Failed to update default permissions in group ".
    1062         groupName($dbh, $webvar{gid}).": $msg")
    1063         if $config{log_failures};
    1064980    # no point in doing extra work
    1065981    fill_permissions($page, \%chperms);
     
    1074990} elsif ($webvar{page} eq 'bulkdomain') {
    1075991  # Bulk operations on domains.  Note all but group move are available on the domain list.
     992##fixme:  do we care about bulk operations on revzones?  Move-to-group, activate, deactivate,
     993# and delete should all be much rarer for revzones than for domains.
    1076994
    1077995  changepage(page => "domlist", errmsg => "You are not permitted to make bulk domain changes")
     
    1080998  fill_grouplist("grouplist");
    1081999
    1082 ##fixme
    1083 ##fixme  push the SQL and direct database fiddling off into a sub in DNSDB.pm
    1084 ##fixme
    1085 
    1086   my $sth = $dbh->prepare("SELECT count(*) FROM domains WHERE group_id=?");
    1087   $sth->execute($curgroup);
    1088   my ($count) = ($sth->fetchrow_array);
     1000  my $count = getZoneCount($dbh, (revrec => 'n', curgroup => $curgroup) );
    10891001
    10901002  $page->param(curpage => $webvar{page});
     
    10931005  $page->param(perpage => $perpage);
    10941006
    1095   my @domlist;
    1096   my $sql = "SELECT domain_id,domain FROM domains".
    1097         " WHERE group_id=?".
    1098         " ORDER BY domain".
    1099         ($offset eq 'all' ? '' : " LIMIT $perpage OFFSET ".$offset*$perpage);
    1100   $sth = $dbh->prepare($sql);
    1101   $sth->execute($curgroup);
     1007  my $domlist = getZoneList($dbh, (revrec => 'n', curgroup => $curgroup) );
    11021008  my $rownum = 0;
    1103   while (my @data = $sth->fetchrow_array) {
    1104     my %row;
    1105     $row{domid} = $data[0];
    1106     $row{domain} = $data[1];
    1107     $rownum++;  # putting this in the expression below causes failures.  *eyeroll*
    1108     $row{newrow} = $rownum % 5 == 0;
    1109     push @domlist, \%row;
    1110   }
    1111   $page->param(domtable => \@domlist);
     1009  foreach my $dom (@{$domlist}) {
     1010    delete $dom->{status};
     1011    delete $dom->{group};
     1012    $dom->{newrow} = (++$rownum) % 5 == 0;
     1013  }
     1014
     1015  $page->param(domtable => $domlist);
    11121016  # ACLs
    11131017  $page->param(maymove => ($permissions{admin} || ($permissions{domain_edit} && $permissions{domain_create} && $permissions{domain_delete})));
     
    11231027  }
    11241028
     1029  # per-action scope checks
    11251030  if ($webvar{bulkaction} eq 'move') {
    11261031    changepage(page => "domlist", errmsg => "You are not permitted to bulk-move domains")
     
    11281033    my $newgname = groupName($dbh,$webvar{destgroup});
    11291034    $page->param(action => "Move to group $newgname");
    1130     my @bulkresults;
    1131     # nngh.  due to alpha-sorting on the previous page, we can't use domid-numeric
    1132     # order here, and since we don't have the domain names until we go around this
    1133     # loop, we can't alpha-sort them here.  :(
    1134     foreach (keys %webvar) {
    1135       my %row;
    1136       next unless $_ =~ /^dom_\d+$/;
    1137       # second security check - does the user have permission to meddle with this domain?
    1138       if (!check_scope(id => $webvar{$_}, type => 'domain')) {
    1139         $row{domerr} = "You are not permitted to make changes to the requested domain";
    1140         $row{domain} = $webvar{$_};
    1141         push @bulkresults, \%row;
    1142         next;
    1143       }
    1144       $row{domain} = domainName($dbh,$webvar{$_});
    1145       my ($code, $msg) = changeGroup($dbh, 'domain', $webvar{$_}, $webvar{destgroup});
    1146       if ($code eq 'OK') {
    1147         logaction($webvar{$_}, $session->param("username"),
    1148                 parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})),
    1149                 "Moved domain ".domainName($dbh, $webvar{$_})." to group $newgname");
    1150         $row{domok} = ($code eq 'OK');
    1151       } else {
    1152         logaction($webvar{$_}, $session->param("username"),
    1153                 parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})),
    1154                 "Failed to move domain ".domainName($dbh, $webvar{$_})." to group $newgname: $msg")
    1155                 if $config{log_failures};
    1156       }
    1157       $row{domerr} = $msg;
    1158       push @bulkresults, \%row;
    1159     }
    1160     $page->param(bulkresults => \@bulkresults);
    1161 
    11621035  } elsif ($webvar{bulkaction} eq 'deactivate' || $webvar{bulkaction} eq 'activate') {
    11631036    changepage(page => "domlist", errmsg => "You are not permitted to bulk-$webvar{bulkaction} domains")
    11641037        unless ($permissions{admin} || $permissions{domain_edit});
    11651038    $page->param(action => "$webvar{bulkaction} domains");
    1166     my @bulkresults;
    1167     foreach (keys %webvar) {
    1168       my %row;
    1169       next unless $_ =~ /^dom_\d+$/;
    1170       # second security check - does the user have permission to meddle with this domain?
    1171       if (!check_scope(id => $webvar{$_}, type => 'domain')) {
    1172         $row{domerr} = "You are not permitted to make changes to the requested domain";
    1173         $row{domain} = $webvar{$_};
    1174         push @bulkresults, \%row;
    1175         next;
    1176       }
    1177       $row{domain} = domainName($dbh,$webvar{$_});
    1178 ##fixme:  error handling on status change
    1179       my $stat = domStatus($dbh,$webvar{$_},($webvar{bulkaction} eq 'activate' ? 'domon' : 'domoff'));
    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'));
    1183       $row{domok} = 1;
    1184 #      $row{domok} = ($code eq 'OK');
    1185 #      $row{domerr} = $msg;
    1186       push @bulkresults, \%row;
    1187     }
    1188     $page->param(bulkresults => \@bulkresults);
    1189 
    11901039  } elsif ($webvar{bulkaction} eq 'delete') {
    11911040    changepage(page => "domlist", errmsg => "You are not permitted to bulk-delete domains")
    11921041        unless ($permissions{admin} || $permissions{domain_delete});
    11931042    $page->param(action => "$webvar{bulkaction} domains");
    1194     my @bulkresults;
    1195     foreach (keys %webvar) {
    1196       my %row;
    1197       next unless $_ =~ /^dom_\d+$/;
    1198       # second security check - does the user have permission to meddle with this domain?
    1199       if (!check_scope(id => $webvar{$_}, type => 'domain')) {
    1200         $row{domerr} = "You are not permitted to make changes to the requested domain";
    1201         $row{domain} = $webvar{$_};
    1202         push @bulkresults, \%row;
    1203         next;
    1204       }
    1205       $row{domain} = domainName($dbh,$webvar{$_});
    1206       my $pargroup = parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec}));
    1207       my $dom = domainName($dbh, $webvar{$_});
    1208       my ($code, $msg) = delDomain($dbh, $webvar{$_});
    1209       if ($code eq 'OK') {
    1210         logaction($webvar{$_}, $session->param("username"), $pargroup, "Deleted domain $dom");
    1211         $row{domok} = ($code eq 'OK');
    1212       } else {
    1213         logaction($webvar{$_}, $session->param("username"), $pargroup, "Failed to delete domain $dom: $msg")
    1214                 if $config{log_failures};
    1215       }
     1043  } else {
     1044    # unknown action, bypass actually doing anything.  it should not be possible in
     1045    # normal operations, and anyone who meddles with the URL gets what they deserve.
     1046    goto DONEBULK;
     1047  } # move/(de)activate/delete if()
     1048
     1049  my @bulkresults;
     1050  # nngh.  due to alpha-sorting on the previous page, we can't use domid-numeric
     1051  # order here, and since we don't have the domain names until we go around this
     1052  # loop, we can't alpha-sort them here.  :(
     1053  foreach (keys %webvar) {
     1054    my %row;
     1055    next unless $_ =~ /^dom_\d+$/;
     1056    # second security check - does the user have permission to meddle with this domain?
     1057    if (!check_scope(id => $webvar{$_}, type => 'domain')) {
     1058      $row{domerr} = "You are not permitted to make changes to the requested domain";
     1059      $row{domain} = $webvar{$_};
     1060      push @bulkresults, \%row;
     1061      next;
     1062    }
     1063    $row{domain} = domainName($dbh,$webvar{$_});
     1064
     1065    # Do the $webvar{bulkaction}
     1066    my ($code, $msg);
     1067    ($code, $msg) = changeGroup($dbh, 'domain', $webvar{$_}, $webvar{destgroup})
     1068        if $webvar{bulkaction} eq 'move';
     1069    if ($webvar{bulkaction} eq 'deactivate' || $webvar{bulkaction} eq 'activate') {
     1070      my $stat = zoneStatus($dbh,$webvar{$_},'n',($webvar{bulkaction} eq 'activate' ? 'domon' : 'domoff'));
     1071      $code = (defined($stat) ? 'OK' : 'FAIL');
     1072      $msg = (defined($stat) ? $DNSDB::resultstr : $DNSDB::errstr);
     1073    }
     1074    ($code, $msg) = delZone($dbh, $webvar{$_}, 'n')
     1075        if $webvar{bulkaction} eq 'delete';
     1076
     1077    # Set the result output from the action
     1078    if ($code eq 'OK') {
     1079      $row{domok} = $msg;
     1080    } elsif ($code eq 'WARN') {
     1081      $row{domwarn} = $msg;
     1082    } else {
    12161083      $row{domerr} = $msg;
    1217       push @bulkresults, \%row;
    1218     }
    1219     $page->param(bulkresults => \@bulkresults);
    1220 
    1221   } # move/(de)activate/delete if()
    1222 
    1223   # not going to handle the unknown $webvar{action} else;  it should not be possible in normal
    1224   # operations, and anyone who meddles with the URL gets what they deserve.
     1084    }
     1085    push @bulkresults, \%row;
     1086
     1087  } # foreach (keys %webvar)
     1088  $page->param(bulkresults => \@bulkresults);
    12251089
    12261090  # Yes, this is a GOTO target.  PTHBTTT.
     
    12351099      $flag = 1 if isParent($dbh, $_, 'group', $webvar{id}, 'user');
    12361100    }
    1237     if ($flag && ($permissions{admin} || $permissions{user_edit})) {
     1101    if ($flag && ($permissions{admin} || $permissions{user_edit} ||
     1102        ($permissions{self_edit} && $webvar{id} == $session->param('uid')) )) {
    12381103      my $stat = userStatus($dbh,$webvar{id},$webvar{userstatus});
    1239       logaction(0, $session->param("username"), parentID($dbh, (id => $webvar{id}, type => 'user')),
    1240         ($stat ? 'Enabled' : 'Disabled')." ".userFullName($dbh, $webvar{id}, '%u'));
    1241       $page->param(resultmsg => ($stat ? 'Enabled' : 'Disabled')." ".userFullName($dbh, $webvar{id}, '%u'));
     1104      $page->param(resultmsg => $DNSDB::resultstr);
    12421105    } else {
    12431106      $page->param(errmsg => "You are not permitted to view or change the requested user");
     
    12551118  $page->param(deluser => $permissions{admin} || $permissions{user_delete});
    12561119
    1257   if ($session->param('resultmsg')) {
    1258     $page->param(resultmsg => $session->param('resultmsg'));
    1259     $session->clear('resultmsg');
    1260   }
    1261   if ($session->param('warnmsg')) {
    1262     $page->param(warnmsg => $session->param('warnmsg'));
    1263     $session->clear('warnmsg');
    1264   }
    1265   if ($session->param('errmsg')) {
    1266     $page->param(errmsg => $session->param('errmsg'));
    1267     $session->clear('errmsg');
    1268   }
     1120  show_msgs();
    12691121  $page->param(curpage => $webvar{page});
    12701122
     
    12931145    $page->param(add => 1) if $webvar{useraction} eq 'add';
    12941146
    1295     my ($code,$msg);
     1147    # can't re-use $code and $msg for update if we want to be able to identify separate failure states
     1148    my ($code,$code2,$msg,$msg2) = ('OK','OK','OK','OK');
    12961149
    12971150    my $alterperms = 0; # flag iff we need to force custom permissions due to user's current access limits
     
    13401193        $permstring = 'i';
    13411194      }
     1195      # "Chained" permissions.  Some permissions imply others;  make sure they get set.
     1196      foreach (keys %permchains) {
     1197        if ($newperms{$_} && !$newperms{$permchains{$_}}) {
     1198          $newperms{$permchains{$_}} = 1;
     1199          $permstring .= ",$permchains{$_}";
     1200        }
     1201      }
    13421202      if ($webvar{useraction} eq 'add') {
    13431203        changepage(page => "useradmin", errmsg => "You do not have permission to add new users")
     
    13471207                ($webvar{makeactive} eq 'on' ? 1 : 0), $webvar{accttype}, $permstring,
    13481208                $webvar{fname}, $webvar{lname}, $webvar{phone});
    1349         logaction(0, $session->param("username"), $curgroup, "Added user $webvar{uname} (uid $msg)")
    1350                 if $code eq 'OK';
    13511209      } else {
    13521210        changepage(page => "useradmin", errmsg => "You do not have permission to edit users")
    1353                 unless $permissions{admin} || $permissions{user_edit};
     1211                unless $permissions{admin} || $permissions{user_edit} ||
     1212                        ($permissions{self_edit} && $session->param('uid') == $webvar{uid});
    13541213        # security check - does the user have permission to access this entity?
    13551214        if (!check_scope(id => $webvar{user}, type => 'user')) {
    13561215          changepage(page => "useradmin", errmsg => "You do not have permission to edit the requested user");
    13571216        }
    1358 # User update is icky.  I'd really like to do this in one atomic
    1359 # operation, but that would duplicate a **lot** of code in DNSDB.pm
     1217# User update is icky.  I'd really like to do this in one atomic operation,
     1218# but that gets hairy by either duplicating a **lot** of code in DNSDB.pm
     1219# or self-torture trying to not commit the transaction until we're really done.
    13601220        # Allowing for changing group, but not coding web support just yet.
    13611221        ($code,$msg) = updateUser($dbh, $webvar{uid}, $webvar{uname}, $webvar{gid}, $webvar{pass1},
     
    13641224        if ($code eq 'OK') {
    13651225          $newperms{admin} = 1 if $webvar{accttype} eq 'S';
    1366           ($code,$msg) = changePermissions($dbh, 'user', $webvar{uid}, \%newperms, ($permstring eq 'i'));
    1367           logaction(0, $session->param("username"), $curgroup,
    1368                 "Updated uid $webvar{uid}, user $webvar{uname} ($webvar{fname} $webvar{lname})");
     1226          ($code2,$msg2) = changePermissions($dbh, 'user', $webvar{uid}, \%newperms, ($permstring eq 'i'));
    13691227        }
    13701228      }
    13711229    }
    13721230
    1373     if ($code eq 'OK') {
    1374 
     1231    if ($code eq 'OK' && $code2 eq 'OK') {
     1232      my %pageparams = (page => "useradmin");
    13751233      if ($alterperms) {
    1376         changepage(page => "useradmin", warnmsg =>
    1377                 "You can only grant permissions you hold.  $webvar{uname} ".
    1378                 ($webvar{useraction} eq 'add' ? 'added' : 'updated')." with reduced access.");
     1234        $pageparams{warnmsg} = "You can only grant permissions you hold.\nUser ".
     1235                ($webvar{useraction} eq 'add' ? "$webvar{uname} added" : "info updated for $webvar{uname}").
     1236                ".\nPermissions ".($webvar{useraction} eq 'add' ? 'added' : 'updated')." with reduced access.";
    13791237      } else {
    1380         changepage(page => "useradmin", resultmsg => "Successfully ".
    1381                 ($webvar{useraction} eq 'add' ? 'added' : 'updated')." user $webvar{uname}");
     1238        $pageparams{resultmsg} = "$msg".($webvar{useraction} eq 'add' ? '' : "\n$msg2");
    13821239      }
     1240      changepage(%pageparams);
    13831241
    13841242    # add/update failed:
     
    13991257      $page->param(pass1 => $webvar{pass1});
    14001258      $page->param(pass2 => $webvar{pass2});
    1401       $page->param(errmsg => $msg);
     1259      $page->param(errmsg => "User info updated but permissions update failed: $msg2") if $code eq 'OK';
     1260      $page->param(errmsg => $msg) if $code ne 'OK';
    14021261      fill_permissions($page, \%newperms);
    14031262      fill_actypelist($webvar{accttype});
    14041263      fill_clonemelist();
    1405       logaction(0, $session->param("username"), $curgroup, "Failed to $webvar{useraction} user ".
    1406         "$webvar{uname}: $msg")
    1407         if $config{log_failures};
    14081264    }
    14091265
     
    14111267
    14121268    changepage(page => "useradmin", errmsg => "You do not have permission to edit users")
    1413         unless $permissions{admin} || $permissions{user_edit};
     1269        unless $permissions{admin} || $permissions{user_edit} ||
     1270                ($permissions{self_edit} && $session->param('uid') == $webvar{user});
    14141271
    14151272    # security check - does the user have permission to access this entity?
     
    14261283    fill_actypelist($userinfo->{type});
    14271284    # not using this yet, but adding it now means we can *much* more easily do so later.
    1428     $page->param(gid => $webvar{group_id});
     1285    $page->param(gid => $userinfo->{group_id});
    14291286
    14301287    my %curperms;
     
    14671324    $page->param(user => userFullName($dbh,$webvar{id}));
    14681325  } elsif ($webvar{del} eq 'ok') {
    1469 ##fixme: find group id user is in (for logging) *before* we delete the user
    1470 ##fixme: get other user data too for log
    1471     my $userref = getUserData($dbh, $webvar{id});
    14721326    my ($code,$msg) = delUser($dbh, $webvar{id});
    14731327    if ($code eq 'OK') {
    14741328      # success.  go back to the user list, do not pass "GO"
    1475       # actions on users have a domain id of 0, always
    1476       logaction(0, $session->param("username"), $curgroup, "Deleted user $webvar{id}/".$userref->{username}.
    1477         " (".$userref->{lastname}.", ".$userref->{firstname}.")");
    1478       changepage(page => "useradmin", resultmsg => "Deleted user ".$userref->{username}.
    1479         " (".$userref->{lastname}.", ".$userref->{firstname}.")");
     1329      changepage(page => "useradmin", resultmsg => $msg);
    14801330    } else {
    1481 # need to find failure mode
    1482       $page->param(del_failed => 1);
    1483       $page->param(errmsg => $msg);
    1484       list_users($curgroup);
    1485       logaction(0, $session->param("username"), $curgroup, "Failed to delete user ".
    1486         "$webvar{id}/".$userref->{username}.": $msg")
    1487         if $config{log_failures};
     1331      changepage(page => "useradmin", errmsg => $msg);
    14881332    }
    14891333  } else {
    14901334    # cancelled.  whee!
    14911335    changepage(page => "useradmin");
     1336  }
     1337
     1338} elsif ($webvar{page} eq 'loclist') {
     1339
     1340  changepage(page => "domlist", errmsg => "You are not allowed access to this function")
     1341        unless $permissions{admin} || $permissions{location_view};
     1342
     1343  # security check - does the user have permission to access this entity?
     1344#  if (!check_scope(id => $webvar{id}, type => 'loc')) {
     1345#    changepage(page => "loclist", errmsg => "You are not permitted to <foo> the requested location/view");
     1346#  }
     1347  list_locations();
     1348  show_msgs();
     1349
     1350# Permissions!
     1351  $page->param(addloc => $permissions{admin} || $permissions{location_create});
     1352  $page->param(delloc => $permissions{admin} || $permissions{location_delete});
     1353
     1354} elsif ($webvar{page} eq 'location') {
     1355
     1356  changepage(page => "domlist", errmsg => "You are not allowed access to this function")
     1357        unless $permissions{admin} || $permissions{location_view};
     1358
     1359  # security check - does the user have permission to access this entity?
     1360#  if (!check_scope(id => $webvar{id}, type => 'loc')) {
     1361#    changepage(page => "loclist", errmsg => "You are not permitted to <foo> the requested location/view");
     1362#  }
     1363
     1364  if ($webvar{locact} eq 'new') {
     1365    # uuhhmm....
     1366  } elsif ($webvar{locact} eq 'add') {
     1367    changepage(page => "loclist", errmsg => "You are not permitted to add locations/views", id => $webvar{parentid})
     1368        unless ($permissions{admin} || $permissions{location_create});
     1369
     1370    my ($code,$msg) = addLoc($dbh, $curgroup, $webvar{locname}, $webvar{comments}, $webvar{iplist});
     1371
     1372    if ($code eq 'OK' || $code eq 'WARN') {
     1373      my %pageparams = (page => "loclist", id => $webvar{parentid},
     1374        defrec => $webvar{defrec}, revrec => $webvar{revrec});
     1375      $pageparams{warnmsg} = $msg."<br><br>\n".$DNSDB::resultstr if $code eq 'WARN';
     1376      $pageparams{resultmsg} = $DNSDB::resultstr if $code eq 'OK';
     1377      changepage(%pageparams);
     1378    } else {
     1379      $page->param(failed       => 1);
     1380      $page->param(errmsg       => $msg);
     1381      $page->param(wastrying    => "adding");
     1382      $page->param(todo         => "Add location/view");
     1383      $page->param(locact       => "add");
     1384      $page->param(id           => $webvar{id});
     1385      $page->param(locname      => $webvar{locname});
     1386      $page->param(comments     => $webvar{comments});
     1387      $page->param(iplist       => $webvar{iplist});
     1388    }
     1389
     1390  } elsif ($webvar{locact} eq 'edit') {
     1391    changepage(page => "loclist", errmsg => "You are not permitted to edit locations/views", id => $webvar{parentid})
     1392        unless ($permissions{admin} || $permissions{location_edit});
     1393
     1394    my $loc = getLoc($dbh, $webvar{loc});
     1395    $page->param(wastrying      => "editing");
     1396    $page->param(todo           => "Edit location/view");
     1397    $page->param(locact         => "update");
     1398    $page->param(id             => $webvar{loc});
     1399    $page->param(locname        => $loc->{description});
     1400    $page->param(comments       => $loc->{comments});
     1401    $page->param(iplist         => $loc->{iplist});
     1402
     1403  } elsif ($webvar{locact} eq 'update') {
     1404    changepage(page => "loclist", errmsg => "You are not permitted to edit locations/views", id => $webvar{parentid})
     1405        unless ($permissions{admin} || $permissions{location_edit});
     1406
     1407    my ($code,$msg) = updateLoc($dbh, $webvar{id}, $curgroup, $webvar{locname}, $webvar{comments}, $webvar{iplist});
     1408
     1409    if ($code eq 'OK') {
     1410      changepage(page => "loclist", resultmsg => $msg);
     1411    } else {
     1412      $page->param(failed       => 1);
     1413      $page->param(errmsg       => $msg);
     1414      $page->param(wastrying    => "editing");
     1415      $page->param(todo         => "Edit location/view");
     1416      $page->param(locact       => "update");
     1417      $page->param(id           => $webvar{loc});
     1418      $page->param(locname      => $webvar{locname});
     1419      $page->param(comments     => $webvar{comments});
     1420      $page->param(iplist       => $webvar{iplist});
     1421    }
     1422  } else {
     1423    changepage(page => "loclist", errmsg => "You are not permitted to add locations/views", id => $webvar{parentid})
     1424        unless ($permissions{admin} || $permissions{location_create});
     1425
     1426    $page->param(todo => "Add location/view");
     1427    $page->param(locact => "add");
     1428    $page->param(locname => ($webvar{locname} ? $webvar{locname} : ''));
     1429    $page->param(iplist => ($webvar{iplist} ? $webvar{iplist} : ''));
     1430
     1431    show_msgs();
    14921432  }
    14931433
     
    15711511  $page->param(forcettl => $webvar{forcettl}) if $webvar{forcettl};
    15721512  $page->param(newttl => $webvar{newttl}) if $webvar{newttl};
     1513  # This next one is arguably better on by default, but Breaking Things Is Bad, Mmmkay?
     1514  $page->param(mergematching => $webvar{mergematching}) if $webvar{mergematching};
    15731515  $page->param(dominactive => 1) if (!$webvar{domactive} && $webvar{doit});     # eww.
    15741516  $page->param(importdoms => $webvar{importdoms}) if $webvar{importdoms};
     
    15891531    }
    15901532
     1533    # Bizarre Things Happen when you AXFR a null-named zone.
     1534    $webvar{importdoms} =~ s/^\s+//;
    15911535    my @domlist = split /\s+/, $webvar{importdoms};
    15921536    my @results;
     
    15941538      my %row;
    15951539      my ($code,$msg) = importAXFR($dbh, $webvar{ifrom}, $domain, $webvar{group},
    1596         $webvar{domstatus}, $webvar{rwsoa}, $webvar{rwns}, ($webvar{forcettl} ? $webvar{newttl} : 0) );
     1540        $webvar{domstatus}, $webvar{rwsoa}, $webvar{rwns}, ($webvar{forcettl} ? $webvar{newttl} : 0),
     1541        $webvar{mergematching});
    15971542      $row{domok} = $msg if $code eq 'OK';
    15981543      if ($code eq 'WARN') {
     
    16051550      }
    16061551      $msg = "<br />\n".$msg if $msg =~ m|<br />|;
    1607       logaction(domainID($dbh, $domain), $session->param("username"), $webvar{group},
    1608         "AXFR import $domain from $webvar{ifrom} ($code): $msg");
    16091552      $row{domain} = $domain;
    16101553      push @results, \%row;
     
    16491592} elsif ($webvar{page} eq 'log') {
    16501593
    1651 ##fixme put in some real log-munching stuff
    1652   my $sql = "SELECT user_id, email, name, entry, date_trunc('second',stamp) FROM log WHERE ";
    16531594  my $id = $curgroup;  # we do this because the group log may be called from (almost) any page,
    16541595                       # but the others are much more limited.  this is probably non-optimal.
    16551596
    16561597  if ($webvar{ltype} && $webvar{ltype} eq 'user') {
    1657     $sql .= "user_id=?";
     1598##fixme:  where should we call this from?
    16581599    $id = $webvar{id};
    16591600    if (!check_scope(id => $id, type => 'user')) {
     
    16631604    $page->param(logfor => 'user '.userFullName($dbh,$id));
    16641605  } elsif ($webvar{ltype} && $webvar{ltype} eq 'dom') {
    1665     $sql .= "domain_id=?";
    16661606    $id = $webvar{id};
    16671607    if (!check_scope(id => $id, type => 'domain')) {
     
    16711611    $page->param(logfor => 'domain '.domainName($dbh,$id));
    16721612  } elsif ($webvar{ltype} && $webvar{ltype} eq 'rdns') {
    1673     $sql .= "rdns_id=?";
    16741613    $id = $webvar{id};
    16751614    if (!check_scope(id => $id, type => 'revzone')) {
     
    16801619  } else {
    16811620    # Default to listing curgroup log
    1682     $sql .= "group_id=?";
    16831621    $page->param(logfor => 'group '.groupName($dbh,$id));
    16841622    # note that scope limitations are applied via the change-group check;
    16851623    # group log is always for the "current" group
    16861624  }
     1625  $webvar{ltype} = 'group' if !$webvar{ltype};
     1626  my $lcount = getLogCount($dbh, (id => $id, logtype => $webvar{ltype})) or push @debugbits, $DNSDB::errstr;
     1627
     1628  $page->param(id => $id);
     1629  $page->param(ltype => $webvar{ltype});
     1630
     1631  fill_fpnla($lcount);
     1632  fill_pgcount($lcount, "log entries", '');
     1633  $page->param(curpage => $webvar{page}.($webvar{ltype} ? "&amp;ltype=$webvar{ltype}" : ''));
     1634
     1635  $sortby = 'stamp';
     1636  $sortorder = 'DESC';  # newest-first;  although filtering is probably going to be more useful than sorting
     1637# sort/order
     1638  $session->param($webvar{page}.'sortby', $webvar{sortby}) if $webvar{sortby};
     1639  $session->param($webvar{page}.'order', $webvar{order}) if $webvar{order};
     1640
     1641  $sortby = $session->param($webvar{page}.'sortby') if $session->param($webvar{page}.'sortby');
     1642  $sortorder = $session->param($webvar{page}.'order') if $session->param($webvar{page}.'order');
     1643
     1644  # Set up the column headings with the sort info
     1645  my @cols = ('fname','username','entry','stamp');
     1646  my %colnames = (fname => 'Name', username => 'Username/Email', entry => 'Log Entry', stamp => 'Date/Time');
     1647  fill_colheads($sortby, $sortorder, \@cols, \%colnames);
     1648
     1649##fixme:  increase per-page limit or use separate limit for log?  some ops give *lots* of entries...
     1650  my $logentries = getLogEntries($dbh, (id => $id, logtype => $webvar{ltype},
     1651        offset => $webvar{offset}, sortby => $sortby, sortorder => $sortorder));
     1652  $page->param(logentries => $logentries);
     1653
    16871654##fixme:
    16881655# - filtering
    16891656# - 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?
    1692   my $sth = $dbh->prepare($sql);
    1693   $sth->execute($id);
    1694   my @logbits;
    1695   while (my ($uid, $email, $name, $entry, $stamp) = $sth->fetchrow_array) {
    1696     my %row;
    1697     $row{userfname} = $name;
    1698     $row{userid} = $uid;
    1699     $row{useremail} = $email;
    1700     $row{logentry} = $entry;
    1701     ($row{logtime}) = ($stamp =~ /^(.+)-\d\d$/);
    1702     push @logbits, \%row;
    1703   }
    1704   $page->param(logentries => \@logbits);
     1657# - on log record creation, bundle "parented" log actions (eg, "AXFR record blah for domain foo",
     1658#   or "Add record bar for new domain baz") into one entry (eg, "AXFR domain foo", "Add domain baz")?
     1659#   need a way to expand this into the complete list, and to exclude "child" entries
    17051660
    17061661  # scope check fail target
     
    17141669
    17151670##common bits
     1671# mostly things in the menu
    17161672if ($webvar{page} ne 'login' && $webvar{page} ne 'badpage') {
    17171673  $page->param(username => $session->param("username"));
     
    17241680##fixme
    17251681  $page->param(mayrdns => 1);
     1682
     1683  $page->param(mayloc => ($permissions{admin} || $permissions{location_view}));
    17261684
    17271685  $page->param(maydefrec => $permissions{admin});
     
    18111769  # than set them locally everywhere.
    18121770  foreach my $sessme ('resultmsg','warnmsg','errmsg') {
    1813     if ($params{$sessme}) {
    1814       $session->param($sessme, $params{$sessme});
     1771    if (my $tmp = $params{$sessme}) {
     1772      $tmp =~ s/^\n//;
     1773      $tmp =~ s|\n|<br />\n|g;
     1774      $session->param($sessme, $tmp);
    18151775      delete $params{$sessme};
    18161776    }
     
    18301790} # end changepage
    18311791
     1792# wrap up the usual suspects for result, warning, or error messages to be displayed
     1793sub show_msgs {
     1794  if ($session->param('resultmsg')) {
     1795    $page->param(resultmsg => $session->param('resultmsg'));
     1796    $session->clear('resultmsg');
     1797  }
     1798  if ($session->param('warnmsg')) {
     1799    $page->param(warnmsg => $session->param('warnmsg'));
     1800    $session->clear('warnmsg');
     1801  }
     1802  if ($session->param('errmsg')) {
     1803    $page->param(errmsg => $session->param('errmsg'));
     1804    $session->clear('errmsg');
     1805  }
     1806} # end show_msgs
     1807
    18321808sub fillsoa {
    1833   my $def = shift;
     1809  my $defrec = shift;
     1810  my $revrec = shift;
    18341811  my $id = shift;
    1835   my $domname = ($def eq 'y' ? '' : "DOMAIN");
    1836 
    1837   $page->param(defrec   => $def);
     1812  my $preserve = shift || 'd';  # Flag to use webvar fields or retrieve from database
     1813
     1814  my $domname = ($defrec eq 'y' ? '' : "DOMAIN");
     1815
     1816  $page->param(defrec   => $defrec);
     1817  $page->param(revrec   => $revrec);
    18381818
    18391819# i had a good reason to do this when I wrote it...
    18401820#  $page->param(domain  => $domname);
    18411821#  $page->param(group   => $DNSDB::group);
    1842   $page->param(isgrp => 1) if $def eq 'y';
    1843   $page->param(parent => ($def eq 'y' ? groupName($dbh, $DNSDB::group) : domainName($dbh, $id)) );
     1822  $page->param(isgrp => 1) if $defrec eq 'y';
     1823  $page->param(parent => ($defrec eq 'y' ? groupName($dbh, $id) :
     1824        ($revrec eq 'n' ? domainName($dbh, $id) : revName($dbh, $id)) ) );
    18441825
    18451826# defaults
     
    18521833  $page->param(defminttl        => $DNSDB::def{minttl});
    18531834
    1854   # there are probably better ways to do this.  TMTOWTDI.
    1855   my %soa = getSOA($dbh,$def,$id);
    1856 
    18571835  $page->param(id       => $id);
    1858   $page->param(recid    => $soa{recid});
    1859   $page->param(prins    => ($soa{prins} ? $soa{prins} : $DNSDB::def{prins}));
    1860   $page->param(contact  => ($soa{contact} ? $soa{contact} : $DNSDB::def{contact}));
    1861   $page->param(refresh  => ($soa{refresh} ? $soa{refresh} : $DNSDB::def{refresh}));
    1862   $page->param(retry    => ($soa{retry} ? $soa{retry} : $DNSDB::def{retry}));
    1863   $page->param(expire   => ($soa{expire} ? $soa{expire} : $DNSDB::def{expire}));
    1864   $page->param(minttl   => ($soa{minttl} ? $soa{minttl} : $DNSDB::def{minttl}));
    1865   $page->param(ttl      => ($soa{ttl} ? $soa{ttl} : $DNSDB::def{soattl}));
     1836
     1837  if ($preserve eq 'd') {
     1838    # there are probably better ways to do this.  TMTOWTDI.
     1839    my $soa = getSOA($dbh,$defrec,$revrec,$id);
     1840
     1841    $page->param(prins  => ($soa->{prins} ? $soa->{prins} : $DNSDB::def{prins}));
     1842    $page->param(contact        => ($soa->{contact} ? $soa->{contact} : $DNSDB::def{contact}));
     1843    $page->param(refresh        => ($soa->{refresh} ? $soa->{refresh} : $DNSDB::def{refresh}));
     1844    $page->param(retry  => ($soa->{retry} ? $soa->{retry} : $DNSDB::def{retry}));
     1845    $page->param(expire => ($soa->{expire} ? $soa->{expire} : $DNSDB::def{expire}));
     1846    $page->param(minttl => ($soa->{minttl} ? $soa->{minttl} : $DNSDB::def{minttl}));
     1847    $page->param(ttl    => ($soa->{ttl} ? $soa->{ttl} : $DNSDB::def{soattl}));
     1848  } else {
     1849    $page->param(prins  => ($webvar{prins} ? $webvar{prins} : $DNSDB::def{prins}));
     1850    $page->param(contact        => ($webvar{contact} ? $webvar{contact} : $DNSDB::def{contact}));
     1851    $page->param(refresh        => ($webvar{refresh} ? $webvar{refresh} : $DNSDB::def{refresh}));
     1852    $page->param(retry  => ($webvar{retry} ? $webvar{retry} : $DNSDB::def{retry}));
     1853    $page->param(expire => ($webvar{expire} ? $webvar{expire} : $DNSDB::def{expire}));
     1854    $page->param(minttl => ($webvar{minttl} ? $webvar{minttl} : $DNSDB::def{minttl}));
     1855    $page->param(ttl    => ($webvar{ttl} ? $webvar{ttl} : $DNSDB::def{soattl}));
     1856  }
    18661857}
    18671858
     
    18721863
    18731864  # get the SOA first
    1874   my %soa = getSOA($dbh,$def,$rev,$id);
    1875 
    1876   $page->param(contact  => $soa{contact});
    1877   $page->param(prins    => $soa{prins});
    1878   $page->param(refresh  => $soa{refresh});
    1879   $page->param(retry    => $soa{retry});
    1880   $page->param(expire   => $soa{expire});
    1881   $page->param(minttl   => $soa{minttl});
    1882   $page->param(ttl      => $soa{ttl});
    1883 
    1884   my $foo2 = getDomRecs($dbh,$def,$rev,$id,$perpage,$webvar{offset},$sortby,$sortorder,$filter);
    1885 
    1886   my $row = 0;
     1865  my $soa = getSOA($dbh,$def,$rev,$id);
     1866
     1867  $page->param(contact  => $soa->{contact});
     1868  $page->param(prins    => $soa->{prins});
     1869  $page->param(refresh  => $soa->{refresh});
     1870  $page->param(retry    => $soa->{retry});
     1871  $page->param(expire   => $soa->{expire});
     1872  $page->param(minttl   => $soa->{minttl});
     1873  $page->param(ttl      => $soa->{ttl});
     1874
     1875  my $foo2 = getDomRecs($dbh,(defrec => $def, revrec => $rev, id => $id, offset => $webvar{offset},
     1876        sortby => $sortby, sortorder => $sortorder, filter => $filter));
     1877
    18871878  foreach my $rec (@$foo2) {
    18881879    $rec->{type} = $typemap{$rec->{type}};
    1889     $rec->{row} = $row % 2;
    1890     $rec->{defrec} = $def;
    1891     $rec->{revrec} = $rev;
    18921880    $rec->{sid} = $webvar{sid};
    1893     $rec->{id} = $id;
    18941881    $rec->{fwdzone} = $rev eq 'n';
    18951882    $rec->{distance} = 'n/a' unless ($rec->{type} eq 'MX' || $rec->{type} eq 'SRV');
    18961883    $rec->{weight} = 'n/a' unless ($rec->{type} eq 'SRV');
    18971884    $rec->{port} = 'n/a' unless ($rec->{type} eq 'SRV');
    1898     $row++;
    18991885# ACLs
    19001886    $rec->{record_edit} = ($permissions{admin} || $permissions{record_edit});
    19011887    $rec->{record_delete} = ($permissions{admin} || $permissions{record_delete});
     1888    $rec->{locname} = '' unless ($permissions{admin} || $permissions{location_view});
    19021889  }
    19031890  $page->param(reclist => $foo2);
     
    19141901  if ($webvar{revrec} eq 'n') {
    19151902    my $domroot = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid}));
    1916     $page->param(name   => $domroot);
     1903    $page->param(name   => ($webvar{name} ? $webvar{name} : $domroot));
    19171904    $page->param(address        => $webvar{address});
    19181905    $page->param(distance       => $webvar{distance})
     
    19281915  }
    19291916# retrieve the right ttl instead of falling (way) back to the hardcoded system default
    1930   my %soa = getSOA($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid});
    1931   $page->param(ttl      => ($webvar{ttl} ? $webvar{ttl} : $soa{minttl}));
     1917  my $soa = getSOA($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid});
     1918  $page->param(ttl      => ($webvar{ttl} ? $webvar{ttl} : $soa->{minttl}));
    19321919}
    19331920
     
    19491936
    19501937sub fill_clonemelist {
    1951   my $sth = $dbh->prepare("SELECT username,user_id FROM users WHERE group_id=$curgroup");
    1952   $sth->execute;
    1953 
    19541938  # shut up some warnings, but don't stomp on caller's state
    19551939  local $webvar{clonesrc} = 0 if !defined($webvar{clonesrc});
    19561940
    1957   my @clonesrc;
    1958   while (my ($username,$uid) = $sth->fetchrow_array) {
    1959     my %row = (
    1960         username => $username,
    1961         uid => $uid,
    1962         selected => ($webvar{clonesrc} == $uid ? 1 : 0)
    1963         );
    1964     push @clonesrc, \%row;
    1965   }
    1966   $page->param(clonesrc => \@clonesrc);
     1941  my $clones = getUserDropdown($dbh, $curgroup, $webvar{clonesrc});
     1942  $page->param(clonesrc => $clones);
    19671943}
    19681944
     
    20912067  my $childlist = join(',',@childgroups);
    20922068
    2093   my $sql = "SELECT count(*) FROM groups WHERE parent_group_id IN ($curgroup".($childlist ? ",$childlist" : '').")".
    2094         ($startwith ? " AND group_name ~* ?" : '').
    2095         ($filter ? " AND group_name ~* ?" : '');
    2096   my $sth = $dbh->prepare($sql);
    2097   $sth->execute(@filterargs);
    2098   my ($count) = ($sth->fetchrow_array);
     2069  my ($count) = getGroupCount($dbh, (childlist => $childlist, curgroup => $curgroup,
     2070        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef) ) );
    20992071
    21002072# fill page count and first-previous-next-last-all bits
     
    21132085
    21142086# set up the headers
    2115   my @cols = ('group','parent','nusers','ndomains');
    2116   my %colnames = (group => 'Group', parent => 'Parent Group', nusers => 'Users', ndomains => 'Domains');
     2087  my @cols = ('group','parent','nusers','ndomains','nrevzones');
     2088  my %colnames = (group => 'Group', parent => 'Parent Group', nusers => 'Users', ndomains => 'Domains', nrevzones => 'Reverse Zones');
    21172089  fill_colheads($sortby, $sortorder, \@cols, \%colnames);
    21182090
     
    21272099  $sortby = 'g2.group_name' if $sortby eq 'parent';
    21282100
    2129   my @grouplist;
    2130   $sql = "SELECT g.group_id, g.group_name, g2.group_name, ".
    2131         "count(distinct(u.username)) AS nusers, count(distinct(d.domain)) AS ndomains ".
    2132         "FROM groups g ".
    2133         "INNER JOIN groups g2 ON g2.group_id=g.parent_group_id ".
    2134         "LEFT OUTER JOIN users u ON u.group_id=g.group_id ".
    2135         "LEFT OUTER JOIN domains d ON d.group_id=g.group_id ".
    2136         "WHERE g.parent_group_id IN ($curgroup".($childlist ? ",$childlist" : '').") ".
    2137         ($startwith ? " AND g.group_name ~* ?" : '').
    2138         ($filter ? " AND g.group_name ~* ?" : '').
    2139         " GROUP BY g.group_id, g.group_name, g2.group_name ".
    2140         " ORDER BY $sortby $sortorder ".
    2141         ($offset eq 'all' ? '' : " LIMIT $perpage OFFSET ".$offset*$perpage);
    2142   $sth = $dbh->prepare($sql);
    2143   $sth->execute(@filterargs);
    2144 
    2145   my $rownum = 0;
    2146   while (my @data = $sth->fetchrow_array) {
    2147     my %row;
    2148     $row{groupid} = $data[0];
    2149     $row{groupname} = $data[1];
    2150     $row{pgroup} = $data[2];
    2151     $row{nusers} = $data[3];
    2152     $row{ndomains} = $data[4];
    2153     $row{bg} = ($rownum++)%2;
    2154     $row{sid} = $sid;
    2155     $row{edgrp} = ($permissions{admin} || $permissions{group_edit});
    2156     $row{delgrp} = ($permissions{admin} || $permissions{group_delete});
    2157     push @grouplist, \%row;
    2158   }
    2159   $page->param(grouptable => \@grouplist);
     2101  my $glist = getGroupList($dbh, (childlist => $childlist, curgroup => $curgroup,
     2102        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef),
     2103        offset => $webvar{offset}, sortby => $sortby, sortorder => $sortorder) );
     2104
     2105  $page->param(grouptable => $glist);
    21602106} # end listgroups()
    21612107
     
    21652111  my $cur = shift || $curgroup;
    21662112
    2167   my @childgroups;
    2168   getChildren($dbh, $logingroup, \@childgroups, 'all');
    2169   my $childlist = join(',',@childgroups);
    2170 
    2171 ##fixme:  need to reorder list so that we can display a pseudotree in group dropdowns
    2172 
    2173   # weesa gonna discard parent_group_id for now
    2174   my $sth = $dbh->prepare("SELECT group_id,parent_group_id,group_name FROM groups ".
    2175         "WHERE group_id IN ($logingroup".($childlist ? ",$childlist" : '').")".
    2176         "ORDER BY group_id");
    2177   $sth->execute;
     2113  # little recursive utility sub-sub
     2114  sub getgroupdrop {
     2115    my $root = shift;
     2116    my $cur = shift;    # to tag the selected group
     2117    my $grplist = shift;
     2118    my $indent = shift || '&nbsp;&nbsp;&nbsp;&nbsp;';
     2119
     2120    my @childlist;
     2121    getChildren($dbh,$root,\@childlist,'immediate');
     2122    return if $#childlist == -1;
     2123    foreach (@childlist) {
     2124      my %row;
     2125      $row{groupval} = $_;
     2126      $row{groupactive} = ($_ == $cur);
     2127      $row{groupname} = $indent.groupName($dbh, $_);
     2128      push @{$grplist}, \%row;
     2129      getgroupdrop($_, $cur, $grplist, $indent.'&nbsp;&nbsp;&nbsp;&nbsp;');
     2130    }
     2131  }
     2132
    21782133  my @grouplist;
    2179   while (my ($groupid,$pargroup,$groupname) = $sth->fetchrow_array()) {
    2180     my %row;
    2181     $row{groupname} = $groupname;
    2182     $row{groupval} = $groupid;
    2183 ##fixme: need magic
    2184 ## ... WTF?
    2185 #    $row{defgroup} = '';
    2186     $row{groupactive} = 1 if $groupid == $cur;
    2187     push @grouplist, \%row;
    2188   }
     2134  push @grouplist, { groupval => $logingroup, groupactive => $logingroup == $curgroup,
     2135        groupname => groupName($dbh, $logingroup) };
     2136  getgroupdrop($logingroup, $curgroup, \@grouplist);
    21892137
    21902138  $page->param("$template_var" => \@grouplist);
    2191 
    21922139} # end fill_grouplist()
     2140
     2141
     2142sub fill_loclist {
     2143  my $cur = shift || $curgroup;
     2144  my $defloc = shift || '';
     2145
     2146  return unless ($permissions{admin} || $permissions{location_view});
     2147
     2148  $page->param(location_view => ($permissions{admin} || $permissions{location_view}));
     2149
     2150  if ($permissions{admin} || $permissions{record_locchg}) {
     2151    my $loclist = getLocDropdown($dbh, $cur, $defloc);
     2152    $page->param(record_locchg => 1);
     2153    $page->param(loclist => $loclist);
     2154  } else {
     2155    my $loc = getLoc($dbh, $defloc);
     2156    $page->param(loc_name => $loc->{description});
     2157  }
     2158} # end fill_loclist()
    21932159
    21942160
     
    21992165  my $childlist = join(',',@childgroups);
    22002166
    2201   my $sql = "SELECT count(*) FROM users WHERE group_id IN ($curgroup".($childlist ? ",$childlist" : '').")".
    2202         ($startwith ? " AND username ~* ?" : '').
    2203         ($filter ? " AND username ~* ?" : '');
    2204   my $sth = $dbh->prepare($sql);
    2205   $sth->execute(@filterargs);
    2206   my ($count) = ($sth->fetchrow_array);
     2167  my $count = getUserCount($dbh, (childlist => $childlist, curgroup => $curgroup,
     2168        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef) ) );
    22072169
    22082170# fill page count and first-previous-next-last-all bits
     
    22292191  $page->param(searchsubs => $searchsubs) if $searchsubs;
    22302192
    2231 # munge sortby for columns in database
    2232   $sortby = 'u.username' if $sortby eq 'user';
    2233   $sortby = 'u.type' if $sortby eq 'type';
    2234   $sortby = 'g.group_name' if $sortby eq 'group';
    2235   $sortby = 'u.status' if $sortby eq 'status';
    2236 
    2237   my @userlist;
    2238   $sql = "SELECT u.user_id, u.username, u.firstname || ' ' || u.lastname AS fname, u.type, g.group_name, u.status ".
    2239         "FROM users u ".
    2240         "INNER JOIN groups g ON u.group_id=g.group_id ".
    2241         "WHERE u.group_id IN ($curgroup".($childlist ? ",$childlist" : '').")".
    2242         ($startwith ? " AND u.username ~* ?" : '').
    2243         ($filter ? " AND u.username ~* ?" : '').
    2244         " ORDER BY $sortby $sortorder ".
    2245         ($offset eq 'all' ? '' : " LIMIT $perpage OFFSET ".$offset*$perpage);
    2246 
    2247   $sth = $dbh->prepare($sql);
    2248   $sth->execute(@filterargs);
    2249 
    2250   my $rownum = 0;
    2251   while (my @data = $sth->fetchrow_array) {
    2252     no warnings "uninitialized";        # Just In Case something stupid happens and a user gets no first or last name
    2253     my %row;
    2254     $row{userid} = $data[0];
    2255     $row{username} = $data[1];
    2256     $row{userfull} = $data[2];
    2257     $row{usertype} = ($data[3] eq 'S' ? 'superuser' : "user");
    2258     $row{usergroup} = $data[4];
    2259     $row{active} = $data[5];
    2260     $row{bg} = ($rownum++)%2;
    2261     $row{sid} = $sid;
    2262     $row{eduser} = ($permissions{admin} || $permissions{user_edit});
    2263     $row{deluser} = ($permissions{admin} || $permissions{user_delete});
    2264     push @userlist, \%row;
    2265   }
    2266   $page->param(usertable => \@userlist);
     2193  my $ulist = getUserList($dbh, (childlist => $childlist, curgroup => $curgroup,
     2194        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef),
     2195        offset => $webvar{offset}, sortby => $sortby, sortorder => $sortorder) );
     2196  # Some UI things need to be done to the list (unlike other lists)
     2197  foreach my $u (@{$ulist}) {
     2198    $u->{eduser} = ($permissions{admin} ||
     2199        ($permissions{user_edit} && $u->{type} ne 'S') ||
     2200        ($permissions{self_edit} && $u->{user_id} == $session->param('uid')) );
     2201    $u->{deluser} = ($permissions{admin} || ($permissions{user_delete} && $u->{type} ne 'S'));
     2202    $u->{type} = ($u->{type} eq 'S' ? 'superuser' : 'user');
     2203  }
     2204  $page->param(usertable => $ulist);
    22672205} # end list_users()
     2206
     2207
     2208sub list_locations {
     2209
     2210  my @childgroups;
     2211  getChildren($dbh, $curgroup, \@childgroups, 'all') if $searchsubs;
     2212  my $childlist = join(',',@childgroups);
     2213
     2214  my $count = getLocCount($dbh, (childlist => $childlist, curgroup => $curgroup,
     2215        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef) ) );
     2216
     2217# fill page count and first-previous-next-last-all bits
     2218  fill_pgcount($count,"locations/views",'');
     2219  fill_fpnla($count);
     2220
     2221  $sortby = 'user';
     2222# sort/order
     2223  $session->param($webvar{page}.'sortby', $webvar{sortby}) if $webvar{sortby};
     2224  $session->param($webvar{page}.'order', $webvar{order}) if $webvar{order};
     2225
     2226  $sortby = $session->param($webvar{page}.'sortby') if $session->param($webvar{page}.'sortby');
     2227  $sortorder = $session->param($webvar{page}.'order') if $session->param($webvar{page}.'order');
     2228
     2229# set up the headers
     2230  my @cols = ('description', 'iplist', 'group');
     2231  my %colnames = (description => 'Location/View Name', iplist => 'Permitted IPs/Ranges', group => 'Group');
     2232  fill_colheads($sortby, $sortorder, \@cols, \%colnames);
     2233
     2234# waffle, waffle - keep state on these as well as sortby, sortorder?
     2235  $page->param("start$startwith" => 1) if $startwith && $startwith =~ /^(?:[a-z]|0-9)$/;
     2236
     2237  $page->param(filter => $filter) if $filter;
     2238  $page->param(searchsubs => $searchsubs) if $searchsubs;
     2239
     2240  my $loclist = getLocList($dbh, (childlist => $childlist, curgroup => $curgroup,
     2241        filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef),
     2242        offset => $webvar{offset}, sortby => $sortby, sortorder => $sortorder) );
     2243  # Some UI things need to be done to the list
     2244  foreach my $l (@{$loclist}) {
     2245    $l->{iplist} = "(All IPs)" if !$l->{iplist};
     2246    $l->{edloc} = ($permissions{admin} || $permissions{loc_edit});
     2247    $l->{delloc} = ($permissions{admin} || $permissions{loc_delete});
     2248  }
     2249  $page->param(loctable => $loclist);
     2250} # end list_locations()
    22682251
    22692252
     
    22822265  foreach my $col (@$cols) {
    22832266    my %coldata;
    2284     $coldata{firstcol} = 1 if $col eq $cols->[0];
    22852267    $coldata{sid} = $sid;
    22862268    $coldata{page} = $webvar{page};
     
    23072289
    23082290
    2309 sub logaction {
    2310   my $domid = shift;
    2311   my $username = shift;
    2312   my $groupid = shift;
    2313   my $entry = shift;
    2314   my $revid = shift || 0;
    2315 
    2316 ##fixme: push SQL into DNSDB.pm
    2317 ##fixme: add bits to retrieve group/domain name info to retain after entity is deleted?
    2318   my $sth = $dbh->prepare("SELECT user_id, firstname || ' ' || lastname FROM users WHERE username=?");
    2319   $sth->execute($username);
    2320   my ($user_id, $fullname) = $sth->fetchrow_array;
    2321 
    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;
    2325 } # end logaction()
    2326 
    2327 
    23282291# we have to do this in a variety of places;  let's make it consistent
    23292292sub fill_permissions {
Note: See TracChangeset for help on using the changeset viewer.