#!/usr/bin/perl
# ipdb/cgi-bin/main.cgi
# Started munging from noc.vianet's old IPDB 04/22/2004
# Current version 05/18/2004 kdeugau@vianet
#use strict;
use warnings;
use CGI::Carp qw/fatalsToBrowser/;
use DBI;
use CommonWeb qw/:ALL/;
use POSIX qw/ceil/;
use NetAddr::IP;
checkDBSanity();
#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(*) ..."
# Not sure if this is needed any more.
sub parseInput($);
my $RESULTS_PER_PAGE = 50;
my %webvar = parse_post();
cleanInput(\%webvar);
# Start new code: 04/22/2004
###
# Initial display: Show master blocks with total allocated subnets, total free subnets
# foreach block (allocations[type=cust])
# check which master it's in
# increment appropriate counter
# foreach block (freeblocks)
# check which master it's in
# increment appropriate counter
# Some things we will need to do every time.
# Why not a global DB handle?
# We already know the DB is happy, (checkDBSanity) otherwise we wouldn't be here.
$ip_dbh = DBI->connect("dbi:mysql:ipdb", "root", "");
# Slurp up the master block list - we need this several places
$sth = $ip_dbh->prepare("select * from masterblocks;");
$sth->execute;
$i=0;
for ($i=0; @data = $sth->fetchrow_array(); $i++) {
$masterblocks[$i] = new NetAddr::IP $data[0];
$allocated{"$masterblocks[$i]"} = 0;
$free{"$masterblocks[$i]"} = 0;
$bigfree{"$masterblocks[$i]"} = 128; # Larger number means smaller block.
# Set to 128 to prepare for IPv6
$routed{"$masterblocks[$i]"} = 0;
}
#main()
if(!defined($webvar{action})) {
$webvar{action} = "FUCK"; #shuts up the warnings.
}
if($webvar{action} eq 'index') {
showSummary();
}
elsif ($webvar{action} eq 'newmaster') {
printHeader('');
$cidr = new NetAddr::IP $webvar{cidr};
print "
Adding $cidr as master block....\n";
$sth = $ip_dbh->prepare("insert into masterblocks values ('$webvar{cidr}')");
$sth->execute;
die $sth->errstr if ($sth->errstr());
# Unrouted blocks aren't associated with a city (yet). We don't rely on this
# elsewhere though; legacy data may have traps and pitfalls in it to break this.
# Thus the "routed" flag.
$sth = $ip_dbh->prepare("insert into freeblocks values ('$webvar{cidr}',".
$cidr->masklen.",'','n')");
$sth->execute;
die $sth->errstr if ($sth->errstr());
print "Success!
\n";
print "start querysub: query '$query'\n";
# this may happen with more than one subcategory. Unlikely, but possible.
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) Customer "name". If doing customer search, and we have non-numerics,
# search on customer name.
# 5) Invalid data which might be interpretable as an IP or something, but
# which probably shouldn't be for reasons of sanity.
if ($category =~ /all/) {
print "Showing all allocations\n";
my $count = countRows('select count(*) from allocations');
$sql = "select * from allocations order by cidr limit $offset,$RESULTS_PER_PAGE";
queryResults($sql, $webvar{page}, $count);
} elsif ($category =~ /cust/) {
# Query for a customer ID.
if ($query =~ /^\s*[0-9]+\s*$/) {
# All numeric. Search on customer ID.
$sql = "select * from allocations where custid like '%$query%'";
queryResults($sql, $webvar{page}, $count);
} else {
print "Searching for a customer based on (partial) name....\n";
$sth = $ip_dbh->prepare("select * from customers where name like '%$query%'");
$sth->execute;
if ($sth->rows eq 1) {
@data = $sth->fetchrow_array;
# Only 1 cust matched.
print "Found 1 cust. Displaying...\n";
$sql = "select * from allocations where custid like '%$data[0]%'";
queryResults($sql, $webvar{page}, $count);
} elsif ($sth->rows == 0) {
# D'Oh! Nothing found!
printAndExit("No customers found. Try searching on a smaller string.");
} else {
# More than one found. List'em and let the searcher decide.
print "Found more than one. Click the customer ID you want to show allocations for:\n";
startTable('custid','name','city','phone','abuse contact','description');
$count = 0;
while (@data = $sth->fetchrow_array) {
# custid,name,street,street2,city,province,pocode,phone,abuse,def_rdns,description
@row = ("$data[0]",
$data[1],$data[4],$data[7],$data[8],$data[10]);
printRow(\@row, 'color1' ) if($count%2==0);
printRow(\@row, 'color2' ) if($count%2!=0);
}
print "\n";
}
}
} elsif ($category =~ /ipblock/) {
# Query is for a partial IP, a CIDR block in some form, or a flat IP.
$query =~ s/\s+//g;
print "Looking for IP-based matches on '$query': \n";
if ($query =~ /\//) {
print "CIDR query. Results may vary.\n";
# 209.91.179/26 should show all /26 subnets in 209.91.179
($net,$maskbits) = split /\//, $query;
if ($query =~ /^(\d{1,3}\.){3}\d{1,3}\/\d{2}$/) {
# /0->/9 are silly to worry about right now. :/
print "Exact subnet search...\n";
$sth = $ip_dbh->prepare("select * from allocations where cidr='$query'");
$sth->execute;
if ($sth->rows == 0) {
print "No matches\n";
} elsif ($sth->rows == 1) {
@data = $sth->fetchrow_array;
print "Found $data[0]\n";
} else {
print "Too many matches (".$sth->rows.", should be 1). Database is b0rked.\n";
}
} else {
# select * from allocations where cidr like '$net%' and cidr like '%$maskbits'
}
} elsif ($query =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
($net,$ip) = ($query =~ /(\d{1,3}\.\d{1,3}\.\d{1,3})\.(\d{1,3})/);
print "Trying to find based on net '$net' and ip '$ip'...";
$sfor = new NetAddr::IP $query;
$sth = $ip_dbh->prepare("select * from allocations where cidr like '$net%'");
$sth->execute;
while (@data = $sth->fetchrow_array()) {
$cidr = new NetAddr::IP $data[0];
if ($cidr->contains($sfor)) {
print "Found '$cidr'!\n";
}
}
} elsif ($query =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.?$/) {
print "3-octet block pattern.";
$sql = "select * from allocations where cidr like '$query%'";
queryResults($sql, $webvar{page}, $count);
} else {
# This shouldn't happen, but if it does, whoever gets it deserves what they get...
printAndExit("Invalid query.");
}
} else {
# This shouldn't happen, but if it does, whoever gets it deserves what they get...
printAndExit("Invalid searchfor.");
}
} # viewBy
# args are: a reference to an array with the row to be printedthe
# class(stylesheet) to use for formatting. and the class - optional.
# if ommitting the class - call the sub as &printRow(\@array)
sub printRow
{
my ($rowRef,$class) = @_;
$class = '' if (!$class);
print "
\n";
foreach my $element (@$rowRef) {
print "
" if (!defined($element));
$element =~ s|\n||g;
print "
$element
\n";
}
print "
";
} # printRow
# 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.
sub queryResults($$$) {
my ($sql, $pageNo, $rowCount) = @_;
my $offset = 0;
$offset = $1 if($sql =~ m/.*limit\s+(.*),.*/);
my $sth = $ip_dbh->prepare($sql);
$sth->execute();
# Need some error checking...
print "About to start showing allocations: ".$ip_dbh->errstr;
startTable('Allocation','CustID','Type','City','Description');
my $count = 0;
while(my @row = ($sth->fetchrow_array())) {
# We need to munge row[0] here. We may also need to extract additional data.
$row[0] = qq($row[0]);
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.
$sth->finish();
my $upper = $offset+$count;
print "
Records found: $rowCount Displaying: $offset - $upper
\n";
print "\n";
# print the page thing..
if ($rowCount > $RESULTS_PER_PAGE) {
my $pages = ceil($rowCount/$RESULTS_PER_PAGE);
print qq(
";
}
} # queryResults
# Prints table headings. Accepts any number of arguments;
# each argument is a table heading.
sub startTable {
print qq(
);
foreach(@_) {
print qq(
$_
);
}
print "
\n";
} # startTable
# parseInput() unused?
###
# given the input as a param, it returns the type of input:
# either ipclass, name etc...
#
sub parseInput($)
{
my $input = $_[0];
return 'ipclass' if($input =~ m/^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.?$/);
return 'ipclass' if($input =~ m/^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.0$/);
return 'ip' if($input =~ m/^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
return 'ipc_nm' if($input =~ m/^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\s*\/\d{2}$/);
return 'ipc_nm' if($input =~ m/^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.0\s*\/\d{2}$/ );
return 'nm' if($input =~ m/^\s*\/\d{2}$/);
return 'name' if($input =~ m/^[\s\w\-]+$/);
return 'unknown';
}
sub countRows($)
{
my $ip_dbh = connectDB();
my $sth = $ip_dbh->prepare($_[0]);
$sth->execute();
my @a = $sth->fetchrow_array();
$sth->finish();
$ip_dbh->disconnect();
return $a[0];
}
# Initial display: Show master blocks with total allocated subnets, total free subnets
sub showSummary
{
print "Content-type: text/html\n\n";
startTable('Master netblock', 'Routed netblocks', 'Allocated netblocks',
'Free netblocks', 'Largest free block');
# Snag the allocations.
# I think it's too confusing to leave out internal allocations.
my $sth = $ip_dbh->prepare("select * from allocations");
$sth->execute();
while (@data = $sth->fetchrow_array()) {
# cidr,custid,type,city,description
# We only need the cidr
my $cidr = new NetAddr::IP $data[0];
foreach $master (@masterblocks) {
if ($master->contains($cidr)) {
$allocated{"$master"}++;
}
}
}
# Snag routed blocks
my $sth = $ip_dbh->prepare("select * from routed");
$sth->execute();
while (@data = $sth->fetchrow_array()) {
# cidr,maskbits,city
# We only need the cidr
my $cidr = new NetAddr::IP $data[0];
foreach $master (@masterblocks) {
if ($master->contains($cidr)) {
$routed{"$master"}++;
}
}
}
# Snag the free blocks.
my $sth = $ip_dbh->prepare("select * from freeblocks");
$sth->execute();
while (@data = $sth->fetchrow_array()) {
# cidr,maskbits,city
# We only need the cidr
my $cidr = new NetAddr::IP $data[0];
foreach $master (@masterblocks) {
if ($master->contains($cidr)) {
$free{"$master"}++;
if ($cidr->masklen < $bigfree{"$master"}) { $bigfree{"$master"} = $cidr->masklen; }
}
}
}
# Print the data.
$count=0;
foreach $master (@masterblocks) {
@row = ("$master",
$routed{"$master"}, $allocated{"$master"}, $free{"$master"},
( ($bigfree{"$master"} eq 128) ? ("<NONE>") : ("/".$bigfree{"$master"}) )
);
printRow(\@row, 'color1' ) if($count%2==0);
printRow(\@row, 'color2' ) if($count%2!=0);
$count++;
}
print "
\n";
print qq(Add new master block\n);
# For some *very* strange reason, we don't have to call printFooter here.
# If we do, the footer comes in twice...
# printFooter;
} # showSummary
# Display detail on master
# Alrighty then! We're showing routed blocks within a single master this time.
# We should be able to steal code from showSummary(), and if I'm really smart
# I'll figger a way to munge the two together. (Once I've done that, everything
# else should follow. YMMV.)
sub showMaster {
printHeader('');
print qq(
Summarizing routed blocks for ).
qq($webvar{block}:
\n);
startTable('Routed block','Routed to','Allocated blocks',
'Free blocks','Largest free block');
my $master = new NetAddr::IP $webvar{block};
my $sth = $ip_dbh->prepare("select * from routed");
$sth->execute();
$i=0;
while (@data = $sth->fetchrow_array()) {
my $cidr = new NetAddr::IP $data[0];
if ($master->contains($cidr)) {
$localmasters[$i++] = $cidr;
$free{"$cidr"} = 0;
$allocated{"$cidr"} = 0;
# Retain the routing destination
$routed{"$cidr"} = $data[2];
}
}
# Count the allocations
# Technically, I could just include everything, but this is "more elegant"
# and fractionally faster in that it doesn't have to compare routed blocks
# against themselves.
$sth = $ip_dbh->prepare("select * from allocations where type='e' or type='c'");
$sth->execute();
while (@data = $sth->fetchrow_array()) {
# cidr,custid,type,city,description
# We only need the cidr
my $cidr = new NetAddr::IP $data[0];
foreach $master (@localmasters) {
if ($master->contains($cidr)) {
$allocated{"$master"}++;
}
}
}
# Snag the free blocks.
$sth = $ip_dbh->prepare("select * from freeblocks");
$sth->execute();
while (@data = $sth->fetchrow_array()) {
# cidr,maskbits,city
# We only need the cidr
my $cidr = new NetAddr::IP $data[0];
foreach $master (@localmasters) {
if ($master->contains($cidr)) {
$free{"$master"}++;
}
}
}
# Print the data.
$count=0;
foreach $master (@localmasters) {
@row = ("$master",
$routed{"$master"}, $allocated{"$master"},
$free{"$master"}, " <null> ");
printRow(\@row, 'color1' ) if($count%2==0);
printRow(\@row, 'color2' ) if($count%2!=0);
$count++;
}
print qq(\n\n).
qq(
Unrouted blocks in $master:
\n);
startTable('Netblock','Range');
# Snag the free blocks.
$count = 0;
$sth = $ip_dbh->prepare("select * from freeblocks where routed='n'");
$sth->execute();
while (@data = $sth->fetchrow_array()) {
# cidr,maskbits,city
# We only need the cidr
my $cidr = new NetAddr::IP $data[0];
if ($master->contains($cidr)) {
@row = ("$cidr", $cidr->range);
printRow(\@row, 'color1' ) if($count%2==0);
printRow(\@row, 'color2' ) if($count%2!=0);
$count++;
}
}
print "\n";
printFooter;
} # showMaster
# Display details of a routed block
# Alrighty then! We're showing allocations within a routed block this time.
# We should be able to steal code from showSummary() and showMaster(), and if
# I'm really smart I'll figger a way to munge all three together. (Once I've
# done that, everything else should follow. YMMV.
# This time, we check the database before spewing, because we may
# not have anything useful to spew.
sub showRBlock {
printHeader('');
my $master = new NetAddr::IP $webvar{block};
my $sth = $ip_dbh->prepare("select * from allocations where type='e' or type='c'");
$sth->execute();
print qq(
Summarizing allocated blocks for ).
qq($master:
\n);
startTable('CIDR allocation','Type','CustID','Description');
$count=0;
while (@data = $sth->fetchrow_array()) {
# cidr,custid,type,city,description
my $cidr = new NetAddr::IP $data[0];
if (!$master->contains($cidr)) { next; }
@row = ("$data[0]",
$data[2], $data[1], $data[4]);
printRow(\@row, 'color1') if ($count%2 == 0);
printRow(\@row, 'color2') if ($count%2 != 0);
$count++;
}
print "\n";
# If the routed block has no allocations, by definition it only has
# one free block, and therefore may be deleted.
if ($count == 0) {
print qq(
No allocations in ).
qq($master.
\n).
qq(\n);
}
print qq(\n
Free blocks within routed ).
qq(submaster $master
\n);
# print qq(
);
startTable('');
# Snag the free blocks. We don't really *need* to be pedantic about avoiding
# unrouted free blocks, but it's better to let the database do the work if we can.
$count = 0;
$sth = $ip_dbh->prepare("select * from freeblocks where routed='y'");
$sth->execute();
while (@data = $sth->fetchrow_array()) {
# cidr,maskbits,city
my $cidr = new NetAddr::IP $data[0];
if ($master->contains($cidr)) {
@row = ($data[0]);
printRow(\@row, 'color1') if ($count%2 == 0);
printRow(\@row, 'color2') if ($count%2 != 0);
$count++;
}
}
print "
\n";
printFooter;
} # showRBlock
sub showFree
{
$| = 1;
my $count = 0;
print "Content-type: text/html\n\n";
my $ip_dbh = connectDB();
my $sth = $ip_dbh->prepare("select count(*) from `ips` where available='1'");
$sth->execute();
my $number = ($sth->fetchrow_array())[0];
$sth = $ip_dbh->prepare("select SUBSTRING_INDEX(ip, '.', '3') sorter from ips where available = '1' group by sorter order by sorter");
$sth->execute();
my $a;
$sth->bind_columns(\$a);
my @ipclass;
while($sth->fetch())
{
push(@ipclass, $a);
}
startTable('','IP class', 'Free IPs', 'Largest Block');
foreach my $ipc (@ipclass)
{
next if (!$ipc);
$sth = $ip_dbh->prepare("select ip, ABS(SUBSTRING_INDEX(ip, '.', '-1')) sorter from ips where ip like '$ipc.%' and available = '1' order by sorter");
$sth->execute();
my $ip;
my @ips;
while($ip = ($sth->fetchrow_array())[0])
{
push(@ips, $ip);
}
my $length = scalar(@ips);
next if($length < 1);
my $last = $length-1;
my $run = 0;
my $max = 0;
my $oldSubnet;
for(my $i = 0; $i < $length; $i++)
{
$ips[$i] =~ m/.*\.(\d+)$/;
my $subnet = $1;
$oldSubnet = $subnet if(!$oldSubnet);
#case when this subnet is contiguous.
if($i == $last ) #case when we're on the last one
{
if($subnet == ($oldSubnet+1)){
$run++;}
if($run > $max){
$max = $run;}
}
elsif ($subnet == ($oldSubnet+1) || $subnet==$oldSubnet )
{
$run++;
$oldSubnet = $subnet;
}
else #case when this subnet is non-contiguous.
{
if($run > $max)
{
$max = $run;
}
$run = 1;
undef($oldSubnet);
}
}#end for
$sth = $ip_dbh->prepare("select count(*) from ips where ip LIKE '$ipc%' and available = '1'");
$sth->execute();
my $numberFree = ($sth->fetchrow_array())[0];
next if($numberFree == 0);
my $viewButton = qq( );
$ipc = qq($ipc);
my @row = ($viewButton, $ipc, $numberFree, $max);
printRow(\@row, 'color1' ) if($count%2==0);
printRow(\@row, 'color2' ) if($count%2!=0);
$count++;
}#end foreach ipclass
$sth->finish();
$ip_dbh->disconnect();
print "";
print "
There are a total of $number unused IPs.
";
}#end sub showFree
sub showIndex
{
$| = 1;
my $count = 0;
print "Content-type: text/html\n\n";
my $ip_dbh = connectDB();
my $sth = $ip_dbh->prepare("select count(*) from `ips`");
$sth->execute();
my $number = ($sth->fetchrow_array())[0];
$sth = $ip_dbh->prepare("select SUBSTRING_INDEX(ip, '.', '3') sorter from ips group by sorter order by sorter");
$sth->execute();
my $a;
$sth->bind_columns(\$a);
my @ipclass;
while($sth->fetch())
{
push(@ipclass, $a);
}
startTable('','IP class', 'Free IPs', 'Largest Block');
foreach my $ipc (@ipclass)
{
next if (!$ipc);
$sth = $ip_dbh->prepare("select ip, ABS(SUBSTRING_INDEX(ip, '.', '-1')) sorter from ips where ip like '$ipc%' order by sorter");
$sth->execute();
my $ip;
my @ips;
while($ip = ($sth->fetchrow_array())[0])
{
push(@ips, $ip);
}
my $length = scalar(@ips);
next if($length < 1);
my $last = $length-1;
my $run = 0;
my $max = 0;
my $oldSubnet;
for(my $i = 0; $i < $length; $i++)
{
$ips[$i] =~ m/.*\.(\d+)$/;
my $subnet = $1;
$oldSubnet = $subnet if(!$oldSubnet);
#case when this subnet is contiguous.
if($i == $last ) #case when we're on the last one
{
if($subnet == ($oldSubnet+1)){
$run++;}
if($run > $max){
$max = $run;}
}
elsif ($subnet == ($oldSubnet+1) || $subnet==$oldSubnet )
{
$run++;
$oldSubnet = $subnet;
}
else #case when this subnet is non-contiguous.
{
if($run > $max)
{
$max = $run;
}
$run = 1;
undef($oldSubnet);
}
}#end for
$sth = $ip_dbh->prepare("select count(*) from ips where ip LIKE '$ipc%' and available =1");
$sth->execute();
my $numberFree = ($sth->fetchrow_array())[0];
next if($numberFree == 0);
my $viewButton = qq( );
$ipc = qq($ipc);
my @row = ($viewButton, $ipc, $numberFree, $max);
printRow(\@row, 'color1' ) if($count%2==0);
printRow(\@row, 'color2' ) if($count%2!=0);
$count++;
}#end foreach ipclass
$sth->finish();
$ip_dbh->disconnect();
print "";
print "
);
}#end sub showIndex
sub showFreeDetail
{
printHeader('');
my $ip_dbh = connectDB();
my $sth = $ip_dbh->prepare("select ip, ABS(SUBSTRING_INDEX(ip, '.', '-1')) sorter from ips where ip like '$webvar{ipc}%' and available = '1' order by sorter");
$sth->execute();
my @ips;
while(my @a = $sth->fetchrow_array())
{
push(@ips, $a[0]);
}
my $length = scalar(@ips);
my $last = $length - 1 ;
my %blocks; #name=subnet it starts at , value=number of ips
my $oldSubnet = -1;
my $first;
my $run = 0;
for(my $i = 0; $i < $length; $i++)
{
$ips[$i] =~ m/.*\.(\d+)/;
my $subnet = $1;
$first = $subnet if(!defined($first));
if($i == $last ) #case when we're on the last one
{
if($subnet == ($oldSubnet+1))
{
$run++;
$blocks{$first} = $run;
}
else
{
$blocks{$first} = $run;
$blocks{$subnet} = 1;
}
}
elsif ($subnet == ($oldSubnet+1) || $subnet== -1 ) #contig.
{
$run++;
$oldSubnet = $subnet;
}
else #case when this subnet is non-contiguous.
{
$blocks{$first} = $run;
$first = $subnet;
$run = 1; #because we have to count this one.
$oldSubnet=$subnet;
}
}
my $rows; #to be subbed into detail.html
my $count = 0;
foreach my $key (sort(keys %blocks))
{
if($count%2==0)
{
$rows .= qq(
\n);
}
$count++;
}
open(HTML, "../detail.html") || die "could not open detail.html : $!";
my $html = join('', );
$html =~ s/\$\$SUBROWS\$\$/$rows/;
$html =~ s/\$\$IPC\$\$/$webvar{ipc}/;
print $html;
close(HTML);
printFooter();
}
sub assignBlock
{
printHeader('');
open HTML, "../assign.html"
or die "Could not open assign.html: $!";
my $html = join('',);
close(HTML);
print $html;
printFooter();
} # assignBlock
# Take info on requested IP assignment and see what we can provide.
sub confirmAssign
{
printHeader('');
# Going to manually validate some items.
# custid and city are automagic.
validateInput();
if (!$webvar{maskbits}) {
printAndExit("Please enter a CIDR block length.");
}
# Snag the list of free blocks that are big enough for the requested allocation.
# Not yet sure what to do other than break if there's nothing.
# Not entirely sure how to detect that, either. :(
# need some logic to handle routing "special case"...
if ($webvar{alloctype} eq 'r') {
$sth = $ip_dbh ->prepare("select * from freeblocks where maskbits<=$webvar{maskbits}".
" and routed='n' order by maskbits desc");
$failmsg = "We do not have a free routeable block of that size. ".
"You will have to either route a set of smaller netblocks or a single smaller netblock."
} else {
$sth = $ip_dbh->prepare("select * from freeblocks where city='$webvar{city}'".
" and maskbits<=$webvar{maskbits} and routed='y' order by maskbits desc");
$failmsg = "You will have to route another superblock to $webvar{city} ".
"from one of the master blocks in Sudbury.";
}
$sth->execute();
@data = $sth->fetchrow_array();
if ($data[0] eq "") {
printAndExit("No suitable free block found. ".$failmsg);
}
# cidr,maskbits,city
# We only need the cidr
my $cidr = new NetAddr::IP $data[0];
print "Found one! \n";
# foreach $master (@masterblocks) {
# if ($master->contains($cidr)) {
# $free{"$master"}++;
# }
# }
# }
$alloc_from = $cidr;
if ($cidr->masklen() == $webvar{maskbits}) {
print "OK: Allocating $cidr to $webvar{custid} \n";
} else {
print "err case: need to allocate subblock of free block $cidr \n";
print "
\n";
print "Need a /$webvar{maskbits} from $cidr: \n";
$maskbits = $cidr->masklen();
while ($maskbits++ < $webvar{maskbits}) {
@subblocks = $cidr->split($maskbits);
print "$subblocks[0]\t$subblocks[1]\n";
} # while
$cidr = $subblocks[0];
print "
\n";
}
# Check for new customer iff we're doing a customer allocation
$custbits = "";
if ($webvar{alloctype} eq 'c') {
# This check to validate off fargo (?) instead
$sth = $ip_dbh->prepare("select * from customers where custid='$webvar{custid}'");
$sth->execute;
if ($sth->rows ne 1) {
open CUSTHTML, "../newcust.html"
or die "Could not open newcust.html: $!";
$custbits = join '', ;
close CUSTHTML;
$custbits =~ s|\$\$CUSTID\$\$|$webvar{custid}|g;
$custbits =~ s|\$\$CUSTCITY\$\$|$webvar{city}|g;
}
}
open HTML, "../confirm.html"
or die "Could not open confirm.html: $!";
my $html = join '', ;
close HTML;
# Stick in customer info as necessary - if it's blank, it just ends
# up as blank lines ignored in the rendering of the page
$html =~ s|\$\$CUSTBITS\$\$|$custbits|g;
# Stick in the allocation data
$html =~ s|\$\$ALLOC_TYPE\$\$|$webvar{alloctype}|g;
$html =~ s|\$\$ALLOC_FROM\$\$|$alloc_from|g;
$html =~ s|\$\$CIDR\$\$|$cidr|g;
$html =~ s|\$\$CITY\$\$|$webvar{city}|g;
$html =~ s|\$\$CUSTID\$\$|$webvar{custid}|g;
$webvar{desc} = desanitize($webvar{desc});
$html =~ s|\$\$DESC\$\$|$webvar{desc}|g;
$html =~ s|\$\$NOTES\$\$|$webvar{notes}|g;
$html =~ s|\$\$ACTION\$\$|insert|g;
print $html;
printFooter();
}
sub insertAssign
{
# Some things are done more than once.
validateInput();
printHeader('');
print "
\n";
# here we do the donkeywork of actually adding a block.
# Check cidr and alloc_from to see how bad it's going to be.
my $sth;
# MySQL doesn't enforce referential integrity, but Postgres can.
# So we insert the customer data (if any) before the allocation.
# Note that city may be DIFFERENT than the city used for allocation!
if ($webvar{newcust} eq 'y') {
$sth = $ip_dbh->prepare("insert into customers values ('$webvar{custid}', ".
"'$webvar{custname}', '$webvar{custaddr1}', '$webvar{custaddr2}', ".
"'$webvar{custcity}', '$webvar{custprov}', '$webvar{custpocode}', ".
"'$webvar{custphone}', '$webvar{custabuse}', '$webvar{custrdns}', ".
"'$webvar{custdesc}')");
$sth->execute;
print "customers: '".$sth->errstr."'\n";
}
$cidr = new NetAddr::IP $webvar{fullcidr};
$alloc_from = new NetAddr::IP $webvar{alloc_from};
if ($webvar{fullcidr} eq $webvar{alloc_from}) {
# Easiest case- insert in one table, delete in the other, and go home.
# insert into allocations values (cidr,custid,type,city,desc) and
# delete from freeblocks where cidr='cidr'
# For data safety on non-transaction DBs, we delete first.
# new logic: is it a routing allocation?
if ($webvar{alloctype} eq 'r') {
# easiest case in the set. Just update the table to indicate where
# the block is routed, and that it's available for further breakdown.
$sth = $ip_dbh->prepare("update freeblocks set routed='y',city='$webvar{city}'".
" where cidr='$webvar{fullcidr}'");
$sth->execute;
$sth = $ip_dbh->prepare("insert into routed values ('$webvar{fullcidr}',".
$cidr->masklen.",'$webvar{city}')");
$sth->execute;
} else {
# Gotta clean the data for db transactions before actually doing this.
$sth = $ip_dbh->prepare("delete from freeblocks where cidr='$webvar{fullcidr}'");
$sth->execute;
$sth = $ip_dbh->prepare("insert into allocations values ('$webvar{fullcidr}',".
"'$webvar{custid}','$webvar{alloctype}','$webvar{city}','$webvar{desc}',".
"'$webvar{notes}')");
$sth->execute;
# Gotta check for errors too.
}
} else {
# OK, this isn't so easy. How bad is it going to get?
# i=0
# while ( )
# newfree[i++] = current->split(newalloc->maskbits)[1]
$wantmaskbits = $cidr->masklen;
$maskbits = $alloc_from->masklen();
$i=0;
while ($maskbits++ < $wantmaskbits) {
@subblocks = $alloc_from->split($maskbits);
$newfreeblocks[$i++] = $subblocks[1];
} # while
# We should now have a collection of the block(s) to be added to freeblocks in @newfreeblocks
# The next block of execution should be in a transaction on the db.
# However, transactions aside, this should fail in the safest way possible.
# Delete old freeblocks entry
$sth = $ip_dbh->prepare("delete from freeblocks where cidr='$webvar{alloc_from}'");
$sth->execute();
print "Delete from freeblocks: '".$ip_dbh->errstr."' \n";
# now we have to do some magic for routing blocks
if ($webvar{alloctype} eq 'r') {
# Insert the new freeblocks entries
# Note that non-routed blocks are assigned to Sudbury by default
# --> that should be changed to a definite null entry of some kind.
$sth = $ip_dbh->prepare("insert into freeblocks values (?, ?, 'Sudbury','n')");
foreach $block (@newfreeblocks) {
$sth->execute("$block", $block->masklen);
print "Adding free block $block: '".$ip_dbh->errstr."' \n";
}
# Insert the entry in the routed table
$sth = $ip_dbh->prepare("insert into routed values ('$cidr',".
$cidr->masklen.",'$webvar{city}')");
$sth->execute;
print "Adding routing entry: '".$ip_dbh->errstr."' \n";
# Insert the (almost) same entry in the freeblocks table
$sth = $ip_dbh->prepare("insert into freeblocks values ('$cidr',".
$cidr->masklen.",'$webvar{city}','y')");
$sth->execute;
print "Adding freeblocks entry: '".$ip_dbh->errstr."' \n";
} else {
# Insert the new freeblocks entries
$sth = $ip_dbh->prepare("insert into freeblocks values (?, ?, ?,'y')");
foreach $block (@newfreeblocks) {
$sth->execute("$block", $block->masklen, $webvar{city});
}
# Insert the allocations entry
$sth = $ip_dbh->prepare("insert into allocations values ('$webvar{fullcidr}',".
"'$webvar{custid}','$webvar{alloctype}','$webvar{city}','$webvar{desc}')");
$sth->execute;
}
}
print qq(
The information was sucessfully added
);
printFooter();
}
sub validateInput
{
#number, city, CustomerID, name, swip, desc, notes
# if(defined($webvar{number}) and $webvar{number} eq '-')
# {
# printAndExit("Please choose the number of IPs to assign");
# }
if ($webvar{city} eq '-') {
printAndExit("Please choose a city.");
}
if ($webvar{alloctype} eq "c") {
if (!$webvar{custid}) {
printAndExit("Please enter a customer ID.");
}
} elsif ($webvar{alloctype} =~ /[eir]/){
# All non-customer allocations MUST be entered with "our" customer ID.
# I have Defined this as 6750400 for consistency.
$webvar{custid} = "6750400";
} else {
# Danger! Danger! alloctype should ALWAYS be set by a dropdown. Anyone
# managing to call things in such a way as to cause this deserves a cryptic error.
printAndExit("Invalid alloctype");
}
# if(!$webvar{name})
# {
# printAndExit("Please enter a customer name");
# }
return 1;
}
sub validateNumber($$$)
{
if(scalar(@_) < 3 )
{
die "Not enough arguments for the validateNumber sub";
}
my ($subnet, $ipc, $number) = @_;
my $upper = $number + $subnet; #upper bound
if($subnet % $number !=0)
{
printAndExit("A block of $number ips cannot start on subnet $subnet. Consult subnet calculator.")
}
if($upper > 256)
{
printAndExit("$number IPs starting at $subnet is greater than 255. Consider shifting to plan B.");
}
#check ips table to make sure that all ips in the range are free.
my $ip_dbh = connectDB();
my $sth = $ip_dbh->prepare("select ABS(SUBSTRING_INDEX(ip, '.', -1)) subnet, SUBSTRING_INDEX(ip,'.', 3) ipclass, available from ips having subnet >= $subnet and subnet < $upper and ipclass='$ipc'");
$sth->execute();
die $sth->errstr if($sth->errstr);
my ($s, $i, $free);
$sth->bind_columns(\$s, \$i, \$free);
while($sth->fetch())
{
if($free != 1)
{
printAndExit("IP address $i.$s has already been assigned");
}
}
my $rows = $sth->rows();
if($rows < $number)
{
printAndExit("There are not enough free ips starting at subnet $subnet to assign $number ips. Try a lower subnet.");
}
}
sub showEdit
{
printHeader('');
my $ipclass = $webvar{ipc};
my $subnet = $webvar{subnet};
if(!defined($subnet) || !defined($ipclass))
{
printAndExit("Not enough information to continue.\n");
}
open(HTML, "../editDisplay.html")|| die "Could not open editDisplay.html";
my $html = join('', );
close(HTML);
my $ip_dbh = connectDB();
my $sth = $ip_dbh->prepare("select city, name, swip, custid, description, notes from `blocks` where ipclass='$ipclass' and subnet='$subnet'");
$sth->execute() ||die;
my ($city, $name, $swip, $cust_id, $desc, $notes);
$sth->bind_columns(\$city, \$name, \$swip, \$cust_id, \$desc, \$notes);
my $rv = $sth->fetch();
$sth->finish();
$ip_dbh->disconnect();
if($rv == 0)
{
printAndExit("Nothing was found for ipclass $ipclass, subnet $subnet");
}
$html =~ s|\$\$IPC\$\$|$ipclass|g;
$html =~ s|\$\$SUBNET\$\$|$subnet|g;
$html =~ s|\$\$CITY\$\$|$city|g;
$html =~ s|\$\$NAME\$\$|$name|g;
if(!defined($swip)){
$swip = '-';}
elsif($swip eq 'Y'){
$swip = 'Yes';}
elsif($swip eq 'N'){
$swip = 'No';}
else{
$swip = '-';}
$html =~ s|||g;
$html =~ s|\$\$CUSTID\$\$|$cust_id|g;
$html =~ s|\$\$DESC\$\$|$desc|g;
$html =~ s|\$\$NOTES\$\$|$notes|g;
print $html;
printFooter();
}
# Displays details of a specific allocation in a form
# Allows update/delete
# action=edit
sub edit
{
printHeader('');
# gotta snag block info from db
$sth = $ip_dbh->prepare("select * from allocations where cidr='$webvar{block}'");
$sth->execute;
@data = $sth->fetchrow_array;
open (HTML, "../editDisplay.html") || die "Could not open editDisplay.html :$!";
my $html = join('', );
# We can't let the city be changed here; this block is a part of
# a larger routed allocation and therefore by definition can't be moved.
# block and city are static.
$html =~ s/\$\$BLOCK\$\$/$webvar{block}/g;
$html =~ s/\$\$CITY\$\$/$data[3]/g;
# Only 'e', 'c', or 'i' types should ever show up here... I hope.
# Whew. Depending on what's in the db for the allocation type,
# make sure that shows up as selected. Gotta love Perl.
# Doing this allows cleanly changing an allocation from customer
# to end-use/internal or vice versa without mangling everything.
# Note that anything changed *to* an internal allocation
# Will Have Its Customer ID Reset To 6750400.
$type = '';
$html =~ s/\$\$TYPE\$\$/$type/g;
# These can be modified, although CustID changes may get ignored.
$html =~ s/\$\$CUSTID\$\$/$data[1]/g;
$html =~ s/\$\$DESC\$\$/$data[4]/g;
$html =~ s/\$\$NOTES\$\$/$data[5]/g;
print $html;
printFooter();
} # edit()
# Stuff new info about a block into the db
# action=update
sub update {
printHeader('');
print "
\n";
print " block: $webvar{block}\n";
print " type: $webvar{alloctype}\n";
print "custid: $webvar{custid}\n";
print " desc: $webvar{desc}\n";
print " notes: $webvar{notes}\n";
# -> Always update desc and notes
# better yet, just update the customer id every time, based on allocation type...
if ($webvar{alloctype} eq 'c') {
print "Updating customer alloc:\n ";
$sth = $ip_dbh->prepare("update allocations set type='$webvar{alloctype}',".
"custid='$webvar{custid}',description='$webvar{desc}',notes='$webvar{notes}' ".
"where cidr='$webvar{block}'");
} else {
print "Updating non-customer alloc:\n ";
$sth = $ip_dbh->prepare("update allocations set type='$webvar{alloctype}',".
"custid='6750400',description='$webvar{desc}',notes='$webvar{notes}' ".
"where cidr='$webvar{block}'");
}
$sth->execute;
if($sth->errstr()) {
print $sth->errstr()
} else {
print "Update successful.\n";
}
print "
\n";
printFooter;
} # update()
# Delete an allocation.
sub remove
{
printHeader('');
#show confirm screen.
open(HTML, "../confirmRemove.html")|| die "Could not open confirmRemove.html :$!";
my $html = join('', );
close(HTML);
if (!defined($webvar{block})) {
printAndExit("Error 332");
}
my ($cidr, $custid, $type, $city, $desc, $notes);
if ($webvar{alloctype} eq 'r') {
$sth = $ip_dbh->prepare("select cidr,city from routed where cidr='$webvar{block}'");
$sth->execute();
die $sth->errstr() if($sth->errstr());
$sth->bind_columns(\$cidr,\$city);
$sth->execute();
$sth->fetch || die $sth->errstr();
$custid = "N/A";
$alloctype = $webvar{alloctype};
$desc = "N/A";
$notes = "N/A";
} else {
my $sth = $ip_dbh->prepare("select * from allocations where cidr='$webvar{block}'");
$sth->execute();
# die $sth->errstr() if($sth->errstr());
$sth->bind_columns(\$cidr, \$custid, \$alloctype, \$city, \$desc, \$notes);
$sth->fetch() || die;
}
if ($alloctype eq 'c') {
$type = "Customer";
} elsif ($alloctype eq 'e') {
$type = "End-use";
} elsif ($alloctype eq 'i') {
$type = "Internal";
} elsif ($alloctype eq 'r') {
$type = "Routing";
} else {
# If someone manages to get here, they deserve what they get.
die "Bad alloctype";
}
print "Type: '$type' alloctype: '$alloctype'";
$html =~ s|Please confirm|Please confirm removal of|;
$html =~ s|\$\$BLOCK\$\$|$cidr|g;
$html =~ s|\$\$TYPEFULL\$\$|$type|g;
$html =~ s|\$\$ALLOCTYPE\$\$|$alloctype|g;
$html =~ s|\$\$CITY\$\$|$city|g;
$html =~ s|\$\$CUSTID\$\$|$custid|g;
$html =~ s|\$\$DESC\$\$|$desc|g;
$html =~ s|\$\$NOTES\$\$|$notes|g;
$html =~ s|\$\$ACTION\$\$|finaldelete|g;
$html =~ s||
Warning: clicking confirm will remove this record entirely.
|;
print $html;
}
###
#
# given a netmask of the form /24 it will return the number of IPs
# e.g.: nm2number('/24') = 256
# Note: no checking.... if the caller does not provide a valid netmask,
# the passed value is returned untouched.
#
sub nm2number
{
my $netmask = $_[0];
if( $netmask =~ m|.*/24.*|){
$netmask = 256;}
elsif( $netmask =~ m|.*/25.*|){
$netmask = 128;}
elsif( $netmask =~ m|.*/26.*|){
$netmask = 64;}
elsif( $netmask =~ m|.*/27.*|){
$netmask = 32;}
elsif( $netmask =~ m|.*/28.*|){
$netmask = 16;}
elsif( $netmask =~ m|.*/29.*|){
$netmask = 8;}
elsif( $netmask =~ m|.*/30.*|){
$netmask = 4;}
elsif( $netmask =~ m|.*/31.*|){
$netmask = 2;}
elsif( $netmask =~ m|.*/32.*|){
$netmask = 1;}
return $netmask;
}
# Delete an allocation. Return it to the freeblocks table; munge
# data as necessary to keep as few records as possible in freeblocks
# to prevent weirdness when allocating blocks later.
sub finalDelete
{
printHeader('');
# if allocation block (type e, c, or i), we delete from allocations
# and clean up routed entries in freeblocks.
# If we're deleting a routed block, we delete from routed and clean
# up non-routed entries in freeblocks.
$cidr = new NetAddr::IP $webvar{block};
# Routing is the "special case"
if ($webvar{alloctype} eq 'r') {
$sth = $ip_dbh->prepare("delete from routed where cidr='$webvar{block}'");
$sth->execute;
# check ^ for errors
$sth = $ip_dbh->prepare("select * from freeblocks where ".
"maskbits<=".$cidr->masklen." and routed='n' order by maskbits desc");
} else {
$sth = $ip_dbh->prepare("delete from allocations where cidr='$webvar{block}'");
$sth->execute;
# check ^ for errors
$sth = $ip_dbh->prepare("select * from freeblocks where city='$webvar{city}'".
" and maskbits<=".$cidr->masklen." and routed='y' order by maskbits desc");
}
# Now we look for larger-or-equal-sized free blocks in the same master (routed)
# (super)block. If there aren't any, we can't combine blocks anyway. If there
# are, we check to see if we can combine blocks.
# Execute the statement prepared in the if-else above.
$sth->execute;
$i=0;
while (@data = $sth->fetchrow_array) {
my $testIP = new NetAddr::IP $data[0];
# NetAddr::IP->compact() attempts to produce the smallest inclusive block
# from the caller and the passed terms.
# EG: if you call $cidr->compact($ip1,$ip2,$ip3) when $cidr, $ip1, $ip2,
# and $ip3 are consecutive /27's starting on .0, you will get an
# array containing a single /25 as element 0
@together = $testIP->compact($cidr);
$num = @together;
if ($num == 1) {
$cidr = $together[0];
$combinelist[$i++] = $testIP;
}
}
# Clear old freeblocks entries - if any. $i==0 if not.
if ($i>0) {
$sth = $ip_dbh->prepare("delete from freeblocks where cidr=?");
foreach $block (@combinelist) {
$sth->execute($block);
}
}
# insert "new" freeblocks entry
if ($webvar{alloctype} eq 'r') {
$sth = $ip_dbh->prepare("insert into freeblocks values ('$cidr',".$cidr->masklen.
",'Sudbury','n')");
} else {
$sth = $ip_dbh->prepare("insert into freeblocks values ('$cidr',".$cidr->masklen.
",'$webvar{city}','y')");
}
$sth->execute;
print "
Success! $webvar{block} deleted.
\n";
printFooter;
} # finalDelete
sub view
{
printHeader('');
my $ipclass = $webvar{ipc};
my $subnet = $webvar{subnet};
if(!defined($subnet) || !defined($ipclass))
{
printAndExit("Not enough information to continue.\n");
}
open(HTML, "../view.html")|| die "Could not open view.html";
my $html = join('', );
close(HTML);
my $ip_dbh = connectDB();
my $sth = $ip_dbh->prepare("select city, name, swip, custid, description, notes from `blocks` where ipclass='$ipclass' and subnet='$subnet'");
$sth->execute() ||die;
my ($city, $name, $swip, $cust_id, $desc, $notes);
$sth->bind_columns(\$city, \$name, \$swip, \$cust_id, \$desc, \$notes);
my $rv = $sth->fetch();
$sth->finish();
$ip_dbh->disconnect();
if($rv == 0)
{
printAndExit("Nothing was found for ipclass $ipclass, subnet $subnet");
}
$html =~ s|\$\$IPC\$\$|$ipclass|g;
$html =~ s|\$\$SUBNET\$\$|$subnet|g;
$html =~ s|\$\$CITY\$\$|$city|g;
$html =~ s|\$\$NAME\$\$|$name|g;
if(!defined($swip)){
$swip = 'N.A.';}
elsif($swip eq 'Y'){
$swip = 'Yes';}
elsif($swip eq 'N'){
$swip = 'No';}
$html =~ s|\$\$SWIP\$\$|$swip|g;
$html =~ s|\$\$CUSTID\$\$|$cust_id|g;
$html =~ s|\$\$DESC\$\$|$desc|g;
$html =~ s|\$\$NOTES\$\$|$notes|g;
print $html;
printFooter();
}