#!/usr/bin/perl # # abuseEmail v1.1.1 # Finds out abuse email addresses for a specified IP address # http://logidac.com/abuseEmail/ # # Created by Guillaume Filion # Copyright (C) 2001 Logidac enr. # 16 Charles-Couillard # Beaumont, Québec # Canada, G0R 1C0 # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # =head1 NAME abuseEmail - Finds out abuse email addresses for a specified IP address =head1 SYNOPSIS B [B] I =head1 DESCRIPTION B receives an IP address and tries to find out, using its internal whois and DNS clients, what is the responsible party's email address. =head1 OPTIONS =item B<--verbose i> Set verbosity level to i. The verbosity levels are: 0: silent, only output the result 1: same as 0 but also output fatal errors (default) 2: same as 1 but also output non-fatal errors 5: The script will explain every action it makes. Set to 5 if you want to understand how abuseEmail works. =item B<--noUseHostname> Don't the hostname to guess some addresses. Depending on the severity of the attack, you may want to try some simple guesses at abuse email addresses. For example, if you've just been portscanned by host.provider.com, writing to abuse@provider.com is a good idea. If you're being DoSed by an attacker, you may bypass the service provider and email directly to their uplink provider. =item B<--UseAbuseNet> abuseEmail can passe all emails it founds into abuse.net whois service. For most domains, this founds the right email address to report your problem. As for noUseHostname, you may want to desactivate this if you're reporting an urgent problem. =item B<--noUseDNSsoa> Don't try to dig the IP subnet's manager email address using DNS SOA. You may want to desactivate this if you're reporting something that is not urgent. =item B<--noUseWhoisIP> Don't use Whois to get IP addresses. This is really here for uniformity, since you almost always will want to use Whois. =item B<--showCommands> This is for educationnal use only. 8) This will show the unix equivalent command for every query made. That way, you can reproduce the technique used by abuseEmail. =item B<--batch> Outputs the result in a way that is easier to parse in a script. The output will look like: 127.123.123.123:abuse@mailprovider.com,roger@domain.top =head1 EXAMPLES =item B The simplest way to use it. Will give a list of email addresses. =item B This is the best way of understanding how abuseEmail works and how to reproduce the results using the regular Unix tools. =item B This could be used in case you really want to get infos about the uplink provided, not the service provider. You could use this in an emergency situation. In all those examples, a.b.c.d must be replaced by a real IP address. =head1 DIAGNOSTICS =item Error: Please specify a host IP address. (F) You did not specified an IP address to lookup. =item Error: This doesn't looks like a numeric IP address. (F) Specifing an hostname will not work, a numeric IP address is required. =item Error: %s is a private IP address (RFC1918). It's a local machine or a spoofed ip, either way, I can't give you any infos on this. (F) Because of an IP address shortage, the IANA (Internet Assigned Numbers Authority), decided to specify addresses that could only be used in private networks, not on the public Internet. You asked this program to lookup this kind of address, as it is a private address, it is not listed in any directory. You may want to ping this address to see if this come from a computer using a private address on your local network. It is also possible that the person who tried to connect to your computer sent a spoofed ip packet, that is, sending an ip packet with an incorrect "from:" tag. There is not much that you can do about this. Sorry. =item Error: %s is a reserved IP address. It's very likely to be a spoofed ip, or your network admin/BOFH is on crack, either way, I can't give you any infos on this. (F) The IP address you specified is a reserved address for experimental purposes; it is almost impossible that such an IP address is used on the Net. What is very likely is that the person who tried to connect to your computer sent a spoofed ip packet, that is, sending an ip packet with an incorrect "from:" tag. There is not much that you can do about this. Sorry. =head1 REQUIRES Perl 5.004, Net::DNS, IO::Socket (included with Perl), Getopt::Long (included with Perl), XWhoisIP (included with abuseEmail) =head1 SEE ALSO dig(1), whois(1), perl(1), Net::Whois(1) =head1 BUGS Yes, there might be some. Please report any one you find to guillaume@filion.org =head1 VERSION Version 1.1.1, 2001-06-28 =head1 TODO =item Better handling of the email addresses, and find out which one are best. =item Remake everything in a more object oriented way and use XML for data. =item Add an option to dig phone numbers. =head1 WEBSITE Visit B for more infos and the lastest version. =head1 AUTHOR Guillaume Filion PGP Fingerprint: 14A6 720A F7BA 6C87 2331 33FD 467E 9198 3DED D5CA =head1 THANKS Great thanks to: =item Russell Fulton who modified Net::XWhois to handle queries on IP addresses. =item Philippe Bourcier of cyberabuse.org who provided me a list of 40 000 IPs with their relative abuse email address. Philippe also provided feedback on abuseEmail. =cut use strict; package XWhoisIP; ## Net::XWhois ## Whois Client Interface Class. ## ## $Date: 2001/03/19 $ ## $Revision: 0.72-IP $ ## $State: Exp $ ## $Author: root $ ## ## Copyright (c) 1998, Vipul Ved Prakash. All rights reserved. ## This code is free software; you can redistribute it and/or modify ## it under the same terms as Perl itself. ## Modified on March 2001 by Russell Fulton to handle IP addresses. #use Data::Dumper; use IO::Socket; use Carp; use vars qw ( $VERSION $AUTOLOAD ); ( $VERSION ) = '$Revision: 0.72 $' =~ /\s+(\d+\.\d+)\s+/; my $CACHE = "/tmp/whois"; my $EXPIRE = 604800; my $ERROR = "croak"; my $TIMEOUT = 60; my %PARSERS = ( ARIN => { netname => 'etname:\s*(\S+)\n+', netblock => 'etblock:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} - \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[\n\s]*', netnumber => 'Netnumber:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[\n\s]*', hostname => 'Hostname:\s*(\S+)[\n\s]*', maintainer => 'Maintainer:\s*(\S+)', record_update => 'Record last updated on (\S+)\.\n+', database_update => 'Database last updated on (.+)\.[\n\s]+The', registrant => '^(.*?)\n\n', results => \&get_results, reverse_mapping => 'Domain System inverse[\s\w]+:[\n\s]+(.*?)\n\n', coordinator => 'Coordinator:[\n\s]+(.*?)\n\n', coordinator_handle =>'Coordinator:[\n\s]+[^\(\)]+\((\S+?)\)', contact_emails => 'Coordinator:[\n\s]+(.*?)\n\n', address => 'Address:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', system => 'System:\s+([^\n]*)\n', non_portable => 'ADDRESSES WITHIN THIS BLOCK ARE NON-PORTABLE', }, APNIC => { netnumber => 'inetnum:\s+(.+)\n', netname => 'etname:\s*(\S+)\n', country => 'country:\s+(\S+)\n', admin_contact => 'admin-c:\s*(\S+)\n', tech_contact => 'tech-c:\s*(\S+)\n', contact_emails => 'e-mail:\s+(\S+\@\S+)', contact_emails_a => 'changed:\s+(\S+\@\S+)', source => 'source:\s*(\S+)\n', }, BRAZIL => { netnumber => 'inetnum:\s+(.+)\n', netname => 'owner:\s+(.+)\n', country => 'country:\s+(\S+)\n', admin_contact => 'admin-c:\s*(\S+)\n', tech_contact => 'tech-c:\s*(\S+)\n', abuse_contact => 'abuse-c:\s*(\S+)\n', contact_emails => 'e-mail:\s+(\S+\@\S+)', source => 'source:\s*(\S+)\n', }, INTERNIC => { name => 'omain Name:\s+(\S+)', status => 'omain Status:\s+(.*?)\s*\n', nameservers => 'in listed order:[\s\n]+(\S+)\s.*?\n\s+(\S*?)\s.*?\n\n', registrant => 'Registrant:\s*\n(.*?)\n\n', contact_admin => 'nistrative Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|\n\n)', contact_tech => 'Technical Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|\n\n)', contact_zone => 'Zone Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|\n\n)', contact_billing => 'Billing Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|\n\n)', contact_emails => '(\S+\@\S+)', contact_handles => '\((\w+\d+)\)', domain_handles => '\((\S*?-DOM)\)', org_handles => '\((\S*?-ORG)\)', not_registered => 'No match', forwardwhois => 'Whois Server: (.*?)(?=\n)', }, INTERNIC_CONTACT => { name => '(.+?)\s+\(.*?\)(?:.*?\@)', address => '\n(.*?)\n[^\n]*?\n\n\s+Re', email => '\s+\(.*?\)\s+(\S+\@\S+)', phone => '\n([^\n]*?)\(F[^\n]+\n\n\s+Re', fax => '\(FAX\)\s+([^\n]+)\n\n\s+Re', }, CANADA => { name => 'domain:\s+(\S+)\n', netname => 'netname:\s+((\S+)\n', nameservers => '-Netaddress:\s+(\S+)', contact_emails => '-Mailbox:\s+(\S+\@\S+)', }, RIPE => { name => 'domain:\s+(\S+)\n', netname => 'etname:\s*(\S+)\n', nameservers => 'nserver:\s+(\S+)', contact_emails => 'notify:\s+(\S+\@\S+)', contact_emails_a => 'e-mail:\s+(\S+\@\S+)', registrants => 'descr:\s+(.+?)\n', }, RIPE_CH => { name => 'domainname:\s+(\S+)\n', nameservers => 'nserver:\s+(\S+)', contact_emails => 'e-mail:\s+(\S+\@\S+)', }, JAPAN => { name => '\[Domain Name\]\s+(\S+)', nameservers => 'Name Server\]\s+(\S+)', contact_emails => '\[Reply Mail\]\s+(\S+\@\S+)', netname => '\s+([^\n]\n)', }, TAIWAN => { name => 'omain Name:\s+(\S+)', netname => 'etname:\s*(\S+)\n', registrant => '^(\S+) \(\S+?DOM)', contact_emails => '(\S+\@\S+)', nameservers => 'servers in listed order:[\s\n]+\%see\-also\s+\.(\S+?)\:', }, KOREA => { name => 'Domain Name\s+:\s+(\S+)', netname => 'Name \s+:\s+(\S+)', nameservers => 'Host Name\s+:\s+(\S+)', contact_emails => 'E\-Mail\s+:\s*(\S+\@\S+)', }, GENERIC => { contact_emails => '(\S+\@\S+)', }, ); my %ASSOC = ( 'whois.arin.net' => [ "ARIN", [ qw/IP/ ] ], 'whois.apnic.net' => [ "APNIC", [ ] ], 'whois.nic.br' => [ "BRAZIL", [ ] ], 'whois.internic.net' => [ "INTERNIC", [ qw/com net org edu/ ] ], 'whois.nic.gov' => [ "INTERNIC", [ qw/gov/ ] ], 'whois.nic.mil' => [ "INTERNIC", [ qw/mil/ ] ], 'whois.isi.edu' => [ "INTERNIC", [ qw/us/ ] ], 'whois.nic.net.sg' => [ "RIPE", [ qw/sg/ ] ], 'whois.aunic.net' => [ "RIPE", [ qw/au/ ] ], 'whois.nic.ch' => [ "RIPE_CH", [ qw/ch/ ] ], 'whois.nic.uk' => [ "INTERNIC", [ qw/uk/ ] ], 'whois.nic.ad.jp' => [ "JAPAN", [ qw/jp/ ] ], 'whois.twnic.net' => [ "TAIWAN", [ qw/tw/ ] ], 'whois.krnic.net' => [ "KOREA", [ qw/kr/ ] ], 'whois.domainz.net.nz' => [ "GENERIC", [ qw/nz/ ] ], 'cdnnet.ca' => [ "CANADA", [ qw/ca/ ] ], 'whois.ripe.net' => [ "RIPE", [ qw( al am at az ma md mk mt ba be bg by nl no ch cy cz pl pt de dk dz ro ru ee eg es se si sk sm su fi fo fr tn tr gb ge gr ua uk hr hu ie va il is it yu li lt lu lv ) ] ], ); my %ARGS = ( 'whois.nic.ad.jp' => { 'S' => '/e' }, 'whois.internic.net' => { 'P' => '=' }, 'whois.networksolutions.com' => { 'P' => '=' }, ); sub get_results { my $ippat = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; # re to match IP my $result; my @results; foreach (split ("\n", shift )) { chomp; if( /^\s/ ) { $result .= " $_"; } else { if( defined $result ) { my($name, $handle, $netblock) = $result =~ /^(.+)\(([^)]+)\)\s* [-A-Z0-9]+\s+($ippat\s+-\s+$ippat)/x ; push(@results, join("\t", $name, $handle, $netblock) ) if defined $name; } $result = $_; } } return @results; } sub register_parser { my ( $self, %args ) = @_; $self->{ _PARSERS }->{ $args{ Name } } = {} unless $args{ Retain }; for ( keys %{ $args{ Parser } } ) { $self->{ _PARSERS }->{ $args{ Name } }->{$_} = $args{ Parser }->{$_}; } return 1; } sub register_association { my ( $self, %args ) = @_; for ( keys %args ) { $self->{ _ASSOC }->{ $_ } = $args{ $_ } }; return 1; } sub register_cache { my ( $self, $cache ) = @_; return ${ $self->{ _CACHE } } = $cache if $cache; } sub guess_server_details { my ( $self, $domain ) = @_; $domain = lc $domain; my ( $server, $parser ); my ( $Dserver, $Dparser ) = ( 'whois.internic.net', { %{ $self->{ _PARSERS }->{ INTERNIC } } } ); SWITCH: for ( keys %{ $self->{ _ASSOC } } ) { if ( grep { $domain =~ m/\.$_$/ } @{ $self->{ _ASSOC }->{ $_ }[1] } ) { $server = $_; $parser = $self->{ _PARSERS }->{ $self->{ _ASSOC }->{ $_ }[0] }; last SWITCH; } } return $server ? [$server, $parser] : [$Dserver, $Dparser]; }; sub new { my ( $class, %args ) = @_; my $self = {}; $self->{ _PARSERS } = \%PARSERS; $self->{ _ASSOC } = \%ASSOC; $self->{ _CACHE } = $args{Cache} || \$CACHE; $self->{ _EXPIRE } = $args{Expire} || \$EXPIRE; $self->{ _ARGS } = \%ARGS; bless $self, $class; $self->personality ( %args ); $self->lookup () if $self->{ Domain }; return $self; } sub personality { my ( $self, %args ) = @_; for ( keys %args ) { chomp $args{ $_}; $self->{ $_ } = $args{ $_ } } $self->{ Parser } = $self->{ _PARSERS }->{ $args{ Format } } if $args{ Format }; unless ( $self->{ Server } ) { my $res = $self->guess_server_details ( $self->{ Domain } ); ( $self->{ Server }, undef ) = @$res; } if( $ASSOC{$self->{ Server }} ) { # added rjf 20/3/01 $self->{ Format } = $ASSOC{$self->{ Server }}->[0]; $self->{ Parser } = $self->{ _PARSERS }->{$self->{ Format }}; } unless ( $self->{ Parser } && $self->{ Format } ) { my $res = $self->guess_server_details ( $self->{ Domain } ); ( undef, $self->{ Parser } ) = @$res; } $self->{ Timeout } = $TIMEOUT unless $self->{ Timeout }; $self->{ Error } = $ERROR unless $self->{ Error }; } sub lookup { my ( $self, %args ) = @_; $self->personality ( %args ); my $cache = $args{ Cache } || ${ $self->{ _CACHE } }; my $domain = $self->{ Domain }; unless ( $self->{ Nocache } ) { READCACHE: { if ( -d $cache ) { last READCACHE unless -e "$cache/$domain"; my $current = time (); open D, "$cache/$domain" || last READCACHE; my @stat = stat ( D ); if ( $current - $stat[ 9 ] > ${ $self->{ _EXPIRE } } ) { close D; last READCACHE; } undef $/; $self->{ Response } = ; return 1; } } } my $server = $self->{ Server }; my $suffix = $self->{ _ARGS }->{ $server }->{S} || ''; my $prefix = $self->{ _ARGS }->{ $server }->{P} || ''; my $sock = $self->_connect ( $self->{ Server } ); return undef unless $sock; print $sock $prefix , $self->{ Domain }, "$suffix\r\n"; { local $/; undef $/; $self->{ Response } = <$sock>; } undef $sock; my $fw = eval { $self->forwardwhois }; my @fwa = (); if ($fw =~ m/\n/) { @fwa = $self->{ Response} =~ m/\s+$self->{ Domain }\n.*?\n*?\s*?.*?Whois Server: (.*?)(?=\n)/isg; $fw = shift @fwa; return undef unless (length($fw) > 0); # pattern not found return undef if ($self->{ Server } eq $fw); #avoid infinite loop } if ( $fw ne "" ) { $self->personality( Format => $self->{_ASSOC}->{$fw}->[0]); $self->{ Server } = $fw; $self->{ Response } = ""; $self->lookup(); } if ( (-d $cache) && (!($self->{Nocache})) ) { open D, "> $cache/$domain" || return; print D $self->{ Response }; close D; } } sub AUTOLOAD { my $self = shift; return undef unless $self->{ Response }; my $key = $AUTOLOAD; $key =~ s/.*://; croak "Method $key not defined." unless exists ${$self->{ Parser }}{$key}; my @matches = (); if ( ref(${$self->{ Parser } }{ $key }) !~ /^CODE/ ) { @matches = $self->{ Response } =~ /${ $self->{ Parser } }{ $key }/sg; } else { @matches = &{ $self->{ Parser }{$key}}($self->response); } my @tmp = split /\n/, join "\n", @matches; for (@tmp) { s/^\s+//; s/\s+$//; chomp }; return wantarray ? @tmp : join "\n", @tmp ; } sub response { my $self = shift; return $self->{ Response }; } sub _connect { my $self = shift; my $machine = shift; my $error = $self->{Error}; my $sock = new IO::Socket::INET PeerAddr => $machine, PeerPort => 'whois', Proto => 'tcp', Timeout => $self->{Timeout} or &$error( "[$@]" ); $sock->autoflush if $sock; return $sock; } sub ignore {} 'True Value.'; ######################## end of Xwhois ########################## ## This is abuseEmail: require IO::Socket; require Getopt::Long; use Net::DNS; #use XWhoisIP; ##### # Options: # # Location of abuseEmail's blacklist my $blacklist=""; Getopt::Long::GetOptions( "verbose=i" => \$main::verbose, "usehostname!" => \$main::useHostname, "useabusenet!" => \$main::useAbusenet, "usednssoa!" => \$main::useDNSsoa, "usewhoisip!" => \$main::useWhoisIP, "showcommands!" => \$main::showCommands, "batch!" => \$main::batch, "help!" => \$main::showHelp, ); if ($main::showHelp) { printUsage(); exit(0); } # Verbose level: # 0: silent, only output the result # 1: same as 0 but also output fatal errors (default) # 2: same as 1 but also output non-fatal errors # 5: noisy: explain every action $main::verbose=1 if not defined $main::verbose; #default # Use the hostname to guess some addresses # 0: no --noUseHostname # 1: yes (default) --useHostname $main::useHostname=1 if not defined $main::useHostname; #default # Pass all the email addresses found into abuse.net directory # 0: no --noUseAbuseNet # 1: yes (default) --useAbuseNet $main::useAbusenet=1 if not defined $main::useAbusenet; #default # Dig the subnet manager's email address using DNS SOA # 0: no --noUseDNSsoa # 1: yes (default) --useDNSsoa $main::useDNSsoa=1 if not defined $main::useDNSsoa; #default # Get some system managers's email addresses using Whois on the IP address # 0: no --noUseWhoisIP # 1: yes (default) --useWhoisIP $main::useWhoisIP=1 if not defined $main::useWhoisIP; #default # Show the Unix shell equivalent of every action taken (so the user can reproduce the technique). # 0: no (default) --noShowCommands # 1: yes --showCommands $main::showCommands=0 if not defined $main::showCommands; #default # Batch mode, outputs something like: # 127.123.123.123:abuse@mailprovider.com, roger@domain.top $main::batch=0 if not defined $main::batch; #default # ################### my $ip = shift || die "Error: Please specify a host IP address.\n"; die "Error: This doesn't looks like a numeric IP address.\n" unless isIP($ip); # Structure of @main::abuseEmails: [email_address, confidence] @main::abuseEmails=(); # Blacklist # The blacklist can contains an unlimited number of entries. # Each entry is a general expression, one entry per line # Blanks lines and commented lines (#) don't count # Optimisation: Keep the most frequent entries at the top of the list, Keep the list short @main::blist=(); # Checking if it's a Private ip address print "Checking if $ip is a Private ip address..." if ($main::verbose>=5); if ( # RFC1918 between("192.168.0.0",$ip,"192.168.255.255") # IANA-CBLK1 || between("172.16.0.0",$ip,"172.31.255.255") # IANA-BBLK-RESERVED || between("10.0.0.0",$ip,"10.255.255.255") # RESERVED-6, RESERVED-10 ) { print "\nError: $ip is a private IP address (RFC1918). It's a local machine or a spoofed ip, either way, I can't give you any infos on this.\n" if ($main::verbose>=1); exit(1); } print "no\n" if ($main::verbose>=5); # Checking if it's a reserved ip address print "Checking if $ip is a reserved ip address..." if ($main::verbose>=5); if ( # IANA reserved -- This list will need to be updated from time to time. # http://archives.neohapsis.com/archives/snort/2000-03/0428.html # http://www.iana.org/assignments/ipv4-address-space between("0.0.0.0",$ip,"0.255.255.255") # RESERVED-1 || between("1.0.0.0",$ip,"1.255.255.255") # RESERVED-9 || between("2.0.0.0",$ip,"2.255.255.255") # RESERVED-2 || between("5.0.0.0",$ip,"5.255.255.255") # RESERVED-5 || between("14.0.0.0",$ip,"14.255.255.255") # NET-PDN || between("23.0.0.0",$ip,"23.255.255.255") # RESERVED-23 || between("27.0.0.0",$ip,"27.255.255.255") # RESERVED-27 || between("31.0.0.0",$ip,"31.255.255.255") # RESERVED-12, RESERVED-31 || between("36.0.0.0",$ip,"36.255.255.255") # RESERVED-36 || between("37.0.0.0",$ip,"37.255.255.255") # RESERVED-37 || between("39.0.0.0",$ip,"39.255.255.255") # RESERVED-39A || between("41.0.0.0",$ip,"41.255.255.255") # RESERVED-41A || between("42.0.0.0",$ip,"42.255.255.255") # RESERVED-42 || between("58.0.0.0",$ip,"60.255.255.255") # RESERVED-58, RESERVED-59, RESERVED-60 || between("67.0.0.0",$ip,"79.255.255.255") # RESERVED-7 || between("82.0.0.0",$ip,"95.255.255.255") # RESERVED-11 || between("96.0.0.0",$ip,"126.255.255.255") # RESERVED-8 || between("127.0.0.0",$ip,"127.255.255.255") # LOOPBACK || between("128.0.0.0",$ip,"128.0.255.255") # RESERVED-3 || between("169.254.0.0",$ip,"169.254.255.255") # LINKLOCAL || between("191.255.0.0",$ip,"191.255.255.255") # RESERVED-4 || between("197.0.0.0",$ip,"197.255.255.255") # RESERVED-13 || between("201.0.0.0",$ip,"201.255.255.255") # RESERVED-14 || between("219.0.0.0",$ip,"223.255.255.255") # RESERVED-5 || between("224.0.0.0",$ip,"239.255.255.255") # MCAST-NET (CLASS D MULTICAST) || between("240.0.0.0",$ip,"255.255.255.255") # IANA - Reserved (CLASS Experimental) ) { print "\nError: $ip is a reserved IP address. It's very likely to be a spoofed ip, or your network admin/BOFH is on crack, either way, I can't give you any infos on this.\n" if ($main::verbose>=1); exit(1); } print "no\n" if ($main::verbose>=5); # Loading the blacklist if ($blacklist) { print "Loading the blacklist..." if ($main::verbose>=5); open (BLIST, $blacklist) || die "Can't open the blacklist file ($blacklist): $.\n"; while (my $data = ) { chomp $data; push (@main::blist, $data) if ($data and not(substr($data,0,1) eq '#')); } close BLIST; print "loaded with ".@main::blist." items.\n" if ($main::verbose>=5); } # Check IP's hostname my $hostname=""; if ($main::useHostname) { print "Checking the hostname associated with $ip... " if ($main::verbose>=5); $hostname = ip2host($ip); do {print (($hostname)?"$hostname":"none")} if ($main::verbose>=5); if ($hostname && (host2ip($hostname) ne $ip)) { # Hum, Paranoid reverse DNS didn't passed, I don't trust this hostname print " Hum, Paranoid reverse DNS didn't passed, I don't trust this hostname" if ($main::verbose>=5); $hostname=""; } print "\n" if ($main::verbose>=5); # Checking directly at abuse.net for this hostname if ($hostname and $main::useAbusenet) { print "\nChecking directly at abuse.net for this hostname..." if ($main::verbose>=5); whoisAbuseAddList($hostname,1); } print "\n" if ($main::verbose>=5); } # Checking DNS zone's SOA if ($main::useDNSsoa) { print "Checking DNS zone's Start of Authority " if ($main::verbose>=5); my $soa; # SOA on the hostname if ($hostname && $main::loose) { # We don't have enough confidence in the addresses we allready got, trying to find more... print "on the hostname...\n" if ($main::verbose>=5); my $nsname="a.".$hostname; while ($nsname=popByte($nsname)) { $soa=DNSsoa($nsname); last if $soa; } if ($soa) { print "found: $soa\n" if ($main::verbose>=5); AddEmailList(soaToEmail($soa),0); } else { print "not found.\n" if ($main::verbose>=5); } } # SOA on ip address # This will usually give use emails of upstream providers print "on the ip address..." if ($main::verbose>=5); $soa=""; my $nsname="a.".inaddr($ip); while ($nsname=popByte($nsname)) { $soa=DNSsoa($nsname); #print "soa: $soa\n"; last if $soa; } if ($soa) { print "found: $soa\n" if ($main::verbose>=5); AddEmailList(soaToEmail($soa),0); } else { print "not found.\n" if ($main::verbose>=5); } } if ($main::useWhoisIP) { print "Checking with ARIN/APNIC/RIPE/AUSNIC/etc..." if ($main::verbose>=5); # Checking with ARIN/APNIC/RIPE/AUSNIC do_whois('whois.arin.net', $ip); } # Done! # Print the emails found with their confidence print "\nFound these abuse addresses:\n" if ($main::verbose>=5); print "Email address\tConfidence (the more, the better)\n" if (($main::verbose>=5) and not ($main::batch)); # Sort the emails by order of confidence. my @emailList = sort {@{$b}[1] <=> @{$a}[1]} @main::abuseEmails; my $emlist if $main::batch; foreach my $foo (@emailList) { if ($main::batch) { # Batch mode. Output looks like: # 127.123.123.123:abuse@mailprovider.com,roger@domain.top $emlist = $emlist . @{$foo}[0] . ","; } else { # Normal mode. Output looks like: # abuse@mailprovider.com 2 # roger@domain.top 1 print @{$foo}[0] ."\t". @{$foo}[1] ."\n"; } } if ($main::batch) { chop $emlist; # remove the trailing comma. print $ip .":". $emlist ."\n"; } exit(0); ################################### SUB ROUTINES ################################################### ## DNS functions sub ip2host { my $foo; my $ip = shift; if (isIP($ip)) { my @aa = split('\.', $ip); my $aaa = pack('C4', @aa); $foo = gethostbyaddr($aaa,2); } print "host $ip\n" if $main::showCommands; return $foo; } sub host2ip { my $host = shift; my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($host); my ($a,$b,$c,$d) = unpack('C4',$addrs[0]); print "host $host\n" if $main::showCommands; return "$a\.$b\.$c\.$d"; } sub DNSsoa { my $domain = shift; my $soa=""; my $res = eval { new Net::DNS::Resolver }; return unless $res; my $query = $res->query($domain, "SOA"); $soa = ($query->answer)[0]->rname if ($query); print "dig $domain soa\n" if $main::showCommands; return $soa; } # Takes 192.168.0.4 and returns 4.0.168.192.in-addr.arpa sub inaddr { my $ip = shift; my $inaddr; if ($ip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/) { $inaddr = "$4\.$3\.$2\.$1\.in-addr.arpa"; } return $inaddr; } ## Misc functions # Return true if a string is empty or contains things of little interest. sub empty { $a = shift; return 1 unless $a; return (($a ne "\n") && not($a =~ /\s/)) ; } # Finds out the domain of an email address sub emailDomain { $a = shift; ($a =~ /.+\@(.+)/); return $1; } ## IP addresses manipulations functions # Checks if this is really an ip address sub isIP { my $ip = shift; return ( ($ip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/) and $1<256 and $2<256 and $3<256 and $4<256 ); } # Transforms an ip address to a decimal number sub ip2dec { my ($ip) = shift; my $dec=0; if ( ($ip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/) and $1<256 and $2<256 and $3<256 and $4<256 ) { $dec = $1*(16777216) + $2*(65536) + $3*(256) + $4*(1); } return $dec; } # Transforms a decimal number to an ip address sub dec2ip { my ($dec) = shift; return 0 if ($dec>=0 or $dec<=4294967295); my @ip; $ip[0]=$dec/16777216; $ip[1]=($ip[0]-int($ip[0]))*16777216/65536; $ip[2]=($ip[1]-int($ip[1]))*65536/256; $ip[3]=($ip[2]-int($ip[2]))*256/1; return (int($ip[0]),"\.",int($ip[1]),"\.",int($ip[2]),"\.",int($ip[3])); } # $a > $b sub greater { my ($a,$b) = @_; return (ip2dec($a) > ip2dec($b)); } # $a < $b sub smaller { my ($a,$b) = @_; return (ip2dec($a) < ip2dec($b)); } # $a <= $ip <= $b sub between { my ($a,$ip,$b) = @_; return ((ip2dec($a) <= ip2dec($ip)) && (ip2dec($ip) <= ip2dec($b))); } # Remove the first byte of an ip address (in fact, removes everything before the first period). sub popByte { my $a = shift; ($a =~ /.+?\.(.+)/); return $1; } sub soaToEmail { my $soa = shift; if ($soa =~ /([^\.]+)\.(.+)/) { return $1."\@".$2; } else { return ""; } } # Returns true if the email is on the blacklist sub blacklisted { my $e = shift; my $blisted=0; foreach my $entry (@main::blist) { if ($entry && ($e =~ /$entry/)) { $blisted=1; last; } } return $blisted; } sub AddEmailList { my ($query,$strict) = shift; if ($main::useAbusenet) { whoisAbuseAddList( emailDomain($query), $strict); } elsif (not blacklisted($query)) { addToList($query); } else { print "this email is blacklisted: refused." if ($main::verbose>=5); } print "\n" if ($main::verbose>=5); } ## Whois functions # Very high level function that queries whois.abuse.net with a domain name and adds the result to the list. sub whoisAbuseAddList { my ($query,$strict) = shift; my @abuse = split(/\n/, whois("whois.abuse.net", $query) ); foreach my $address (@abuse) { if ($address =~ /([^\s]+\@[^\s]+)/) { my $possibleEmail=$1; if (not($strict) or ($hostname =~ emailDomain($possibleEmail)) ) { print "found $possibleEmail\n" if ($main::verbose>=5); # Checking if this email is blacklisted if (not blacklisted($possibleEmail)) { addToList($possibleEmail); addConfidence($possibleEmail) if not($address =~ "(default, no info)"); } else { print "this email is blacklisted: refused." if ($main::verbose>=5); } print "\n" if ($main::verbose>=5); } } } } # Low level function to add an email address to the list. # Check if the address is allready prensent. sub addToList { my $e = shift; my $found=0; $e =~ tr/A-Z/a-z/; foreach my $address (@main::abuseEmails) { if ($e eq @{$address}[0]) { $found=1; last; } } if ($found) { addConfidence($e); } else { # Add to the list with 0 confidence push (@main::abuseEmails, [$e, 0]); } } # Add confidence to a particular email address sub addConfidence { my $e = shift; # Increment our confidence in $e foreach my $foo (@main::abuseEmails) { if (@{$foo}[0] eq $e) { ++@{$foo}[1]; last; } } } # Whois client function # Based on GeekTools Whois Proxy v2.2.5 # Code mangled by Robb Ballard # Copyright 1999 CenterGate Research Group, LLC # http://www.geektools.com/software.php # ex: whois("whois.corenic.net","logidac.com"); sub whois { my $nicserv = $_[0]; my $myquery = $_[1]; my $output=""; print "whois -h $nicserv $myquery\n" if $main::showCommands; my $port = getservbyname 'whois', 'tcp'; my $remote = IO::Socket::INET->new( Proto=>'tcp', PeerAddr=>$nicserv, PeerPort=>$port, Timeout=>'15' ); if (!$remote) { print "Unable to connect to $nicserv.\n" if ($main::verbose>=2); return ""; } if ($nicserv eq "whois.networksolutions.com") { print $remote "L $myquery\r\n"; } elsif ($nicserv eq "whois.arin.net") { print $remote "NE $myquery\t\n"; } else { print $remote "$myquery\r\n"; } my @result = <$remote>; close $remote; foreach my $resline (@result) { $output = $output." ".$resline; } return $output; } sub do_whois{ my ($server, $target) = @_; my %secondary = ( "APNIC" => 'whois.apnic.net', "RIPE" => 'whois.ripe.net', "KRNIC" => 'whois.krnic.net', "JPNIC" => 'whois.nic.ad.jp', "BRAZIL" => 'whois.nic.br', ); print "whois -h $server $target\n" if $main::showCommands; my $whois = eval { new XWhoisIP ( Server => $server, Domain => $target) }; return unless $whois; my $netname = $whois->netname(); my $coord = $whois->contact_emails(); my $coord_a = $whois->contact_emails_a() if (($server eq "whois.apnic.net") or ($server eq "whois.ripe.net")); if( ! $whois->netname() ) { my @r = $whois->results(); foreach my $r ( @r ) { my( $name, $handle, $block) = split(/\t/, $r ); my $s; if( ($s = block_size($block, $target)) < 0 ) { print "Error: $target not in $block" if ($main::verbose>=2); } else { print "$block -- size $s\n" if ($main::verbose>=5); do_whois($server, $handle); } } } my $sub; if( (($sub) = $netname =~ /^(\w+)-/) and $secondary{$sub} ) { print "$netname, jumping to $secondary{$sub}\n" if ($main::verbose>=5); return do_whois( $secondary{$sub}, $target ); } $coord=$coord."\n".$coord_a if defined $coord_a; print "Coords found: \"$coord\"\n" if ($main::verbose>=4) and $coord; my @emails = split(/\n/, $coord); foreach my $em (@emails) { if ($em =~ /([^\s]+\@[^\s]+)/) { AddEmailList($1); } } } sub block_size { my ($block, $target ) = @_; my ($ip1, $ip2) = $block =~ /([0-9.]+)\s*-\s*([0-9.]+)/; my @ip1 = split(/\./, $ip1); my @ip2 = split(/\./, $ip2); my @t = split(/\./, $target); my $s = 0;; for( my $i=3; $i>=0; $i--) { if( (my $d = ($ip2[$i] - $ip1[$i] + 1)) != 1) { if( $d == 256 ) { $s += 8; } else { my $b; for($b = 0; $d > 1; $d /= 2) {$b++} $s += $b; } } if ($t[$i] < $ip1[$i] and $t[$i] > $ip2[$i]) { return -1; } } return $s; } sub printUsage() { print << "EOU"; Usage: abuseEmail [options] ip Options: --help Prints this. --versbose=i Set the verbose level to i. Current verbose levels are: # 0: silent, only output the result # 1: same as 0 but also output fatal errors (default) # 2: same as 1 but also output non-fatal errors # 5: noisy: explain every action --useHostname Specify if you want to use the hostname to guess some --noUseHostname adresses. Default: yes --useAbuseNet Specify if you want to pass all the email addresses found --noUseAbuseNet into abuse.net whois directory. Default: yes --useDNSsoa Specify if you want to dig the subnet manager's email --noUseDNSsoa address using DNS SOA. Default: yes --useWhoisIP Specify if you want to get some system managers's email --noUseWhoisIP addresses using Whois on the IP address. Default: yes --showCommands Show the Unix shell equivalent of every action taken (so the user can reproduce the technique). --batch Output the result in an easy to parse way. Examples: abuseEmail a.b.c.d abuseEmail --verbose=5 --showCommands a.b.c.d abuseEmail --noUseHostname --noUseAbuseNet --verbose=2 a.b.c.d Here a.b.c.d must be replaced by a real IP address. EOU } __END__