#!/usr/bin/perl # # spam_report.plx # my $version = '1.05'; # 6-28-04, michael@bizsystems.com # GPL'd, see Copyright notice in the package README file # use strict; #use diagnostics; use Mail::SpamCannibal::SiteConfig; my $spamcannibal = new Mail::SpamCannibal::SiteConfig; ######################################################### ######## READ THIS FILE FOR CONFIGURATION ############### ######################################################### # SET for your system # my $config = { # used for testing, overides host:post # 'file' => '/path/to/cache', # port to interrogate on local/remote host, REQUIRED 8686, used by LaBrea::Tarpit 'd_port' => '8687', # host to interrogate, defaults to localhost # 'd_host' => 'localhost', # host response timeout, default 180 seconds # # 'd_timeout' => 180, # THE REST USED TO ANALYZE syslog files, see man LaBrea::Tarpit # /directory/path/filename.cache.file for http cache files # 'cache' => $spamcannibal->{SPMCNBL_ENVIRONMENT}.'/labrea.cache', # umask for cache file creation, defaults to 033 # # 'umask' => '', # time to keep threads that have dropped out, default 600 seconds # # 'cull' => 600, # default 600, seconds to keep old threads # number of dead threads to keep in 'ESCAPED' # 'scanners' => 100, # keep this many dead threads }; $ENV{SCRIPT_NAME} =~ m|/[a-zA-Z0-9\._]+|; # script name to $& # SET THESE for your system my $look_n_feel = { # defaults shown 'face' => 'VERDANA,ARIAL,HELVETICA,SANS-SERIF', 'color' => '#ffffcc', 'bakgnd' => '#000000', 'images' => 'images/', # REQUIRED, path to images # below are for html file caching # the directory for the cache file MUST be writable by the web server 'html_cache_file' => './tmp/'. $& .'_page.cache', # required 'html_expire' => 300, # cache expiration, secs 'whois' => 'cannibal', }; ############### END CONFIG ########################## # button text URL # comment out or modify as needed for your site my @buttons = ( 'HOME' => 'http://spamcannibal.org onClick="var url = /(.*\/)/; var result = location.pathname.match(url);self.location = result[0];return false;"', 'SUMMARY' => 'summary onClick="return(please_wait(this));"', "SOURCE IP's" => 'attackers onClick="return(please_wait(this));"', 'ACTIVE' => 'captured onClick="return(please_wait(this));"', 'ESCAPED' => 'escaped onClick="return(please_wait(this));"', ); my @extras = (); my $first_page = 'summary'; # from above my $summary_days = 5; # number of days to show in capture summary my $buttwidth = 120; # should not need to be changed my $logo_text = q|  SpamCannibal |; =pod # add extra buttons like this... @_ = split('\.', $0); # get file extension $_ = pop @_; @extras = ( # extra buttons '' => '', # button space 'WEST SITE' => 'http://west.spamcannibal.org/spam_report.'.$_, ); =cut ######################################################### ############ no more user setable parameters ############ ######################################################### use LaBrea::Tarpit 1.17 qw( prep_report restore_tarpit ); use LaBrea::Tarpit::Report 1.09 qw( gen_short short_report time2local capture_summary guests_by_IP guests got_away my_IPs port_stats other_sites get_config get_versions make_buttons make_image_cache ); use LaBrea::Tarpit::Util qw( http_date daemon2_cache script_name upd_cache page_is_current labrea_whoami ); my ($image_cache,$use_cache,$error,$rpt,$sht,$html,$report,$short,$out,%tarpit); # make keys of the commands or hrefs @_ = ( 'short' => 'short', 'ERROR' => 'error', ); for (my $i=0; $i<=$#buttons;$i+=2) { $buttons[$i+1] =~ /(\S+)/; push @_,$buttons[$i],$1; } my %buttext = reverse @_; # get proposed action my $page = $ENV{QUERY_STRING}; $page = $first_page unless exists $buttext{$page}; my ($mtime,$upd); if ( $page eq 'short' ) { # skip the main cache check $mtime = time - $look_n_feel->{html_expire}; $use_cache = page_is_current($mtime, $look_n_feel->{html_cache_file}.'.'.$page); } else { # first thing, check the cache age ($mtime,$upd) = daemon2_cache( $look_n_feel->{html_cache_file}.'.mem', $config, $look_n_feel->{html_expire}, ); $error = $@; if ($error) { # was there a timeout error $page = 'error'; # falls through elsif's # note that $upd & $use_cache will be false } else { restore_tarpit(\%tarpit,$look_n_feel->{html_cache_file}.'.mem') unless ($use_cache = page_is_current($mtime, # always set $use_cache $look_n_feel->{html_cache_file}.'.'.$page)) || $page eq 'other'; # but skip restore if tarpit not needed } } ############## if (! $upd && $use_cache) { # skip the updates } ############## elsif ( $page eq 'summary' ) { # print STDERR "summary\n"; my (@csdate,@csctd); $out = { 'capture_summary' => undef, 'versions' => 'Created by: ', # capture statistics # all fields B 'cs_days' => $summary_days, 'cs_date' => \@csdate, # epoch midnight of capt date 'cs_ctd' => \@csctd, # captured this date }; prep_report(\%tarpit,$out); get_versions($out,$look_n_feel,$out,'dbtarpit'); capture_summary($out,$look_n_feel,$out); $report = q|
Page last updated | . &time2local($out->{now}, $out->{tz}) . q|

These reports show the spam / virus connection attempts to this sites mail host. The data is provided by the log output of the Mail::SpamCannibal tarpit daemon. For more information, download the complete package from CPAN.org

For complete information, read the Document link on the home page of this web site.

| . $out->{capture_summary} . q|
Briefly:
SpamCannibal's TCP/IP tarpit stops spam by telling the spamming server to send very small packets. This is accomplished by reducing the transmission window to minimum size. The result is that the spam server attempts to send over and over - ideally bringing the spam server to a virtual halt for a long time or perhaps indefinitely. SpamCannibal blocks spam at the source and eliminates that network traffic to your site since the spam never leaves the originating server.

This same strategy works equally well when SpamCannibal's tarpit daemon is configured to defend against DoS attacks. SpamCannibal uses a continually updated database containing the IP addresses of spam or DoS servers and blocks their ability to connect using its TCP/IP tarpit or IP drop capability. | . $out->{versions} . q|

|; $rpt = \$report; } ############## elsif ( $page eq 'attackers' ) { # print STDERR "attackers\n"; my (@thsip,@thnum); $out = { # threads per teergrubed host 'guests_by_IP' => undef, 'th_srcIP' => \@thsip, 'th_numTH' => \@thnum, # number threads this IP }; prep_report(\%tarpit,$out); guests_by_IP($out,$look_n_feel,$out); $rpt = \$out->{guests_by_IP}; } ############## elsif ( $page eq 'captured' ) { # print STDERR "captured\n"; my (@tgsip,@tgsp,@tgdp,@tgcap,@tglst,@tgpst); $out = { # teergrubed hosts 'guests' => undef, 'tg_srcIP' => \@tgsip, # B 'tg_sPORT' => \@tgsp, # B 'tg_dPORT' => \@tgdp, 'tg_captr' => \@tgcap, # capture epoch time 'tg_last' => \@tglst, # last contact 'tg_prst' => \@tgpst, # persistent [true|false] }; prep_report(\%tarpit,$out); guests($out,$look_n_feel,$out); $rpt = \$out->{guests}; } ############## elsif ( $page eq 'escaped' ) { # print STDERR "escaped\n"; my (@scsip,@scdp,@scpst,@sclst); $out = { # scanning hosts lost 'got_away' => undef, 'sc_srcIP' => \@scsip, # B 'sc_dPORT' => \@scdp, # attacked port 'sc_prst' => \@scpst, # persistent [true|false] 'sc_last' => \@sclst, # last contact }; prep_report(\%tarpit,$out); got_away($out,$look_n_feel,$out); $rpt = \$out->{got_away}; } ############## elsif ( $page eq 'local-ips' && ! ($error = get_config($config,$look_n_feel)) ) { # get config file # print STDERR "local-ips\n"; my (@phdip,@phpst); $out = { # phantom IP's used (from our IP block) 'my_IPs' => undef, 'ph_dstIP' => \@phdip, # B 'ph_prst' => \@phpst, # persistent [true|false] }; prep_report(\%tarpit,$out); my_IPs($out,$look_n_feel,$out); $rpt = \$out->{my_IPs}; } ############## elsif ( $page eq 'trends' ) { # print STDERR "trends\n"; my (@ports,@portstats,); $out = { # port statistics 'port_intervals' => $config->{port_intvls}, 'port_intvls' => $config->{port_intvls}, 'ports' => \@ports, # scanned port list 'portstats' => \@portstats, }; prep_report(\%tarpit,$out); port_stats($out,$look_n_feel,$out); $rpt = \$out->{port_intervals}; $image_cache = make_image_cache($look_n_feel->{images}); } ############## elsif ( $page eq 'others' ) { # print STDERR "others\n"; $out = { 'other_sites' => undef, }; $report = q| This is the recent Hacktivity at other sites running
LaBrea::Tarpit. To include your site in this list send an email
To: Michael@BizSystems.com
Subject: LaBrea::Tarpit site

containing the exact URL of your report script - i.e.

http://www.foo.com/html_report.cgi

To get the most recent list of sites using LaBrea::Tarpit, see:
scans.bizsystems.net/other_sites.txt
    or
www.bizsystems.net/downloads
|; other_sites($out,$look_n_feel,$out); $_ = 'onClick="return(please_wait(this));"'; $out->{other_sites} =~ s|(http:[^\"]+\")|$1 $_|g; $out->{other_sites} =~ s//$report/o; $rpt = \$out->{other_sites}; } ############## elsif ( $page eq 'short' ) { # print STDERR "short\n"; # prep short $out = {}; gen_short($config,$out); $error = $@; $sht = \$short; # flag and pointer short_report($sht,$out) unless $error; undef $rpt; } if ( $error ) { $upd = 1; # unconditional undef $sht; $page = 'error'; $report = qq|error, try again later.

The server said....

$error |; $rpt = \$report; } #elsif ( $upd && ! $sht ) { # new short report if update needed # $sht = \$short; # short_report($sht,$out); #} if ( $rpt ) { # if cache update needed $logo_text = ' ' unless $logo_text; ## MAKE STANDARD TOP AND BUTTON BAR $html = qq| SpamCannibal $page| . ($image_cache || '') . q|

| . $logo_text . qq| SpamCannibal $buttext{$page}
|; # make a two column page @_ = (@buttons,@extras); $html .= q|
| . make_buttons($look_n_feel,script_name,$page,\@_,$buttwidth) . q|
 
| . $$rpt . q|
  | . make_buttons($look_n_feel,script_name,$page,\@buttons) . q|
|; $rpt = \$html; } ## UPDATE CACHED PAGES upd_cache($look_n_feel->{html_cache_file},$page,$rpt,$sht); # the file $_ = $look_n_feel->{html_cache_file} .'.'. $page; $mtime = (stat($_))[9]; # file last modified ## SERVICE WEB REQUEST local(*FH); open(FH,$_); my $size = (stat FH)[7]; my $ctype = ($page ne 'short') ? 'text/html' : 'text/plain'; my $xhead = 'X-LaBrea'; my $xhv = labrea_whoami; #################################################### my $r; eval { require Apache; $r = Apache->request; }; unless ($@) { # unless error, it's Apache $r->status(200); $r->content_type($ctype); $r->header_out("Content-length","$size"); $r->header_out("Last-modified",http_date($mtime)); $r->header_out("Expires",http_date($mtime + $look_n_feel->{html_expire})); $r->header_out($xhead,$xhv); $r->send_http_header; $r->send_fd(*FH); close FH; return 200; # HTTP_OK } else { # sigh... no mod_perl undef $/; my $textp = ; # slurp file close FH; $/ = "\n"; print q |Content-type: |, $ctype, q| Content-length: |, $size, q| Last-modified: |, http_date($mtime), q| Connection: close Expires: |, http_date($mtime + $look_n_feel->{html_expire}), qq| $xhead: $xhv $textp|; } 1;