Index: branches/stable/dns.cgi
===================================================================
--- branches/stable/dns.cgi	(revision 547)
+++ branches/stable/dns.cgi	(revision 548)
@@ -3,5 +3,5 @@
 ##
 # $Id$
-# Copyright 2008-2012 Kris Deugau <kdeugau@deepnet.cx>
+# Copyright 2008-2013 Kris Deugau <kdeugau@deepnet.cx>
 # 
 #    This program is free software: you can redistribute it and/or modify
@@ -30,4 +30,5 @@
 use Net::DNS;
 use DBI;
+
 use Data::Dumper;
 
@@ -65,8 +66,7 @@
 $webvar{revrec} = 'n' if !$webvar{revrec};	# non-reverse (domain) records
 
-# load some local system defaults (mainly DB connect info)
-# note this is not *absolutely* fatal, since there's a default dbname/user/pass in DNSDB.pm
-# we'll catch a bad DB connect string once we get to trying that
-##fixme:  pass params to loadConfig, and use them there, to allow one codebase to support multiple sites
+# create a DNSDB object.  this loads some local system defaults and connects to the DB
+# with the credentials configured
+##fixme:  pass params for loadConfig, and use them there, to allow one codebase to support multiple sites
 my $dnsdb = new DNSDB;
 
@@ -75,4 +75,5 @@
 $footer->param(version => $DNSDB::VERSION);
 
+##fixme:  slim chance this could be triggered on errors other than DB failure?
 if (!$dnsdb) {
   print "Content-type: text/html\n\n";
@@ -87,53 +88,34 @@
 $header->param(orgname => $dnsdb->{orgname}) if $dnsdb->{orgname} ne 'Example Corp';
 
-# persistent stuff needed on most/all pages
-my $sid = ($webvar{sid} ? $webvar{sid} : undef);
-my $session = new CGI::Session("driver:File", $sid, {Directory => $dnsdb->{sessiondir}})
+my $logingroup;
+my $curgroup;
+my @viewablegroups;
+
+# retrieve the session ID from our cookie, if possible
+my $sid = $q->cookie('dnsadmin_session');
+
+# see if the session loads
+my $session = CGI::Session->load("driver:File", $sid, {Directory => $dnsdb->{sessiondir}})
 	or die CGI::Session->errstr();
-#$sid = $session->id() if !$sid;
+
 if (!$sid) {
-  # init stuff.  can probably axe this down to just above if'n'when user manipulation happens
-  $sid = $session->id();
-  $session->expire($dnsdb->{timeout});
-# need to know the "upper" group the user can deal with;  may as well
-# stick this in the session rather than calling out to the DB every time.
-  $session->param('logingroup',1);
-  $session->param('curgroup',1);	# yes, we *do* need to track this too.  er, probably.
-  $session->param('domlistsortby','domain');
-  $session->param('domlistorder','ASC');
-  $session->param('revzonessortby','revnet');
-  $session->param('revzonesorder','ASC');
-  $session->param('useradminsortby','user');
-  $session->param('useradminorder','ASC');
-  $session->param('grpmansortby','group');
-  $session->param('grpmanorder','ASC');
-  $session->param('reclistsortby','host');
-  $session->param('reclistorder','ASC');
-  $session->param('loclistsortby','description');
-  $session->param('loclistorder','ASC');
-  $session->param('logsortby','stamp');
-  $session->param('logorder','DESC');
+  $webvar{page} = 'login';
+} else {
+  # we have a session to load from, maybe
+  $logingroup = ($session->param('logingroup') ? $session->param('logingroup') : 1);
+  $curgroup = ($session->param('curgroup') ? $session->param('curgroup') : $logingroup);
+  # security check - does the user have permission to view this entity?
+  # this is a prep step used "many" places
+  $dnsdb->getChildren($logingroup, \@viewablegroups, 'all');
+  push @viewablegroups, $logingroup;
+##fixme: make sessions persist through closing the site?
+# this even bridges browser close too.  hmm...
+  $webvar{page} = 'domlist' if !$webvar{page};
 }
 
-# Just In Case.  Stale sessions should not be resurrectable.
-if ($sid ne $session->id()) {
-  $sid = '';
-  changepage(page=> "login", sessexpired => 1);
-}
-
-# normal expiry, more or less
-if ($session->is_expired) {
-  $sid = '';
-  changepage(page=> "login", sessexpired => 1);
-}
-
-my $logingroup = ($session->param('logingroup') ? $session->param('logingroup') : 1);
-my $curgroup = ($session->param('curgroup') ? $session->param('curgroup') : $logingroup);
-
-# decide which page to spit out...
-# also set $webvar{page} before we try to use it.
+# set $webvar{page} before we try to use it.
 $webvar{page} = 'login' if !$webvar{page};
 
-# per-page startwith, filter, searchsubs
+## per-page startwith, filter, searchsubs
 
 ##fixme:  complain-munge-and-continue with non-"[a-z0-9-.]" filter and startwith
@@ -150,8 +132,41 @@
 $webvar{searchsubs} =~ s/[^yn]//g if $webvar{searchsubs};
 
-$session->param($webvar{page}.'startwith', $webvar{startwith}) if defined($webvar{startwith});
-$session->param($webvar{page}.'filter', $webvar{filter}) if defined($webvar{filter});
+# pagination
+my $perpage = 15;  # Just In Case
+$perpage = $dnsdb->{perpage} if $dnsdb->{perpage};
+my $offset = ($webvar{offset} ? $webvar{offset} : 0);
+
+## set up "URL to self" (whereami edition)
+# @#$%@%@#% XHTML - & in a URL must be escaped.  >:(
+my $uri_self = $ENV{REQUEST_URI};
+$uri_self =~ s/\&([a-z])/\&amp\;$1/g;
+
+# le sigh.  and we need to strip any previous action
+$uri_self =~ s/\&amp;action=[^&]+//g;
+
+# much magic happens.  if startwith or a search string change (to, from, or
+# across, in the request vs whatever's in the session) then the offset should
+# be reset to 0 so that the first/prev/next/last widget populates correctly,
+# and so that the list of whatever we're looking at actually shows things
+# (since we may have started on page 42 of 300 with a LOOOOONG list, but we
+# now only need 3 pages for the filtered list).
+# while we're at it, plonk these into the session for safekeeping.
+if (defined($webvar{startwith})) {
+  if ($webvar{startwith} ne $session->param($webvar{page}.'startwith')) {
+    $uri_self =~ s/\&amp;offset=[^&]//;
+    $offset = 0;
+  }
+  $session->param($webvar{page}.'startwith', $webvar{startwith});
+}
+if (defined($webvar{filter})) {
+  if ($webvar{filter} ne $session->param($webvar{page}.'filter')) {
+    $uri_self =~ s/\&amp;offset=[^&]//;
+    $offset = 0;
+  }
+  $session->param($webvar{page}.'filter', $webvar{filter})
+}
 $session->param($webvar{page}.'searchsubs', $webvar{searchsubs}) if defined($webvar{searchsubs});
 
+# and now that the search/filter criteria for this page are set, put them in some globals for actual use.
 my $startwith = $session->param($webvar{page}.'startwith');
 my $filter = $session->param($webvar{page}.'filter');
@@ -162,12 +177,4 @@
 push @filterargs, "^[$startwith]" if $startwith;
 push @filterargs, $filter if $filter;
-
-## set up "URL to self"
-# @#$%@%@#% XHTML - & in a URL must be escaped.  >:(
-my $uri_self = $ENV{REQUEST_URI};
-$uri_self =~ s/\&([a-z])/\&amp\;$1/g;
-
-# le sigh.  and we need to strip any previous action
-$uri_self =~ s/\&amp;action=[^&]+//g;
 
 # and search filter options.  these get stored in the session, but discarded
@@ -181,22 +188,16 @@
 
 # Fix up $uri_self so we don't lose the session/page
-$uri_self .= "?sid=$sid&amp;page=$webvar{page}" if $uri_self =~ m{/dns.cgi$};
-$uri_self = "$ENV{SCRIPT_NAME}?sid=$sid&amp;page=$webvar{page}$1" if $uri_self =~ m{/dns.cgi\&(.+)$};
-
-# pagination
-my $perpage = 15;
-$perpage = $dnsdb->{perpage} if $dnsdb->{perpage};
-my $offset = ($webvar{offset} ? $webvar{offset} : 0);
+$uri_self .= "?page=$webvar{page}" if $uri_self =~ m{/dns.cgi$};
+$uri_self = "$ENV{SCRIPT_NAME}?page=$webvar{page}$1" if $uri_self =~ m{/dns.cgi\&(.+)$};
+
+## end uri_self monkeying
 
 # NB:  these must match the field name and SQL ascend/descend syntax respectively
+# sortby is reset to a suitable "default", then re-reset to whatever the user has
+# clicked on last in the record=listing subs, but best to put a default here.
 my $sortby = "domain";
 my $sortorder = "ASC";
 
-# security check - does the user have permission to view this entity?
-# this is a prep step used "many" places
-my @viewablegroups;
-$dnsdb->getChildren($logingroup, \@viewablegroups, 'all');
-push @viewablegroups, $logingroup;
-
+# Create the page template object.  Display a reasonable error page and whine if the template doesn't exist.
 my $page;
 eval {
@@ -217,6 +218,9 @@
 }
 
-# handle login redirect
+my $sesscookie;
+
+# handle can-happen-on-(almost)-any-page actions
 if ($webvar{action}) {
+
   if ($webvar{action} eq 'login') {
     # Snag ACL/permissions here too
@@ -226,5 +230,19 @@
     if ($userdata) {
 
+      # (re)create the session
+      $session = new CGI::Session("driver:File", $sid, {Directory => $dnsdb->{sessiondir}})
+        or die CGI::Session->errstr();
+      $sid = $session->id();
+
+      $sesscookie = $q->cookie( -name => 'dnsadmin_session',
+        -value => $sid,
+        -expires => "+".$dnsdb->{timeout},
+        -secure => 0,
+## fixme:  need to extract root path for cookie, so as to limit cookie to dnsadmin instance
+#        -path => $url
+        );
+
       # set session bits
+      $session->expire($dnsdb->{timeout});
       $session->param('logingroup',$userdata->{group_id});
       $session->param('curgroup',$userdata->{group_id});
@@ -232,5 +250,38 @@
       $session->param('username',$webvar{username});
 
-      changepage(page => "domlist");
+# for reference.  seems we don't need to set these on login any more.
+#  $session->param('domlistsortby','domain');
+#  $session->param('domlistorder','ASC');
+#  $session->param('revzonessortby','revnet');
+#  $session->param('revzonesorder','ASC');
+#  $session->param('useradminsortby','user');
+#  $session->param('useradminorder','ASC');
+#  $session->param('grpmansortby','group');
+#  $session->param('grpmanorder','ASC');
+#  $session->param('reclistsortby','host');
+#  $session->param('reclistorder','ASC');
+#  $session->param('loclistsortby','description');
+#  $session->param('loclistorder','ASC');
+#  $session->param('logsortby','stamp');
+#  $session->param('logorder','DESC');
+
+      ## "recover my link" - tack on request bits and use requested page instead of hardcoding domlist
+      # this could possibly be compacted by munging changepage a little so we don't have to deconstruct
+      # and reconstruct the URI argument list.
+      my %target = (page => "domlist");
+      if ($webvar{target} && $webvar{target} =~ /\?/) {
+        my $tmp = (split /\?/, $webvar{target})[1];
+        $tmp =~ s/^\&//;
+        my @targs = split /\&/, $tmp;
+        foreach (@targs) {
+          my ($k,$v) = split /=/;
+          $target{$k} = $v if $k;
+          # if we're going through a "session expired" login, we may have a different
+          # "current group" than the login group.
+          $session->param('curgroup', $v) if $k eq 'curgroup';
+##fixme:  page=record goes "FOOM", sometimes - cause/fix?
+        }
+      }
+      changepage(%target);
 
     } else {
@@ -243,10 +294,18 @@
     $session->flush();
 
+    my $sesscookie = $q->cookie( -name => 'dnsadmin_session',
+      -value => $sid,
+      -expires => "-1",
+      -secure => 0,
+## fixme:  need to extract root path for cookie, so as to limit cookie to dnsadmin instance
+#      -path => $url
+      );
+
     my $newurl = "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}";
     $newurl =~ s|/[^/]+$|/|;
-    print "Status: 302\nLocation: $newurl\n\n";
+    print $q->redirect( -uri => $newurl, -cookie => $sesscookie);
     exit;
 
-  } elsif ($webvar{action} eq 'chgroup') {
+  } elsif ($webvar{action} eq 'chgroup' && $webvar{page} ne 'login') {
     # fiddle session-stored group data
     # magic incantation to... uhhh...
@@ -276,7 +335,11 @@
       changepage(%args);
     }
-
-  }
+    # add offset back *into* $uri_self if we're also currently looking at a live record list.
+    if ($webvar{page} eq 'reclist' && $webvar{defrec} eq 'n') {
+      $uri_self .= "\&amp;offset=$offset";
+    }
+  } # done action=chgroup
 } # handle global webvar{action}s
+
 
 # finally check if the user was disabled.  we could just leave this for logout/session expiry,
@@ -295,11 +358,29 @@
 $dnsdb->initActionLog($session->param('uid'));
 
-$page->param(sid => $sid) unless $webvar{page} eq 'login';	# no session ID on the login page
+##
+## Per-page processing
+##
 
 if ($webvar{page} eq 'login') {
 
-  $page->param(loginfailed => 1) if $webvar{loginfailed};
-  $page->param(sessexpired => 1) if $webvar{sessexpired};
+  my $target = $ENV{REQUEST_URI};
+  $target =~ s/\&/\&amp;/g;
+  $page->param(target => $target); # needs to be trimmed a little, maybe?
+
+  $page->param(sessexpired => 1) if (!$sid && $target !~ m|/$|);
+
+  if ($webvar{loginfailed}) {
+    $page->param(loginfailed => 1);
+    $webvar{target} =~ s/\&/\&amp;/g;	# XHTML we do (not) love you so
+    $page->param(target => $webvar{target}) if $webvar{target};
+  }
+#  if $webvar{sessexpired};	 # or this with below?
+  if ($session->is_expired) {
+    $page->param(sessexpired => 1);
+    $session->delete();   # Just to make sure
+    $session->flush();
+  }
   $page->param(version => $DNSDB::VERSION);
+  $page->param(script_self => ($ENV{SCRIPT_NAME} =~ m|/([^/]+)$|)[0]);
 
 } elsif ($webvar{page} eq 'domlist' or $webvar{page} eq 'index') {
@@ -337,5 +418,5 @@
   $webvar{group} = $curgroup if !$webvar{group};
   fill_grouplist("grouplist", $webvar{group});
-  fill_loclist();
+  fill_loclist($curgroup, $webvar{defloc} ? $webvar{defloc} : '');
 
   if ($session->param('add_failed')) {
@@ -363,5 +444,6 @@
   $webvar{makeactive} = 0 if !defined($webvar{makeactive});
 
-  my ($code,$msg) = $dnsdb->addDomain($webvar{domain}, $webvar{group}, ($webvar{makeactive} eq 'on' ? 1 : 0));
+  my ($code,$msg) = $dnsdb->addDomain($webvar{domain}, $webvar{group}, ($webvar{makeactive} eq 'on' ? 1 : 0),
+        $webvar{defloc});
 
   if ($code eq 'OK') {
@@ -373,6 +455,6 @@
     $session->param('add_failed', 1);
 ##fixme:  domain a security risk for XSS?
-    changepage(page => "newdomain", domain => $webvar{domain}, errmsg => $msg,
-	makeactive => ($webvar{makeactive} ? 'y' : 'n'), group => $webvar{group});
+    changepage(page => "newdomain", errmsg => $msg, domain => $webvar{domain},
+	group => $webvar{group}, makeactive => ($webvar{makeactive} ? 'y' : 'n'), defloc => $webvar{defloc});
   }
 
@@ -640,5 +722,6 @@
 
     my @recargs = ($webvar{defrec}, $webvar{revrec}, $webvar{parentid},
-	\$webvar{name}, \$webvar{type}, \$webvar{address}, $webvar{ttl}, $webvar{location});
+	\$webvar{name}, \$webvar{type}, \$webvar{address}, $webvar{ttl}, $webvar{location},
+	$webvar{expires}, $webvar{stamp});
     if ($webvar{type} == $reverse_typemap{MX} or $webvar{type} == $reverse_typemap{SRV}) {
       push @recargs, $webvar{distance};
@@ -688,5 +771,8 @@
     $page->param(ttl		=> $recdata->{ttl});
     $page->param(typelist	=> $dnsdb->getTypelist($webvar{revrec}, $recdata->{type}));
-
+    if ($recdata->{stampactive}) {
+      $page->param(stamp => $recdata->{stamp});
+      $page->param(stamp_until => $recdata->{expires});
+    }
     if ($webvar{defrec} eq 'n') {
       fill_loclist($curgroup, $recdata->{location});
@@ -704,4 +790,5 @@
     my ($code,$msg) = $dnsdb->updateRec($webvar{defrec}, $webvar{revrec}, $webvar{id}, $webvar{parentid},
 	\$webvar{name}, \$webvar{type}, \$webvar{address}, $webvar{ttl}, $webvar{location},
+	$webvar{expires}, $webvar{stamp},
 	$webvar{distance}, $webvar{weight}, $webvar{port});
 
@@ -1001,5 +1088,5 @@
   $page->param(perpage => $perpage);
 
-  my $domlist = $dnsdb->getZoneList(revrec => 'n', curgroup => $curgroup);
+  my $domlist = $dnsdb->getZoneList(revrec => 'n', curgroup => $curgroup, offset => $offset);
   my $rownum = 0;
   foreach my $dom (@{$domlist}) {
@@ -1693,5 +1780,6 @@
 
 # start output here so we can redirect pages.
-print "Content-type: text/html\n\n", $header->output;
+print $q->header( -cookie => $sesscookie);
+print $header->output;
 
 ##common bits
@@ -1722,6 +1810,8 @@
   $page->param(inlogingrp => $curgroup == $logingroup);
 
-# fill in the URL-to-self
+# fill in the URL-to-self for the group tree and search-by-letter
   $page->param(whereami => $uri_self);
+# fill in general URL-to-self
+  $page->param(script_self => "$ENV{SCRIPT_NAME}?".($curgroup ? "curgroup=$curgroup" : ''));
 }
 
@@ -1766,4 +1856,8 @@
 
   my @childlist;
+
+  # some magic to control bad offsets on group change
+  my $grp_uri_self = $uri_self;
+  $grp_uri_self =~ s/\&amp;offset=[^&]+// unless ($webvar{page} eq 'reclist' && $webvar{defrec} eq 'n');
 
   my $grptree = HTML::Template->new(filename => 'templates/grptree.tmpl');
@@ -1775,5 +1869,5 @@
     $row{grpname} = $dnsdb->groupName($_);
     $row{grpnum} = $_;
-    $row{whereami} = $uri_self;
+    $row{whereami} = $grp_uri_self;
     $row{curgrp} = ($_ == $cur);
     $row{expanded} = $dnsdb->isParent($_, 'group', $cur, 'group');
@@ -1805,5 +1899,5 @@
 
   # handle user check
-  my $newurl = "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}?sid=$sid";
+  my $newurl = "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}?";
   foreach (sort keys %params) {
 ## fixme:  something is undefined here on add location
@@ -1814,5 +1908,5 @@
   $session->flush();
 
-  print "Status: 302\nLocation: $newurl\n\n";
+  print $q->redirect ( -url => $newurl, -cookie => $sesscookie);
   exit;
 } # end changepage
@@ -1901,10 +1995,9 @@
   $page->param(ttl	=> $soa->{ttl});
 
-  my $foo2 = $dnsdb->getDomRecs(defrec => $def, revrec => $rev, id => $id, offset => $webvar{offset},
+  my $foo2 = $dnsdb->getRecList(defrec => $def, revrec => $rev, id => $id, offset => $webvar{offset},
 	sortby => $sortby, sortorder => $sortorder, filter => $filter);
 
   foreach my $rec (@$foo2) {
     $rec->{type} = $typemap{$rec->{type}};
-    $rec->{sid} = $webvar{sid};
     $rec->{fwdzone} = $rev eq 'n';
     $rec->{distance} = 'n/a' unless ($rec->{type} eq 'MX' || $rec->{type} eq 'SRV'); 
@@ -1915,4 +2008,14 @@
     $rec->{record_delete} = ($permissions{admin} || $permissions{record_delete});
     $rec->{locname} = '' unless ($permissions{admin} || $permissions{location_view});
+# Timestamps
+    if ($rec->{expires}) {
+      $rec->{stamptype} = $rec->{ispast} ? 'expired at' : 'expires at';
+    } else {
+      $rec->{stamptype} = 'valid after';
+    }
+    # strip seconds and timezone?  no, not yet.  could probably offer a config knob on this display at some point.
+#    $rec->{stamp} =~ s/:\d\d-\d+$//;
+    delete $rec->{expires};
+    delete $rec->{ispast};
   }
   $page->param(reclist => $foo2);
@@ -1945,4 +2048,6 @@
   my $soa = $dnsdb->getSOA($webvar{defrec}, $webvar{revrec}, $webvar{parentid});
   $page->param(ttl	=> ($webvar{ttl} ? $webvar{ttl} : $soa->{minttl}));
+  $page->param(stamp_until => ($webvar{expires} eq 'until'));
+  $page->param(stamp => $webvar{stamp});
 }
 
@@ -2006,12 +2111,4 @@
   my $parent = shift;
 
-  # Fix display/UI bug where if you are not on the first page of the list, and
-  # you add a search term or click one of the "starts with" links, you end up
-  # on a page showing nothing.
-  # For bonus points, this reverts to the original offset on clicking the "All" link (mostly)
-  if ($offset ne 'all') {
-    $offset-- while ($offset * $perpage) >= $pgcount;
-  }
-
   $page->param(ntot => $pgcount);
   $page->param(nfirst => (($offset eq 'all' ? 0 : $offset)*$perpage+1));
@@ -2044,4 +2141,5 @@
   fill_fpnla($count);
 
+  $sortby = ($webvar{revrec} eq 'n' ? 'domain' : 'revnet');
 # sort/order
   $session->param($webvar{page}.'sortby', $webvar{sortby}) if $webvar{sortby};
@@ -2071,5 +2169,5 @@
   my $zonelist = $dnsdb->getZoneList(childlist => $childlist, curgroup => $curgroup, revrec => $webvar{revrec},
 	filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef),
-	offset => $webvar{offset}, sortby => $sortby, sortorder => $sortorder
+	offset => $offset, sortby => $sortby, sortorder => $sortorder
 	);
 # probably don't need this, keeping for reference for now
@@ -2292,5 +2390,4 @@
   foreach my $col (@$cols) {
     my %coldata;
-    $coldata{sid} = $sid;
     $coldata{page} = $webvar{page};
     $coldata{offset} = $webvar{offset} if $webvar{offset};
