#!/usr/bin/perl use strict; use warnings; ## Scott Wiersdorf ## Created: Thu Aug 30 09:52:53 MDT 2007 ## $Id: received_from,v 1.3 2007/10/25 15:53:23 scott Exp $ ## get Received IP from last hop, only if this message is new ## this works like get_newer_ips, but operates on an entire mailbox ## sample usage ## ## 5 22 * * * /usr/local/bin/formail -c -s < $HOME/Mail/spam | $HOME/bin/received_from --days=30 --last_hop=deep2.securesites.net --ignore=204.202.9.74 --class_c | /usr/bin/sort -u > $HOME/spammers && /usr/bin/scp -p -i $HOME/.ssh/id_dsa_spammers $HOME/spammers deep2.securesites.net:~/ use Time::Local 'timelocal'; use Getopt::Long; my %opt = ( days => 30, last_hop => 'localhost', ignore => [ '127.0.0.1' ], class_c => 0, c_limit => 20 ); unless( GetOptions(\%opt, 'help|h', 'version|v', 'verbose', 'days|d=i', 'last_hop=s', 'ignore:s', \@{ $opt{ignore} }, 'class_c', 'c_limit=i',) ) { usage(); } usage() if $opt{help}; our $VERSION = '0.20'; if( $opt{version} ) { die <<_VERSION_; received_from: $VERSION _VERSION_ } $opt{last_hop} =~ s/\./\\./g; if( $opt{verbose} ) { print STDERR <<_VERBOSE_; verbose: $opt{verbose} days: $opt{days} last_hop: $opt{last_hop} class_c: $opt{class_c} c_limit: $opt{c_limit} ignore: @{$opt{ignore}} _VERBOSE_ } ## read the mailbox my $state = 0; my $last = "\n"; my %class_c = (); my %seen_ip = (); MAIL_LINE: while( <> ) { ## looking for a new '^From ' header w/ correct date if( $state == 0 ) { my $date; unless( $last eq "\n" and ($date) = $_ =~ /^From \S+\s+(.+)$/mo ) { $last = $_; next MAIL_LINE; } $last = $_; ## reset this $date =~ s/ / /g; my %m = (Jan=>0, Feb=>1, Mar=>2, Apr=>3, May=>4, Jun=>5, Jul=>6, Aug=>7, Sep=>8, Oct=>9, Nov=>10, Dec=>11); my @d = split /[: ]/, $date; print STDERR "DATE: $date" if $opt{verbose}; unless( timelocal($d[5],$d[4],$d[3],$d[2],$m{$d[1]},$d[6]-1900) >= time-($opt{days}*24*60*60) ) { print STDERR "...skipping\n" if $opt{verbose}; next MAIL_LINE; } print STDERR "...ok!\n" if $opt{verbose}; $state = 1; next MAIL_LINE; } ## got matching '^From ' header, now looking for Received headers elsif( $state == 1 ) { ## end of headers if( $_ eq "\n" ) { $last = $_; $state = 0; next MAIL_LINE; } ## look for Received header unless( m!^Received:\s+from.*\[([\d\.]+)\].*?\)\s+by\s+$opt{last_hop}\s!mo ) { next MAIL_LINE; } my $ip = $1; ## check ignore list (127.0.0.1 is the default) for my $skip_ip ( @{ $opt{ignore} } ) { if( ($skip_ip =~ /\.$/o && $ip =~ /^\Q$skip_ip\E/) or $ip eq $skip_ip ) { print STDERR "Skipping '$ip' because it matches an entry in our ignore list ($skip_ip)\n" if $opt{verbose}; $last = $_; $state = 0; next MAIL_LINE; } } ## count times this class C has abused us, but from different hosts if( $opt{class_c} ) { my ($c) = $ip =~ /^(\d+\.\d+\.\d+\.)\d+$/; $class_c{$c}++ unless $seen_ip{$ip}; ## only count this IP once $seen_ip{$ip} = $c; } else { print $ip . "\n"; } $last = $_; $state = 0; next MAIL_LINE; } } if( $opt{class_c} ) { for my $ip ( keys %seen_ip ) { if( $class_c{$seen_ip{$ip}} > $opt{c_limit} ) { print $seen_ip{$ip} . "\n"; } else { print $ip . "\n"; } } } exit; sub usage { die <<_USAGE_; usage: received_from [options] mbox1 [mbox2 ...] received_from scans an email message or mailbox and prints IP addresses of hosts connecting to last_hop on stdout. options: --help this message --version show version and exit --verbose show lots of extra information (uses stderr) --days=n specifies how many days in the past to scan for (default is 30) --last_hop=host IP addresses making connections to *this* host will be printed (default = localhost) --ignore=ip ignore this ip address. Useful for skipping spam that may have relayed from somewhere you don't want to block. Patterns may be specified by leaving off octets but keeping the trailing dot (e.g., "212.42."). This option may be specified multiple times. (default = 127.0.0.1) --class_c lump together class c addresses whe c_limit is hit --c_limit=n how many times a unique address in a class c must appear before the whole class is banned example: formail -c -s < ~/Mail/spam | received_from --ignore=1.2.3.4 --last_hop=my.mail.server --class_c _USAGE_ }