#!/usr/bin/perl -w # contact.pl - Find site contact from IP # Copyright 2001, Dan Pelleg, daniel+hunch@pelleg.org # # Released under the BSD license # # Use: $0 IP # use strict; use Getopt::Std; use Socket; # some debug directives my($debug_level, $D_CONNECT, $D_PARSE, $D_PARSE2, $D_LOOKUP, $D_STATES, $D_EXTRA) = (0, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20); $debug_level = $D_CONNECT | $D_LOOKUP; my %opts; getopts('d:w', \%opts); $debug_level = $opts{d} if(defined($opts{d})); my $whois_cmd_mode = 'NORMAL'; if(exists($opts{w})) { dbg_print($D_CONNECT, "Using old-style whois command"); $whois_cmd_mode = 'OLD'; } my $attacker = shift(@ARGV) or die "Usage: $0 [-d debug-level] [-w] IP"; if($attacker !~ /^[.\d]+$/) { # not a numeric argument - convert to one my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($attacker); if(@addrs) { $attacker = inet_ntoa($addrs[0]); } else { print "Cannot find IP for host $attacker\n"; exit(1); } } my $contact_email = &find_contact($attacker); if($contact_email) { print "$contact_email\n"; } else { print "Could not find contact email\n"; } sub find_contact { my $ip = shift(@_) or die; my ($second_lookup_server, $second_lookup_handle) = arin_lookup_nonrecursive($ip); my ($next_lookup_server, $next_lookup_handle) = ($second_lookup_server, $second_lookup_handle); my $nlookups = 0; while(defined($next_lookup_server)) { # if another lookup is needed, go do it ($next_lookup_server, $next_lookup_handle) = find_contact_secondary($next_lookup_server, $next_lookup_handle); last if($nlookups++ > 3); } # return no appropriate handle, or original one if none found return $next_lookup_server ? $second_lookup_handle : $next_lookup_handle; } sub find_contact_secondary { my ($db, $orig_handle) = @_; my $contact_handle; # map db to whois server my %whois_srvs = ( 'RIPE' => 'whois.ripe.net', 'APNIC' => 'whois.apnic.net', 'KRNIC' => 'whois.krnic.net', 'VERIO' => 'rwhois.verio.net', 'BR' => 'whois.nic.br', 'LACNIC' => 'whois.lacnic.net', ); if($db eq 'ARIN') { return arin_lookup_nonrecursive($orig_handle); } my $whois_server = $whois_srvs{$db} or die("Can't find DB: $db"); my $next_lookup_server; my @contacts; # list of refs to hashes, each a possible contact my $contact; # contact to look for # contact WHOIS for reverse lookup dbg_print($D_CONNECT, "Connecting to $whois_server, handle $orig_handle"); open(WHOIS, whois_cmd($whois_server, $orig_handle)) or die ("Can't run whois"); my %attr; # stores key/value pairs my $state = 'begin'; my $locate; # handle to look for # set the handle to look for my $handle_key='nic-hdl'; if($db eq 'BR') { # special case $handle_key='nic-hdl-br'; } while() { chop; dbg_print($D_PARSE, $_); next if($state eq 'begin' && /^\[.*\]$/); # server name shown in some whois clients' output if(/(^%)|(^\# English)/i) { # comment line if($state eq 'begin') { $state = 'saw_comment'; dbg_print($D_STATES, "state -> $state"); } next; } if(/^\s*$/) { # empty line if($state eq 'saw_comment') { # first empty line - preamble is over $state = 'search'; # start reading dbg_print($D_STATES, "state -> $state"); next; } if($state eq 'search') { # first "real" paragraph seen $state = 'locate'; # find a handle for a contact dbg_print($D_STATES, "state -> $state"); $contact = $attr{'tech-c'} or die("No tech-c record!"); # find tech contact's handle dbg_print($D_LOOKUP, "in search of " . join(',', split("\n", $contact))); } else { # we're in the locate state if(defined($attr{$handle_key})) { # is this someone we're looking for? if(grep(/^$attr{$handle_key}$/, split("\n", $contact))) { # yes! store in list of possible contacts dbg_print($D_LOOKUP, "found details for $attr{$handle_key}"); push(@contacts, { %attr } ); } } %attr = (); # empty the attribute list } next; } # an informative line if(/refer to the (\w+) Whois (DB|Database)/i) { $next_lookup_server = $1; dbg_print($D_LOOKUP, "need a secondary lookup: $next_lookup_server"); next; } if(/at whois.registro.br and at http:\/\/whois.nic.br/i) { $next_lookup_server = "BR"; dbg_print($D_LOOKUP, "need a secondary lookup: $next_lookup_server"); next; } if(/^\[ Technical Contact Information \]\s*$/i) { dbg_print($D_PARSE2, "special hack for tech contact"); $attr{'nic-hdl'} = "$db-special-tech-c"; next; } if(/^\s*(\w+([\- ]\w+)*)\s*:\s*(.*)/) { # parse key/value my ($key, $val) = (lc($1), $3); dbg_print($D_PARSE2, "got $key=$val"); if(defined($attr{$key})) { # append if something already defined (multi-line/multi-value field) $attr{$key} .= "\n$val"; } else { $attr{$key} = "$val"; } if($db eq 'KRNIC' && $key =~ /(IP Address)|(Technical Contact Information )/i) { dbg_print($D_PARSE2, "special hack for org info"); $attr{'tech-c'} = "$db-special-tech-c"; next; } } else { # list of weird lines that can be ignored next if($state ne 'locate'); # not in mode that requires to understand the lines next if($db eq 'KRNIC' && /^\[ /); # KRNIC paragraph headers warn("cannot parse line $_"); } } close(WHOIS); # enumerate contacts for my $cont (@contacts) { next unless(defined($cont->{'e-mail'})); my $sanitized_contact = (split(' ', $cont->{'e-mail'}))[0]; dbg_print($D_LOOKUP, "possible contact: $sanitized_contact"); if(!defined($contact_handle)) { # first contact we have? use it $contact_handle = $sanitized_contact; dbg_print($D_LOOKUP, "using first hit $contact_handle"); } else { # decide if to use previous contact or current one # try to see if security/abuse keywords appear anywhere for this contact my $prefer_current = 0; $prefer_current = 1 if(defined($cont->{'person'}) && ($cont->{'person'} =~ /(security)|(abuse)/i)); $prefer_current = 1 if(defined($cont->{'e-mail'}) && ($cont->{'e-mail'} =~ /(security)|(abuse)/i)); if($prefer_current) { dbg_print($D_LOOKUP, "Preferring " . $cont->{'e-mail'} . " over $contact_handle (keyword)"); $contact_handle = $cont->{'e-mail'} } else { dbg_print($D_LOOKUP, "Not liking " . $cont->{'e-mail'} . ", keeping $contact_handle"); } } } return $next_lookup_server ? ($next_lookup_server, $orig_handle) : (undef, $contact_handle); } sub arin_lookup_nonrecursive { my $handle = shift(@_); my $second_lookup_server; # undefined if not needed my $found_handle; my $possible_handle; my $possible_email; my $comment = ""; # contact ARIN for reverse lookup dbg_print($D_CONNECT, "Connecting to ARIN, handle $handle"); open(ARIN, whois_cmd('whois.arin.net', $handle)) or die ("Can't run whois"); my $state = 'search'; while() { chop; dbg_print($D_PARSE, $_); if($state eq 'saw_coord') { # should have email in this line if(/\s+([\w\-\.\+]+)\@(\w[\w\-\.]*)\s*$/) { $possible_email = "$1\@$2"; dbg_print($D_PARSE, "found possible email $possible_email"); $state = 'done'; next; } } if(/^\s*Coordinator:\s*$/) { # expect email in next line $state = 'saw_coord'; dbg_print($D_STATES, "state -> $state"); next; } if(/^\s*OrgAbuseEmail:\s*([\w\-\.\+]+)\@(\w[\w\-\.]*)\s*$/) { # OrgAbuseEmail record $possible_email = "$1\@$2"; # overwrite an existing email, if there dbg_print($D_PARSE, "found possible email $possible_email"); $state = 'done'; next; } if(/^\s*TechEmail:\s*([\w\-\.\+]+)\@(\w[\w\-\.]*)\s*$/) { # TechEmail record if(!defined($possible_email)) { # don't overwrite previous info $possible_email = "$1\@$2"; dbg_print($D_PARSE, "found possible email $possible_email"); $state = 'done'; } next; } # collect comment lines if(/^comment:\s*(.*)/i) { $comment .= " $1"; next; } if(/^\s+(.*)/) { $comment .= " $1"; next; } if(/.*\(([^\)]+)\)/) { # keep track of stuff in parenthesis (prefer last pair of parens) $possible_handle = $1; dbg_print($D_LOOKUP, "possible handle: $possible_handle"); } } close(ARIN); # see if the comment redirects us to another server if ($comment =~ /has been transferred to (\w+) for administrative/i) { $second_lookup_server = $1; } if ($comment =~ /found in the (\w+) database/) { $second_lookup_server = $1; } if ($comment =~ /the (\w+) database at whois/) { $second_lookup_server = $1; } if ($comment =~ /has been delegated to (\w+)/) { $second_lookup_server = $1; } if ($comment =~ /refer to the (\w+) Whois Database/i) { $second_lookup_server = $1; } if ($comment =~ /This IP address range is under (\w+) responsibility/i) { $second_lookup_server = $1; } if ($comment =~ /^Korea Telecom/i) { $second_lookup_server = 'KRNIC'; } if ($comment =~ /^Comite Gestor da Internet no Brasil/i) { $second_lookup_server = 'BR'; } if ($comment =~ /^\s*Asia Pacific Network Information Centre\s*$/i) { $second_lookup_server = 'APNIC'; } if($second_lookup_server) { # if a secondary lookup needed, do not use the email found dbg_print($D_LOOKUP, "need a secondary lookup: $second_lookup_server"); $found_handle = $handle; } elsif(defined($possible_handle) && !defined($possible_email)) { # a secondary handle for an ARIN handle needed $second_lookup_server = 'ARIN'; $found_handle = $possible_handle; } elsif(defined($possible_email)) { $found_handle = $possible_email; } else { $found_handle = $handle; } return($second_lookup_server, $found_handle); } sub dbg_print { my($level, $msg) = @_; if($level & $debug_level) { print STDERR "$msg\n"; } } sub whois_cmd { my($server, $handle) = @_; if($whois_cmd_mode eq 'OLD') { return "/usr/bin/whois $handle\@$server |"; # RH5 } else { return "/usr/bin/whois -h $server $handle |"; # FreeBSD, RH7 } }