#host-mapper #version 0.022 #Cedric Nelson, November 2007 #cedric.nelson@gmail.com use WWW::Mechanize; use Getopt::Std; use DBI; use Crypt::SSLeay; my ($flags, $dirty, @switches, @hosts, @nodes, $default_username, $default_password, $username, $password, $protocol, $port, $url, $realm, $database_file, $DSN, $dbh, $input_camdb, $input_macdb, $input_db_file, $output_file, @unaccounted_for); #Set variables $url = '/level/15/exec/-/show/mac-address-table/CR'; $realm = 'level_15_access'; #Example path: #$database_file = '\\\\server\\share\\SysInfo.mdb'; $database_file = ''; $DSN = "driver=Microsoft Access Driver (*.mdb);dbq=$database_file"; $default_username = ''; $default_password = ''; #Parse invokation options. if (@ARGV) { $flags = analyze_options(@ARGV); foreach my $flag (keys %{$flags}) { if ($flag !~ /^-([seupmcidqh])$/) { print "Unrecognized option: $flag\n\n"; $dirty = 1; show_help(); } } unless ($dirty == 1) { if (${$flags}{'-s'}) { foreach my $item (@{${$flags}{'-s'}}) { if (-e $item) {push @switches, parse_files_macro($item);} else {push @switches, $item;} } } if (${$flags}{'-e'}) {$port = '443';$protocol = 'https';} else {$port = '80';$protocol = 'http';} if (${$flags}{'-u'}) {$username = @{${$flags}{'-u'}}[0];} if (${$flags}{'-p'}) {$password = @{${$flags}{'-p'}}[0];} unless (${$flags}{'-u'}) {$username = $default_username;} unless (${$flags}{'-p'}) {$password = $default_password;} if (${$flags}{'-m'}) { foreach my $item (@{${$flags}{'-m'}}) { if (-e $item) {push @nodes, parse_files_macro($item);} else {push @nodes, $item;} } } if (${$flags}{'-c'}) {$input_camdb = @{${$flags}{'-c'}}[0];} if (${$flags}{'-i'}) {$input_macdb = @{${$flags}{'-i'}}[0];} if (${$flags}{'-d'}) {$input_db_file = @{${$flags}{'-d'}}[0];} if (${$flags}{'-q'}) { foreach my $item (@{${$flags}{'-q'}}) { if (-e $item) {push @hosts, parse_files_macro($item);} else {push @hosts, $item;} } } unless (${$flags}{'-m'}) {@nodes = @hosts;} ###Actions based on flags### #Search for a host entry if (${$flags}{'-q'}) { #Search an existing database if (${$flags}{'-d'}) { my $mapped_macs_ref = load_mapdb($input_db_file); search_macdb($mapped_macs_ref, \&print_mac_entry, @hosts); print "Hosts unaccounted for:\n"; foreach my $host (@unaccounted_for) {print "$host\n";} } #Search using combinations of other resources else { my $mapped_macs_ref = map_macro(); search_macdb($mapped_macs_ref, \&print_mac_entry, @hosts); save_mapdb($mapped_macs_ref); print "Hosts unaccounted for:\n"; foreach my $host (@unaccounted_for) {print "$host\n";} } } #Create a host-map database elsif ((${$flags}{'-s'} || ${$flags}{'-c'}) && (${$flags}{'-m'} || + ${$flags}{'-i'})) { my $mapped_macs_ref = map_macro(); save_mapdb($mapped_macs_ref); } #Create a cam-database elsif (${$flags}{'-s'} || ${$flags}{'-c'}) { my $cam_ref = cam_macro(); save_camdb($cam_ref); } #Create a mac-database elsif (${$flags}{'-m'} || ${$flags}{'-i'}) { my $mac_ref = mac_macro(); save_macdb($mac_ref); } elsif (${$flags}{'-h'}) {show_help();} else {print "I don't understand this combination of options. Exitin +g...\n";} } } #Or display help if there are none. else {show_help();} ### Macros Section ### #Parse Files Macro #Make an array of items by reading them from file(s) sub parse_files_macro { my (@files) = @_; my (@items); foreach my $file (@files) { open FH, ($file) or warn "Can't open $file for parsing. Skipping i +t.\n"; while (<FH>) { chomp($_); push @items, $_; } close FH; } return @items; } #CAM Table Macro #1. Build trunkport-hash from switches #2. Get cam-hash from file or from switches sub cam_macro { my ($cam_ref); #Get cam-hash if (${$flags}{'-c'}) {$cam_ref = load_camdb($input_camdb);} elsif (${$flags}{'-s'}) { trunk_macro(); foreach my $ip (@switches) { my $dirty_output = get_data($ip, $port, $realm, $username, $pass +word, $url); $cam_ref = parse_cam($dirty_output); } } else {die "In order to proceed I need a CAM table specified.\n";} return ($cam_ref); } #MAC Table Macro #1. Get mac-hash from a file, host(s), or from default database sub mac_macro { my ($mac_ref); #Get mac-hash if (${$flags}{'-i'}) {$mac_ref = load_macdb($input_macdb);} elsif (${$flags}{'-m'}) { $mac_ref = get_macs(@nodes); } elsif (-e $database_file) {$mac_ref = get_mac_db();} elsif (${$flags}{'-q'}) { $mac_ref = get_macs(@nodes); } else {die "In order to proceed I need a host-MAC table specified.\n" +;} return ($mac_ref); } #Trunk Macro #1. Get trunkport-hash from switches sub trunk_macro { my ($trunk_ref); my $url = '/level/15/exec/-/show/cdp/neighbors/CR'; foreach my $ip (@switches) { my $dirty_output = get_data($ip, $port, $realm, $username, $passwo +rd, $url); $trunk_ref = parse_trunks($dirty_output); } return ($trunk_ref); } #End trunk_macro #Map Macro #1. Get cam-hash #2. Get mac-hash #4. Map MACs sub map_macro { my ($cam_ref, $mac_ref, $mapped_macs_ref); $cam_ref = cam_macro(); $mac_ref = mac_macro(); #Map MACs $mapped_macs_ref = map_macs($cam_ref, $mac_ref); return ($cam_ref, $mac_ref, $mapped_macs_ref); } #End map_macro ### Subroutine Section ### #Anonymous code block for scoping %options, @list properly. { my %options; my @list; #Defines relationship of invokation arguments as a hash. sub analyze_options { my ($element); ($element, @list) = @_; #Add new options to the hash. unless ($options{$element}) {$options{$element} = [];} #Parse the list of arguments. while (@list) { my $item = shift(@list); #If the element is an option, parse it with remaining arguments. if ($item =~ /^-/) {analyze_options($item, @list);} #If the element is an argument, it belongs to the preceding opti +on. else {push @{$options{$element}}, $item;} } #Return a reference to your hash of options. return \%options; } #End analyze_options } #End anonymous code block. #Grabs html data from a switch, and passes it to a function. sub get_data { my ($ip, $port, $realm, $username, $password, $url, $function) = @_; my $spider; $spider = WWW::Mechanize->new(autocheck => 1); $spider->credentials("$ip:$port", $realm, $username, $password); $spider->get("$protocol://$ip$url"); if (defined($function)) {$function->($spider->content());} else {return $spider->content();} } #End get_data #Anonymous code block for get_macs { my %macs; #Retrieve MAC information from host(s) sub get_macs { my (@hosts) = @_; unless (@hosts) {@hosts = 'localhost';} foreach my $host (@hosts) { my ($command, $output, $mac); $command = "wmic /node:'$host' nic where NetConnectionID='Local +Area Connection' get MACAddress"; @output = `$command`; #Collect all MAC addresses from output foreach my $entry (@output) { if ($entry =~ /([a-f0-9]{2}:){5}[a-f0-9]{2}/i) { $mac = $&; $mac =~ s/://g; $mac =~ s/([a-z0-9]{4})/$1\./gi; $mac =~ s/\.$//; unless ($macs{$mac}) {$macs{$mac} = $host;} } } } return \%macs; } #End get_macs }#End anonymous code block #Anonymous code block { my %cam; #Parse html data for MAC information #Note that parse_cam is dependant on data created from parse_trunks sub parse_cam { my ($dirty_output, $function) = @_; my ($output, @lines, $hostname); #Parse data for hostname if ($dirty_output =~ m{<BODY><H1>(.*)</H1><PRE>}) {$hostname = $1; +} else {$hostname = 'unknown_device';} #Cut out un-neeeded output if ($dirty_output =~ m{---- ----------- -------- ----- +(.*)Total Mac Addresses for this criterion}s) { $output = $1; } else {print "No match found for MACs regex.\n";} #Parse data for MACs @lines = split /\n/, $output; shift @lines; #Format MACs and re-assign to hash #Table format: Switch,VLAN,MAC,Type,Port #Hash format: MAC => [Switch, Port, VLAN] foreach my $line (@lines) { $line =~ s/^ +//; $line =~ s/( )+/,/g; $line = "$hostname," . $line; my ($switch, $vlan, $mac, $type, $port) = split /,/, $line; $port =~ tr/\r\n//d; if (is_host_port($hostname, $port)) { unless ($cam{$mac}) {$cam{$mac} = [$switch, $port, $vlan];} } } if (defined($function)) {$function->(\%cam);} else {return \%cam;} } #End parse_cam } #End anonymous code block #Anonymous code block { my %switch_trunks; #Parse html data for trunk-port information #This is dependant on cdp being enabled sub parse_trunks { my ($dirty_output, $function) = @_; my ($hostname, $output, @lines); if ($dirty_output =~ m{<BODY><H1>(.*)</H1><PRE>}) {$hostname = $1; +} else {$hostname = 'unknown_device';} #Isolate CDP information if ($dirty_output =~ m{Port ID(.*)</DL>}s) {$output = $1;} else {print "No match found for CDP regex.\n";} #Parse Trunk-port information @lines = split /\n/, $output; foreach (@lines) { #Isolate trunk-port entries with the format: #<interface> <number>/<number> or <interface> <number> if ($_ =~ /(\w+ \d+\/\d+|\w+ \d+)/) { my $match = $1; #Format trunk-port to match mac-address-table output #You may need to add to this, depending on what modules #your switches are using. $match =~ s/Gig /Gi/; #Add any trunk-ports to an array for this switch unless ($switch_trunks{$hostname}) {$switch_trunks{$hostname} += [];} push @{$switch_trunks{$hostname}}, $match; } } if (defined($function)) {$function->(\%switch_trunks);} else {return \%switch_trunks;} } #End parse_trunks #Determine if a specified host is directly connected to switch sub is_host_port { my ($switch, $switch_port) = @_; my $is_host_port = 'maybe'; foreach my $trunk_port (@{$switch_trunks{$switch}}) { if ($switch_port eq $trunk_port) {$is_host_port = 'no';} } if ($is_host_port eq 'no') {return 0;} else {return 1;} } #End is_access_port } #End anonymous code block #Anonymous block for get_mac_db { my %macs; #Read the default host-mac db sub get_mac_db { $dbh = DBI->connect("dbi:ODBC:$DSN", '','') or die "$DBI::errs +tr\n"; my $sql = "SELECT ComputerName, MACAddress FROM tblSysInfo"; my $sth = $dbh->prepare($sql); $sth->execute(); my @row; while (@row = $sth->fetchrow_array) { my ($hostname, $macaddress) = @row; #Format MACs to conform to cisco's style if ($macaddress =~ /([a-z0-9]{2}:){5}[a-z0-9]{2}/i) { $macaddress =~ s/://g; $macaddress =~ s/([a-z0-9]{4})/$1\./gi; $macaddress =~ s/\.$//; } $macs{$macaddress} = $hostname; } return \%macs; } #End get_mac_db } #End anonymous code block. #Save mac database to csv file sub save_mapdb { my ($output_hash) = @_; unless ($output_file) {$output_file = 'hostmap-database.csv';} open OUTPUT, ">$output_file" or die "Can't open $output_file for wri +ting.\n"; print OUTPUT "Hostname,Switch,Port,VLAN,MAC\n"; while (my ($mac, $arrayref) = each %$output_hash) { my (@row) = (@$arrayref[0], @$arrayref[1], @$arrayref[2], @$arrayr +ef[3], $mac); $line = join(",", @row); print OUTPUT ($line, "\n"); } close OUTPUT; } #End save_mapdb #Anonymous block for load_mapdb { my %mapped_mac_info; #Load a host-map from a file sub load_mapdb { my ($input_db) = @_; if (-e $input_db) { open INPUT, "<$input_db" or die "Can't open $input_db.\n"; while (<INPUT>) { chomp($_); my ($line) = $_; my (@entry) = split /,/, $_; my ($hostname, $switch, $port, $vlan, $mac) = @entry; unless ($mac eq 'MAC') { $mapped_mac_info{$mac} = [$hostname, $switch, $port, $vlan]; } } close INPUT; return \%mapped_mac_info; } else {print "I can't open $input_db.\n";} } #End load_mapdb } #End anonymous code block #Anonymous block for load_camdb { my %cam_db; #Load a cam database from a file sub load_camdb { my ($input_db) = @_; if (-e $input_db) { open INPUT, "<$input_db" or die "Can't open $input_db.\n"; while (<INPUT>) { chomp($_); my ($switch, $port, $vlan, $mac) = split /,/, $_; unless ($mac eq 'MAC') { $cam_db{$mac} = [$switch, $port, $vlan, $mac]; } } close INPUT; return \%cam_db; } else {print "I can't open $input_db.\n";} } #End load_camdb } #End anonymous code block sub save_camdb { my ($cam_ref) = @_; open OUTPUT, ">cam-database.csv" or die "Can't open cam-database.csv + for writing.\n"; print OUTPUT "Switch,Port,VLAN,MAC\n"; while (my ($mac, $arrayref) = each %$cam_ref) { my (@row) = (@$arrayref[0],@$arrayref[1],@$arrayref[2],$mac); print OUTPUT (join(',', @row), "\n"); } close OUTPUT; } #End save_camdb sub save_macdb { my ($mac_ref) = @_; open OUTPUT, ">mac-database.csv" or die "Can't open mac-database.csv + for writing.\n"; print OUTPUT "Hostname, MAC\n"; while (my ($mac, $hostname) = each %$mac_ref) { my (@row) = ($hostname, $mac); print OUTPUT (join(',', @row), "\n"); } close OUTPUT; } #End save_macdb #Anonymous block for load_macdb { my %mac_db; #Load a mac database from a file sub load_macdb { my ($input_db) = @_; if (-e $input_db) { open INPUT, "<$input_db" or die "Can't open $input_db.\n"; while (<INPUT>) { chomp($_); my ($hostname, $mac) = split /,/, $_; unless ($mac eq 'MAC') { $mac_db{$mac} = $hostname; } } close INPUT; return \%mac_db; } else {print "I can't open $input_db.\n";} } #End load_macdb } #End anonymous code block #Search a macdb for a host, and take action for matches. sub search_macdb { my ($macdb_ref, $action_ref, @hosts) = @_; foreach $host (@hosts) { my ($match_found) = 0; while (my ($host_mac, $cam_array_ref) = each %$macdb_ref) { my ($macdb_host) = @$cam_array_ref[0]; if ($host =~ /$macdb_host/i) { my ($switch, $port, $vlan) = (@$cam_array_ref[1], @$cam_array_ +ref[2], @$cam_array_ref[3]); $action_ref->($host, $switch, $port, $vlan); $match_found = 1; } } if ($match_found == 0) { print "No entry for $host found.\n\n"; push @unaccounted_for, $host; } } } #End search_macdb #Prints a mac entry. sub print_mac_entry { my ($host, $switch, $port, $vlan) = @_; print "Hostname: $host\nSwitch: $switch\nPort: $port\nVLAN: $vlan\n\ +n"; } #End print_mac_entry #Anonymous block for map_macs { my %mapped_mac_info; #Maps hostnames to ports using the MAC address sub map_macs { my ($switch_mac_hash, $host_mac_hash) = @_; #There should be a more efficient way to find MAC matches while (my ($switch_mac, $cam_array_ref) = each %$switch_mac_hash) +{ while (my ($host_mac, $hostname) = each %$host_mac_hash) { if ($switch_mac =~ /$host_mac/i) { my ($switch, $port, $vlan) = (@$cam_array_ref[0], @$cam_arra +y_ref[1], @$cam_array_ref[2]); unless ($mapped_mac_info{$host_mac}) { $mapped_mac_info{$host_mac} = [$hostname, $switch, $port, +$vlan]; } } } } return \%mapped_mac_info; } #End map_macs } #End anonymous code block. sub show_help { my ($error) = @_; if ($error) {print "$error\n\n";} my ($help) = <<END; host-mapper Version 0.022 Cedric Nelson, 2007 A utility for describing where hosts are physically connected. Usage: host-mapper <options> Options: -s <sw | file>... Query switch(es) for CAM table(s). (Used alone, -s will save a cam-database +) -e Establish encrypted connection to switch +(es). -u <...> Username for connection to switch(es). -p <...> Password for connection to switch(es). -m <host | file>... Query host(s) for MAC address(es). (Used alone, -m will save a mac-database +) (Using -s & -m alone will save a host-map database.) -c <file> Use cam-database from file. (A substitute for -s) -i <file> Use mac-database from file. (A substitute for -m) -d <file> Use host-map database from file. (A substitute for -s and -m) -q <host | file>... Query host-map for specified host(s). (Used with -s & -m, or -d) -h Display help. END print "$help\n"; } #End show_help
In reply to host-switchport mapper by colakong
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |