#!/usr/bin/perl # ipdb/cgi-bin/main.cgi ### # SVN revision info # $Date: 2015-02-11 23:43:22 +0000 (Wed, 11 Feb 2015) $ # SVN revision $Rev: 692 $ # 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 POSIX qw(ceil); use NetAddr::IP; use Frontier::Client; 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} = ''; } # split up some linked data for static IPs via guided allocation. needed for breadcrumbs lite. ($webvar{alloc_from},$webvar{rdepth}) = split /,/, $webvar{alloc_from} if $webvar{alloc_from} =~ /,/; # $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. ##fixme: consider just passing \%webvar to allocateBlock()? # collect per-IP rDNS fields. only copy over the ones that actually have something in them. my %iprev; foreach (keys %webvar) { $iprev{$_} = $webvar{$_} if /host_[\d.a-fA-F:]+/ && $webvar{$_}; } # Easier to see and cosmetically fiddle the list like this my %insert_args = ( cidr => $webvar{fullcidr}, fbid => $webvar{fbid}, reserve => $webvar{reserve}, parent => $webvar{parent}, custid => $webvar{custid}, type => $webvar{alloctype}, city => $webvar{city}, desc => $webvar{desc}, notes => $webvar{notes}, circid => $webvar{circid}, privdata => $webvar{privdata}, nodeid => $webvar{node}, rdns => $webvar{rdns}, vrf => $webvar{vrf}, vlan => $webvar{vlan}, user => $authuser, ); my ($code,$msg) = allocateBlock($ip_dbh, %insert_args, iprev => \%iprev); if ($code eq 'OK') { if ($webvar{alloctype} =~ /^.i$/) { $msg =~ s|/32||; $page->param(staticip => $msg); $page->param(custid => $webvar{custid}); $page->param(parent => $webvar{parent}, pool => $webvar{alloc_from}); $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}); # breadcrumbs lite! provide at least a link to the parent of the block we just allocated. my $binfo = getBlockData($ip_dbh, $webvar{parent}); $page->param(parentid => $webvar{parent}); $page->param(parentblock => $binfo->{block}); 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}}; } } ## hmmm.... is this even useful? if (0) { # 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; } } # VRF. Not a full validity check, just a basic sanity check. if ($webvar{vrf}) { # Trim leading and trailing whitespace first $webvar{vrf} =~ s/^\s+//; $webvar{vrf} =~ s/\s+$//; if ($webvar{vrf} !~ /^[\w\d_.-]{1,32}$/) { $page->param(err => "VRF values may only contain alphanumerics, and may not be more than 32 characters"); return; } } # VLAN. Should we allow/use VLAN names, or just the numeric ID? if ($webvar{vlan}) { # Trim leading and trailing whitespace first $webvar{vlan} =~ s/^\s+//; $webvar{vlan} =~ s/\s+$//; # ... ve make it ze configurable thingy! if ($IPDB::numeric_vlan) { if ($webvar{vlan} !~ /^\d+$/) { $page->param(err => "VLANs must be numeric"); return; } } else { if ($webvar{vlan} !~ /^[\w\d_.-]+$/) { $page->param(err => "VLANs must be alphanumeric"); 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{id}, $webvar{basetype}); $page->param(id => $webvar{id}); $page->param(basetype => $webvar{basetype}); # Clean up extra whitespace on alloc type. Mainly a legacy-data cleanup. $blockinfo->{type} =~ s/\s//; my $cached; # Get rDNS info; duplicates a bit of getBlockData but also does the RPC call if possible ($blockinfo->{rdns},$cached) = getBlockRDNS($ip_dbh, id => $webvar{id}, type => $blockinfo->{type}, user => $authuser); # visual flag that we're working IPDB-local, not off more authoritative data in dnsadmin $page->param(cached => $cached); my $cidr = new NetAddr::IP $blockinfo->{block}; # Limit the per-IP rDNS list based on CIDR length; larger ones just take up too much space. # Also, don't show on IP pools; the individual IPs will have a space for rDNS # Don't show on single IPs; these use the "pattern" field if ($IPDBacl{$authuser} =~ /c/ && $cidr->masklen != $cidr->bits && ($cidr->bits - $cidr->masklen) <= $IPDB::maxrevlist && $blockinfo->{type} !~ /^.[dpi]/ # do we want to allow v6 at all? #&& ! $cidr->{isv6} ) { $page->param(r_iplist => getRDNSbyIP($ip_dbh, id => $webvar{id}, type => $blockinfo->{type}, range => $blockinfo->{block}, user => $authuser) ); } # consider extending this to show time as well as date my ($lastmod,undef) = split /\s+/, $blockinfo->{lastmod}; $page->param(lastmod => $lastmod); # $page->param(lastmod => $blockinfo->{lastmod}); $page->param(block => $blockinfo->{block}); $page->param(city => $blockinfo->{city}); $page->param(custid => $blockinfo->{custid}); ##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, $blockinfo->{block}); # $page->param(havenodeid => $nodeid); $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.. if ($IPDBacl{$authuser} =~ /c/) { my $nlist = getNodeList($ip_dbh); if ($nodeid) { foreach (@{$nlist}) { $$_{selme} = ($$_{node_id} == $nodeid); } } $page->param(nodelist => $nlist); } ## end node hack $page->param(rdns => $blockinfo->{rdns}); $page->param(vrf => $blockinfo->{vrf}); $page->param(vlan => $blockinfo->{vlan}); # not happy with the upside-down logic, but... $page->param(swipable => $blockinfo->{type} !~ /.i/); $page->param(swip => $blockinfo->{swip} ne 'n') if $blockinfo->{swip}; $page->param(circid => $blockinfo->{circuitid}); $page->param(desc => $blockinfo->{description}); $page->param(notes => $blockinfo->{notes}); # 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}, rdns => $webvar{rdns}, vrf => $webvar{vrf}, vlan => $webvar{vlan}, user => $authuser, ); # Semioptional values $updargs{privdata} = $webvar{privdata} if $IPDBacl{$authuser} =~ /s/; $updargs{node} = $webvar{node} if $webvar{node}; # collect per-IP rDNS fields. only copy over the ones that actually have something in them. my %iprev; foreach (keys %webvar) { $iprev{$_} = $webvar{$_} if /host_[\d.a-fA-F:]+/ && $webvar{$_}; } my ($code,$msg) = updateBlock($ip_dbh, %updargs, iprev => \%iprev); 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 $binfo = getBlockData($ip_dbh, $webvar{block}, $webvar{basetype}); my $pblock = getBlockData($ip_dbh, $binfo->{parent_id}); $page->param(backid => $binfo->{parent_id}); $page->param(backblock => $pblock->{block}); $page->param(backpool => ($webvar{basetype} eq 'i')); # 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 => $binfo->{block}); $page->param(rdns => $webvar{rdns}); $page->param(city => $webvar{city}); $page->param(disptype => $disp_alloctypes{$webvar{alloctype}}); $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; $blockdata = getBlockData($ip_dbh, $webvar{block}, $webvar{basetype}); if ($blockdata->{parent_id} == 0) { # $webvar{alloctype} eq 'mm' $blockdata->{city} = "N/A"; $blockdata->{custid} = "N/A"; $blockdata->{circuitid} = "N/A"; $blockdata->{description} = "N/A"; $blockdata->{notes} = "N/A"; $blockdata->{privdata} = "N/A"; } # end cases for different alloctypes $page->param(blockid => $webvar{block}); $page->param(basetype => $webvar{basetype}); $page->param(block => $blockdata->{block}); $page->param(rdns => $blockdata->{rdns}); # maybe need to apply more magic here? # most allocations we *do* want to autodelete the forward as well as reverse; for a handful we don't. # -> all real blocks (nb: pool IPs need extra handling) # -> NOC/private-IP (how to ID?) # -> anything with a pattern matching $IPDB::domain? if ($blockdata->{type} !~ /^.i$/) { $page->param(autodel => 1); } $page->param(disptype => $disp_alloctypes{$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}, $webvar{basetype}); my $pinfo = getBlockData($ip_dbh, $blockinfo->{parent_id}, 'b'); my ($code,$msg) = deleteBlock($ip_dbh, $webvar{block}, $webvar{basetype}, $webvar{delforward}, $authuser); $page->param(block => $blockinfo->{block}); $page->param(bdisp => $q->escapeHTML($disp_alloctypes{$blockinfo->{type}})); $page->param(delparent_id => $blockinfo->{parent_id});# if $webvar{rdepth}; if ($pinfo) { $page->param(delparent => $pinfo->{block}); $page->param(pdisp => $q->escapeHTML($disp_alloctypes{$pinfo->{type}})); } $page->param(returnpool => ($webvar{basetype} eq 'i') ); if ($code =~ /^WARN(POOL|MERGE)/) { my ($pid,$pcidr) = split /,/, $msg; my $real_pinfo = getBlockData($ip_dbh, $pid, 'b'); $page->param(parent_id => $pid); $page->param(parent => $pcidr); $page->param(real_disp => $q->escapeHTML($disp_alloctypes{$real_pinfo->{type}})); $page->param(mergeip => $code eq 'WARNPOOL'); } if ($code eq 'WARN') { $msg =~ s/\n/
\n/g; $page->param(genwarn => $msg); } if ($code eq 'OK' || $code =~ /^WARN/) { syslog "notice", "$authuser deallocated '".$blockinfo->{type}."'-type netblock $webvar{block} ". $blockinfo->{custid}.", ".$blockinfo->{city}.", desc='".$blockinfo->{description}."'"; mailNotify($ip_dbh, 'da', "REMOVED: ".$disp_alloctypes{$blockinfo->{type}}." $webvar{block}", $disp_alloctypes{$blockinfo->{type}}." $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