#!/usr/bin/perl
# Copyright (C) 2009-2010, Lorenzo Martínez Rodríguez
use LWP::UserAgent;
use Net::DNS;
use Net::Address::IP::Local;
use Getopt::Std;


#Function to show usage
sub Usage
{
	die "\nUsage $0 [-v 0|1] [-i <IP address> | -d <domain>] [-T 0|1] [-f emailfrom -t emailto -s smtp_server] [-h]\n";
}


#Function to search on TXT type DNS records the reason the IP has been blacklisted
sub checkTXT
{
	my $buscaip=$_[1];
	$buscaip=~m/(\d+)\.(\d+)\.(\d+)\.(\d+)/;
	$ip_al_reves=$4.".".$3.".".$2.".".$1;
	my $dns  = Net::DNS::Resolver->new;
	$dns->udp_timeout(5); #5 seconds to Timeout
	my $txtdns = $dns->search("$ip_al_reves.$_[0]",'TXT');
	my $return="";
	if ($txtdns ne '')
	{
		foreach ($txtdns->answer)
		{
			 if ($_->type eq "TXT") 
       	 		 {
				$return.= $_->rdatastr."\n";
			 }
		}
	}
	return "$return";
	
}

#Function to extract list of MX Records with a given domain. Useful to check all MX blacklisting
sub searchMX
{
	my @return;
	my $domain=$_[0];
        my $dns   = Net::DNS::Resolver->new;
        my $mxdns = $dns->query($domain,'MX');
        foreach ($mxdns->answer)
        {
                if ($_->type eq "MX")
                {
                        my @regmx=split (/ /,$_->rdatastr);
                        chop $regmx[1];

                        my $dns2 = Net::DNS::Resolver->new;
                        my $adns= $dns->query($regmx[1]);
                        foreach ($adns->answer)
                        {
                                my $ip= $_->rdatastr;
                                push (@return,$ip);
                        }
                }
        }
	return (@return);
}

#Function to check one IP against one BlackList
sub check_ip_bl
{
	my $ip=$_[0];
	my $bl=$_[1];
        if ($verbose!=0) {print "Checking $ip against $bl list...\n";}
	if ($hilos == 1) {while ($all_started != 1){sleep(1);} }
        my $answer=checkTXT ($bl,$ip);
        if ($verbose>=1)
        {
        	if ($answer ne "")
                {
                        print "* $bl Blacklisted!\n REASON: $answer\n";
                }
                else {print "$bl Not Listed\n";}
         } elsif ($verbose == 0 && $answer ne "")
         {
                $found=1;
                $listas.="* $bl Blacklisted!\n REASON: $answer\n";
                print "* $bl Blacklisted!\n REASON: $answer\n";
         }
}

#Function to check an IP against Spam-ip.com blacklist
sub check_spam_ip
{
	my $ip = $_[0];
	my $url="http://spam-ip.com/find.php";
        my $browser = LWP::UserAgent->new;
        if ($verbose!=0) {print "Checking $ip against SPAM-IP list...\n";}
        if ($hilos == 1) {while ($all_started != 1){sleep(1);} }
        my $response = $browser->post(
        	$url,
	        "Content" => [id=>$ip],
	);

        $respuesta=$response->content;
	my $razon="";
	my $match = "Download Results as .CSV";
	if ($respuesta =~ m/$match/)
	{
		$razon="Check http://spam-ip.com";
	}
        if ($verbose>=1)
        {
                if ($razon ne "")
                {
                        print "* SPAM-IP Blacklisted!\n REASON: $razon\n";
                }
                else {print "SPAM-IP Not Listed\n";}
         } elsif ($verbose == 0 && $razon ne "")
         {
                $found=1;
                $listas.="* SPAM-IP Blacklisted!\n REASON: $razon\n";
                print "* SPAM-IP Blacklisted!\n REASON: $razon\n";
	}
}



#Function to check if one IP address against all black lists concurrently
sub checkip
{
	my $ip=$_[0]; #Current IP
	print "Checking IP: $ip\n";
	#Sources to test
	@blacklists=("access.redhawk.org", "bl.csma.biz", "bl.spamcannibal.org", "bl.spamcop.net", "bl.technovision.dk", "blackholes.five-ten-sg.com", "blackholes.wirehub.net", "blacklist.sci.kun.nl", "block.dnsbl.sorbs.net", "blocked.hilli.dk","cart00ney.surriel.com","cbl.abuseat.org","dev.null.dk", "dialup.blacklist.jippg.org", "dialups.mail-abuse.org","dialups.visi.com","dnsbl.ahbl.org","dnsbl.antispam.or.id", "dnsbl.cyberlogic.net","dnsbl.kempt.net", "dnsbl.njabl.org", "dnsbl.sorbs.net", "dnsbl-1.uceprotect.net","dnsbl-2.uceprotect.net", "dnsbl-3.uceprotect.net","dsbl.dnsbl.net.au", "duinv.aupads.org", "dul.dnsbl.sorbs.net","dul.ru", "fl.chickenboner.biz","hil.habeas.com","http.dnsbl.sorbs.net","http.opm.blitzed.org", "intruders.docs.uu.se","korea.services.net", "l1.spews.dnsbl.sorbs.net","l2.spews.dnsbl.sorbs.net","mail-abuse.blacklist.jippg.org","map.spam-rbl.com","misc.dnsbl.sorbs.net", "msgid.bl.gweep.ca", "multihop.dsbl.org", "no-more-funn.moensted.dk","ohps.dnsbl.net.au", "omrs.dnsbl.net.au", "orid.dnsbl.net.au", "orvedb.aupads.org","osps.dnsbl.net.au","osrs.dnsbl.net.au", "owfs.dnsbl.net.au","owps.dnsbl.net.au","probes.dnsbl.net.au","proxy.bl.gweep.ca","psbl.surriel.com","pss.spambusters.org.ar","rbl.schulte.org","rbl.snark.net","rbl.triumf.ca","rdts.dnsbl.net.au","relays.bl.gweep.ca","relays.bl.kundenserver.de","relays.mail-abuse.org","relays.nether.net","ricn.dnsbl.net.au","rmst.dnsbl.net.au","rsbl.aupads.org","sbl.csma.biz", "sbl.spamhaus.org","sbl-xbl.spamhaus.org","smtp.dnsbl.sorbs.net","socks.dnsbl.sorbs.net","socks.opm.blitzed.org","sorbs.dnsbl.net.au","spam.dnsbl.sorbs.net","spam.olsentech.net","spam.wytnij.to","spamguard.leadmon.net","spamsites.dnsbl.net.au","spamsources.dnsbl.info","spamsources.fabel.dk", "spews.dnsbl.net.au", "t1.dnsbl.net.au", "ucepn.dnsbl.net.au", "unconfirmed.dsbl.org", "web.dnsbl.sorbs.net","whois.rfc-ignorant.org","will-spam-for-food.eu.org","wingate.opm.blitzed.org","xbl.spamhaus.org", "zen.spamhaus.org","zombie.dnsbl.sorbs.net" );
	if ($hilos == 1)
	{
		use threads;
		use threads::shared;
		my @array_threads;
		share($found); share($listas); share($all_started); #Shared variables. Used by threads.
		$all_started=0;
		foreach $bl (@blacklists)
		{
			$thr = threads->new(\&check_ip_bl,$ip,$bl);
        		push (@array_threads,$thr);
		}
                $thr = threads->new(\&check_spam_ip,$ip);
                push (@array_threads,$thr);

		$all_started=1; #If all threads have been started, allow the answers to be printed
	
		foreach $thr_array(@array_threads) #Wait until all threads have finished
		{
       		 	$thr_array->join();
		}
	}
	else
	{
		foreach $bl (@blacklists)
                {
			check_ip_bl($ip,$bl);#if threads not used, iteration is being used
		}
		check_spam_ip($ip); #Then make this check
	}
	
	my ($subjemail, $messemail);

	if ($found eq 1) 
	{
		$subjemail = "IP $ip is blacklisted!!!!";
		$messemail = "IP Address $ip has been detected on next lists: \n$listas\n";		

		if ($from && $to) #Just in case of optional mailing needed
		{
			use Email::Sender::Simple qw(sendmail);
			use Email::Simple::Creator;
			use Email::Sender::Transport::SMTP;

			my $transport = Email::Sender::Transport::SMTP->new({
			    host => $smtp,
			    port => 25,
			  });

			my $email = Email::Simple->create(
			    header => [
			    To      => $to,
			    From    => $from,
			    Subject => $subjemail,
			    ],
			    body => $messemail,
			  );

			sendmail($email, { transport => $transport });
			$found=0; $listas=""; #reset to 0
		}
	}
}


#MAIN

#Decreasing process priority to avoid overload with threads
setpriority (0,0,20);

print "am I Spammer? 28/08/2011 (http://www.lorenzomartinez.es/projs/amispammer)\nBy Lorenzo Martinez (lorenzo\@lorenzomartinez.es)\n";

#Search for -h
my $help = 0;
foreach (@ARGV)
{
	$help=1 if  ($_ =~ /^-h/i);
}

#Switches
getopt('ivftsdT');

die Usage if ( ($opt_i && $opt_d) || ($help eq 1) ); # -i or -d, both are not valid at once, or -h

my $found=0; $listas="";#Global vars needed

@ips; #IPs to be checked
if ($opt_d) #Domain specified with -d
{	
	@ips=searchMX ($opt_d); #List of IPs
}else #else get IP address with -i or automatically
{
	$ips[0]=$opt_i || Net::Address::IP::Local->public || "0.0.0.0"; #Get IP
}

($ips[0] ne "NULL") || die Usage; # Show Usage if IP not possible

$verbose=$opt_v||0; 
$from=$opt_f;
$to=$opt_t;
$smtp=$opt_s||"127.0.0.1";
$hilos=$opt_T;

if (($hilos ne 1) && ($hilos ne 0)) {$hilos = 1;} #By default we will use threads

checkip ($_) foreach (@ips); #Check every IP address

