#!/usr/bin/perl # ipdb/cgi-bin/main.cgi ### # SVN revision info # $Date$ # SVN revision $Rev$ # Last update by $Author$ ### # Copyright (C) 2004-2013 Kris Deugau use strict; use warnings; use CGI::Carp qw(fatalsToBrowser); use CGI::Simple; use HTML::Template; use DBI; use POSIX qw(ceil); use NetAddr::IP; use Sys::Syslog; # don't remove! required for GNU/FHS-ish install from tarball ##uselib## use CustIDCK; use MyIPDB; openlog "IPDB","pid","$IPDB::syslog_facility"; ## Environment. Collect some things, process some things, set some things... # 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'})) { $authuser = '__temptest'; } else { $authuser = $ENV{'REMOTE_USER'}; } # anyone got a better name? :P my $thingroot = $ENV{SCRIPT_FILENAME}; $thingroot =~ s|cgi-bin/main.cgi||; syslog "debug", "$authuser active, $ENV{'REMOTE_ADDR'}"; ##fixme there *must* be a better order to do things in so this can go back where it was # CGI fiddling done here so we can declare %webvar so we can alter $webvar{action} # to show the right page on DB errors. # 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 value have either encoded or bare newlines. # Also applies to privdata. $page->param(notes => $q->escapeHTML($webvar{notes},'y')); # Check to see if user is allowed to do anything with sensitive data my $privdata = ''; $page->param(privdata => $q->escapeHTML($webvar{privdata},'y')) if $IPDBacl{$authuser} =~ /s/; # Yay! This now has it's very own little home. $page->param(billinguser => $webvar{userid}) if $webvar{userid}; ##fixme: this is only needed iff confirm.tmpl and # confirmRemove.tmpl are merged (quite possible, just # a little tedious) $page->param(action => "insert"); } # end confirmAssign # Do the work of actually inserting a block in the database. sub insertAssign { if ($IPDBacl{$authuser} !~ /a/) { $aclerr = 'addblock'; return; } # Some things are done more than once. return if !validateInput(); if (!defined($webvar{privdata})) { $webvar{privdata} = ''; } # $code is "success" vs "failure", $msg contains OK for a # successful netblock allocation, the IP allocated for static # IP, or the error message if an error occurred. my ($code,$msg) = allocateBlock($ip_dbh, $webvar{fullcidr}, $webvar{alloc_from}, $webvar{custid}, $webvar{alloctype}, $webvar{city}, $webvar{desc}, $webvar{notes}, $webvar{circid}, $webvar{privdata}, $webvar{node}, $webvar{vrf}); if ($code eq 'OK' || $code eq 'WARN') { if ($webvar{alloctype} =~ /^.i$/) { $msg =~ s|/32||; $page->param(staticip => $msg); $page->param(custid => $webvar{custid}); $page->param(billinguser => $webvar{billinguser}); mailNotify($ip_dbh, "a$webvar{alloctype}", "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 { my $netblock = new NetAddr::IP $webvar{fullcidr}; $page->param(fullcidr => $webvar{fullcidr}); $page->param(alloctype => $disp_alloctypes{$webvar{alloctype}}); $page->param(custid => $webvar{custid}); if ($webvar{alloctype} eq 'pr' && $webvar{billinguser}) { $page->param(billinguser => $webvar{billinguser}); $page->param(custid => $webvar{custid}); $page->param(netaddr => $netblock->addr); $page->param(masklen => $netblock->masklen); } mailNotify($ip_dbh, "a$webvar{alloctype}", "ADDED: $disp_alloctypes{$webvar{alloctype}} allocation", "$disp_alloctypes{$webvar{alloctype}} $webvar{fullcidr} allocated to customer $webvar{custid}\n". "Description: $webvar{desc}\n\nAllocated by: $authuser\n"); } syslog "notice", "$authuser allocated '$webvar{fullcidr}' to '$webvar{custid}' as ". "'$webvar{alloctype}' ($msg)"; } else { syslog "err", "Allocation of '$webvar{fullcidr}' to '$webvar{custid}' as ". "'$webvar{alloctype}' by $authuser failed: '$msg'"; $page->param(err => "Allocation of $webvar{fullcidr} as '$disp_alloctypes{$webvar{alloctype}}'". " failed:
\n$msg\n"); } } # end insertAssign() # Does some basic checks on common input data to make sure nothing # *really* weird gets in to the database through this script. # Does NOT do complete input validation!!! sub validateInput { if ($webvar{city} eq '-') { $page->param(err => 'Please choose a city'); return; } # Alloctype check. chomp $webvar{alloctype}; if (!grep /$webvar{alloctype}/, keys %disp_alloctypes) { # 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. $page->param(err => 'Invalid alloctype'); return; } # CustID check # We have different handling for customer allocations and "internal" or "our" allocations if ($def_custids{$webvar{alloctype}} eq '') { if (!$webvar{custid}) { $page->param(err => 'Please enter a customer ID.'); return; } # Crosscheck with billing. my $status = CustIDCK->custid_exist($webvar{custid}); if ($CustIDCK::Error) { $page->param(err => "Error verifying customer ID: ".$CustIDCK::ErrMsg); return; } if (!$status) { $page->param(err => "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; } # print "\n"; } else { # New! Improved! And now Loaded From The Database!! if ((!$webvar{custid}) || ($webvar{custid} ne 'STAFF')) { $webvar{custid} = $def_custids{$webvar{alloctype}}; } } # Check POP location my $flag; if ($webvar{alloctype} eq 'rm') { $flag = 'for a routed netblock'; foreach (@poplist) { if (/^$webvar{city}$/) { $flag = 'n'; last; } } } else { $flag = 'n'; ##fixme: hook to force-set POP or city on certain alloctypes # if ($webvar{alloctype =~ /foo,bar,bz/ { $webvar{pop} = 'blah'; } if ($webvar{pop} && $webvar{pop} =~ /^-$/) { $flag = 'to route the block from/through'; } } # if the alloctype has a restricted city/POP list as determined above, # and the reqested city/POP does not match that list, complain if ($flag ne 'n') { $page->param(err => "Please choose a valid POP location $flag. Valid ". "POP locations are currently:
\n".join (" - ", @poplist)); return; } return 'OK'; } # end validateInput # Displays details of a specific allocation in a form # Allows update/delete # action=edit sub edit { # snag block info from db my $blockinfo = getBlockData($ip_dbh, $webvar{block}); # Clean up extra whitespace on alloc type. Mainly a legacy-data cleanup. $blockinfo->{type} =~ s/\s//; $page->param(block => $webvar{block}); $page->param(custid => $blockinfo->{custid}); $page->param(city => $blockinfo->{city}); $page->param(circid => $blockinfo->{circuitid}); $page->param(desc => $blockinfo->{description}); $page->param(notes => $blockinfo->{notes}); ##fixme The check here should be built from the database # Need to expand to support pool types too if ($blockinfo->{type} =~ /^.[ne]$/ && $IPDBacl{$authuser} =~ /c/) { $page->param(changetype => 1); $page->param(alloctype => [ { selme => ($blockinfo->{type} eq 'me'), type => "me", disptype => "Dialup netblock" }, { selme => ($blockinfo->{type} eq 'de'), type => "de", disptype => "Dynamic DSL netblock" }, { selme => ($blockinfo->{type} eq 'ce'), type => "ce", disptype => "Dynamic cable netblock" }, { selme => ($blockinfo->{type} eq 'we'), type => "we", disptype => "Dynamic wireless netblock" }, { selme => ($blockinfo->{type} eq 'cn'), type => "cn", disptype => "Customer netblock" }, { selme => ($blockinfo->{type} eq 'en'), type => "en", disptype => "End-use netblock" }, { selme => ($blockinfo->{type} eq 'in'), type => "in", disptype => "Internal netblock" }, ] ); } else { $page->param(disptype => $disp_alloctypes{$blockinfo->{type}}); $page->param(type => $blockinfo->{type}); } ## node hack my ($nodeid,$nodename) = getNodeInfo($ip_dbh, $webvar{block}); $page->param(havenodeid => $nodeid); if ($blockinfo->{type} eq 'fr' || $blockinfo->{type} eq 'bi' || $blockinfo->{type} eq 'ai') { $page->param(typesupportsnodes => 1); $page->param(nodename => $nodename); ##fixme: this whole hack needs cleanup and generalization for all alloctypes ##fixme: arguably a bug that presence of a nodeid implies it can be changed.. # but except for manual database changes, only the two types fr and bi can # (currently) have a nodeid set in the first place. if ($IPDBacl{$authuser} =~ /c/) { my $nlist = getNodeList($ip_dbh); foreach (@{$nlist}) { $$_{selme} = ($$_{node_id} == $nodeid); } $page->param(nodelist => $nlist); } } ## end node hack $page->param(vrf => $blockinfo->{vrf}); my ($lastmod,undef) = split /\s+/, $blockinfo->{lastmod}; $page->param(lastmod => $lastmod); # not happy with the upside-down logic, but... $page->param(swipable => $blockinfo->{type} !~ /.i/); $page->param(swip => $blockinfo->{swip} ne 'n') if $blockinfo->{swip}; # Check to see if we can display sensitive data $page->param(nocling => $IPDBacl{$authuser} =~ /s/); $page->param(privdata => $blockinfo->{privdata}); # ACL trickery - these two template booleans control the presence of all form/input tags $page->param(maychange => $IPDBacl{$authuser} =~ /c/); $page->param(maydel => $IPDBacl{$authuser} =~ /d/); } # edit() # Stuff new info about a block into the db # action=update sub update { if ($IPDBacl{$authuser} !~ /c/) { $aclerr = 'updateblock'; return; } # Make sure incoming data is in correct format - custID among other things. return if !validateInput; $webvar{swip} = 'n' if !$webvar{swip}; my %updargs = ( custid => $webvar{custid}, city => $webvar{city}, description => $webvar{desc}, notes => $webvar{notes}, circuitid => $webvar{circid}, block => $webvar{block}, type => $webvar{alloctype}, swip => $webvar{swip}, vrf => $webvar{vrf}, ); # Semioptional values $updargs{privdata} = $webvar{privdata} if $IPDBacl{$authuser} =~ /s/; $updargs{node} = $webvar{node} if $webvar{node}; my ($code,$msg) = updateBlock($ip_dbh, %updargs); if ($code eq 'FAIL') { syslog "err", "$authuser could not update block/IP '$webvar{block}': '$msg'"; $page->param(err => "Could not update block/IP $webvar{block}: $msg"); return; } # If we get here, the operation succeeded. syslog "notice", "$authuser updated $webvar{block}"; ##fixme: log details of the change? old way is in the .debug stream anyway. ##fixme: need to wedge something in to allow "update:field" notifications ## hmm. how to tell what changed? O_o mailNotify($ip_dbh, 's:swi', "SWIPed: $disp_alloctypes{$webvar{alloctype}} $webvar{block}", "$webvar{block} had SWIP status changed to \"Yes\" by $authuser") if $webvar{swip} eq 'on'; ## node hack if ($webvar{node} && $webvar{node} ne '-') { my $nodename = getNodeName($ip_dbh, $webvar{node}); $page->param(nodename => $nodename); } ## end node hack # Link back to browse-routed or list-pool page on "Update complete" page. my $cblock; if (my $pooltype = ($webvar{alloctype} =~ /^(.)i$/) ) { $page->param(backpool => 1); $cblock = ipParent($ip_dbh, $webvar{block}); } else { $cblock = blockParent($ip_dbh, $webvar{block}); } $page->param(backblock => $cblock->{cidr}); # Do some HTML fiddling here instead of using ESCAPE=HTML in the template, # because otherwise we can't convert \n to
. *sigh* $webvar{notes} = $q->escapeHTML($webvar{notes}); # escape first... $webvar{notes} =~ s/\n/
\n/; # ... then convert newlines $webvar{privdata} = ($webvar{privdata} ? $q->escapeHTML($webvar{privdata}) : " "); $webvar{privdata} =~ s/\n/
\n/; $page->param(cidr => $webvar{block}); $page->param(city => $webvar{city}); $page->param(disptype => $disp_alloctypes{$webvar{alloctype}}); $page->param(vrf => $webvar{vrf}); $page->param(custid => $webvar{custid}); $page->param(swip => $webvar{swip} eq 'on' ? 'Yes' : 'No'); $page->param(circid => $webvar{circid}); $page->param(desc => $webvar{desc}); $page->param(notes => $webvar{notes}); $page->param(privdata => $webvar{privdata}) if $IPDBacl{$authuser} =~ /s/; } # update() # Delete an allocation. sub remove { if ($IPDBacl{$authuser} !~ /d/) { $aclerr = 'delblock'; return; } # Serves'em right for getting here... if (!defined($webvar{block})) { $page->param(err => "Can't delete a block that doesn't exist"); return; } my $blockdata; if ($webvar{alloctype} eq 'rm') { $blockdata->{block} = $webvar{block}; $blockdata->{city} = getRoutedCity($ip_dbh, $webvar{block}); $blockdata->{custid} = "N/A"; $blockdata->{type} = $webvar{alloctype}; $blockdata->{circuitid} = "N/A"; $blockdata->{description} = "N/A"; $blockdata->{notes} = "N/A"; $blockdata->{privdata} = "N/A"; } elsif ($webvar{alloctype} eq 'mm') { $blockdata->{block} = $webvar{block}; $blockdata->{city} = "N/A"; $blockdata->{custid} = "N/A"; $blockdata->{type} = $webvar{alloctype}; $blockdata->{circuitid} = "N/A"; $blockdata->{description} = "N/A"; $blockdata->{notes} = "N/A"; $blockdata->{privdata} = "N/A"; } else { $blockdata = getBlockData($ip_dbh, $webvar{block}) } # end cases for different alloctypes $page->param(block => $blockdata->{block}); $page->param(disptype => $disp_alloctypes{$blockdata->{type}}); $page->param(type => $blockdata->{type}); $page->param(city => $blockdata->{city}); $page->param(custid => $blockdata->{custid}); $page->param(circid => $blockdata->{circuitid}); $page->param(desc => $blockdata->{description}); $blockdata->{notes} = $q->escapeHTML($blockdata->{notes}); $blockdata->{notes} =~ s/\n/
\n/; $page->param(notes => $blockdata->{notes}); $blockdata->{privdata} = $q->escapeHTML($blockdata->{privdata}); $blockdata->{privdata} = ' ' if !$blockdata->{privdata}; $blockdata->{privdata} =~ s/\n/
\n/; $page->param(privdata => $blockdata->{privdata}) if $IPDBacl{$authuser} =~ /s/; $page->param(delpool => $blockdata->{type} =~ /^.[pd]$/); } # end remove() # 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. # Remove IPs from pool listing if necessary sub finalDelete { if ($IPDBacl{$authuser} !~ /d/) { $aclerr = 'delblock'; return; } # need to retrieve block data before deleting so we can notify on that my $blockinfo = getBlockData($ip_dbh, $webvar{block}); # hack pthui. This goes away with the new structure, since routed # and master blocks can be retrieved with getBlockData() if ($webvar{alloctype} eq 'rm') { $blockinfo = { custid => 'N/A', city => $webvar{city}, description => 'N/A' }; } elsif ($webvar{alloctype} eq 'mm') { $blockinfo = { custid => 'N/A', city => 'N/A', description => 'N/A' }; } my ($code,$msg) = deleteBlock($ip_dbh, $webvar{block}, $webvar{alloctype}); $page->param(block => $webvar{block}); if ($code eq 'OK') { syslog "notice", "$authuser deallocated '$webvar{alloctype}'-type netblock $webvar{block} ". $blockinfo->{custid}.", ".$blockinfo->{city}.", desc='".$blockinfo->{description}."'"; mailNotify($ip_dbh, 'da', "REMOVED: $disp_alloctypes{$webvar{alloctype}} $webvar{block}", "$disp_alloctypes{$webvar{alloctype}} $webvar{block} deallocated by $authuser\n". "CustID: ".$blockinfo->{custid}."\nCity: ".$blockinfo->{city}. "\nDescription: ".$blockinfo->{description}."\n"); } else { $page->param(failmsg => $msg); if ($webvar{alloctype} =~ /^.i$/) { syslog "err", "$authuser could not deallocate static IP '$webvar{block}': '$msg'"; } else { syslog "err", "$authuser could not deallocate netblock '$webvar{block}': '$msg'"; $page->param(netblock => 1); } } } # finalDelete