#!/usr/bin/perl
# ipdb/cgi-bin/admin.cgi
# Hack interface to make specific changes to IPDB that (for one reason
# or another) can't be made through the main interface.
#
###
# SVN revision info
# $Date: 2010-07-27 21:06:18 +0000 (Tue, 27 Jul 2010) $
# SVN revision $Rev: 449 $
# Last update by $Author: kdeugau $
###
# Copyright (C) 2004-2010 - Kris Deugau
use strict;
use warnings;
use CGI::Carp qw(fatalsToBrowser);
use CGI::Simple;
use HTML::Template;
use DBI;
use CommonWeb qw(:ALL);
use CustIDCK;
#use POSIX qw(ceil);
use NetAddr::IP;
use Sys::Syslog;
# don't remove!  required for GNU/FHS-ish install from tarball
##uselib##
use MyIPDB;
openlog "IPDB-admin","pid","$IPDB::syslog_facility";
# Collect the username from HTTP auth.  If undefined, we're in a test environment.
my $authuser;
if (!defined($ENV{'REMOTE_USER'})) {
  $authuser = '__temptest';
} else {
  $authuser = $ENV{'REMOTE_USER'};
}
syslog "debug", "$authuser active";
# 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("Database error: $errstr\n");
}
initIPDBGlobals($ip_dbh);
##fixme - need to autofill this somehow
$ENV{HTML_TEMPLATE_ROOT} = '/home/kdeugau/dev/ipdb/trunk/templates';
if ($IPDBacl{$authuser} !~ /A/) {
  print "Content-Type: text/html\n\n".
	"\n
\n\tAccess denied \n".
	qq(\t \n).
	qq(\t \n).
	"\n\n".
	qq(Access to this tool is restricted.  Contact the IPDB administrator  \n).
	"for more information.\n\n\n";
  exit;
}
# 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  (list with multiple selection)
my %webvar = $q->Vars;
print "Content-type: text/html\n\n".
	"\n\n\t[IPDB admin tools] \n".
	qq(\t \n).
	qq(\t \n).
	"\n\n".
	"IPDB - Administrative Tools \n \n";
if(!defined($webvar{action})) {
  $webvar{action} = "";   #shuts up the warnings.
  my $typelist = '';
  $sth = $ip_dbh->prepare("select type,listname from alloctypes where listorder < 900 order by listorder");
  $sth->execute;
  my @data = $sth->fetchrow_array;
  $typelist .= "$data[1] \n";
  while (my @data = $sth->fetchrow_array) {
    $typelist .= "$data[1] \n";
  }
  my $masterlist = '';
  $sth = $ip_dbh->prepare("select cidr,mtime from masterblocks order by cidr");
  $sth->execute;
  while (my @data = $sth->fetchrow_array) {
    $masterlist .= "$data[0] ($data[1]) \n";
  }
  print qq(WARNING:  There are FAR fewer controls on what you can do here.  Use the
main interface if at all possible.
 
 
 rWHOIS tools:
Edit customer data for rWHOIS  - data used for
blocks with the SWIP box checkmarked.  Links to edit/add data are on this page.
List IP Pools  for manual tweaking and updates
Manage users  (add/remove users;  change
internal access controls - note that this does NOT include IP-based limits) 
Manage email notice options  (pick which events
and allocation types cause notifications;  configure recipient lists for notices)
 Consistency check tools 
General :  Check general netblock consistency. 
Free space :  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 'Back  to main ';
}
## Possible actions.
if ($webvar{action} eq 'alloc') {
  # OK, we know what we're allocating.
  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");
  }
  $sth = $ip_dbh->prepare("select def_custid from alloctypes where type='$webvar{alloctype}'");
  $sth->execute;
  my @data = $sth->fetchrow_array;
  my $custid = $data[0];
  if ($custid eq '') {
    if ($webvar{custid} !~ /^(?:\d{10}|\d{7}|STAFF)(?:-\d\d?)?$/) {
      # Force uppercase for now...
      $webvar{custid} =~ tr/a-z/A-Z/;
      # Crosscheck with billing.
      my $status = CustIDCK->custid_exist($webvar{custid});
      if ($CustIDCK::Error) {
	printError("Error verifying customer ID: ".$CustIDCK::ErrMsg);
	return;
      }
      if (!$status) {
	printError("Customer ID not valid.  Make sure the Customer ID ".
	  "is correct. \nUse STAFF for staff static IPs, and $IPDB::defcustid for any other ".
	  "non-customer assignments.");
	return;
      }
    }
    # Type that doesn't have a default custid
    $custid = $webvar{custid};
  }
  my $cidr = new NetAddr::IP $webvar{cidr};
  my @data;
  if ($webvar{alloctype} eq 'rm') {
    $sth = $ip_dbh->prepare("select cidr from freeblocks where cidr >>='$cidr' and routed='n'");
    $sth->execute;
    @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];
  } 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')");
    $sth->execute;
    @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];
  }
  my $alloc_from = new NetAddr::IP $data[0];
  $sth->finish;
  my $cities = '';
  foreach my $city (@citylist) {
    $cities .= "$city \n";
  }
  print qq(
);
} elsif ($webvar{action} eq 'confirm') {
  print "Assigning $webvar{cidr} to $webvar{custid} (\"$webvar{desc}\") as ".
	"$disp_alloctypes{$webvar{alloctype}}... \n";
  # Only need to check city here.
  if ($webvar{city} eq '-') {
    printError("Invalid customer location!  Go back and select customer's location.");
  } else {
    if ($webvar{alloctype} =~ /^.i$/) {
      $sth = $ip_dbh->prepare("update poolips set available='n', custid='$webvar{custid}', ".
	"city='$webvar{city}', description='$webvar{desc}', notes='$webvar{notes}' ".
	"where ip='$webvar{cidr}'");
      $sth->execute;
      if ($sth->err) {
	print "Allocation failed!  DBI said:\n".$sth->errstr."\n";
        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}'";
	mailNotify($ip_dbh, "a$webvar{alloctype}",
	  "$disp_alloctypes{$webvar{alloctype}} $webvar{cidr} allocated to customer".
	  " $webvar{custid}\n".
	  "Description: $webvar{desc}\n\nAllocated by: $authuser\n");
      }
    } else {
      my ($retcode,$msg) = allocateBlock($ip_dbh, $webvar{cidr}, $webvar{alloc_from},
	$webvar{custid}, $webvar{alloctype}, $webvar{city}, $webvar{desc}, $webvar{notes},
	$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";
        syslog "err", "($authuser) Allocation of '$webvar{cidr}' to '$webvar{custid}' as ".
		"'$webvar{alloctype}' failed: '$msg'";
      }
    } # static IP vs netblock
  } # done city check
} 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 =~ 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( $_ \n);
  }
  $html =~ s/\$\$BLOCKLIST\$\$/$availblocks/g;
  print $html;
} elsif ($webvar{action} eq 'touch') {
  print "Touching master $webvar{whichmaster}\n";
  $sth = $ip_dbh->prepare("update masterblocks set mtime=now() where cidr='$webvar{whichmaster}'");
  $sth->execute;
  if ($sth->err) {
    print "Error updating modified timestamp on master $webvar{whichmaster}: ".$sth->errstr."\n";
  }
} elsif ($webvar{action} eq 'listcust') {
  print qq(Add new entry:\n
);
  print "Click CustID to edit existing customer contact data:\n".
	"
\nCustID Name Tech handle  \n";
  $sth = $ip_dbh->prepare("select custid,name,tech_handle from customers order by custid");
  $sth->execute;
  while (my @data = $sth->fetchrow_array) {
    print qq($data[0]  ).
	"$data[1] $data[2]  \n";
  }
  print "
\n";
} 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});
  }
  $sth = $ip_dbh->prepare("select custid,name,street,city,province,".
	"country,pocode,phone,tech_handle,abuse_handle,admin_handle,special ".
	"from customers where custid='$webvar{custid}'");
  $sth->execute;
  my ($custid, $name, $street, $city, $prov, $country, $pocode, $phone, $tech, $abuse, $admin, $special) =
	$sth->fetchrow_array;
  print qq( 
Explanation for "Special" field: 
This is a temporary place to define the WHOIS "net name" for a block.
It may be removed later, more likely migrated elsewhere.
It's formatted like this, one line for each custom net name:
NetName[CIDR block]: NET-NAME 
Example:
NetName192.168.236.0/24: MEGAWIDGET-1 
Note:
Spacing is important - there should only be ONE space, in between the colon and the net name.
 The CIDR block name nust include all four octets - no short forms are accepted.
 Net names must be all uppercase, and consist only of A-Z, 0-9, and - (same as for SWIPed net names).
  
 
);
} elsif ($webvar{action} eq 'updcust') {
  $sth = $ip_dbh->prepare("UPDATE customers SET".
	" name=?, street=?, city=?, province=?, country=?, pocode=?,".
	" phone=?, tech_handle=?, abuse_handle=?, admin_handle=?, special=?".
	" WHERE custid=?");
  $sth->execute($webvar{name}, $webvar{street}, $webvar{city}, $webvar{province}, 
	$webvar{country}, $webvar{pocode}, $webvar{phone}, $webvar{tech_handle}, 
	$webvar{abuse_handle}, $webvar{admin_handle}, $webvar{special}, $webvar{custid});
  print "Updated $webvar{custid} \n".
	qq(
CustID: $webvar{custid}  
Name: $webvar{name}  
Street: $webvar{street}  
City: $webvar{city}  
Province/State: $webvar{province}  
Country: $webvar{country}  
Postal/ZIP Code: $webvar{pocode}  
Phone: $webvar{phone}  
Contacts/ARIN Handles: 
 Tech: $webvar{tech_handle} 
 Abuse: $webvar{abuse_handle} 
 Admin: $webvar{admin_handle} 
  
"Special": $webvar{special}  
Back  to rWHOIS customer list \n);
} elsif ($webvar{action} eq 'showpools') {
  print "IP Pools currently allocated:\n".
	"\nPool # of free IPs  \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($key  ).
	"$poolfree{$key}  \n";
  }
  print "
\n";
} 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='".
	(($webvar{available} eq 'y') ? 'y' : 'n').
	"', notes='$webvar{notes}', description='$webvar{desc}' ".
	"where ip='$webvar{ip}'");
  $sth->execute;
  if ($sth->err) {
    print "Error updating pool IP $webvar{ip}: $@ \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 \n";
    syslog "notice", "$authuser updated pool IP $webvar{ip}";
  }
} elsif ($webvar{action} eq 'showusers') {
  print "Notes: \n".
	"Admin users automatically get all other priviledges.\n".
	" Everyone has basic read access.\n".
	" Add new user:\n".
	"Username:  \n".
	"Password:    Password is pre-encrypted (MUST be crypt() encrypted) \n".
	" \n";
  print " Users with access:\n\n";
} elsif ($webvar{action} eq 'updacl') {
  print "Updating ACL for $webvar{username}: \n";
  my $acl = 'b';
  if ($webvar{admin} eq 'on') {
    $acl .= "acdsA";
  } else {
    $acl .= ($webvar{add} eq 'on' ? 'a' : '').
	($webvar{change} eq 'on' ? 'c' : '').
	($webvar{del} eq 'on' ? 'd' : '').
	($webvar{sysnet} eq 'on' ? 's' : '');
  }
  print "New ACL: $acl \n";
  $sth = $ip_dbh->prepare("update users set acl='$acl' where username='$webvar{username}'");
  $sth->execute;
  print "OK\n" if !$sth->err;
  print qq(Back  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 " Error adding user: ".$sth->errstr;
  } else {
    print "OK\n";
  }
  print qq(Back  to user listing\n);
} elsif ($webvar{action} eq 'deluser') {
  print "Deleting user $webvar{username}. \n";
  $sth = $ip_dbh->prepare("delete from users where username='$webvar{username}'");
  $sth->execute;
  print "OK\n" if !$sth->err;
  print qq(Back  to user listing\n);
} elsif ($webvar{action} eq 'emailnotice') {
  print "Email notice management: \nClick the email addresses to edit that list.";
  $sth = $ip_dbh->prepare("SELECT action,reciplist FROM notify");
  $sth->execute;
  print "\n";
  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 "$action_out ".
	qq($reciplist  ).
	qq(Delete  \n);
  }
  print qq(Known "special" codes: 
	swi: Notify if block being updated has SWIP flag set 
   
);
# add new entries from this tangle:
  print "Add new notification: \n".
	"Note:  Failure notices on most conditions are not yet supported.\n";
  print qq(
  
);
  ## done spitting out add-new-spam-me-now table
} elsif ($webvar{action} eq 'addnotice') {
  $webvar{alloctype} = $webvar{special} if $webvar{msgaction} eq 's:';
  if ($webvar{msgaction} && $webvar{alloctype} && $webvar{reciplist}) {
    $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";
    $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! \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. \n};
  }
  print qq(Back to email notice list \n);
} elsif ($webvar{action} eq 'delnotice') {
  print "Deleting notices on ".dispNoticeCode($webvar{code}.$webvar{alloctype}).":\n";
  $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! \n"
  }
  print qq(Back to email notice list \n);
} elsif ($webvar{action} eq 'ednotice') {
  print "Editing recipient list for '".dispNoticeCode($webvar{code})."': \n";
  $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( \n).
	qq( \n";
} elsif ($webvar{action} eq 'updnotice') {
  print "Updating recipient list for '".dispNoticeCode($webvar{code})."': \n";
  $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! \n"
  }
  print qq(Back to email notice list \n);
} elsif ($webvar{action} ne '') {
  print "webvar{action} check failed: Don't know how to $webvar{action}";
}
# Hokay.  This is a little different.  We have a few specific functions here:
#  -> Assign arbitrary subnet from arbitrary free space
#  -> Tweak individual DB fields
#
print qq(Back  to main interface\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;
# Tweak allocfrom into shape.
sub fix_allocfrom {
  if ($webvar{allocfrom} =~ /^(\d+\.){2}\d+$/) {
    # 3-octet class C specified
    $webvar{allocfrom} .= ".0/24";
  } elsif ($webvar{allocfrom} =~ /^(\d+\.){3}\d+$/) {
    # 4-octet IP specified;  
    $webvar{allocfrom} .= "/24";
  }
}
# List free blocks in a /24 for arbitrary manual allocation
sub showfree($) {
  my $cidr = new NetAddr::IP $_[0];
  print "Showing free blocks in $cidr \n".
	"\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\n";
  # notes
  print "Notes: \n".
	"\nUse the main interface to update description and notes fields\n".
	" Changing the allocation type here will NOT affect IP pool data.\n".
	"  \n";
}
# 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}': '$@'";
  } else {
    # If we get here, the operation succeeded.
    syslog "notice", "$authuser updated $webvar{block}";
    print "Allocation $webvar{block} updated \n";
  }
  # need to get /24 that block is part of
  my @bits = split /\./, $webvar{block};
  $bits[3] = "0/24";
  showAllocs((join ".", @bits));
}
# showPool()
# List all IPs in a pool, and allow arbitrary admin changes to each
# Allow changes to ALL fields
sub showPool($) {
  my $pool = new NetAddr::IP $_[0];
  print qq(Listing pool $pool:\nUpdate the following record:\n";
}
# interpret the notify codes
sub dispNoticeCode {
  my $code = shift;
  my $action_out = '';
  if ($code =~ /^s:/) {
    $code =~ s/^s:/Special: /;
    return $code;
  }
  if ($code =~ /^f:(.+)$/) {
    $code =~ s/^f://;
    $action_out = "Failure on ";
  }
  if (my $target = $code =~ /^n(.+)/) {
    $action_out .= "New ";
    if ($1 eq 'ci') { $action_out .= "city"; }
    elsif ($1 eq 'no') { $action_out .= "node"; }
    else { $action_out .= '<unknown>'; }
  } else {
    my ($action,$target) = ($code =~ /^(.)(.+)$/);
    if ($action eq 'a')      { $action_out .= 'Add '; }
    elsif ($action eq 'u')   { $action_out .= 'Update '; }
    elsif ($action eq 'd')   { $action_out .= 'Delete '; }
##fixme:  what if we get something funky?
# What about the eleventy-billion odd combinations possible?
# this should give an idea of the structure tho
    if ($target eq 'a') { $action_out .= "all"; }
    elsif ($target eq '.i') {
      $action_out .= "all static IPs";
    }
    else { $action_out .= $disp_alloctypes{$target}; }
  }
  return $action_out;
}