use strict; use Win32::ODBC; ### Written to compare IP of machine to IP range of site that it's in McAfee's EPO Database ### If machine is not in correct range, script finds what container it fits in ### Written because EPO IP resort option does not permit checking without remediation ### Requires: Predefined ODBC connection to the EPO database which should be called by db_open ### Requires: Replace ODBC_CONNECTION_NAME with your ODBC connection name ### Warning: If Site levels are not a catch all IP range in EPO, query_epo will need to be adjusted. ### Warning: If you don't type the site name EXACTLY (Caps,punctuation, spaces,etc.)as it is in EPO, the script will return incorrect data ### Usage: script.pl "Container Name" my($db,%Ranges,%Machines); my $location=$ARGV[0]; &db_open('ODBC_CONNECTION_NAME'); &query_epo($location); &db_close; &checker; ### foreach IP in the Machines list convert the IP to a decimal and compare to Container IP ranges. sub checker{ my %badmachines; foreach my $machinekey (keys %Machines){ my ($inrange); my ($dec,$decflip)=&ip2dec($Machines{$machinekey}->{'IPAddress'}); foreach my $rangekey (sort keys %{$Ranges{$location}}){ if(&inrange($Ranges{$location}{$rangekey}{'IP_Start'},$dec,$Ranges{$location}{$rangekey}{'IP_End'})||&inrange($Ranges{$location}{$rangekey}{'IP_Start'},$decflip,$Ranges{$location}{$rangekey}{'IP_End'})){ $inrange=1; } } if ($inrange!=1){ foreach my $locationkey (keys %Ranges){ foreach my $rangekey(keys %{$Ranges{$locationkey}}){ if(&inrange($Ranges{$locationkey}{$rangekey}{'IP_Start'},$dec,$Ranges{$locationkey}{$rangekey}{'IP_End'})||&inrange($Ranges{$location}{$rangekey}{'IP_Start'},$decflip,$Ranges{$locationkey}{$rangekey}{'IP_End'})){ $Machines{$machinekey}{'MoveTo'}=$locationkey; } } } print "$Machines{$machinekey}->{'NodeName'}\t$Machines{$machinekey}->{'IPAddress'}\t$Machines{$machinekey}->{'MoveTo'}\n"; } } } ### converts an IP octet to binary ### ### Called by bin2ip ### Not used in script, but added JIC ever needed sub getoctet { my $var1="0"x 24 . $_[0]; return unpack("N", pack("B32",$var1)); } ### Splits the binary representation of an IP into octets ### Not used by the script, but added JIC ever needed sub bin2ip { print &getoctet(substr($_[0],0,8))."."; print &getoctet(substr($_[0],8,8))."."; print &getoctet(substr($_[0],16,8))."."; print &getoctet(substr($_[0],24,8))."\n"; } ### Converts a decimal to a binary ### ### called by ip2dec sub dec2bin { my $str = unpack("B32", pack("N", shift)); return $str; } ### Converts a binary to a decimal ### called by ip2dec sub bin2dec { return unpack("N", pack("B32", substr("0" x 32 . shift, -32))); } ### converts a binary to a decimal, but flips the bits first. ### Negative decimals are bit flipped in binary, i.e. 0 becomes 1 and 1 becomes 0. ### Ugly. There's a better way. ### called by ip2dec sub bin2dec_bitflipped{ my $temp=$_[0]; $temp=~s/0/x/g; $temp=~s/1/0/g; $temp=~s/x/1/g; return -unpack("N", pack("B32", substr("0" x 32 . $temp, -32))); } ### converts an IP Address to a decimal and a bit-flipped decimal (accounts for negative numbers) ### Called by checker sub ip2dec{ my @octets=split(/\./,$_[0]); my $bin=join('',map(substr(dec2bin($_),-8),@octets)); my $dec=&bin2dec($bin); my $decflip=&bin2dec_bitflipped($bin); return $dec,$decflip; } ### Check to see if number is in range ### if AB>C (for negative numbers) it is in the range ### Called by Checker sub inrange{ if (($_[0] < = $_[1])&&($_[1] <= $_[2]) || ($_[0] >= $_[1])&&($_[1] >= $_[2])) { return 1; }else{ return 0; } } ### Opens ODBC Connection sub db_open(){ $db = new Win32::ODBC($_[0])||die "can't connect"; } ### Closes ODBC Connection sub db_close{ $db->Close(); } ### Pulls all the IP ranges from EPO into a multidimensional hash called 'Ranges' ### If Site levels are not a catch all range, the $query variable will need to be adjusted sub query_epo(){ my ($parentid,$query); $query="select BranchNode.NodeName,ipsubnetmask.AutoID,BranchNode.ParentID,ipsubnetmask.ParentID,ipsubnetmask.IP_Start,ipsubnetmask.IP_End ". "from ipsubnetmask join BranchNode ". "on IPSubnetMask.ParentID = BranchNode.AutoID ". "where BranchNode.ParentID not in (\'1\',\'2\')"; #eliminates Directory and Site levels because they are a catch all IP rannge $db->Sql("$query"); while ($db->FetchRow()) { my (%data) = $db->DataHash(); ### Dump the data into a more permanent hash called Ranges $Ranges{$data{'NodeName'}}{$data{'AutoID'}}={ 'IP_Start'=>$data{'IP_Start'}, 'IP_End'=>$data{'IP_End'}, 'ParentID'=>$data{'ParentID'} }; if ($data{'NodeName'} eq $location){ #hack to get the ParentID for next query. Not proud of this. $parentid=$data{'ParentID'}; } } $query="select leafnode.autoID,leafnode.NodeName,leafnode.ParentID,computerproperties.IPAddress ". "from leafnode join computerproperties ". "on leafnode.autoID=computerproperties.ParentID ". "where leafnode.ParentID = \'$parentid\'"; $db->Sql("$query"); while ($db->FetchRow()) { my (%data) = $db->DataHash(); ### Dump the data into a more permanent hash called Ranges $Machines{$data{'autoID'}} = { 'NodeName'=>$data{'NodeName'}, 'IPAddress'=>$data{'IPAddress'}, 'ParentID'=>$data{'ParentID'} }; } if (!$parentid){ print "does not exist in EPO\n"; } }