Index: /branches/dns/addmaster.html
===================================================================
--- /branches/dns/addmaster.html	(revision 262)
+++ /branches/dns/addmaster.html	(revision 262)
@@ -0,0 +1,11 @@
+<div class="indent">
+<div class="heading">Add new master IP block</div><br>
+<table class="regular" bgcolor="black" cellspacing="1" cellpadding="1">
+<form method="POST" action="/ip/cgi-bin/main.cgi" class="regular">
+<tr class="color1"><td>Master block to add: (CIDR)&nbsp</td><td><input type=text name=cidr></td></tr> <tr class="color2">
+<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
+<input type="hidden" name="action" value="newmaster">
+</form>
+</tr>
+</table>
+</div>
Index: anches/dns/addmaster.shtml
===================================================================
--- /branches/dns/addmaster.shtml	(revision 261)
+++ 	(revision )
@@ -1,13 +1,0 @@
-<!--#include virtual="header.inc" -->
-<div class="indent">
-<div class="heading">Add new master IP block</div><br>
-<table class="regular" bgcolor="black" cellspacing="1" cellpadding="1">
-<form method="POST" action="/ip/cgi-bin/main.cgi" class="regular">
-<tr class="color1"><td>Master block to add: (CIDR)&nbsp</td><td><input type=text name=cidr></td></tr> <tr class="color2">
-<td class="center" colspan="2"><input type="submit" value="  Assign  "></td>
-<input type="hidden" name="action" value="newmaster">
-</form>
-</tr>
-</table>
-</div>
-<!--#include virtual="footer.inc" -->
Index: /branches/dns/alloctypes.html
===================================================================
--- /branches/dns/alloctypes.html	(revision 261)
+++ /branches/dns/alloctypes.html	(revision 262)
@@ -14,5 +14,5 @@
 <tr class="color1">
 <td>Customer netblock (default)</td><td>A direct allocation /30 or larger to a
-customer.</td>
+customer.  Note that this does NOT include RADIUS-routed "netblocks".</td>
 </tr>
 
@@ -65,10 +65,25 @@
 </tr><tr class="color1">
 <td>Dynamic cable block</td><td>Netblock for (mostly residential) DHCP cable.</td>
+</tr><tr class="color2">
+<td>Dynamic WiFi block</td><td>Netblock for (mostly residential) (mostly) PPPoE wireless.</td>
 
+</tr><tr class="color1">
+<td>Reserve for WAN blocks</td><td>Reserve a chunk of IP space for core routers/etc.</td>
 </tr><tr class="color2">
+<td>Reserve for dynamic-route DSL netblocks</td><td>Reserve a chunk of IP space for
+RADIUS-assigned "netblocks" from a PPPoE pool.  Note that this is similar to the static DSL
+pool, but IP space is to be allocated as /30 and larger netblocks, not single static IPs.</td>
+</tr><tr class="color1">
+<td>WAN block</td><td>Individual netblock assignment for a core router.  Always taken from a
+block which has previously been reserved for such assignments.</td>
+</tr><tr class="color2">
+<td>Dynamic-route DSL netblock</td><td>Customer assignment for a RADIUS-assigned PPPoE
+"netblock".</td>
+
+</tr><tr class="color1">
 <td>Routing</td><td>Blocks not actually assigned to a service on their own, but which
 have been routed to individual POPs- typically /24, /23, /22</td>
 
-</tr><tr class="color1">
+</tr><tr class="color2">
 <td>Master block</td><td>Our allocations from ARIN</td>
 </tr>
Index: /branches/dns/cgi-bin/CommonWeb.pm
===================================================================
--- /branches/dns/cgi-bin/CommonWeb.pm	(revision 261)
+++ /branches/dns/cgi-bin/CommonWeb.pm	(revision 262)
@@ -53,15 +53,24 @@
 }
 
-sub printHeader($) #(cgiurl)
-{
-	my $cgiURL = $_[0];
-	print "Content-type: text/html\n\n";
-	open(FILE, "../header.inc") || die $!;
-	while (<FILE>) 
-	{
-		$_ =~ s/\$\$CGIURL\$\$/$cgiURL/g;
-		print $_;
-	}
-	close(FILE);
+
+sub printHeader {
+  my $title = shift;
+  print "Content-type: text/html\n\n";
+# This doesn't work well.  Must investigate.
+#  my $realm = shift;
+#  print qq(WWW-Authenticate: Basic realm="$realm"\n) if $realm;
+  open FILE, "../header.inc"
+	or carp $!;
+  my $html = join('',<FILE>);
+  close FILE;
+
+  $html =~ s/\$\$TITLE\$\$/$title/;
+# Necessary for mangling arbitrary bits of the header
+  my $i=0;
+  while (defined(my $param = shift)) {
+    $html =~ s/\$\$EXTRA$i\$\$/$param/g;
+    $i++;
+  }
+  print $html;
 }
 
Index: /branches/dns/cgi-bin/IPDB.pm
===================================================================
--- /branches/dns/cgi-bin/IPDB.pm	(revision 261)
+++ /branches/dns/cgi-bin/IPDB.pm	(revision 262)
@@ -23,5 +23,5 @@
 @EXPORT_OK    = qw(
 	%disp_alloctypes %list_alloctypes %def_custids @citylist @poplist @masterblocks
-	%allocated %free %routed %bigfree
+	%allocated %free %routed %bigfree %IPDBacl
 	&initIPDBGlobals &connectDB &finish &checkDBSanity &allocateBlock &deleteBlock
 	&mailNotify
@@ -31,5 +31,5 @@
 %EXPORT_TAGS	= ( ALL => [qw(
 		%disp_alloctypes %list_alloctypes %def_custids @citylist @poplist
-		@masterblocks %allocated %free %routed %bigfree
+		@masterblocks %allocated %free %routed %bigfree %IPDBacl
 		&initIPDBGlobals &connectDB &finish &checkDBSanity &allocateBlock
 		&deleteBlock &mailNotify
@@ -50,4 +50,5 @@
 our %routed;
 our %bigfree;
+our %IPDBacl;
 
 # Let's initialize the globals.
@@ -83,4 +84,5 @@
   $sth = $dbh->prepare("select cidr from masterblocks order by cidr");
   $sth->execute;
+  return (undef,$sth->errstr) if $sth->err;
   for (my $i=0; my @data = $sth->fetchrow_array(); $i++) {
     $masterblocks[$i] = new NetAddr::IP $data[0];
@@ -91,5 +93,12 @@
     $routed{"$masterblocks[$i]"} = 0;
   }
+
+  # Load ACL data.  Specific username checks are done at a different level.
+  $sth = $dbh->prepare("select username,acl from users");
+  $sth->execute;
   return (undef,$sth->errstr) if $sth->err;
+  while (my @data = $sth->fetchrow_array) {
+    $IPDBacl{$data[0]} = $data[1];
+  }
 
   return (1,"OK");
@@ -375,4 +384,5 @@
       }; # end eval
       if ($@) {
+	$msg .= ": ".$@;
 	eval { $dbh->rollback; };
 	return ('FAIL',$msg);
@@ -416,10 +426,14 @@
     my @poolip_list = $pool->hostenum;
     if ($class eq 'all') { # (DSL-ish block - *all* IPs available
-      $sth->execute($pool->addr);
+      if ($pool->addr !~ /\.0$/) {	# .0 causes weirdness.
+	$sth->execute($pool->addr);
+      }
       for (my $i=0; $i<=$#poolip_list; $i++) {
 	$sth->execute($poolip_list[$i]->addr);
       }
       $pool--;
-      $sth->execute($pool->addr);
+      if ($pool->addr !~ /\.255$/) {	# .255 can cause weirdness.
+	$sth->execute($pool->addr);
+      }
     } else { # (real netblock)
       for (my $i=1; $i<=$#poolip_list; $i++) {
Index: /branches/dns/cgi-bin/admin.cgi
===================================================================
--- /branches/dns/cgi-bin/admin.cgi	(revision 261)
+++ /branches/dns/cgi-bin/admin.cgi	(revision 262)
@@ -33,12 +33,4 @@
 }
 
-if ($authuser !~ /^(kdeugau|jodyh|jipp)$/) {
-  print "Content-Type: text/html\n\n".
-	"<html><head><title>Access denied</title></head><body>\n".
-	'Access to this tool is restricted.  Contact <a href="mailto:kdeugau@vianet.ca">Kris</a> '.
-	"for more information.</body></html>\n";
-  exit;
-}
-
 syslog "debug", "$authuser active";
 
@@ -53,4 +45,12 @@
 }
 initIPDBGlobals($ip_dbh);
+
+if ($IPDBacl{$authuser} !~ /A/) {
+  print "Content-Type: text/html\n\n".
+	"<html><head><title>Access denied</title></head><body>\n".
+	'Access to this tool is restricted.  Contact <a href="mailto:kdeugau@vianet.ca">Kris</a> '.
+	"for more information.</body></html>\n";
+  exit;
+}
 
 my %webvar = parse_post();
@@ -90,4 +90,6 @@
 </form>
 <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)
 );
 } else {
@@ -123,4 +125,11 @@
     printAndExit("Can't allocate from outside a free block!!\n")
         if !$data[0];
+  } elsif ($webvar{alloctype} =~ /^(.)i$/) {
+    $sth = $ip_dbh->prepare("select cidr from allocations where cidr >>='$cidr' and (type like '_d' or type like '_p')");
+    $sth->execute;
+    @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];
   } else {
     $sth = $ip_dbh->prepare("select cidr from freeblocks where cidr >>='$cidr' and not (routed='n')");
@@ -202,5 +211,5 @@
       print "Allocation failed!  IPDB::allocateBlock said:\n$msg\n";
       syslog "err", "($authuser) Allocation of '$webvar{cidr}' to '$webvar{custid}' as ".
-	"'$webvar{type}' failed: '$msg'";
+	"'$webvar{alloctype}' failed: '$msg'";
     }
   } # done city check
@@ -267,7 +276,77 @@
     syslog "notice", "$authuser updated pool IP $webvar{ip}";
   }
-#  showPool("$data[0]");
-#} else {
-#  print "webvar{action} check failed: $webvar{action}";
+} elsif ($webvar{action} eq 'showusers') {
+  print "Notes:<br>\n".
+	"<li>Admin users automatically get all other priviledges.\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>Username</td><td>Add new</td><td>Change</td>".
+	"<td>Delete</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=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";
+} elsif ($webvar{action} eq 'updacl') {
+  print "Updating ACL for $webvar{username}:<br>\n";
+  my $acl = 'b';
+  if ($webvar{admin} eq 'on') {
+    $acl .= "acdA";
+  } else {
+    $acl .= ($webvar{add} eq 'on' ? 'a' : '').
+	($webvar{change} eq 'on' ? 'c' : '').
+	($webvar{del} eq 'on' ? 'd' : '');
+  }
+  print "New ACL: $acl<br>\n";
+
+  $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);
+
+} elsif ($webvar{action} eq 'newuser') {
+  print "Adding user $webvar{username}...\n";
+  my $cr_pass = ($webvar{preenc} ? $webvar{password} :
+	crypt $webvar{password}, join('',('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64]));
+  $sth = $ip_dbh->prepare("insert into users (username,password,acl) values ".
+	"('$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);
+
+} elsif ($webvar{action} eq 'deluser') {
+  print "Deleting user $webvar{username}.<br>\n";
+  $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);
+
+} elsif ($webvar{action} ne '<NULL>') {
+  print "webvar{action} check failed: Don't know how to $webvar{action}";
 }
 
Index: /branches/dns/cgi-bin/extras/db2rwhois.pl
===================================================================
--- /branches/dns/cgi-bin/extras/db2rwhois.pl	(revision 261)
+++ /branches/dns/cgi-bin/extras/db2rwhois.pl	(revision 262)
@@ -17,4 +17,5 @@
 use NetAddr::IP;
 use MyIPDB;
+use Digest::MD5 qw(md5_hex);
 
 $ENV{"PATH"} = "/bin;/usr/bin";
@@ -41,12 +42,12 @@
 # Note that this ASS-U-MEs that we do not add master IP blocks-
 # there should probably be a separate system for doing that.
-my $sth = $dbh->prepare("select * from masterblocks;");
+my $sth = $dbh->prepare("select cidr,ctime from masterblocks;");
 $sth->execute;
 my $i=0;
 GETMASTERS: while (my @data = $sth->fetchrow_array()) {
 
-  if ($data[0] =~ /^(192.168.0.0|172.16.0.0|10.0.0.0)/) {
-    next GETMASTERS;
-  }
+#  if ($data[0] =~ /^(192.168.0.0|172.16.0.0|10.0.0.0)/) {
+#    next GETMASTERS;
+#  }
   $masterblocks[$i] = new NetAddr::IP $data[0];
 
@@ -55,4 +56,5 @@
   my $date;
   chomp ($date = `/bin/date +"%Y%m%d"`);
+  my ($ctime,undef) = split /\s+/, $data[1];
 
 # Whew!  Ugly little varmint.
@@ -73,5 +75,5 @@
 	"Admin-Contact: ISP-ARIN-HANDLE\n".
 	"Abuse-Contact: abuse\@example.com\n".
-	"Created: 20040308\n".
+	"Created: $ctime\n".
 	"Updated: $date\n".
 	"Updated-By: noc\@example.com\n";
@@ -87,8 +89,11 @@
 # Make sure to remove the private netblocks from this.
 # No use or point in broadcasting our use of them.
-$sth = $dbh->prepare("select * from allocations where not (cidr <<= '192.168.0.0/16') and not (cidr <<= '172.16.0.0/12') and not (cidr <<= '10.0.0.0/8') and custid='6750400'");
+#$sth = $dbh->prepare("select * from allocations where not (cidr <<= '192.168.0.0/16') and not (cidr <<= '172.16.0.0/12') and not (cidr <<= '10.0.0.0/8')");
+# cidr,custid,type,city,description,notes,maskbits,circuitid,createstamp,modifystamp,newcustid
+
+$sth = $dbh->prepare("select cidr,custid,type,city,description,createstamp,modifystamp from allocations");
 $sth->execute;
 
-while (my @data = $sth->fetchrow_array()) {
+while (my ($cidr,$custid,$type,$city,$description,$ctime,$mtime) = $sth->fetchrow_array()) {
 
 # We get master block info from @masterblocks.
@@ -105,6 +110,6 @@
  # Updated-By: noc@example.com
 
-  # Get the "full" network number
-  my $net = new NetAddr::IP $data[0];
+  # Get the "full" network number - just in case.
+  my $net = new NetAddr::IP $cidr;
 
 # Assumptions:  All data in ipdb is public
@@ -121,11 +126,6 @@
         or print "File open error: '$!' on '$rwhoisDataPath/$masterfilename'\n";
 
-# cidr custid type city description notes maskbits
-
-# Creation date in record to eventually be "correct";  near-term will
-# be master's creation date;  immediate is today's date.
-my $date;
-      chomp ($date = `/bin/date +"%Y%m%d"`);
 if ($data[4] =~ /^\s*$/) { $data[4] = 'Friendly ISP'; }
+print "$type-type $net for $custid, created $ctime, modified $mtime\n";
       print MASTERFILE "---\nID: NETBLK-ISP.$master\n".
   	"Auth-Area: $master\n".
@@ -133,10 +133,10 @@
 	"IP-Network: $net\n".
 	"IP-Network-Block: ".$net->range."\n".
-	"Organization: $data[4]\n".
+	"Organization: $description\n".
 #	"Tech-Contact: $data[9]\n".
 	"Tech-Contact: abuse\@example.com\n".
 	"Admin-Contact: ISP-ARIN-HANDLE\n".
-	"Created: $date\n".
-	"Updated: $date\n".
+	"Created: $ctime\n".
+	"Updated: $mtime\n".
 	"Updated-By: noc\@example.com\n";
     }
@@ -154,2 +154,5 @@
 
 $dbh->disconnect;
+
+# This doesn't work, with a vengeance.
+#print qx{ /usr/bin/rwhois_indexer -c /etc/rwhoisd/rwhoisd.conf -i -v -s txt };
Index: /branches/dns/cgi-bin/ipdb.psql
===================================================================
--- /branches/dns/cgi-bin/ipdb.psql	(revision 261)
+++ /branches/dns/cgi-bin/ipdb.psql	(revision 262)
@@ -23,5 +23,6 @@
 
 CREATE TABLE "masterblocks" (
-	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY
+	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
+	"ctime" timestamp DEFAULT now()
 );
 
@@ -33,5 +34,6 @@
 	"cidr" cidr DEFAULT '255.255.255.255/32' NOT NULL PRIMARY KEY,
 	"maskbits" integer DEFAULT 128,
-	"city" character varying(30) DEFAULT ''
+	"city" character varying(30) DEFAULT '',
+	"ctime" timestamp DEFAULT now()
 );
 
@@ -70,4 +72,6 @@
 	"circuitid" character varying(128) DEFAULT '' NOT NULL,
 	"newcustid" integer,
+	"createstamp" timestamp DEFAULT now(),
+	"modifystamp" timestamp DEFAULT now(),
 	CHECK (((available = 'y'::bpchar) OR (available = 'n'::bpchar)))
 );
@@ -86,4 +90,6 @@
 	"maskbits" integer DEFAULT 128,
 	"circuitid" character varying(128) DEFAULT '',
+	"createstamp" timestamp DEFAULT now(),
+	"modifystamp" timestamp DEFAULT now(),
 	"newcustid" integer
 );
@@ -112,5 +118,6 @@
 
 CREATE TABLE "cities" (
-	"city" character varying(30) DEFAULT '' NOT NULL PRIMARY KEY,
+	"id" serial NOT NULL PRIMARY KEY,
+	"city" character varying(30) DEFAULT '' NOT NULL,
 	"routing" character(1) DEFAULT 'n' NOT NULL
 );
@@ -119,15 +126,4 @@
 GRANT ALL on "cities" to "kdeugau";
 GRANT ALL on "cities" to "ipdb";
-
---
--- Selected TOC Entries:
---
-\connect - ipdb
-
---
--- TOC Entry ID 2 (OID 92809)
---
--- Name: alloctypes Type: TABLE Owner: ipdb
---
 
 CREATE TABLE "alloctypes" (
@@ -140,20 +136,7 @@
 );
 
---
--- TOC Entry ID 3 (OID 92809)
---
--- Name: alloctypes Type: ACL Owner: 
---
-
 REVOKE ALL on "alloctypes" from PUBLIC;
 GRANT ALL on "alloctypes" to "kdeugau";
 GRANT ALL on "alloctypes" to "ipdb";
-
---
--- Data for TOC Entry ID 4 (OID 92809)
---
--- Name: alloctypes Type: TABLE DATA Owner: ipdb
---
-
 
 COPY "alloctypes" FROM stdin;
@@ -172,6 +155,4 @@
 si	Static IP - Server pool	Server pool IP	20	6750400
 wc	Reserve for WAN blocks	WAN IP blocks	200	6750400
-wr	Internal WAN block	Internal WAN block	201	6750400
-pc	Reserve for dynamic-route DSL netblocks	Dynamic-route netblocks	202	6750400
 en	End-use netblock	End-use netblock	100	6750400
 me	Dialup netblock	Dialup netblock	101	DIAL-RES
@@ -180,4 +161,38 @@
 we	Dynamic WiFi block	Dynamic WiFi block	104	WL-RES
 rm	Routing	Routed netblock	500	6750400
-pr	Dynamic-route DSL netblock	Dynamic-route DSL	203	
+pc	Reserve for dynamic-route DSL netblocks	Dynamic-route netblocks	201	6750400
+pr	Dynamic-route DSL netblock	Dynamic-route DSL	221	
+wr	WAN block	WAN block	220	6750400
 \.
+
+--
+-- Trigger and matching function to update modifystamp on allocations, poolips
+--
+CREATE FUNCTION up_modtime () RETURNS OPAQUE AS '
+    BEGIN
+	NEW.modifystamp := ''now'';
+	RETURN NEW;
+    END;
+' LANGUAGE 'plpgsql';
+
+CREATE TRIGGER up_modtime BEFORE UPDATE ON allocations
+    FOR EACH ROW EXECUTE PROCEDURE up_modtime();
+
+CREATE TRIGGER up_modtime BEFORE UPDATE ON poolips
+    FOR EACH ROW EXECUTE PROCEDURE up_modtime();
+
+--
+-- User data table - required for proper ACLs
+--
+
+CREATE TABLE "users" (
+	"username" varchar(16) NOT NULL PRIMARY KEY,
+	"password" varchar(16) DEFAULT '',
+	"acl" varchar(16) DEFAULT 'b'
+);
+
+CREATE TABLE "dns" (
+	"ip" inet NOT NULL PRIMARY KEY,
+	"hostname" character varying(128),
+	"auto" character(1) DEFAULT 'y'
+);
Index: /branches/dns/cgi-bin/main.cgi
===================================================================
--- /branches/dns/cgi-bin/main.cgi	(revision 261)
+++ /branches/dns/cgi-bin/main.cgi	(revision 262)
@@ -22,5 +22,6 @@
 openlog "IPDB","pid","local2";
 
-# Collect the username from HTTP auth.  If undefined, we're in a test environment.
+# Collect the username from HTTP auth.  If undefined, we're in
+# a test environment, or called without a username.
 my $authuser;
 if (!defined($ENV{'REMOTE_USER'})) {
@@ -39,17 +40,15 @@
 ($ip_dbh,$errstr) = connectDB_My;
 if (!$ip_dbh) {
-  printAndExit("Database error: $errstr\n");
+  exitError("Database error: $errstr\n");
 }
 initIPDBGlobals($ip_dbh);
 
-#prototypes
-sub viewBy($$);		# feed it the category and query
-sub queryResults($$$);	# args is the sql, the page# and the rowCount
-# Needs rewrite/rename
-sub countRows($);	# returns first element of first row of passed SQL
-			# Only usage passes "select count(*) ..."
+# Headerize!  Make sure we replace the $$EXTRA0$$ bit as needed.
+printHeader('', ($IPDBacl{$authuser} =~ /a/ ?
+	'<td align=right><a href="/ip/cgi-bin/main.cgi?action=assign">Add new assignment</a>' : ''
+	));
+
 
 # Global variables
-my $RESULTS_PER_PAGE = 50;
 my %webvar = parse_post();
 cleanInput(\%webvar);
@@ -64,20 +63,30 @@
 if($webvar{action} eq 'index') {
   showSummary();
+} elsif ($webvar{action} eq 'addmaster') {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+  } else {
+    open HTML, "<../addmaster.html";
+    print while <HTML>;
+  }
 } elsif ($webvar{action} eq 'newmaster') {
-  printHeader('');
-
-  my $cidr = new NetAddr::IP $webvar{cidr};
-
-  print "<div type=heading align=center>Adding $cidr as master block....</div>\n";
-
-  # Allow transactions, and raise an exception on errors so we can catch it later.
-  # Use local to make sure these get "reset" properly on exiting this block
-  local $ip_dbh->{AutoCommit} = 0;
-  local $ip_dbh->{RaiseError} = 1;
-
-  # Wrap the SQL in a transaction
-  eval {
-    $sth = $ip_dbh->prepare("insert into masterblocks values ('$webvar{cidr}')");
-    $sth->execute;
+
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+  } else {
+
+    my $cidr = new NetAddr::IP $webvar{cidr};
+
+    print "<div type=heading align=center>Adding $cidr as master block....</div>\n";
+
+    # Allow transactions, and raise an exception on errors so we can catch it later.
+    # Use local to make sure these get "reset" properly on exiting this block
+    local $ip_dbh->{AutoCommit} = 0;
+    local $ip_dbh->{RaiseError} = 1;
+
+    # Wrap the SQL in a transaction
+    eval {
+      $sth = $ip_dbh->prepare("insert into masterblocks values ('$webvar{cidr}')");
+      $sth->execute;
 
 # Unrouted blocks aren't associated with a city (yet).  We don't rely on this
@@ -85,21 +94,23 @@
 # Thus the "routed" flag.
 
-    $sth = $ip_dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed)".
+      $sth = $ip_dbh->prepare("insert into freeblocks (cidr,maskbits,city,routed)".
 	" values ('$webvar{cidr}',".$cidr->masklen.",'<NULL>','n')");
-    $sth->execute;
-
-    # If we get here, everything is happy.  Commit changes.
-    $ip_dbh->commit;
-  }; # end eval
-
-  if ($@) {
-    carp "Transaction aborted because $@";
-    eval { $ip_dbh->rollback; };
-    syslog "err", "Could not add master block '$webvar{cidr}' to database: '$@'";
-    printError("Could not add master block $webvar{cidr} to database: $@");
-  } else {
-    print "<div type=heading align=center>Success!</div>\n";
-    syslog "info", "$authuser added master block $webvar{cidr}";
-  }
+      $sth->execute;
+
+      # If we get here, everything is happy.  Commit changes.
+      $ip_dbh->commit;
+    }; # end eval
+
+    if ($@) {
+      carp "Transaction aborted because $@";
+      eval { $ip_dbh->rollback; };
+      syslog "err", "Could not add master block '$webvar{cidr}' to database: '$@'";
+      printError("Could not add master block $webvar{cidr} to database: $@");
+    } else {
+      print "<div type=heading align=center>Success!</div>\n";
+      syslog "info", "$authuser added master block $webvar{cidr}";
+    }
+
+  } # ACL check
 
 } # end add new master
@@ -113,15 +124,4 @@
 elsif($webvar{action} eq 'listpool') {
   listPool();
-}
-elsif($webvar{action} eq 'search') {
-  printHeader('');
-  if (!$webvar{input}) {
-    # No search term.  Display everything.
-    viewBy('all', '');
-  } else {
-    # Search term entered.  Display matches.
-    # We should really sanitize $webvar{input}, no?
-    viewBy($webvar{searchfor}, $webvar{input});
-  }
 }
 
@@ -153,5 +153,4 @@
 # which is not in any way guaranteed to provide anything useful.
 else {
-  printHeader('');
   my $rnd = rand 500;
   my $boing = sprintf("%.2f", rand 500);
@@ -171,5 +170,5 @@
 print qq(<div align=right style="position: absolute; right: 30px;">).
 	qq(<a href="/ip/cgi-bin/admin.cgi">Admin tools</a></div><br>\n)
-	if $authuser =~ /kdeugau|jodyh|jipp/;
+	if $IPDBacl{$authuser} =~ /A/;
 
 # We print the footer here, so we don't have to do it elsewhere.
@@ -181,112 +180,4 @@
 
 
-sub viewBy($$) {
-  my ($category,$query) = @_;
-
-  # Local variables
-  my $sql;
-
-#print "<pre>\n";
-
-#print "start querysub: query '$query'\n";
-# this may happen with more than one subcategory.  Unlikely, but possible.
-
-  # Calculate start point for LIMIT clause
-  my $offset = ($webvar{page}-1)*$RESULTS_PER_PAGE;
-
-# Possible cases:
-# 1) Partial IP/subnet.  Treated as "first-three-octets-match" in old IPDB,
-#    I should be able to handle it similarly here.
-# 2a) CIDR subnet.  Treated more or less as such in old IPDB.
-# 2b) CIDR netmask.  Not sure how it's treated.
-# 3) Customer ID.  Not handled in old IPDB
-# 4) Description.
-# 5) Invalid data which might be interpretable as an IP or something, but
-#    which probably shouldn't be for reasons of sanity.
-
-  if ($category eq 'all') {
-
-    print qq(<div class="heading">Showing all netblock and static-IP allocations</div><br>\n);
-
-    # Need to assemble SQL query in this order to avoid breaking things.
-    $sql = "select cidr,custid,type,city,description from searchme";
-    my $count = countRows("select count(*) from ($sql) foo");
-    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
-    queryResults($sql, $webvar{page}, $count);
-
-  } elsif ($category eq 'cust') {
-
-    print qq(<div class="heading">Searching for Customer IDs containing '$query'</div><br>\n);
-
-    # Query for a customer ID.  Note that we can't restrict to "numeric-only"
-    # as we have non-numeric custIDs in the legacy data.  :/
-    $sql = "select cidr,custid,type,city,description from searchme where custid ilike '%$query%'";
-    my $count = countRows("select count(*) from ($sql) foo");
-    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
-    queryResults($sql, $webvar{page}, $count);
-
-  } elsif ($category eq 'desc') {
-
-    print qq(<div class="heading">Searching for descriptions containing '$query'</div><br>\n);
-    # Query based on description (includes "name" from old DB).
-    $sql = "select cidr,custid,type,city,description from searchme where description ilike '%$query%'";
-    my $count = countRows("select count(*) from ($sql) foo");
-    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
-    queryResults($sql, $webvar{page}, $count);
-
-  } elsif ($category =~ /ipblock/) {
-
-    # Query is for a partial IP, a CIDR block in some form, or a flat IP.
-    print qq(<div class="heading">Searching for IP-based matches on '$query'</div><br>\n);
-
-    $query =~ s/\s+//g;
-    if ($query =~ /\//) {
-      # 209.91.179/26 should show all /26 subnets in 209.91.179
-      my ($net,$maskbits) = split /\//, $query;
-      if ($query =~ /^(\d{1,3}\.){3}\d{1,3}\/\d{2}$/) {
-	# /0->/9 are silly to worry about right now.  I don't think
-	# we'll be getting a class A anytime soon.  <g>
-        $sql = "select cidr,custid,type,city,description from searchme where cidr='$query'";
-	queryResults($sql, $webvar{page}, 1);
-      } else {
-	print "Finding all blocks with netmask /$maskbits, leading octet(s) $net<br>\n";
-	# Partial match;  beginning of subnet and maskbits are provided
-	$sql = "select cidr,custid,type,city,description from searchme where ".
-		"text(cidr) like '$net%' and text(cidr) like '%$maskbits'";
-	my $count = countRows("select count(*) from ($sql) foo");
-	$sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
-	queryResults($sql, $webvar{page}, $count);
-      }
-    } elsif ($query =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
-      # Specific IP address match
-      my $sfor = new NetAddr::IP $query;
-# We do this convoluted roundabout way of finding things in order
-# to bring up matches for single IPs that are within a static block;
-# we want to show both the "container" block and the static IP itself.
-      $sth = $ip_dbh->prepare("select cidr from searchme where cidr >>= '$sfor'");
-      $sth->execute;
-      while (my @data = $sth->fetchrow_array()) {
-        my $cidr = new NetAddr::IP $data[0];
-	queryResults("select cidr,custid,type,city,description from searchme where ".
-		"cidr='$cidr'", $webvar{page}, 1);
-      }
-    } elsif ($query =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.?$/) {
-      print "Finding matches where the first three octets are $query<br>\n";
-      $sql = "select cidr,custid,type,city,description from searchme where ".
-		"text(cidr) like '$query%'";
-      my $count = countRows("select count(*) from ($sql) foo");
-      $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
-      queryResults($sql, $webvar{page}, $count);
-    } else {
-      # This shouldn't happen, but if it does, whoever gets it deserves what they get...
-      printError("Invalid query.");
-    }
-  } else {
-    # This shouldn't happen, but if it does, whoever gets it deserves what they get...
-    printError("Invalid searchfor.");
-  }
-} # viewBy
-
-
 # args are: a reference to an array with the row to be printed and the 
 # class(stylesheet) to use for formatting.
@@ -313,59 +204,4 @@
 
 
-# Display certain types of search query.  Note that this can't be
-# cleanly reused much of anywhere else as the data isn't neatly tabulated.
-# This is tied to the search sub tightly enough I may just gut it and provide
-# more appropriate tables directly as needed.
-sub queryResults($$$) {
-  my ($sql, $pageNo, $rowCount) = @_;
-  my $offset = 0;
-  $offset = $1 if($sql =~ m/.*limit\s+(.*),.*/);
-
-  my $sth = $ip_dbh->prepare($sql);
-  $sth->execute();
-
-  startTable('Allocation','CustID','Type','City','Description/Name');
-  my $count = 0;
-
-  while (my @data = $sth->fetchrow_array) {
-    # cidr,custid,type,city,description
-    # Prefix subblocks with "Sub "
-    my @row = ( (($data[2] =~ /^.r$/) ? 'Sub ' : '').
-	qq(<a href="/ip/cgi-bin/main.cgi?action=edit&block=$data[0]">$data[0]</a>),
-	$data[1], $disp_alloctypes{$data[2]}, $data[3], $data[4]);
-    # Allow listing of pool if desired/required.
-    if ($data[2] =~ /^.[pd]$/) {
-      $row[0] .= ' &nbsp; <a href="/ip/cgi-bin/main.cgi?action=listpool'.
-	"&pool=$data[0]\">List IPs</a>";
-    }
-    printRow(\@row, 'color1', 1) if ($count%2==0); 
-    printRow(\@row, 'color2', 1) if ($count%2!=0);
-    $count++;
-  }
-
-  # Have to think on this call, it's primarily to clean up unfetched rows from a select.
-  # In this context it's probably a good idea.
-  $sth->finish();
-
-  my $upper = $offset+$count;
-  print "<tr><td colspan=10 bgcolor=white class=regular>Records found: $rowCount<br><i>Displaying: $offset - $upper</i></td></tr>\n";
-  print "</table></center>\n";
-
-  # print the page thing..
-  if ($rowCount > $RESULTS_PER_PAGE) {
-    my $pages = ceil($rowCount/$RESULTS_PER_PAGE);
-    print qq(<div class="center"> Page: );
-    for (my $i = 1; $i <= $pages; $i++) {
-      if ($i == $pageNo) {
-	print "<b>$i&nbsp;</b>\n";
-      } else {
-	print qq(<a href="/ip/cgi-bin/main.cgi?page=$i&input=$webvar{input}&action=search&searchfor=$webvar{searchfor}">$i</a>&nbsp;\n);
-      }
-    }
-    print "</div>";
-  }
-} # queryResults
-
-
 # Prints table headings.  Accepts any number of arguments;
 # each argument is a table heading.
@@ -380,18 +216,6 @@
 
 
-# Return first element of passed SQL query
-sub countRows($) {
-  my $sth = $ip_dbh->prepare($_[0]);
-  $sth->execute();
-  my @a = $sth->fetchrow_array();
-  $sth->finish();
-  return $a[0];
-}
-
-
 # Initial display:  Show master blocks with total allocated subnets, total free subnets
 sub showSummary {
-  # this is horrible-ugly-bad and will Go Away real soon now(TM)
-  print "Content-type: text/html\n\n";
 
   startTable('Master netblock', 'Routed netblocks', 'Allocated netblocks',
@@ -420,5 +244,6 @@
 
   # Count the free blocks.
-  $sth = $ip_dbh->prepare("select count(*) from freeblocks where cidr <<= ?");
+  $sth = $ip_dbh->prepare("select count(*) from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n')");
   foreach my $master (@masterblocks) {
     $sth->execute("$master");
@@ -428,5 +253,6 @@
 
   # Find the largest free block in each master
-  $sth = $ip_dbh->prepare("select maskbits from freeblocks where cidr <<= ? order by maskbits limit 1");
+  $sth = $ip_dbh->prepare("select maskbits from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n') order by maskbits limit 1");
   foreach my $master (@masterblocks) {
     $sth->execute("$master");
@@ -448,5 +274,7 @@
   }
   print "</table>\n";
-  print qq(<a href="/ip/addmaster.shtml">Add new master block</a><br><br>\n);
+  if ($IPDBacl{$authuser} =~ /a/) {
+    print qq(<a href="/ip/cgi-bin/main.cgi?action=addmaster">Add new master block</a><br><br>\n);
+  }
   print "Note:  Free blocks noted here include both routed and unrouted blocks.\n";
 
@@ -460,5 +288,4 @@
 # else should follow.  YMMV.)
 sub showMaster {
-  printHeader('');
 
   print qq(<center><div class="heading">Summarizing routed blocks for ).
@@ -502,5 +329,6 @@
 
     # Count the free blocks.
-    $sth = $ip_dbh->prepare("select count(*) from freeblocks where cidr <<= ?");
+    $sth = $ip_dbh->prepare("select count(*) from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n')");
     foreach my $master (@localmasters) {
       $sth->execute("$master");
@@ -510,5 +338,6 @@
 
     # Get the size of the largest free block
-    $sth = $ip_dbh->prepare("select maskbits from freeblocks where cidr <<= ? order by maskbits limit 1");
+    $sth = $ip_dbh->prepare("select maskbits from freeblocks where cidr <<= ? and ".
+	"(routed='y' or routed='n') order by maskbits limit 1");
     foreach my $master (@localmasters) {
       $sth->execute("$master");
@@ -534,10 +363,12 @@
     print qq(<hr width="60%"><center><div class="heading">No allocations in ).
         qq($master.</div>\n).
-        qq(<form action="/ip/cgi-bin/main.cgi" method=POST>\n).
-        qq(<input type=hidden name=action value="delete">\n).
-        qq(<input type=hidden name=block value="$master">\n).
-        qq(<input type=hidden name=alloctype value="mm">\n).
-        qq(<input type=submit value=" Remove this master ">\n).
-        qq(</form></center>\n);
+	($IPDBacl{$authuser} =~ /d/ ?
+	        qq(<form action="/ip/cgi-bin/main.cgi" method=POST>\n).
+	        qq(<input type=hidden name=action value="delete">\n).
+	        qq(<input type=hidden name=block value="$master">\n).
+	        qq(<input type=hidden name=alloctype value="mm">\n).
+	        qq(<input type=submit value=" Remove this master ">\n).
+	        qq(</form></center>\n) :
+		'');
 
   } # end check for existence of routed blocks in master
@@ -573,5 +404,4 @@
 # not have anything useful to spew.
 sub showRBlock {
-  printHeader('');
 
   my $master = new NetAddr::IP $webvar{block};
@@ -621,10 +451,12 @@
     print qq(<hr width="60%"><center><div class="heading">No allocations in ).
 	qq($master.</div></center>\n).
-	qq(<form action="/ip/cgi-bin/main.cgi" method=POST>\n).
-	qq(<input type=hidden name=action value="delete">\n).
-	qq(<input type=hidden name=block value="$master">\n).
-	qq(<input type=hidden name=alloctype value="rm">\n).
-	qq(<input type=submit value=" Remove this block ">\n).
-	qq(</form>\n);
+	($IPDBacl{$authuser} =~ /d/ ?
+		qq(<form action="/ip/cgi-bin/main.cgi" method=POST>\n).
+		qq(<input type=hidden name=action value="delete">\n).
+		qq(<input type=hidden name=block value="$master">\n).
+		qq(<input type=hidden name=alloctype value="rm">\n).
+		qq(<input type=submit value=" Remove this block ">\n).
+		qq(</form>\n) :
+		'');
   }
 
@@ -645,5 +477,5 @@
     # Include some HairyPerl(TM) to prefix subblocks with "Sub "
     my @row = ((($data[1] ne 'y' && $data[1] ne 'n') ? 'Sub ' : '').
-	qq(<a href="/ip/cgi-bin/main.cgi?action=assign&block=$cidr&fbtype=$data[1]">$cidr</a>),
+	($IPDBacl{$authuser} =~ /a/ ? qq(<a href="/ip/cgi-bin/main.cgi?action=assign&block=$cidr&fbtype=$data[1]">$cidr</a>) : $cidr),
 	$cidr->range);
     printRow(\@row, 'color1') if ($count%2 == 0);
@@ -658,5 +490,4 @@
 # List the IPs used in a pool
 sub listPool {
-  printHeader('');
 
   my $cidr = new NetAddr::IP $webvar{pool};
@@ -704,5 +535,5 @@
     my @row = ( qq(<a href="/ip/cgi-bin/main.cgi?action=edit&block=$data[0]">$data[0]</a>),
 	$data[1],$data[2],$data[3],
-	( ($data[2] eq 'n') ?
+	( (($data[2] eq 'n') && ($IPDBacl{$authuser} =~ /d/)) ?
 	  ("<a href=\"/ip/cgi-bin/main.cgi?action=delete&block=$data[0]&".
 	   "alloctype=$data[4]\">Unassign this IP</a>") :
@@ -721,5 +552,9 @@
 # be one of two templates, and the lists come from the database.
 sub assignBlock {
-  printHeader('');
+
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
 
   my $html;
@@ -799,5 +634,8 @@
 # Take info on requested IP assignment and see what we can provide.
 sub confirmAssign {
-  printHeader('');
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
 
   my $cidr;
@@ -958,6 +796,9 @@
 # Do the work of actually inserting a block in the database.
 sub insertAssign {
+  if ($IPDBacl{$authuser} !~ /a/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
   # Some things are done more than once.
-  printHeader('');
   return if !validateInput();
 
@@ -973,7 +814,7 @@
       print qq(<div class="center"><div class="heading">The IP $msg has been allocated to customer $webvar{custid}</div></div>);
       # Notify tech@example.com
-      mailNotify('tech@example.com',"ADDED: $disp_alloctypes{$webvar{alloctype}} allocation",
-	"$disp_alloctypes{$webvar{alloctype}} $msg allocated to customer $webvar{custid}\n".
-	"Description: $webvar{desc}\n\nAllocated by: $authuser\n");
+#      mailNotify('tech@example.com',"ADDED: $disp_alloctypes{$webvar{alloctype}} allocation",
+#	"$disp_alloctypes{$webvar{alloctype}} $msg allocated to customer $webvar{custid}\n".
+#	"Description: $webvar{desc}\n\nAllocated by: $authuser\n");
     } else {
       print qq(<div class="center"><div class="heading">The block $webvar{fullcidr} was ).
@@ -981,5 +822,5 @@
     }
     syslog "notice", "$authuser allocated '$webvar{fullcidr}' to '$webvar{custid}' as ".
-	"'$webvar{alloctype}'";
+	"'$webvar{alloctype}' ($msg)";
   } else {
     syslog "err", "Allocation of '$webvar{fullcidr}' to '$webvar{custid}' as ".
@@ -1057,5 +898,4 @@
 # action=edit
 sub edit {
-  printHeader('');
 
   my $sql;
@@ -1064,7 +904,7 @@
   # because I'm lazy, we'll try to make the SELECT's bring out identical)ish) data
   if ($webvar{block} =~ /\/32$/) {
-    $sql = "select ip,custid,type,city,circuitid,description,notes from poolips where ip='$webvar{block}'";
+    $sql = "select ip,custid,type,city,circuitid,description,notes,modifystamp from poolips where ip='$webvar{block}'";
   } else {
-    $sql = "select cidr,custid,type,city,circuitid,description,notes from allocations where cidr='$webvar{block}'"
+    $sql = "select cidr,custid,type,city,circuitid,description,notes,modifystamp from allocations where cidr='$webvar{block}'"
   }
 
@@ -1076,10 +916,4 @@
   # Clean up extra whitespace on alloc type
   $data[2] =~ s/\s//;
-
-##fixme LEGACY CODE
-  # Postfix "i" on pool IP types
-  if ($data[2] =~ /^[cdsmw]$/) {
-    $data[2] .= "i";
-  }
 
   open (HTML, "../editDisplay.html")
@@ -1093,5 +927,7 @@
 # Needs thinking.  Have to allow changes to city to correct errors, no?
   $html =~ s/\$\$BLOCK\$\$/$webvar{block}/g;
-  $html =~ s/\$\$CITY\$\$/$data[3]/g;
+
+  if ($IPDBacl{$authuser} =~ /c/) {
+    $html =~ s/\$\$CUSTID\$\$/<input type=text name=custid value="$data[1]" maxlength=15 class="regular">/;
 
 # Screw it.  Changing allocation types gets very ugly VERY quickly- especially
@@ -1102,26 +938,60 @@
 
 ##fixme The check here should be built from the database
-  if ($data[2] =~ /^.[ne]$/) {
-    # Block that can be changed
-    my $blockoptions = "<select name=alloctype><option".
+    if ($data[2] =~ /^.[ne]$/) {
+      # Block that can be changed
+      my $blockoptions = "<select name=alloctype><option".
 	(($data[2] eq 'me') ? ' selected' : '') ." value='me'>Dialup netblock</option>\n<option".
-	(($data[2] eq 'de') ? ' selected' : '') ." value='de'>Dynamic DSL netblock</option>\n<option". 	(($data[2] eq 'dc') ? ' selected' : '') ." value='dc'>Dynamic cable netblock</option>\n<option".
-	(($data[2] eq 'ce') ? ' selected' : '') ." value='ce'>Dynamic cable netblock</option>\n<option". 	(($data[2] eq 'dc') ? ' selected' : '') ." value='dc'>Dynamic cable netblock</option>\n<option".
-	(($data[2] eq 'we') ? ' selected' : '') ." value='we'>Dynamic wireless netblock</option>\n<option". 	(($data[2] eq 'dc') ? ' selected' : '') ." value='dc'>Dynamic cable netblock</option>\n<option".
+	(($data[2] eq 'de') ? ' selected' : '') ." value='de'>Dynamic DSL netblock</option>\n<option".
+	(($data[2] eq 'ce') ? ' selected' : '') ." value='ce'>Dynamic cable netblock</option>\n<option".
+	(($data[2] eq 'we') ? ' selected' : '') ." value='we'>Dynamic wireless netblock</option>\n<option".
 	(($data[2] eq 'cn') ? ' selected' : '') ." value='cn'>Customer netblock</option>\n<option".
 	(($data[2] eq 'en') ? ' selected' : '') ." value='en'>End-use netblock</option>\n<option".
 	(($data[2] eq 'in') ? ' selected' : '') ." value='in'>Internal netblock</option>\n".
 	"</select>\n";
-    $html =~ s/\$\$TYPESELECT\$\$/$blockoptions/g;
+      $html =~ s/\$\$TYPESELECT\$\$/$blockoptions/g;
+    } else {
+      $html =~ s/\$\$TYPESELECT\$\$/$disp_alloctypes{$data[2]}<input type=hidden name=alloctype value="$data[2]">/g;
+    }
+    $html =~ s/\$\$CITY\$\$/<input type=text name=city value="$data[3]">/g;
+    $html =~ s/\$\$CIRCID\$\$/<input type="text" name="circid" value="$data[4]" maxlength=64 size=64 class="regular">/g;
+    $html =~ s/\$\$DESC\$\$/<input type="text" name="desc" value="$data[5]" maxlength=64 size=64 class="regular">/g;
+    $html =~ s|\$\$NOTES\$\$|<textarea rows="8" cols="64" name="notes" class="regular">$data[6]</textarea>|g;
   } else {
-    $html =~ s/\$\$TYPESELECT\$\$/$disp_alloctypes{$data[2]}<input type=hidden name=alloctype value="$data[2]">/g;
-  }
-
-  # These can be modified, although CustID changes may get ignored.
-  $html =~ s/\$\$CUSTID\$\$/$data[1]/g;
-  $html =~ s/\$\$TYPE\$\$/$data[2]/g;
-  $html =~ s/\$\$CIRCID\$\$/$data[4]/g;
-  $html =~ s/\$\$DESC\$\$/$data[5]/g;
-  $html =~ s/\$\$NOTES\$\$/$data[6]/g;
+    $html =~ s/\$\$CUSTID\$\$/$data[1]/g;
+    $html =~ s/\$\$TYPESELECT\$\$/$disp_alloctypes{$data[2]}/g;
+    $html =~ s/\$\$CITY\$\$/$data[3]/g;
+    $html =~ s/\$\$CIRCID\$\$/$data[4]/g;
+    $html =~ s/\$\$DESC\$\$/$data[5]/g;
+    $html =~ s/\$\$NOTES\$\$/$data[6]/g;
+  }
+  my ($lastmod,undef) = split /\s+/, $data[7];
+  $html =~ s/\$\$LASTMOD\$\$/$lastmod/g;
+
+  # Allows us to "correctly" colour backgrounds in table
+  my $i=1;
+
+  # More ACL trickery - we can live with forms that don't submit,
+  # but we can't leave the extra table rows there, and we *really*
+  # can't leave the submit buttons there.
+  my $updok = '';
+  if ($IPDBacl{$authuser} =~ /c/) {
+    $updok = qq(<tr class="color$i"><td colspan=2><div class="center">).
+	qq(<input type="submit" value=" Update this block " class="regular">).
+	"</div></td></tr></form>\n";
+    $i++;
+  }
+  $html =~ s/\$\$UPDOK\$\$/$updok/g;
+
+  my $delok = '';
+  if ($IPDBacl{$authuser} =~ /d/) {
+    $delok = qq(<form method="POST" action="main.cgi">
+	<tr class="color$i"><td colspan=2 class="regular"><div class=center>
+	<input type="hidden" name="action" value="delete">
+	<input type="hidden" name="block" value="$webvar{block}">
+	<input type="hidden" name="alloctype" value="$data[2]">
+	<input type=submit value=" Delete this block ">
+	</div></td></tr>);
+  }
+  $html =~ s/\$\$DELOK\$\$/$delok/;
 
   print $html;
@@ -1133,8 +1003,7 @@
 # action=update
 sub update {
-  printHeader('');
 
   # Make sure incoming data is in correct format - custID among other things.
-  validateInput;
+  return if !validateInput;
 
   # SQL transaction wrapper
@@ -1143,8 +1012,6 @@
     my $sql;
     if (my $pooltype = ($webvar{alloctype} =~ /^(.)i$/) ) {
-      # Note the hack ( available='n' ) to work around "update" additions
-      # to static IP space.  Eww.
       $sql = "update poolips set custid='$webvar{custid}',notes='$webvar{notes}',".
-	"circuitid='$webvar{circid}',description='$webvar{desc}',available='n' ".
+	"circuitid='$webvar{circid}',description='$webvar{desc}',city='$webvar{city}' ".
 	"where ip='$webvar{block}'";
     } else {
@@ -1194,5 +1061,9 @@
 # Delete an allocation.
 sub remove {
-  printHeader('');
+  if ($IPDBacl{$authuser} !~ /d/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
+
   #show confirm screen.
   open HTML, "../confirmRemove.html"
@@ -1284,5 +1155,8 @@
 # Remove IPs from pool listing if necessary
 sub finalDelete {
-  printHeader('');
+  if ($IPDBacl{$authuser} !~ /d/) {
+    printError("You shouldn't have been able to get here.  Access denied.");
+    return;
+  }
 
   my ($code,$msg) = deleteBlock($ip_dbh, $webvar{block}, $webvar{alloctype});
@@ -1292,6 +1166,6 @@
     syslog "notice", "$authuser deallocated '$webvar{alloctype}'-type netblock $webvar{block}";
     # Notify tech@ when a block/IP is deallocated
-    mailNotify('tech@example.com',"REMOVED: $disp_alloctypes{$webvar{alloctype}} $webvar{block}",
-	"$disp_alloctypes{$webvar{alloctype}} $webvar{block} deallocated by $authuser\n");
+#    mailNotify('tech@example.com',"REMOVED: $disp_alloctypes{$webvar{alloctype}} $webvar{block}",
+#	"$disp_alloctypes{$webvar{alloctype}} $webvar{block} deallocated by $authuser\n");
   } else {
     if ($webvar{alloctype} =~ /^.i$/) {
@@ -1307,4 +1181,16 @@
 
 
+sub exitError {
+  my $errStr = $_[0];
+  printHeader('','');
+  print qq(<center><p class="regular"> $errStr </p>
+<input type="button" value="Back" onclick="history.go(-1)">
+</center>
+);
+  printFooter();
+  exit;
+} # errorExit
+
+
 # Just in case we manage to get here.
 exit 0;
Index: /branches/dns/cgi-bin/search.cgi
===================================================================
--- /branches/dns/cgi-bin/search.cgi	(revision 262)
+++ /branches/dns/cgi-bin/search.cgi	(revision 262)
@@ -0,0 +1,464 @@
+#!/usr/bin/perl
+# ipdb/cgi-bin/search.cgi
+# Started splitting search functions (quick and otherwise) from
+# main IPDB interface 03/11/2005
+###
+# SVN revision info
+# $Date$
+# SVN revision $Rev$
+# Last update by $Author$
+###
+# Copyright 2005 Kris Deugau <kdeugau@deepnet.cx>
+
+use strict;		
+use warnings;	
+use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use CommonWeb qw(:ALL);
+use MyIPDB;
+use POSIX qw(ceil);
+use NetAddr::IP;
+
+# Don't need a username or syslog here.  syslog left active for debugging.
+use Sys::Syslog;
+openlog "IPDBsearch","pid","local2";
+
+# Why not a global DB handle?  (And a global statement handle, as well...)
+# Use the connectDB function, otherwise we end up confusing ourselves
+my $ip_dbh;
+my $sth;
+my $errstr;
+($ip_dbh,$errstr) = connectDB_My;
+if (!$ip_dbh) {
+  printAndExit("Failed to connect to database: $errstr\n");
+}
+checkDBSanity($ip_dbh);
+initIPDBGlobals($ip_dbh);
+
+# Global variables
+my $RESULTS_PER_PAGE = 10;
+my %webvar = parse_post();
+cleanInput(\%webvar);
+
+if (!defined($webvar{stype})) {
+  $webvar{stype} = "<NULL>";   #shuts up the warnings.
+}
+
+printHeader('Searching...');
+
+if ($webvar{stype} eq 'q') {
+  # Quick search.
+
+  if (!$webvar{input}) {
+    # No search term.  Display everything.
+    viewBy('all', '');
+  } else {
+    # Search term entered.  Display matches.
+    # We should really sanitize $webvar{input}, no?
+    my $searchfor;
+    # Chew up leading and trailing whitespace
+    $webvar{input} =~ s/^\s+//;
+    $webvar{input} =~ s/\s+$//;
+    if ($webvar{input} =~ /^[\d\.]+(\/\d{1,3})?$/) {
+      # IP addresses should only have numbers, digits, and maybe a slash+netmask
+      $searchfor = "ipblock";
+    } elsif ($webvar{input} =~ /^\d+$/) {
+      # All-digits, new custID
+      $searchfor = "cust";
+    } else {
+      # Anything else.
+      $searchfor = "desc";
+    }
+    viewBy($searchfor, $webvar{input});
+  }
+
+} elsif ($webvar{stype} eq 'c') {
+  # Complex search.
+
+  # Several major cases, and a whole raft of individual cases.
+  # -> Show all types means we do not need to limit records retrieved by type
+  # -> Show all cities means we do not need to limit records retrieved by city
+  # Individual cases are for the CIDR/IP, CustID, Description, Notes, and individual type
+  # requests.
+
+  my $sqlconcat;
+  if ($webvar{which} eq 'all') {
+    # Must match *all* specified criteria.	## use INTERSECT or EXCEPT
+    $sqlconcat = "INTERSECT";
+  } elsif ($webvar{which} eq 'any') {
+    # Match on any specified criteria		## use UNION
+    $sqlconcat = "UNION";
+  } else {
+    # We can't get here.  PTHBTT!
+    printAndExit "PTHBTT!!  Your search has been rejected due to Microsoft excuse #4432: ".
+	"Not enough mana";
+  }
+
+# We actually construct a monster SQL statement for all criteria.
+# Iff something has been entered, it will be used as a filter.
+# Iff something has NOT been entered, we still include it but in
+# such a way that it does not actually filter anything out.
+
+  # Columns actually returned.  Slightly better than hardcoding it
+  # in each (sub)select
+  my $cols = "cidr,custid,type,city,description";
+
+  # First chunk of SQL.  Filter on custid, description, and notes as necessary.
+  my $sql = "(select $cols from searchme where $webvar{custexclude} custid ilike '%$webvar{custid}%')".
+	" $sqlconcat (select $cols from searchme where $webvar{descexclude} description ilike '%$webvar{desc}%')".
+	" $sqlconcat (select $cols from searchme where $webvar{notesexclude} notes ilike '%$webvar{notes}%')";
+
+  # If we're not supposed to search for all types, search for the selected types.
+  if ($webvar{alltypes} ne 'on') {
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{typeexclude} type in (";
+    foreach my $key (keys %webvar) {
+      $sql .= "'$1'," if $key =~ /type\[(..)\]/;
+    }
+    chop $sql;
+    $sql .= "))";
+  }
+
+  # If we're not supposed to search for all cities, search for the selected cities.
+  # This could be vastly improved with proper foreign keys in the database.
+  if ($webvar{allcities} ne 'on') {
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{cityexclude} city in (";
+    $sth = $ip_dbh->prepare("select city from cities where id=?");
+    foreach my $key (keys %webvar) {
+      if ($key =~ /city\[(\d+)\]/) {
+        $sth->execute($1);
+        my $city;
+        $sth->bind_columns(\$city);
+        $sth->fetch;
+        $city =~ s/'/''/;
+        $sql .= "'$city',";
+      }
+    }
+    chop $sql;
+    $sql .= "))";
+  }
+
+  ## CIDR query options.
+  $webvar{cidr} =~ s/\s+//;	# Hates the nasty spaceseseses we does.
+  if ($webvar{cidr} =~ /\//) {
+    # 209.91.179/26 should show all /26 subnets in 209.91.179
+    my ($net,$maskbits) = split /\//, $webvar{cidr};
+    if ($webvar{cidr} =~ /^(\d{1,3}\.){3}\d{1,3}\/\d{2}$/) {
+      # /0->/9 are silly to worry about right now.  I don't think
+      # we'll be getting a class A anytime soon.  <g>
+      $sql .= " $sqlconcat (select $cols from searchme where ".
+	"$webvar{cidrexclude} cidr='$webvar{cidr}')";
+    } else {
+      # Partial match;  beginning of subnet and maskbits are provided
+      # Show any blocks with the leading octet(s) and that masklength
+      $sql .= " $sqlconcat (select $cols from searchme where $webvar{cidrexclude} ".
+	"(text(cidr) like '$net%' and masklen(cidr)=$maskbits))";
+    }
+  } elsif ($webvar{cidr} =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
+    # Specific IP address match.  Will show either a single netblock,
+    # or a static pool plus an IP.
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{cidrexclude} ".
+	"cidr >>= '$webvar{cidr}')";
+  } elsif ($webvar{cidr} =~ /^\d{1,3}(\.(\d{1,3}(\.(\d{1,3}\.?)?)?)?)?$/) {
+    # Leading octets in CIDR
+    $sql .= " $sqlconcat (select $cols from searchme where $webvar{cidrexclude} ".
+	"text(cidr) like '$webvar{cidr}%')";
+  } else {
+    # This shouldn't happen, but if it does, whoever gets it deserves what they get...
+    printAndExit("Invalid netblock query.");
+  } # done with CIDR query options.
+
+  # Find the offset for multipage results
+  my $offset = ($webvar{page}-1)*$RESULTS_PER_PAGE;
+
+  # Find out how many rows the "core" query will return.
+  my $count = countRows($sql);
+
+  if ($count == 0) {
+    printError "No matches found.  Try eliminating one of the criteria,".
+	" or making one or more criteria more general.";
+  } else {
+    # Add the limit/offset clauses
+    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+    # And tell the user.
+    print "<div class=heading>Searching...............</div>\n";
+    queryResults($sql, $webvar{page}, $count);
+  }
+
+} else { # how script was called.  General case is to show the search criteria page.
+
+  # Display search page.  We have to do this here, because otherwise
+  # we can't retrieve data from the database for the types and cities.  >:(
+  my $html;
+  open HTML,"<../compsearch.html";
+  $html = join('',<HTML>);
+  close HTML;
+
+# Generate table of types
+  my $typetable = "<table class=regular cellspacing=0>\n<tr>";
+  $sth = $ip_dbh->prepare("select type,dispname from alloctypes where listorder <500 ".
+	"order by listorder");
+  $sth->execute;
+  my $i=0;
+  while (my @data = $sth->fetchrow_array) {
+    $typetable .= "<td><input type=checkbox name=type[$data[0]]>$data[1]</td>";
+    $i++;
+    $typetable .= "</tr>\n<tr>"
+	if ($i % 4 == 0);
+  }
+  if ($i %4 == 0) {
+    $typetable =~ s/<tr>$//;
+  } else {
+    $typetable .= "</tr>\n";
+  }
+  $typetable .= "</table>\n";
+
+# Generate table of cities
+  my $citytable = "<table class=regular cellspacing=0>\n<tr>";
+  $sth = $ip_dbh->prepare("select id,city from cities order by city");
+  $sth->execute;
+  my $i=0;
+  while (my @data = $sth->fetchrow_array) {
+    $citytable .= "<td><input type=checkbox name=city[$data[0]]>$data[1]</td>";
+    $i++;
+    $citytable .= "</tr>\n<tr>"
+	if ($i % 5 == 0);
+  }
+  if ($i %5 == 0) {
+    $citytable =~ s/<tr>$//;
+  } else {
+    $citytable .= "</tr>\n";
+  }
+  $citytable .= "</table>\n";
+
+  $html =~ s/\$\$TYPELIST\$\$/$typetable/;
+  $html =~ s/\$\$CITYLIST\$\$/$citytable/;
+
+  print $html;
+}
+
+# Shut down and clean up.
+finish($ip_dbh);
+printFooter;
+# We shouldn't need to directly execute any code below here;  it's all subroutines.
+exit 0;
+
+
+# viewBy()
+# The quick search
+# Takes a category descriptor and a query string
+# Creates appropriate SQL to run the search and display the results
+# with queryResults()
+sub viewBy($$) {
+  my ($category,$query) = @_;
+
+  # Local variables
+  my $sql;
+
+  # Calculate start point for LIMIT clause
+  my $offset = ($webvar{page}-1)*$RESULTS_PER_PAGE;
+
+# Possible cases:
+# 1) Partial IP/subnet.  Treated as "octet-prefix".
+# 2a) CIDR subnet.  Exact match.
+# 2b) CIDR netmask.  YMMV but it should be octet-prefix-with-netmask
+#	(ie, all matches with the octet prefix *AND* that netmask)
+# 3) Customer ID.  "Match-any-segment"
+# 4) Description.  "Match-any-segment"
+# 5) Invalid data which might be interpretable as an IP or something, but
+#    which probably shouldn't be for reasons of sanity.
+
+  if ($category eq 'all') {
+
+    print qq(<div class="heading">Showing all netblock and static-IP allocations</div><br>\n);
+    $sql = "select * from searchme";
+    my $count = countRows($sql);
+    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+    queryResults($sql, $webvar{page}, $count);
+
+  } elsif ($category eq 'cust') {
+
+    print qq(<div class="heading">Searching for Customer IDs containing '$query'</div><br>\n);
+
+    # Query for a customer ID.  Note that we can't restrict to "numeric-only"
+    # as we have non-numeric custIDs in the legacy data.  :/
+    $sql = "select * from searchme where custid ilike '%$query%'";
+    my $count = countRows($sql);
+    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+    queryResults($sql, $webvar{page}, $count);
+
+  } elsif ($category eq 'desc') {
+
+    print qq(<div class="heading">Searching for descriptions containing '$query'</div><br>\n);
+    # Query based on description (includes "name" from old DB).
+    $sql = "select * from searchme where description ilike '%$query%'";
+    my $count = countRows($sql);
+    $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+    queryResults($sql, $webvar{page}, $count);
+
+  } elsif ($category =~ /ipblock/) {
+
+    # Query is for a partial IP, a CIDR block in some form, or a flat IP.
+    print qq(<div class="heading">Searching for IP-based matches on '$query'</div><br>\n);
+
+    $query =~ s/\s+//g;
+    if ($query =~ /\//) {
+      # 209.91.179/26 should show all /26 subnets in 209.91.179
+      my ($net,$maskbits) = split /\//, $query;
+      if ($query =~ /^(\d{1,3}\.){3}\d{1,3}\/\d{2}$/) {
+	# /0->/9 are silly to worry about right now.  I don't think
+	# we'll be getting a class A anytime soon.  <g>
+        $sql = "select * from searchme where cidr='$query'";
+	queryResults($sql, $webvar{page}, 1);
+      } else {
+	print "Finding all blocks with netmask /$maskbits, leading octet(s) $net<br>\n";
+	# Partial match;  beginning of subnet and maskbits are provided
+	$sql = "select * from searchme where text(cidr) like '$net%' and ".
+		"text(cidr) like '%$maskbits'";
+	my $count = countRows($sql);
+	$sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+	queryResults($sql, $webvar{page}, $count);
+      }
+    } elsif ($query =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
+      # Specific IP address match
+      print "4-octet pattern found;  finding netblock containing IP $query<br>\n";
+      my ($net,$ip) = ($query =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})/);
+      my $sfor = new NetAddr::IP $query;
+      $sth = $ip_dbh->prepare("select * from searchme where text(cidr) like '$net%'");
+      $sth->execute;
+      while (my @data = $sth->fetchrow_array()) {
+        my $cidr = new NetAddr::IP $data[0];
+	if ($cidr->contains($sfor)) {
+	  queryResults("select * from searchme where cidr='$cidr'", $webvar{page}, 1);
+	}
+      }
+    } elsif ($query =~ /^(\d{1,3}\.){1,3}\d{1,3}\.?$/) {
+      print "Finding matches with leading octet(s) $query<br>\n";
+      $sql = "select * from searchme where text(cidr) like '$query%'";
+      my $count = countRows($sql);
+      $sql .= " order by cidr limit $RESULTS_PER_PAGE offset $offset";
+      queryResults($sql, $webvar{page}, $count);
+    } else {
+      # This shouldn't happen, but if it does, whoever gets it deserves what they get...
+      printError("Invalid query.");
+    }
+  } else {
+    # This shouldn't happen, but if it does, whoever gets it deserves what they get...
+    printError("Invalid searchfor.");
+  }
+} # viewBy
+
+
+# args are: a reference to an array with the row to be printed and the 
+# class(stylesheet) to use for formatting.
+# if ommitting the class - call the sub as &printRow(\@array)
+sub printRow {
+  my ($rowRef,$class) = @_;
+
+  if (!$class) {
+    print "<tr>\n";
+  } else {
+    print "<tr class=\"$class\">\n";
+  }
+
+ELEMENT:  foreach my $element (@$rowRef) {
+    if (!defined($element)) {
+      print "<td></td>\n";
+      next ELEMENT;
+    }
+    $element =~ s|\n|</br>|g;
+    print "<td>$element</td>\n";
+  }
+  print "</tr>";
+} # printRow
+
+
+# queryResults()
+# Display search queries based on the passed SQL.
+# Takes SQL, page number (for multipage search results), and a total count.
+sub queryResults($$$) {
+  my ($sql, $pageNo, $rowCount) = @_;
+  my $offset = 0;
+  $offset = $1 if($sql =~ m/.*limit\s+(.*),.*/);
+
+  my $sth = $ip_dbh->prepare($sql);
+  $sth->execute();
+
+  startTable('Allocation','CustID','Type','City','Description/Name');
+  my $count = 0;
+
+  while (my @data = $sth->fetchrow_array) {
+
+    # cidr,custid,type,city,description,notes
+    # Another bit of HairyPerl(TM) to prefix subblocks with "Sub"
+    my @row = (($data[2] =~ /^.r$/ ? 'Sub ' : '').
+	qq(<a href="/ip/cgi-bin/main.cgi?action=edit&block=$data[0]">$data[0]</a>),
+	$data[1], $disp_alloctypes{$data[2]}, $data[3], $data[4]);
+    # Allow listing of pool if desired/required.
+    if ($data[2] =~ /^.[pd]$/) {
+      $row[0] .= ' &nbsp; <a href="/ip/cgi-bin/main.cgi?action=listpool'.
+	"&pool=$data[0]\">List IPs</a>";
+    }
+    printRow(\@row, 'color1', 1) if ($count%2==0); 
+    printRow(\@row, 'color2', 1) if ($count%2!=0);
+    $count++;
+  }
+
+  # Have to think on this call, it's primarily to clean up unfetched rows from a select.
+  # In this context it's probably a good idea.
+  $sth->finish();
+
+  my $upper = $offset+$count;
+  print "<tr><td colspan=10 bgcolor=white class=regular>Records found: $rowCount<br><i>Displaying: ".($offset+1)." - $upper</i></td></tr>\n";
+  print "</table></center>\n";
+
+  # print the page thing..
+  if ($rowCount > $RESULTS_PER_PAGE) {
+    my $pages = ceil($rowCount/$RESULTS_PER_PAGE);
+    print qq(<div class="center"> Page: );
+    for (my $i = 1; $i <= $pages; $i++) {
+      if ($i == $pageNo) {
+	print "<b>$i&nbsp;</b>\n";
+      } else {
+	print qq(<a href="/ip/cgi-bin/search.cgi?page=$i&stype=$webvar{stype}&);
+	if ($webvar{stype} eq 'c') {
+	  print "cidr=$webvar{cidr}&custid=$webvar{custid}&desc=$webvar{desc}&".
+		"notes=$webvar{notes}&which=$webvar{which}&alltypes=$webvar{alltypes}&".
+		"allcities=$webvar{allcities}&";
+	  foreach my $key (keys %webvar) {
+	    if ($key =~ /^(?:type|city)\[/) {
+	      print "$key=$webvar{$key}&";
+	    }
+	  }
+	} else {
+	  print "input=$webvar{input}&";
+	}
+	print qq(">$i</a>&nbsp;\n);
+      }
+    }
+    print "</div>";
+  }
+} # queryResults
+
+
+# Prints table headings.  Accepts any number of arguments;
+# each argument is a table heading.
+sub startTable {
+  print qq(<center><table width="98%" cellspacing="0" class="center"><tr>);
+
+  foreach(@_) {
+    print qq(<td class="heading">$_</td>);
+  }
+  print "</tr>\n";
+} # startTable
+
+
+# Return count of rows to be returned in a "real" query
+# with the passed SQL statement
+sub countRows($) {
+  # Note that the "as foo" is required
+  my $sth = $ip_dbh->prepare("select count(*) from ($_[0]) as foo");
+  $sth->execute();
+  my @a = $sth->fetchrow_array();
+  $sth->finish();
+  return $a[0];
+}
Index: /branches/dns/changes.html
===================================================================
--- /branches/dns/changes.html	(revision 261)
+++ /branches/dns/changes.html	(revision 262)
@@ -12,4 +12,17 @@
 <tr><td class="heading" colspan=2>Changes to the IPDB</td><tr>
 
+<tr class="color2">
+<td valign=top>03/10/2005</td>
+<td>Finally!  Support for "container"/"reserve" netblocks to make tracking superblocks like
+209.91.191.0/24 easier.  Also supports "dynamically routed DSL netblocks";  ie,
+netblock-aligned chunks of static DSL IPs with netblock-ish subnet masks such as
+209.91.186.0/25.
+</td></tr>
+<tr class="color1">
+<td valign=top>02/09/2005</td>
+<td>Pool handling has been cleaned up - all PPP-ish pools (modem, DSL, WiFi) now generate a
+full list of IPs rather than reserving the network, gateway, and broadcast IPs necessary on
+a "real" subnet.  They will also not display this incorrect and useless information.
+</td></tr>
 <tr class="color2">
 <td valign=top>01/27/2005</td>
Index: /branches/dns/compsearch.html
===================================================================
--- /branches/dns/compsearch.html	(revision 262)
+++ /branches/dns/compsearch.html	(revision 262)
@@ -0,0 +1,35 @@
+<div class="indent">
+<div class="heading">Complex Search</div>
+<form action="/ip/cgi-bin/search.cgi" method=POST>
+<table bgcolor="black" cellspacing="1" cellpadding="2">
+<tr class="color1">
+<td colspan=2>Match on:<input type=radio checked name=which value="all">All
+<input type=radio name=which value="any">Any
+<td colspan=2 align=right><input type=submit value="Search Now"></td>
+</td>
+<tr class="color1">
+<td>Exclude?<input type=checkbox name=cidrexclude value='not'></td>
+<td>IP/netblock:</td><td colspan=2><input name=cidr></td>
+</tr><tr class="color2">
+<td>Exclude?<input type=checkbox name=custexclude value='not'></td>
+<td>CustID:</td><td colspan=2><input name=custid></td>
+</tr><tr class="color1">
+<td>Exclude?<input type=checkbox name=descexclude value='not'></td>
+<td>Description:</td><td colspan=2><input name=desc></td>
+</tr><tr class="color2">
+<td>Exclude?<input type=checkbox name=notesexclude value='not'></td>
+<td>Notes:</td><td colspan=2><input name=notes></td>
+</tr><tr class="color1">
+<td>Exclude?<input type=checkbox name=typeexclude value='not'></td>
+<td>Types:</td><td colspan=2><input type=checkbox name=alltypes checked>Show all types
+$$TYPELIST$$</td>
+</tr><tr class="color2">
+<td>Exclude?<input type=checkbox name=cityexclude value='not'></td>
+<td>Cities:</td><td colspan=2><input type=checkbox name=allcities checked>Show all cities
+$$CITYLIST$$</td>
+</tr>
+<input type=hidden name=stype value=c>
+<input type=hidden name=page value=1>
+</table>
+</form>
+</div>
Index: /branches/dns/editDisplay.html
===================================================================
--- /branches/dns/editDisplay.html	(revision 261)
+++ /branches/dns/editDisplay.html	(revision 262)
@@ -8,31 +8,20 @@
 <tr class="color1"><td class=heading>IP block:</td><td class="regular">$$BLOCK$$</td></tr>
 
-<tr class="color2"><td class=heading>City:</td><td class="regular">
-<input type=text name=city value="$$CITY$$"></td></tr>
+<tr class="color2"><td class=heading>City:</td><td class="regular">$$CITY$$</td></tr>
 
 <tr class="color1"><td class=heading>Type:</td><td class=regular>$$TYPESELECT$$</td></tr>
 
-<tr class="color2"><td class=heading>CustID:</td><td class="regular">
-<input type=text name=custid value="$$CUSTID$$" maxlength=15 class="regular"></td></tr>
+<tr class="color2"><td class=heading>CustID:</td><td class="regular">$$CUSTID$$</td></tr>
 
-<tr class="color1"><td class="heading">Circuit ID:</td><td class="regular">
-<input type="text" name="circid" value="$$CIRCID$$" maxlength=64 size=64 class="regular"></td></tr>
+<tr class="color1"><td class=heading>Last modified:</td><td class=regular>$$LASTMOD$$</td></tr>
 
-<tr class="color2"><td class="heading">Description/Name:</td><td class="regular">
-<input type="text" name="desc" value="$$DESC$$" maxlength=64 size=64 class="regular"></td></tr>
+<tr class="color2"><td class="heading">Circuit ID:</td><td class="regular">$$CIRCID$$</td></tr>
 
-<tr class="color1"><td class="heading" valign="top">Notes:</td><td class="regular">
-<textarea rows="8" cols="64" name="notes" class="regular">$$NOTES$$</textarea></td></tr>
+<tr class="color1"><td class="heading">Description/Name:</td><td class="regular">$$DESC$$</td></tr>
 
-<tr class="color2"><td colspan=2 class=regular><div class="center">
-<input type="submit" value=" Update this block " class="regular">
-</div></td></tr></form>
-<form method="POST" action="main.cgi">
-<tr class="color1"><td colspan=2 class="regular"><div class=center>
-<input type="hidden" name="action" value="delete">
-<input type="hidden" name="block" value="$$BLOCK$$">
-<input type="hidden" name="alloctype" value="$$TYPE$$">
-<input type=submit value=" Delete this block ">
-</div></td></tr>
+<tr class="color2"><td class="heading" valign="top">Notes:</td><td class="regular">$$NOTES$$</td></tr>
+
+$$UPDOK$$
+$$DELOK$$
 </form>
 
Index: /branches/dns/header.inc
===================================================================
--- /branches/dns/header.inc	(revision 261)
+++ /branches/dns/header.inc	(revision 262)
@@ -40,17 +40,14 @@
 <tr class="color1">
 <td width=10></td>
-<form method="POST" action="/ip/cgi-bin/main.cgi">
-<td>Search:
+<form method="POST" action="/ip/cgi-bin/search.cgi">
+<td width=390>Quick Search:
 <input type="text" name="input" size="20" maxlength="50" class="regular">
-<input type=radio name="searchfor" value="ipblock">IP/IP block
-<input type=radio name="searchfor" value="desc" checked=yes>Description
-<input type=radio name="searchfor" value="cust">Customer ID
 <input type=hidden name=page value="1">
-<input type=hidden name=action value="search">
+<input type=hidden name=stype value="q">
 <input type=submit value="Go!" class="heading">
 <input type="button" value=" Help? " onclick="openHelp()" class="regular">
-&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
-<a href="/ip/cgi-bin/main.cgi?action=assign">Add new assignment</a>
-</td>
+</td><td width=10></td><td><a href="/ip/cgi-bin/search.cgi">Complex Search</a></td>
+<td width=60></td>
+$$EXTRA0$$
 </form>
 </tr>
Index: /branches/dns/help.html
===================================================================
--- /branches/dns/help.html	(revision 261)
+++ /branches/dns/help.html	(revision 262)
@@ -11,5 +11,5 @@
 <table class="regular">
 
-<tr><td class="heading">Searches:</td><tr>
+<tr><td class="heading">Quick Searches:</td><tr>
 
 <tr class="color1">
Index: /branches/dns/index.shtml
===================================================================
--- /branches/dns/index.shtml	(revision 261)
+++ /branches/dns/index.shtml	(revision 262)
@@ -1,2 +1,1 @@
-<!--#include file="header.inc"-->
 <!--#include virtual="/ip/cgi-bin/main.cgi?action=index" -->
