Index: trunk/cgi-bin/admin.cgi
===================================================================
--- trunk/cgi-bin/admin.cgi	(revision 515)
+++ trunk/cgi-bin/admin.cgi	(revision 517)
@@ -15,6 +15,7 @@
 use warnings;
 use CGI::Carp qw(fatalsToBrowser);
+use CGI::Simple;
+use HTML::Template;
 use DBI;
-use CommonWeb qw(:ALL);
 #use POSIX qw(ceil);
 use NetAddr::IP;
@@ -39,4 +40,20 @@
 
 syslog "debug", "$authuser active";
+
+# Set up the CGI object...
+my $q = new CGI::Simple;
+# ... and get query-string params as well as POST params if necessary
+$q->parse_query_string;
+
+# Convenience;  saves changing all references to %webvar
+##fixme:  tweak for handling <select multiple='y' size=3> (list with multiple selection)
+my %webvar = $q->Vars;
+
+# anyone got a better name?  :P
+my $thingroot = $ENV{SCRIPT_FILENAME};
+$thingroot =~ s|cgi-bin/admin.cgi||;
+
+# Set up some globals
+$ENV{HTML_TEMPLATE_ROOT} = $thingroot."templates";
 
 # Why not a global DB handle?  (And a global statement handle, as well...)
@@ -47,92 +64,79 @@
 ($ip_dbh,$errstr) = connectDB_My;
 if (!$ip_dbh) {
-  printAndExit("Database error: $errstr\n");
-}
-initIPDBGlobals($ip_dbh);
+  $webvar{action} = "dberr";
+} else {
+  initIPDBGlobals($ip_dbh);
+}
+
+# handle DB error output
+if ($webvar{action} eq 'dberr') {
+  my $page = HTML::Template->new(filename => "admin/dberr.tmpl");
+  $page->param(errmsg => $errstr);
+  print "Content-Type: text/html\n\n".$page->output;
+  exit;
+}
 
 if ($IPDBacl{$authuser} !~ /A/) {
-  print "Content-Type: text/html\n\n".
-	"<html>\n<head>\n\t<title>Access denied</title>\n".
-	qq(\t<link rel="stylesheet" type="text/css" href="/ip/ipdb.css">\n).
-	qq(\t<link rel="stylesheet" type="text/css" href="/ip/local.css">\n).
-	"</head>\n<body>\n".
-	qq(Access to this tool is restricted.  Contact the <a href="mailto:ipdbadmin\@example.com">IPDB administrator</a> \n).
-	"for more information.\n</body>\n</html>\n";
+  my $page = HTML::Template->new(filename => "admin/aclerr.tmpl");
+##fixme:  need params for IPDB admin email and name
+  $page->param(ipdbadmin_email => 'ipdbadmin@example.com');
+  $page->param(ipdbadmin_name => 'the IPDB administrator');
+  print "Content-Type: text/html\n\n".$page->output;
   exit;
 }
 
-my %webvar = parse_post();
-cleanInput(\%webvar);
-
-print "Content-type: text/html\n\n".
-	"<html>\n<head>\n\t<title>[IPDB admin tools]</title>\n".
-	qq(\t<link rel="stylesheet" type="text/css" href="/ip/ipdb.css">\n).
-	qq(\t<link rel="stylesheet" type="text/css" href="/ip/local.css">\n).
-	"</head>\n<body>\n".
-	"<h2>IPDB - Administrative Tools</h2>\n<hr>\n";
+my $header = HTML::Template->new(filename => "admin/header.tmpl");
 
 if(!defined($webvar{action})) {
-  $webvar{action} = "<NULL>";   #shuts up the warnings.
-
-  my $typelist = '';
+  $webvar{action} = "main";   #shuts up the warnings.
+}
+
+my $page;
+if (-e "$ENV{HTML_TEMPLATE_ROOT}/admin/$webvar{action}.tmpl") {
+  $page = HTML::Template->new(filename => "admin/$webvar{action}.tmpl");
+} else {
+  $page = HTML::Template->new(filename => "admin/dunno.tmpl");
+}
+
+# handle index page
+if ($webvar{action} eq 'main') {
+  $header->param(mainpage => 1);
+
   $sth = $ip_dbh->prepare("select type,listname from alloctypes where listorder < 900 order by listorder");
   $sth->execute;
-  my @data = $sth->fetchrow_array;
-  $typelist .= "<option value='$data[0]' selected>$data[1]</option>\n";
-  while (my @data = $sth->fetchrow_array) {
-    $typelist .= "<option value='$data[0]'>$data[1]</option>\n";
-  }
-
-  my $masterlist = '';
+
+  my @typelist;
+  my $count = 0;
+  while (my ($type,$listname) = $sth->fetchrow_array) {
+    my %row = (
+	selected => $count++,
+	type => $type,
+	dispname => $listname
+	);
+    push @typelist, \%row;
+  }
+  $page->param(typelist => \@typelist);
+
+  my @masterlist;
   $sth = $ip_dbh->prepare("select cidr,mtime from masterblocks order by cidr");
   $sth->execute;
-  while (my @data = $sth->fetchrow_array) {
-    $masterlist .= "<option value='$data[0]'>$data[0] ($data[1])</option>\n";
-  }
-
-  print qq(WARNING:  There are FAR fewer controls on what you can do here.  Use the
-main interface if at all possible.
-<hr>
-<form action="admin.cgi" method="POST">
-<input type=hidden name=action value=alloc>
-Allocate block/IP: <input name=cidr> as <select name=alloctype>$typelist</select> to <input name=custid>
-<input type=submit value=" GIMME!! "></form>
-<hr><form action="admin.cgi" method="POST">
-<input type=hidden name=action value=alloctweak>
-Manually update allocation data in this /24: <input name=allocfrom>
-<input type=submit value="Show allocations">
-</form>
-
-<hr>rWHOIS tools:
-<form action="admin.cgi" method="POST">
-<input type=hidden name=action value=touch>
-Bump "last updated" timestamp on this master: <select name=whichmaster>$masterlist</select>
-<input type=submit value="Update timestamp"> (Sets timestamp to "now")</form>
-<a href="admin.cgi?action=listcust">Edit customer data for rWHOIS</a> - data used for
-blocks with the SWIP box checkmarked.  Links to edit/add data are on this page.
-
-<hr><a href="admin.cgi?action=showpools">List IP Pools</a> for manual tweaking and updates
-
-<hr><a href="admin.cgi?action=showusers">Manage users</a> (add/remove users;  change
-internal access controls - note that this does NOT include IP-based limits)<br>
-<a href="admin.cgi?action=emailnotice">Manage email notice options</a> (pick which events
-and allocation types cause notifications;  configure recipient lists for notices)
-
-<hr>Consistency check tools<br>
-<a href="consistency-check.pl">General</a>:  Check general netblock consistency.<br>
-<a href="freespace.pl">Free space</a>:  List total and aggregate free space.  Does not 
-include private networks (192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8)
-);
-} else {
-  print '<a href="/ip/cgi-bin/admin.cgi">Back</a> to main<hr>';
-}
-
-
-## Possible actions.
-if ($webvar{action} eq 'alloc') {
-  # OK, we know what we're allocating.
+  while (my ($cidr,$mtime) = $sth->fetchrow_array) {
+    my %row = (
+	master => $cidr,
+	masterdate => $mtime
+	);
+    push @masterlist, \%row;
+  }
+  $page->param(masterlist => \@masterlist);
+
+}
+
+## Non-default actions.
+
+elsif ($webvar{action} eq 'alloc') {
 
   if ($webvar{cidr} !~ /^\s*(\d{1,3}\.){3}\d{1,3}(\/\d{2})?\s*$/) {
-    printAndExit("Can't allocate something that's not a netblock/ip");
+    $page->param(errmsg => "Can't allocate something that's not a netblock/ip");
+    goto ERRJUMP;
   }
 
@@ -148,12 +152,12 @@
       my $status = CustIDCK->custid_exist($webvar{custid});
       if ($CustIDCK::Error) {
-	printError("Error verifying customer ID: ".$CustIDCK::ErrMsg);
-	return;
+	$page->param(errmsg => "Error verifying customer ID: ".$CustIDCK::ErrMsg);
+	goto ERRJUMP;
       }
       if (!$status) {
-	printError("Customer ID not valid.  Make sure the Customer ID ".
+	$page->param(errmsg => "Customer ID not valid.  Make sure the Customer ID ".
 	  "is correct.<br>\nUse STAFF for staff static IPs, and $IPDB::defcustid for any other ".
 	  "non-customer assignments.");
-	return;
+	goto ERRJUMP;
       }
     }
@@ -169,6 +173,8 @@
     @data = $sth->fetchrow_array;
 # User deserves errors if user can't be bothered to find the free block first.
-    printAndExit("Can't allocate from outside a free block!!\n")
-        if !$data[0];
+    if (!$data[0]) {
+      $page->param(errmsg => "Can't allocate from outside a free block!!");
+      goto ERRJUMP;
+    }
   } elsif ($webvar{alloctype} =~ /^(.)i$/) {
     $sth = $ip_dbh->prepare("select cidr from allocations where cidr >>='$cidr' and (type like '_d' or type like '_p')");
@@ -176,6 +182,8 @@
     @data = $sth->fetchrow_array;
 # User deserves errors if user can't be bothered to find the pool and a free IP first.
-    printAndExit("Can't allocate static IP from outside a pool!!\n")
-	if !$data[0];
+    if (!$data[0]) {
+      $page->param(errmsg => "Can't allocate static IP from outside a pool!!");
+      goto ERRJUMP;
+    }
   } else {
     $sth = $ip_dbh->prepare("select cidr from freeblocks where cidr >>='$cidr' and not (routed='n')");
@@ -183,6 +191,8 @@
     @data = $sth->fetchrow_array;
 # User deserves errors if user can't be bothered to find the free block first.
-    printAndExit("Can't allocate from outside a routed block!!\n")
-        if !$data[0];
+    if (!$data[0]) {
+      $page->param(errmsg => "Can't allocate from outside a routed block!!");
+      goto ERRJUMP;
+    }
   }
 
@@ -190,54 +200,30 @@
   $sth->finish;
 
-  my $cities = '';
+  my @cities;
   foreach my $city (@citylist) {
-    $cities .= "<option>$city</option>\n";
-  }
-
-  print qq(<table class=regular>
-<form method=POST action=admin.cgi>
-<tr class=color1>
-<td>Allocating:</td>
-<td>$cidr<input type=hidden name=cidr value="$cidr"></td>
-</tr><tr class=color2>
-<td>Type:</td><td>$disp_alloctypes{$webvar{alloctype}}
-<input type=hidden name=alloctype value="$webvar{alloctype}"></td>
-</tr><tr class=color1>
-<td>Allocated from:</td>
-<td>$alloc_from<input type=hidden name=alloc_from value="$alloc_from"></td>
-</tr><tr class="color2">
-<td>Customer ID:</td><td>$custid<input type=hidden name=custid value="$custid"></td>
-</tr><tr class=color1>
-<td>Customer location:</td><td>
-<select name="city"><option selected>-</option>
-$cities
-</select>
-&nbsp;<a href="javascript:popNotes('/ip/newcity.html')">Add new location</a>
-</td>
-</tr>
-<tr class="color2">
-<td>Circuit ID:</td><td><input name=circid size=40></td>
-</tr><tr class="color1">
-<td>Description/Name:</td><td><input name="desc" size=40></td>
-</tr><tr class="color2">
-<td>Notes:</td><td><textarea name="notes" rows="3" cols="40"></textarea></td>
-</tr><tr class="warning">
-<td colspan=2><center>WARNING:  This will IMMEDIATELY assign this block!!</center></td>
-</tr><tr class="color2">
-<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
-<input type="hidden" name="action" value="confirm">
-</form>
-</tr>
-</table>
-);
-
+     my %row = (city => $city);
+     push @cities, \%row;
+  }
+  $page->param(
+	cidr => $cidr,
+	disptype => $disp_alloctypes{$webvar{alloctype}},
+	type => $webvar{alloctype},
+	alloc_from => $alloc_from,
+	custid => $custid,
+	citylist => \@cities
+	);
 
 } elsif ($webvar{action} eq 'confirm') {
 
-  print "Assigning $webvar{cidr} to $webvar{custid} (\"$webvar{desc}\") as ".
-	"$disp_alloctypes{$webvar{alloctype}}...<br>\n";
+  $page->param(
+	cidr => $webvar{cidr},
+	custid => $webvar{custid},
+	desc => $webvar{desc},
+	disptype => $disp_alloctypes{$webvar{alloctype}}
+	);
   # Only need to check city here.
   if ($webvar{city} eq '-') {
-    printError("Invalid customer location!  Go back and select customer's location.");
+    $page->param(locerr => "Invalid customer location!  Go back and select customer's location.");
+    goto ERRJUMP;
   } else {
     if ($webvar{alloctype} =~ /^.i$/) {
@@ -247,9 +233,8 @@
       $sth->execute;
       if ($sth->err) {
-	print "Allocation failed!  DBI said:\n".$sth->errstr."\n";
+	$page->param(errmsg => $sth->errstr);
         syslog "err", "($authuser) Allocation of '$webvar{cidr}' to '$webvar{custid}' as ".
 		"'$webvar{alloctype}' failed: '".$sth->errstr."'";
       } else {
-	print "Allocation OK!\n";
 	syslog "notice", "$authuser allocated '$webvar{cidr}' to '$webvar{custid}' as ".
 		"'$webvar{alloctype}'";
@@ -263,9 +248,8 @@
 	$webvar{circid});
       if ($retcode eq 'OK') {
-	print "Allocation OK!\n";
 	syslog "notice", "$authuser allocated '$webvar{cidr}' to '$webvar{custid}' as ".
 		"'$webvar{alloctype}'";
       } else {
-	print "Allocation failed!  IPDB::allocateBlock said:\n$msg\n";
+	$page->param(errmsg => $msg);
         syslog "err", "($authuser) Allocation of '$webvar{cidr}' to '$webvar{custid}' as ".
 		"'$webvar{alloctype}' failed: '$msg'";
@@ -276,54 +260,39 @@
 
 } elsif ($webvar{action} eq 'alloctweak') {
+
   fix_allocfrom();
   showAllocs($webvar{allocfrom});
+
 } elsif ($webvar{action} eq 'update') {
+
   update();
-} elsif ($webvar{action} eq 'assign') {
-  # Display a list of possible blocks within the requested block.
-  open (HTML, "../admin_alloc.html")
-	or croak "Could not open admin_alloc.html :$!";
-  my $html = join('', <HTML>);
-  $html =~ s/\$\$MASK\$\$/$webvar{masklen}/g;
-  $html =~ s/\$\$ALLOCFROM\$\$/$webvar{allocfrom}/g;
-
-  my $from = new NetAddr::IP $webvar{allocfrom};
-  my @blocklist = $from->split($webvar{masklen});
-  my $availblocks;
-  foreach (@blocklist) {
-    $availblocks .= qq(<tr><td colspan=2 align=center><input type=radio name=block value="$_">$_</td></tr>\n);
-  }
-  $html =~ s/\$\$BLOCKLIST\$\$/$availblocks/g;
-
-  print $html;
+
 } elsif ($webvar{action} eq 'touch') {
-  print "Touching master $webvar{whichmaster}\n";
+
+  $page->param(master => $webvar{whichmaster});
   $sth = $ip_dbh->prepare("update masterblocks set mtime=now() where cidr='$webvar{whichmaster}'");
   $sth->execute;
   if ($sth->err) {
-    print "<p>Error updating modified timestamp on master $webvar{whichmaster}: ".$sth->errstr."\n";
-  }
+    $page->param(errmsg => $sth->errstr);
+  }
+
 } elsif ($webvar{action} eq 'listcust') {
-  print qq(Add new entry:\n
-<form action=admin.cgi method=POST>
-<table border=1><tr>
-<input type=hidden name=action value=edcust>
-<input type=hidden name=newcust value=1>
-<td>CustID:</td><td><input name=custid></td>
-<td align=center><input type=submit value="Go to edit page for this custid"></td></tr>
-</form></table>
-);
-  print "<p>Click CustID to edit existing customer contact data:\n".
-	"<table border=1>\n<tr><td>CustID</td><td>Name</td><td>Tech handle</td></tr>\n";
+
   $sth = $ip_dbh->prepare("select custid,name,tech_handle from customers order by custid");
   $sth->execute;
+  my @custlist;
   while (my @data = $sth->fetchrow_array) {
-    print qq(<tr><td><a href="admin.cgi?action=edcust&custid=$data[0]">$data[0]</td>).
-	"<td>$data[1]</td><td>$data[2]</td></tr>\n";
-  }
-  print "</table>\n";
+    my %row = (
+	custid => $data[0],
+	custname => $data[1],
+	tech => $data[2]
+	);
+    push @custlist, \%row;
+  }
+  $page->param(custlist => \@custlist);
+
 } elsif ($webvar{action} eq 'edcust') {
+
   if ($webvar{newcust}) {
-    print "got here?\n";
     $sth = $ip_dbh->prepare("INSERT INTO customers (custid) VALUES (?)");
     $sth->execute($webvar{custid});
@@ -335,46 +304,22 @@
   my ($custid, $name, $street, $city, $prov, $country, $pocode, $phone, $tech, $abuse, $admin, $special) =
 	$sth->fetchrow_array;
-  print qq(<form action=admin.cgi method=POST>
-<table border=1><tr>
-<input type=hidden name=action value=updcust>
-<td>CustID:</td><td>$custid<input type=hidden name=custid value=$custid></td>
-<td>Name:</td><td><input name=name value="$name"></td></tr>
-<tr><td>Street:</td><td><input name=street value="$street"></td>
-<!-- <td>Street2:</td><td><input name=street2></td> -->
-<td>City:</td><td><input name=city value="$city"></td></tr>
-<tr><td>Province/State: (2-letter code)</td><td><input name=province value="$prov" length=2 size=2></td>
-<td>Country: (2-letter code)</td><td><input name=country value="$country" length=2 size=2></td></tr>
-<tr><td>Postal/ZIP Code:</td><td><input name=pocode value="$pocode"></td>
-<td>Phone:</td><td><input name=phone value="$pocode"></td></tr>
-<!-- <td>Default rDNS:</td><td><input name=def_rdns></td></tr>
-<td>Description:</td><td><input name=description></td> -->
-<tr><td>Contacts/ARIN Handles:</td><td>
- Tech: <input name=tech_handle value="$tech"><br>
- Abuse: <input name=abuse_handle value="$abuse"><br>
- Admin: <input name=admin_handle value="$admin"><br>
-Note:  Only tech is required at the moment.
-</td>
-<td>"Special":</td><td><textarea name=special rows=4 cols=50>$special</textarea></td>
-</tr>
-<tr><td colspan=4 align=center><input type=submit value="Update"></td></tr>
-</form></table>
-<div style="margin-left:5px">
-<h3>Explanation for "Special" field:</h3>
-This is a temporary place to define the WHOIS "net name" for a block.
-It may be removed later, more likely migrated elsewhere.
-<p>It's formatted like this, one line for each custom net name:
-<pre>NetName[CIDR block]: NET-NAME</pre>
-Example:
-<pre>NetName192.168.236.0/24: MEGAWIDGET-1</pre>
-Note:
-<ul style="margin-top: 0px;">
-<li>Spacing is important - there should only be ONE space, in between the colon and the net name.
-<li>The CIDR block name nust include all four octets - no short forms are accepted.
-<li>Net names must be all uppercase, and consist only of A-Z, 0-9, and - (same as for SWIPed net names).
-</ul>
-</div>
-);
+
+  $page->param(
+	custid => $custid,
+	name => $name,
+	street => $street,
+	city => $city,
+	prov => $prov,
+	country => $country,
+	pocode => $pocode,
+	phone => $phone,
+	tech => $tech,
+	abuse => $abuse,
+	admin => $admin,
+	special => $special
+	);
 
 } elsif ($webvar{action} eq 'updcust') {
+
   $sth = $ip_dbh->prepare("UPDATE customers SET".
 	" name=?, street=?, city=?, province=?, country=?, pocode=?,".
@@ -384,98 +329,80 @@
 	$webvar{country}, $webvar{pocode}, $webvar{phone}, $webvar{tech_handle}, 
 	$webvar{abuse_handle}, $webvar{admin_handle}, $webvar{special}, $webvar{custid});
-  print "Updated $webvar{custid}<br>\n".
-	qq(<table border=1>
-<tr><td>CustID:</td><td>$webvar{custid}</td></tr>
-<tr><td>Name:</td><td>$webvar{name}</td></tr>
-<tr><td>Street:</td><td>$webvar{street}</td></tr>
-<tr><td>City:</td><td>$webvar{city}</td></tr>
-<tr><td>Province/State:</td><td>$webvar{province}</td></tr>
-<tr><td>Country:</td><td>$webvar{country}</td></tr>
-<tr><td>Postal/ZIP Code:</td><td>$webvar{pocode}</td></tr>
-<tr><td>Phone:</td><td>$webvar{phone}</td></tr>
-<!-- <td>Default rDNS:</td><td>$webvar{def_rdns}</td></tr> -->
-<tr><td>Contacts/ARIN Handles:</td><td>
- Tech: $webvar{tech_handle}<br>
- Abuse: $webvar{abuse_handle}<br>
- Admin: $webvar{admin_handle}<br>
-</td></tr>
-<tr><td>"Special":</td><td><pre>$webvar{special}</pre></td></tr>
-</table>
-<a href="admin.cgi?action=listcust">Back</a> to rWHOIS customer list<br>\n);
+  $page->param(
+	custid => $webvar{custid},
+	name => $webvar{name},
+	street => $webvar{street},
+	city => $webvar{city},
+	prov => $webvar{province},
+	country => $webvar{country},
+	pocode => $webvar{pocode},
+	phone => $webvar{phone},
+	tech => $webvar{tech_handle},
+	abuse => $webvar{abuse_handle},
+	admin => $webvar{admin_handle},
+	special => $webvar{special}
+	);
 
 } elsif ($webvar{action} eq 'showpools') {
-  print "IP Pools currently allocated:\n".
-	"<table border=1>\n<tr><td>Pool</td><td># of free IPs</td></tr>\n";
-  $sth = $ip_dbh->prepare("select cidr from allocations where type like '%p' or type like '%d' order by cidr");
-  $sth->execute;
-  my %poolfree;
-  while (my @data = $sth->fetchrow_array) {
-    $poolfree{$data[0]} = 0;
-  }
-  $sth = $ip_dbh->prepare("select pool,ip from poolips where available='y' order by ip");
-  $sth->execute;
-  while (my @data = $sth->fetchrow_array) {
-    $poolfree{$data[0]}++;
-  }
-  foreach my $key (keys %poolfree) {
-    print qq(<tr><td><a href="admin.cgi?action=tweakpool&pool=$key">$key</a></td>).
-	"<td>$poolfree{$key}</td></tr>\n";
-  }
-  print "</table>\n";
+
+  $sth = $ip_dbh->prepare("select pool, count(*) from poolips where available='y' group by pool order by pool");
+  $sth->execute;
+  my @poollist;
+  while (my ($pool,$free) = $sth->fetchrow_array) {
+    my %row = (
+	pool => $pool,
+	free => $free
+	);
+    push @poollist, \%row;
+  }
+  $page->param(poollist => \@poollist);
+
 } elsif ($webvar{action} eq 'tweakpool') {
+
   showPool($webvar{pool});
+
 } elsif ($webvar{action} eq 'updatepool') {
 
   $sth = $ip_dbh->prepare("update poolips set custid='$webvar{custid}', ".
-	"city='$webvar{city}', type='$webvar{type}', available='".
+	"city=?, type='$webvar{type}', available='".
 	(($webvar{available} eq 'y') ? 'y' : 'n').
-	"', notes='$webvar{notes}', description='$webvar{desc}' ".
+	"', notes=?, description=? ".
 	"where ip='$webvar{ip}'");
-  $sth->execute;
+  $sth->execute($webvar{city},$webvar{notes},$webvar{desc});
+  $page->param(ip => $webvar{ip});
   if ($sth->err) {
-    print "Error updating pool IP $webvar{ip}: $@<hr>\n";
-    syslog "err", "$authuser could not update pool IP $webvar{ip}: $@";
-  } else {  
-    $sth = $ip_dbh->prepare("select pool from poolips where ip='$webvar{ip}'");
-    $sth->execute;
-    my @data = $sth->fetchrow_array;
-    print "$webvar{ip} in $data[0] updated\n<hr>\n";
+    $page->param(errmsg => $sth->errstr);
+    syslog "err", "$authuser could not update pool IP $webvar{ip}: ".$sth->errstr;
+  } else {
     syslog "notice", "$authuser updated pool IP $webvar{ip}";
   }
+  $sth = $ip_dbh->prepare("select pool from poolips where ip='$webvar{ip}'");
+  $sth->execute;
+  my @data = $sth->fetchrow_array;
+  $page->param(pool => $data[0]);
+
 } elsif ($webvar{action} eq 'showusers') {
-  print "Notes:<br>\n".
-	"<li>Admin users automatically get all other priviledges.\n".
-	"<li>Everyone has basic read access.\n".
-	"<hr>Add new user:<form action=admin.cgi method=POST>\n".
-	"Username: <input name=username><br>\n".
-	"Password: <input name=password> <input type=checkbox name=preenc>Password is pre-encrypted (MUST be crypt() encrypted)<br>\n".
-	"<input type=submit value='Add user'><input type=hidden name=action value=newuser></form>\n";
-
-  print "<hr>Users with access:\n<table border=1>\n";
-  print "<tr><td></td><td align=center colspan=3>General access</td></tr>\n";
-  print "<tr><td>Username</td><td>Add new</td><td>Change</td>".
-	"<td>Delete</td><td>Systems/Networking</td><td>Admin user</td></tr>\n".
-	"<form action=admin.cgi method=POST>\n";
+
   $sth = $ip_dbh->prepare("select username,acl from users order by username");
   $sth->execute;
-  while (my @data = $sth->fetchrow_array) {
-    print "<form action=admin.cgi method=POST><input type=hidden name=action value=updacl>".
-	qq(<tr><td>$data[0]<input type=hidden name=username value="$data[0]"></td><td>).
-    # Now for the fun bit.  We have to pull apart the ACL field and
-    # output a bunch of checkboxes.
-    	"<input type=checkbox name=add".($data[1] =~ /a/ ? ' checked=y' : '').
-	"></td><td><input type=checkbox name=change".($data[1] =~ /c/ ? ' checked=y' : '').
-	"></td><td><input type=checkbox name=del".($data[1] =~ /d/ ? ' checked=y' : '').
-	"></td><td><input type=checkbox name=sysnet".($data[1] =~ /s/ ? ' checked=y' : '').
-	"></td><td><input type=checkbox name=admin".($data[1] =~ /A/ ? ' checked=y' : '').
-	qq(></td><td><input type=submit value="Update"></td></form>\n).
-	"<form action=admin.cgi method=POST><td><input type=hidden name=action value=deluser>".
-	"<input type=hidden name=username value=$data[0]>".
-	qq(<input type=submit value="Delete user"></tr></form>\n);
-
-  }
-  print "</table>\n";
+  my @userlist;
+  while (my ($username,$acl) = $sth->fetchrow_array) {
+##fixme: funky things happening with HTML::Template here;  shouldn't need the "logic ? iftrue : iffalse" structure
+    my %row = (
+	username => $username,
+	can_add => ($acl =~ /a/ ? 1 : 0),
+	can_change => ($acl =~ /c/ ? 1 : 0),
+	can_del => ($acl =~ /d/ ? 1 : 0),
+	sysnet => ($acl =~ /s/ ? 1 : 0),
+	is_admin => ($acl =~ /A/ ? 1 : 0),
+	acl => $acl
+	);
+    push @userlist, \%row;
+  }
+  $page->param(userlist => \@userlist);
+
 } elsif ($webvar{action} eq 'updacl') {
-  print "Updating ACL for $webvar{username}:<br>\n";
+
+  $page->param(username => $webvar{username});
   my $acl = 'b';
   if ($webvar{admin} eq 'on') {
@@ -487,14 +414,13 @@
 	($webvar{sysnet} eq 'on' ? 's' : '');
   }
-  print "New ACL: $acl<br>\n";
+  $page->param(acl => $acl);
 
   $sth = $ip_dbh->prepare("update users set acl='$acl' where username='$webvar{username}'");
   $sth->execute;
-  print "OK\n" if !$sth->err;
-
-  print qq(<hr><a href="admin.cgi?action=showusers">Back</a> to user listing\n);
+  $page->param(errmsg => $sth->errstr) if $sth->err;
 
 } elsif ($webvar{action} eq 'newuser') {
-  print "Adding user $webvar{username}...\n";
+
+  $page->param(username => $webvar{username});
   my $cr_pass = ($webvar{preenc} ? $webvar{password} :
 	crypt $webvar{password}, join('',('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64]));
@@ -502,70 +428,29 @@
 	"('$webvar{username}','$cr_pass','b')");
   $sth->execute;
-  if ($sth->err) {
-    print "<br>Error adding user: ".$sth->errstr;
-  } else {
-    print "OK\n";
-  }
-
-  print qq(<hr><a href="admin.cgi?action=showusers">Back</a> to user listing\n);
+  $page->param(errmsg => $sth->errstr) if $sth->err;
 
 } elsif ($webvar{action} eq 'deluser') {
-  print "Deleting user $webvar{username}.<br>\n";
+
+  $page->param(username => $webvar{username});
   $sth = $ip_dbh->prepare("delete from users where username='$webvar{username}'");
   $sth->execute;
-  print "OK\n" if !$sth->err;
-
-  print qq(<hr><a href="admin.cgi?action=showusers">Back</a> to user listing\n);
+  $page->param(errmsg => $sth->errstr) if $sth->err;
 
 } elsif ($webvar{action} eq 'emailnotice') {
-  print "<h4>Email notice management:</h4>\nClick the email addresses to edit that list.";
+
   $sth = $ip_dbh->prepare("SELECT action,reciplist FROM notify");
   $sth->execute;
-
-  print "<table border=1>\n";
+  my @spamlist;
   while (my ($notice_code,$reciplist) = $sth->fetchrow_array() ) {
 ##fixme: hairy mess, only a few things call mailNotify() anyway, so many possible notices won't work.
     my $action_out = dispNoticeCode($notice_code);
-    print "<tr><td>$action_out</td>".
-	qq(<td><a href="admin.cgi?action=ednotice&code=$notice_code">$reciplist</a></td>).
-	qq(<td><a href="admin.cgi?action=delnotice&code=$notice_code">Delete</a></tr>\n);
-  }
-  print qq(<tr><td colspan=2>Known "special" codes:<br>
-<ul style="margin-top: 0px; margin-bottom: 0px;">
-	<li>swi: Notify if block being updated has SWIP flag set</li>
-</ul></td></tr>
-</table>
-);
-
-# add new entries from this tangle:
-  print "<h4>Add new notification:</h4>\n".
-	"Note:  Failure notices on most conditions are not yet supported.\n";
-
-  print qq(<table border=1><form action=admin.cgi method="POST">
-<input type=hidden name=action value=addnotice>
-<tr>
-<td>Recipients</td><td colspan=3><textarea name=reciplist cols=50 rows=5></textarea></td></tr>
-<tr><td>Action</td><td>
-	<table><tr>
-		<td><input type=radio name=msgaction value=a>Add &nbsp;
-		<input type=radio name=msgaction value=u>Update &nbsp;
-		<input type=radio name=msgaction value=d>Delete &nbsp;
-		<input type=radio name=msgaction value=n>New listitem</td>
-	</tr><tr>
-		<td>
-		<input type=radio name=msgaction value=s:>Special: <input name=special>(requires code changes)
-	</td></tr></table>
-</td>
-<td>Failure?</td><td><input type=checkbox name=onfail></td></tr>
-<tr><td>Event/Allocation type:</td><td colspan=3>
-	<table>
-	<tr>
-		<td><input type=radio name=alloctype value=a>All allocations</td>
-		<td><input type=radio name=alloctype value=.i>All static IPs</td>
-		<td><input type=radio name=alloctype value=ci>New city</td>
-		<td><input type=radio name=alloctype value=no>New node</td>
-	</tr>
-	<tr>
-);
+    my %row = (
+	action => $action_out,
+	code => $notice_code,
+	recips => $reciplist
+	);
+    push @spamlist, \%row;
+  }
+  $page->param(spamlist => \@spamlist);
 
   $sth = $ip_dbh->prepare("SELECT type,dispname FROM alloctypes WHERE listorder < 500 ".
@@ -573,75 +458,77 @@
   $sth->execute;
   my $i=0;
+  my @typelist;
   while (my ($type,$disp) = $sth->fetchrow_array) {
-    print "		<td><input type=radio name=alloctype value=$type>$disp</td>";
-    $i++;
-    print "	</tr>\n\t<tr>"
-	if ($i % 4 == 0);
-  }
-
-  print qq(	</tr>
-	</table>
-</tr>
-<tr><td colspan=4 align=center><input type=submit value="Add notice"></td></tr>
-</table>
-</form>
-);
-  ## done spitting out add-new-spam-me-now table
+    my %row = (
+	type => $type,
+	disptype => $disp,
+# ahh, off-by-one counts, how we do love thee...  NOT!
+	newrow => ($i+2 > $sth->rows ? 1 : (++$i % 4)),
+	);
+    push @typelist, \%row;
+  }
+  $page->param(typelist => \@typelist);
 
 } elsif ($webvar{action} eq 'addnotice') {
+
   $webvar{alloctype} = $webvar{special} if $webvar{msgaction} eq 's:';
   if ($webvar{msgaction} && $webvar{alloctype} && $webvar{reciplist}) {
+    $page->param(cantry => 1);
     $webvar{reciplist} =~ s/[\r\n]+/,/g;
     $webvar{msgaction} = "f:$webvar{msgaction}" if $webvar{onfail};
-    print "Adding notice to $webvar{reciplist} for ".dispNoticeCode($webvar{msgaction}.$webvar{alloctype}).":\n";
+    $page->param(reciplist => $webvar{reciplist});
+    $page->param(dispnotice => dispNoticeCode($webvar{msgaction}.$webvar{alloctype}));
     $sth = $ip_dbh->prepare("INSERT INTO notify (action, reciplist) VALUES (?,?)");
 ##fixme:  automagically merge reciplists iff action already exists
     $sth->execute($webvar{msgaction}.$webvar{alloctype}, $webvar{reciplist});
-    if ($sth->err) {
-      print "Failed:  DB error: ".$sth->errstr."\n";
-    } else {
-      print "OK!<br>\n"
-    }
-  } else {
-    print "Need to specify at least one recipient, an action, and an allocation type. ".
-	qq{("Special" content is considered an allocation type).  Hit the Back button and try again.<br>\n};
-  }
-  print qq(<a href="admin.cgi?action=emailnotice">Back to email notice list</a>\n);
+    $page->param(addfailed => $sth->errstr) if $sth->err;
+  }
 
 } elsif ($webvar{action} eq 'delnotice') {
-  print "Deleting notices on ".dispNoticeCode($webvar{code}.$webvar{alloctype}).":\n";
+
+  $page->param(dispnotice => dispNoticeCode($webvar{code}.$webvar{alloctype}));
   $sth = $ip_dbh->prepare("DELETE FROM notify WHERE action=?");
   $sth->execute($webvar{code});
-  if ($sth->err) {
-    print "Failed:  DB error: ".$sth->errstr."\n";
-  } else {
-    print "OK!<br>\n"
-  }
-  print qq(<a href="admin.cgi?action=emailnotice">Back to email notice list</a>\n);
+  $page->param(delfailed => $sth->errstr) if $sth->err;
 
 } elsif ($webvar{action} eq 'ednotice') {
-  print "<h4>Editing recipient list for '".dispNoticeCode($webvar{code})."':</h4>\n";
+
+  $page->param(dispnotice => dispNoticeCode($webvar{code}));
+  $page->param(code => $webvar{code});
   $sth = $ip_dbh->prepare("SELECT reciplist FROM notify WHERE action=?");
   $sth->execute($webvar{code});
   my ($reciplist) = $sth->fetchrow_array;
   $reciplist =~ s/,/\n/g;
-  print qq(<form action=admin.cgi method=POST><input type=hidden name=code value="$webvar{code}">\n).
-	qq(<input type=hidden name=action value="updnotice"><table border=1><tr><td>).
-	qq(<textarea cols="40" rows="5" name=reciplist>$reciplist</textarea></td><td><input type=submit value="Update">\n).
-	"</td></tr></table></form>\n";
+  $page->param(reciplist => $reciplist);
+
 } elsif ($webvar{action} eq 'updnotice') {
-  print "<h4>Updating recipient list for '".dispNoticeCode($webvar{code})."':</h4>\n";
+
+  $page->param(dispnotice => dispNoticeCode($webvar{code}));
   $sth = $ip_dbh->prepare("UPDATE notify SET reciplist=? WHERE action=?");
   $webvar{reciplist} =~ s/[\r\n]+/,/g;
   $sth->execute($webvar{reciplist}, $webvar{code});
-  if ($sth->err) {
-    print "Failed:  DB error: ".$sth->errstr."\n";
-  } else {
-    print "OK!<br>\n"
-  }
-  print qq(<a href="admin.cgi?action=emailnotice">Back to email notice list</a>\n);
+  $page->param(updfailed => $sth->errstr) if $sth->err;
+
 } elsif ($webvar{action} ne '<NULL>') {
-  print "webvar{action} check failed: Don't know how to $webvar{action}";
-}
+  $page->param(dunno => $webvar{action});
+}
+
+ERRJUMP: print "Content-type: text/html\n\n".$header->output;
+print $page->output;
+
+##fixme:  make me a footer param!
+print qq(<hr><div><a href="$IPDB::webpath/">Back</a> to main interface</div>\n);
+
+# We print the footer here, so we don't have to do it elsewhere.
+my $footer = HTML::Template->new(filename => "footer.tmpl");
+# we're already in the admin tools, no need to provide a bottom link.  maybe.
+#$footer->param(adminlink => ($IPDBacl{$authuser} =~ /A/));
+
+print $footer->output;
+
+$ip_dbh->disconnect;
+
+exit;
+
 
 # Hokay.  This is a little different.  We have a few specific functions here:
@@ -649,12 +536,4 @@
 #  -> Tweak individual DB fields
 #
-
-print qq(<hr><a href="/ip/">Back</a> to main interface</a>\n);
-
-printFooter;
-
-$ip_dbh->disconnect;
-
-exit;
 
 
@@ -671,84 +550,55 @@
 
 
-# List free blocks in a /24 for arbitrary manual allocation
-sub showfree($) {
-  my $cidr = new NetAddr::IP $_[0];
-  print "Showing free blocks in $cidr<br>\n".
-	"<table border=1>\n";
-  $sth = $ip_dbh->prepare("select * from freeblocks where cidr <<= '$cidr' order by cidr");
-  $sth->execute;
-  while (my @data = $sth->fetchrow_array) {
-    my $temp = new NetAddr::IP $data[0];
-    print "<tr><form action=admin.cgi method=POST><input type=hidden name=action value=assign>\n".
-	qq(<td>$temp<input type=hidden name=allocfrom value="$temp"></td>\n).
-	"<td>".
-	(($temp->masklen == 30) ? '<input type=hidden name=masklen value=30>30'
-	  : "<select name=masklen><option>30</option>\n<option>29</option>\n") .
-	(($temp->masklen < 29) ? "<option>28</option>\n" : '') .
-	(($temp->masklen < 28) ? "<option>27</option>\n" : '') .
-	(($temp->masklen < 27) ? "<option>26</option>\n" : '') .
-	(($temp->masklen < 26) ? "<option>25</option>\n" : '') .
-	(($temp->masklen < 25) ? "<option>24</option>\n" : '') .
-	"</td>".
-	qq(<td>$data[2]</td><td><input type=submit value="Allocate from here"></td>).
-	"\n</form></tr>\n";
-  }
-  print "</table>\n";
-}
-
-
 # Show allocations to allow editing.
-sub showAllocs($) {
-  my $cidr = new NetAddr::IP $_[0];
-  print "Edit custID, allocation type, city for allocations in ".
-	"$cidr:\n<table border=1>";
-  $sth = $ip_dbh->prepare("select * from allocations where cidr <<= '$cidr' order by cidr");
-  $sth->execute;
-  while (my @data = $sth->fetchrow_array) {
-    print "<tr><form action=admin.cgi method=POST><input type=hidden name=action value=update>\n".
-	qq(<td>$data[0]<input type=hidden value="$data[0]" name=block></td>\n).
-	qq(<td><input name=custid value="$data[1]"></td>\n).
-	"<td><select name=alloctype>";
-
+sub showAllocs {
+
+  my $within = new NetAddr::IP $_[0];
+  $page->param(within => $within);
+
+  $sth = $ip_dbh->prepare("select cidr,custid,type,city,description from allocations where cidr <<= '$within' order by cidr");
+  $sth->execute;
+  my @blocklist;
+  while (my ($cidr,$custid,$type,$city,$desc) = $sth->fetchrow_array) {
+    my %row = (
+	cidr => $cidr,
+	custid => $custid,
+	city => $city,
+	desc => $desc,
+	);
+
+##fixme:  don't wanna retrieve the whole type list *every time around the outer loop*
     my $sth2 = $ip_dbh->prepare("select type,listname from alloctypes".
 	" where listorder < 500 and not (type like '_i') order by listorder");
     $sth2->execute;
-    while (my @types = $sth2->fetchrow_array) {
-      print "<option". (($data[2] eq $types[0]) ? ' selected' : '') .
-	" value='$types[0]'>$types[1]</option>\n";
+    my @typelist;
+    while (my ($listtype,$dispname) = $sth2->fetchrow_array) {
+      my %subrow = (
+	type => $listtype,
+	dispname => $dispname,
+	selected => ($listtype eq $type)
+	);
+      push @typelist, \%subrow;
     }
-
-    print qq(<td><input name=city value="$data[3]"></td>\n).
-	"<td>$data[4]</td><td>$data[5]</td>".
-	qq(<td><input type=submit value="Update"></td></form></tr>\n);
-  }
-  print "</table>\n";
-
-  # notes
-  print "<hr><b>Notes:</b>\n".
-	"<ul>\n<li>Use the main interface to update description and notes fields\n".
-	"<li>Changing the allocation type here will NOT affect IP pool data.\n".
-	"</ul>\n";
-}
+    $row{typelist} = \@typelist;
+    push @blocklist, \%row;
+  }
+  $page->param(blocklist => \@blocklist);
+} # end showAllocs()
 
 
 # Stuff updates into DB
 sub update {
-  eval {
-    # Relatively simple SQL transaction here.  Note that we're deliberately NOT
-    # updating notes/desc here as it's available through the main interface.
-    $sth = $ip_dbh->prepare("update allocations set custid='$webvar{custid}',".
-	"city='$webvar{city}',type='$webvar{alloctype}' where cidr='$webvar{block}'");
-    $sth->execute;
-    $ip_dbh->commit;
-  };
-  if ($@) {
-    carp "Transaction aborted because $@";
-    eval { $ip_dbh->rollback; };
-    syslog "err", "$authuser could not update block '$webvar{block}': '$@'";
+  # Relatively simple SQL transaction here.  Note that we're deliberately NOT
+  # updating notes/desc here as it's available through the main interface.
+  $sth = $ip_dbh->prepare("update allocations set custid='$webvar{custid}',".
+	"city=?,type='$webvar{alloctype}' where cidr='$webvar{block}'");
+  $sth->execute($webvar{city});
+
+  $page->param(block => $webvar{block});
+  if ($sth->err) {
+    $page->param(updfailed => $sth->errstr);
+    syslog "err", "$authuser could not update block '$webvar{block}': '".$sth->errstr."'";
   } else {
-    # If we get here, the operation succeeded.
     syslog "notice", "$authuser updated $webvar{block}";
-    print "Allocation $webvar{block} updated<hr>\n";
   }
   # need to get /24 that block is part of
@@ -756,5 +606,5 @@
   $bits[3] = "0/24";
   showAllocs((join ".", @bits));
-}
+} # end update()
 
 
@@ -764,33 +614,34 @@
 sub showPool($) {
   my $pool = new NetAddr::IP $_[0];
-  print qq(Listing pool $pool:\n<table border=1>
-<form action=admin.cgi method=POST>
-<input type=hidden name=action value=updatepool>
-<tr><td align=right>Customer ID:</td><td><input name=custid></td></tr>
-<tr><td align=right>Customer location:</td><td><input name=city></td></tr>
-<tr><td align=right>Type:</td><td><select name=type><option selected>-</option>\n);
 
   $sth = $ip_dbh->prepare("select type,listname from alloctypes where type like '_i' order by listorder");
   $sth->execute;
-  while (my @data = $sth->fetchrow_array) {
-    print "<option value='$data[0]'>$data[1]</option>\n";
-  }
-
-  print qq(</select></td></tr>
-<tr><td align=right>Available?</td><td><input type=checkbox value=y></td></tr>
-<tr><td align=right>Description/name:</td><td><input name=desc size=40></td></tr>
-<tr><td align=right>Notes:</td><td><textarea name=notes rows=3 cols=40></textarea></td></tr>
-<tr><td colspan=2 align=center><input type=submit value="Update"></td></tr>
-).
-	"</table>Update the following record:<table border=1>\n";
-  $sth = $ip_dbh->prepare("select pool,ip,custid,city,type,available,description,notes from poolips where pool='$pool' order by ip");
-  $sth->execute;
-  while (my @data = $sth->fetchrow_array) {
-    print qq(<tr><td><input type=radio name=ip value="$data[1]">$data[1]</td>).
-	"<td>$data[2]</td><td>$data[3]</td><td>$data[4]</td>".
-	"<td>$data[5]</td><td>$data[6]</td><td>$data[7]</td></tr>\n";
-  }
-  print "</form></table>\n";
-}
+  my @typelist;
+  while (my ($type,$dispname) = $sth->fetchrow_array) {
+    my %row = (
+	type => $type,
+	dispname => $dispname
+	);
+    push @typelist, \%row;
+  }
+  $page->param(typelist => \@typelist);
+
+  $sth = $ip_dbh->prepare("select ip,custid,city,type,available,description,notes from poolips where pool='$pool' order by ip");
+  $sth->execute;
+  my @iplist;
+  while (my ($ip,$custid,$city,$type,$avail,$desc,$notes) = $sth->fetchrow_array) {
+    my %row = (
+	ip => $ip,
+	custid => $custid,
+	city => $city,
+	type => $type,
+	avail => $avail,
+	desc => $desc,
+	notes => $notes
+	);
+    push @iplist, \%row;
+  }
+  $page->param(iplist => \@iplist);
+} # end showPool()
 
 
