#!/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: 2004-12-01 20:42:57 +0000 (Wed, 01 Dec 2004) $
# SVN revision $Rev: 94 $
# Last update by $Author: kdeugau $
###

use strict;
use warnings;
use CGI::Carp qw(fatalsToBrowser);
use DBI;
use CommonWeb qw(:ALL);
use IPDB qw(:ALL);
#use POSIX qw(ceil);
use NetAddr::IP;

use Sys::Syslog;

openlog "IPDB-admin","pid","local2";

# 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'};
}

if ($authuser !~ /^(kdeugau|jodyh|__temptest)$/) {
  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";

my %webvar = parse_post();
cleanInput(\%webvar);

my %full_alloc_types = (
	"ci","Cable pool IP",
	"di","DSL pool IP",
	"si","Server pool IP",
	"mi","Static dialup IP",
	"wi","Static wireless IP",
	"cp","Cable pool",
	"dp","DSL pool",
	"sp","Server pool",
	"mp","Static dialup pool",
	"wp","Static wireless pool",
	"dn","Dialup netblock",
	"dy","Dynamic DSL netblock",
	"dc","Dynamic cable netblock",
	"cn","Customer netblock",
	"ee","End-use netblock",
	"rr","Routed netblock",
	"ii","Internal netblock",
	"mm","Master block"
);

my $ip_dbh = connectDB;
my $sth;

print "Content-type: text/html\n\n".
	"<html>\n<head>\n\t<title>TEST [IPDB admin tools] TEST</title>\n</head>\n<body>\n".
	"<h2>IPDB - Administrative Tools</h2>\n<hr>\n";

if(!defined($webvar{action})) {
  $webvar{action} = "<NULL>";   #shuts up the warnings.
  print qq(WARNING:  There are FAR fewer controls on what you can do here.  Use the
main interface if at all possible.
<hr><form action="admin.cgi" method="POST">
<input type=hidden name=action value=alloc>
Allocate block from this /24: <input name=allocfrom>
<input type=submit value="List available free blocks">
</form>
<hr><form action="admin.cgi" method="POST">
<input type=hidden name=action value=alloctweak>
Manually update allocation data in this /24: <input name=allocfrom>
<input type=submit value="Show allocations">
);
} else {
  print '<a href="/ip/cgi-bin/admin.cgi">Back</a> to main<hr>';
}

if ($webvar{action} eq 'alloc') {
  fix_allocfrom();
  showfree($webvar{allocfrom});
} elsif ($webvar{action} eq 'alloctweak') {
  fix_allocfrom();
  showAllocs($webvar{allocfrom});
} elsif ($webvar{action} eq 'update') {
  update();
} elsif ($webvar{action} eq 'assign') {
  # Display a list of possible blocks within the requested block.
  open (HTML, "../admin_alloc.html")
	or croak "Could not open admin_alloc.html :$!";
  my $html = join('', <HTML>);
  $html =~ s/\$\$MASK\$\$/$webvar{masklen}/g;
  $html =~ s/\$\$ALLOCFROM\$\$/$webvar{allocfrom}/g;

  my $from = new NetAddr::IP $webvar{allocfrom};
  my @blocklist = $from->split($webvar{masklen});
  my $availblocks;
  foreach (@blocklist) {
    $availblocks .= qq(<tr><td colspan=2 align=center><input type=radio name=block value="$_">$_</td></tr>\n);
  }
  $html =~ s/\$\$BLOCKLIST\$\$/$availblocks/g;

  print $html;
} elsif ($webvar{action} eq 'confirm') {
  print "Assigning $webvar{block} to $webvar{custid} (\"$webvar{desc}\")...\n";
  allocBlock($ip_dbh, $webvar{allocfrom}, $webvar{block}, $webvar{alloctype},
	$webvar{custid}, $webvar{city}, $webvar{desc}, $webvar{notes});
  #my ($dbh,from,block,$type,$custid,$city,$desc,$notes) = @_;
} else {
  print "webvar{action} check failed";
}

# Hokay.  This is a little different.  We have a few specific functions here:
#  -> Assign arbitrary subnet from arbitrary free space
#  -> Tweak individual DB fields
#


printFooter;

$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";
  }
}


# Do the gruntwork of allocating a block.  This should really be in IPDB.pm.
sub allocBlock($$$$$$$$) {
  my ($dbh,undef,undef,$type,$custid,$city,$desc,$notes) = @_;
  my $from = new NetAddr::IP $_[1];
  my $block = new NetAddr::IP $_[2];

  # First, figure out what free blocks will get mangled.
  if ($from eq $block) {
    # Whee!  Easy.  Just allocate the block
  } else {
    # The complex case.  An allocation from a larger block.
    
    # Gotta snag the free blocks left over.
    my $wantmaskbits = $block->masklen;
    my $maskbits = $from->masklen;

    my @newfreeblocks;	# Holds free blocks generated from splitting the source freeblock.

    my $i=0;
    my $tmp_from = $from;	# So we don't munge $from
    while ($maskbits++ < $wantmaskbits) {
      my @subblocks = $tmp_from->split($maskbits);
      $newfreeblocks[$i++] = (($block->within($subblocks[0])) ? $subblocks[1] : $subblocks[0]);
      $tmp_from = ( ($block->within($subblocks[0])) ? $subblocks[0] : $subblocks[1] );
    } # while

# insert the data here.  Woo.
      # Begin SQL transaction block
      eval {
	# Delete old freeblocks entry
	$sth = $ip_dbh->prepare("delete from freeblocks where cidr='$from'");
	$sth->execute();

	# Insert the new freeblocks entries
	$sth = $ip_dbh->prepare("insert into freeblocks values (?, ?, ".
		"(select city from routed where cidr >>= '$block'),'y')");
	foreach my $block (@newfreeblocks) {
	  $sth->execute("$block", $block->masklen);
	}
	# Insert the allocations entry
	$sth = $ip_dbh->prepare("insert into allocations values ('$block',".
		"'$custid','$type','$city','$desc','$notes',".$block->masklen.")");
	$sth->execute;

	$ip_dbh->commit;
      }; # end eval
      if ($@) {
	carp "Transaction aborted because $@";
	eval { $ip_dbh->rollback; };
	syslog "err", "Allocation of '$block' to '$custid' as ".
		"'$type' by $authuser failed: '$@'";
	print "Allocation of $block as $full_alloc_types{$type} failed.\n";
      } else {
	syslog "notice", "$authuser allocated '$block' to '$custid'".
		" as '$type'";
	print "OK!<br>\n";
      }

  }
  # need to get /24 that block is part of
  my @bits = split /\./, $webvar{block};
  $bits[3] = "0/24";
  showAllocs((join ".", @bits));
}

# List free blocks in a /24 for arbitrary manual allocation
sub showfree($) {
  my $cidr = new NetAddr::IP $_[0];
  print "Showing free blocks in $cidr<br>\n".
	"<table border=1>\n";
  $sth = $ip_dbh->prepare("select * from freeblocks where cidr <<= '$cidr' order by cidr");
  $sth->execute;
  while (my @data = $sth->fetchrow_array) {
    my $temp = new NetAddr::IP $data[0];
    print "<tr><form action=admin.cgi method=POST><input type=hidden name=action value=assign>\n".
	qq(<td>$temp<input type=hidden name=allocfrom value="$temp"></td>\n).
	"<td>".
	(($temp->masklen == 30) ? '<input type=hidden name=masklen value=30>30'
	  : "<select name=masklen><option>30</option>\n<option>29</option>\n") .
	(($temp->masklen < 29) ? "<option>28</option>\n" : '') .
	(($temp->masklen < 28) ? "<option>27</option>\n" : '') .
	(($temp->masklen < 27) ? "<option>26</option>\n" : '') .
	(($temp->masklen < 26) ? "<option>25</option>\n" : '') .
	(($temp->masklen < 25) ? "<option>24</option>\n" : '') .
	"</td>".
	qq(<td>$data[2]</td><td><input type=submit value="Allocate from here"></td>).
	"\n</form></tr>\n";
  }
  print "</table>\n";
}


# Show allocations to allow editing.
sub showAllocs($) {
  my $cidr = new NetAddr::IP $_[0];
  print "Edit custID, allocation type, city for allocations in ".
	"$cidr:\n<table border=1>";
  $sth = $ip_dbh->prepare("select * from allocations where cidr <<= '$cidr' order by cidr");
  $sth->execute;
  while (my @data = $sth->fetchrow_array) {
    print "<tr><form action=admin.cgi method=POST><input type=hidden name=action value=update>\n".
	qq(<td>$data[0]<input type=hidden value="$data[0]" name=block></td>\n).
	qq(<td><input name=custid value="$data[1]"></td>\n);

    print "<td><select name=alloctype><option".
        (($data[2] eq 'cn') ? ' selected' : '') ." value='cn'>Customer netblock</option>\n<option".
	(($data[2] eq 'si') ? ' selected' : '') ." value='si'>Static IP - Server pool</option>\n<option".
	(($data[2] eq 'ci') ? ' selected' : '') ." value='ci'>Static IP - Cable</option>\n<option".
	(($data[2] eq 'di') ? ' selected' : '') ." value='di'>Static IP - DSL</option>\n<option".
	(($data[2] eq 'mi') ? ' selected' : '') ." value='mi'>Static IP - Dialup</option>\n<option".
	(($data[2] eq 'wi') ? ' selected' : '') ." value='wi'>Static IP - Wireless</option>\n<option".
	(($data[2] eq 'sp') ? ' selected' : '') ." value='sp'>Static Pool - Server pool</option>\n<option".
	(($data[2] eq 'cp') ? ' selected' : '') ." value='cp'>Static Pool - Cable</option>\n<option".
	(($data[2] eq 'dp') ? ' selected' : '') ." value='dp'>Static Pool - DSL</option>\n<option".
	(($data[2] eq 'mp') ? ' selected' : '') ." value='mp'>Static Pool - Dialup</option>\n<option".
	(($data[2] eq 'wp') ? ' selected' : '') ." value='wp'>Static Pool - Wireless</option>\n<option".
	(($data[2] eq 'ee') ? ' selected' : '') ." value='ee'>End-use netblock</option>\n<option".
	(($data[2] eq 'dn') ? ' selected' : '') ." value='dn'>Dialup netblock</option>\n<option".
	(($data[2] eq 'dy') ? ' selected' : '') ." value='dy'>Dynamic DSL netblock</option>\n<option".
	(($data[2] eq 'dc') ? ' selected' : '') ." value='dc'>Dynamic cable netblock</option>\n<option".
	(($data[2] eq 'ii') ? ' selected' : '') ." value='ii'>Internal netblock</option>\n".
        "</select></td>\n";
    print qq(<td><input name=city value="$data[3]"></td>\n).
	"<td>$data[4]</td><td>$data[5]</td>".
	qq(<td><input type=submit value="Update"></td></form></tr>\n);
  }
  print "</table>\n";

  # notes
  print "<hr><b>Notes:</b>\n".
	"<ul>\n<li>Use the main interface to update description and notes fields\n".
	"<li>Changing the allocation type here will NOT affect IP pool data.\n".
	"</ul>\n";
}


# 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/IP '$webvar{block}': '$@'";
  } else {
    # If we get here, the operation succeeded.
    syslog "notice", "$authuser updated $webvar{block}";
    print "Allocation $webvar{block} updated<hr>\n";
  }
  # need to get /24 that block is part of
  my @bits = split /\./, $webvar{block};
  $bits[3] = "0/24";
  showAllocs((join ".", @bits));
}
