全部博文(1144)
分类: LINUX
2009-12-09 11:52:17
#!/usr/local/bin/perl # Hunnypot: Copyright 2004 Jeremy Kister # Released under Perl's Artistic License. # Function: Get information about spammers and infected machines # Author: Jeremy Kister (hunnypot-devel @t jeremykister.com) # :set tabstop=3 in vi # version 0.6b use strict; use IO::Socket::INET; use IO::Multiplex; use Net::DNS; use DBI; chdir('/') || die "cannot chdir /: $!\n"; my $DEBUG = $ENV{'DEBUG'} || 0; my $dbun = $ENV{'DBUN'}; my $dbpw = $ENV{'DBPW'}; my $driver = $ENV{'DRIVER'}; my $dsn = "DBI:${driver}:"; my $dbserver = $ENV{'DBSERVER'}; if($driver =~ /Sybase/){ $dsn .= "server=$dbserver"; }else{ $dsn .= "host=${dbserver};database=$ENV{'DBNAME'}"; } my $heloname = $ENV{'HELONAME'} || 'hunnypot.localdomain'; my $ip = $ENV{'LISTEN_IP'} || '0.0.0.0'; my (%client,%smtpcache); print "STARTING SERVER..\n" if($DEBUG); unless(defined($dbun) && defined($dbpw) && defined($driver) && defined($dbserver)){ print "proper environment variables not set; sleeping 10 seconds\n"; sleep 10; die; } $| = 1; my $mux = new IO::Multiplex; my $server = IO::Socket::INET->new(Proto => 'tcp', LocalAddr => $ip, LocalPort => 25, Listen => 30, Reuse => 1) || die "cannot set up socket: $!\n"; my ($uid,$gid) = (getpwnam('nobody'))[2,3]; $( = $) = $gid; $! = 0; $< = $> = $uid; die "unable to chid nobody: $!\n" if($!); my $dbh = DBI->connect($dsn, $dbun, $dbpw, {PrintError => 1}); until($dbh){ $dbh = DBI->connect($dsn, $dbun, $dbpw, {PrintError => 1}); unless($dbh){ print "could not connect to database: $DBI::errstr (sleeping 10)\n"; sleep 10; } } $SIG{ALRM} = sub { # clear unused smtp status cache while(my($key,$value) = each %smtpcache){ if($value < (time() - 399)){ print "removing smtpcache for $key ($value)\n" if($DEBUG); delete $smtpcache{$key}; } } alarm(3600); }; alarm(3600); $mux->listen($server); $mux->set_callback_object(__PACKAGE__); $mux->loop; sub mux_connection { my $package = shift; my $mux = shift; my $fh = shift; my $peer = $fh->peerhost(); $client{ip}{$fh} = $peer; $client{$peer} ++; my $total = $mux->handles; if(($total > 20) || ($client{$peer} > 3)){ $mux->write($fh, "451 too many concurrent connections\r\n"); $mux->shutdown($fh,1); print "Disconnected: $peer ([$client{$peer}/3] [${total}/20] connections)\n"; }else{ $mux->write($fh, "220 ${heloname} ESMTP\r\n"); $mux->set_timeout($fh, 30); print "Connection from: $peer ([$client{$peer}/3] [${total}/20])\n"; } } sub mux_timeout { my $package = shift; my $mux = shift; my $fh = shift; $mux->write($fh, "451 timeout\r\n"); $mux->shutdown($fh,1); print "$client{ip}{$fh} -> timeout\n" if($DEBUG); } sub mux_eof { my $package = shift; my $mux = shift; my $fh = shift; $mux->set_timeout($fh, undef); print "$client{ip}{$fh} -> eof\n" if($DEBUG); $mux->shutdown($fh,1); } sub mux_close { my $package = shift; my $mux = shift; my $fh = shift; print "$client{ip}{$fh} -> close\n" if($DEBUG); $client{$client{ip}{$fh}} --; foreach('ip','mf','rt','bytes'){ delete $client{$_}{$fh} } $mux->close($fh); my @handles = $mux->handles; my $total = @handles; print "STATUS: [${total}/20]\n"; if($total > 0){ foreach my $handle (@handles){ my $peerhost = $handle->peerhost(); if($peerhost =~ /^(\d{1,3}\.){3}\d{1,3}$/){ print "Remaining host: [${peerhost}]\n" if($DEBUG); }else{ # bug in IO::Multiplex ? print "Removing rouge handle: [${peerhost}]\n" if($DEBUG); $mux->close($handle); } } } } sub mux_input { my $package = shift; my $mux = shift; my $fh = shift; my $input = shift; $mux->set_timeout($fh, undef); $mux->set_timeout($fh, 30); $$input =~ s{^(.*)\n+}{ } or return; chop(my $line = $1); $$input = ''; $client{bytes}{$fh} += length($line); if($client{bytes}{$fh} > 1024){ print "$client{ip}{$fh}: too much data - shutting down\n"; $mux->write($fh, "451 too much data\r\n"); $mux->shutdown($fh,1); } print "$client{ip}{$fh}: ${line}\n"; if($line =~ /^(HE|EH)LO/i){ $mux->write($fh, "250 ${heloname}\r\n"); }elsif($line =~ /^VRFY/i){ $mux->write($fh, "252 send some mail, i'll try my best\r\n"); }elsif($line =~ /^MAIL FROM:\s*\([^>]*)\>?/i){ $client{mf}{$fh} = $1; $mux->write($fh, "250 ok\r\n"); }elsif($line =~ /^RCPT TO:\s*\(([a-z0-9_\.\+\-\=\?\^\#]+)\@(([a-z0-9\-]+\.)+[a-z0-9]{2,4}))\>?/i){ my $rt = $1; my $user = $2; my $domain = $3; if(exists($client{mf}{$fh})){ $client{rt}{$fh} = $rt; my $remaining = ($smtpcache{$domain} - (time() - 399)); if(exists($smtpcache{$domain}) && ($remaining > 0)){ print "$client{ip}{$fh} -> have cache for $domain ($remaining remaining)\n" if($DEBUG); }else{ my @mx = mx($domain); my $nummx = @mx; my $lastmx = $mx[-1]; my $highest = $lastmx->exchange if(defined($lastmx)); if($nummx > 1){ foreach my $rr (@mx){ my $exchanger = $rr->exchange; if(($exchanger =~ /^(127|0|10|255|224)\./) || ($exchanger =~ /^192\.168\./) || ($exchanger =~ /^172\.(1(6|7|8|9)|2\d|3(0|1))\./) || ($exchanger =~ /^192\.0\.2\./)){ next; }else{ # dont want to connect to lowest priority MX; that's me last if($exchanger eq $highest); print "$client{ip}{$fh} -> attempting connection to ${exchanger}..\n" if($DEBUG); my $sock = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => $exchanger, PeerPort => 25, Timeout => 10); $mux->set_timeout($fh, undef); $mux->set_timeout($fh, 30); # because this could be time consuming if($sock){ print "$client{ip}{$fh} -> connected.\n" if($DEBUG); my @banner = getlines($sock); if($banner[-1] =~ /^220\s/){ print $sock "HELO ${heloname}\r\n"; my @helo = getlines($sock); if($helo[-1] =~ /^250\s/){ print $sock "MAIL FROM:<>\r\n"; my @mf = getlines($sock); if($mf[-1] =~ /^250\s/){ # this is as far as we can tset # (if we test RCPT TO, we run into greylisters, # invalid mailboxes, over quota, etc) print "$client{ip}{$fh} -> primary ok; caching data.\n" if($DEBUG); $smtpcache{$domain} = time(); print $sock "QUIT\r\n"; close $sock; last; } } } }else{ print "$client{ip}{$fh} -> failed\n" if($DEBUG); } } } }else{ print "$client{ip}{$fh} -> skipping socket test (only mx is me)\n" if($DEBUG); $smtpcache{$domain} = time(); # only MX must be me or an open relay scan } } if(exists($smtpcache{$domain})){ $mux->write($fh, "250 ok\r\n"); my $sql = 'SELECT id,times FROM hunnypot WHERE ip = ' . $dbh->quote($client{ip}{$fh}); $sql .= ' AND mf = ' . $dbh->quote($client{mf}{$fh}) . ' AND rt = '; $sql .= $dbh->quote($client{rt}{$fh}); my $sth = $dbh->prepare($sql); $sth->execute; my $row = $sth->fetchrow_hashref; my $id = $row->{id}; my $times = $row->{times}; if(defined($id) && defined($times)){ my $new = ($times + 1); my $sql = 'UPDATE hunnypot SET times = ' . $new . ', timestamp = ' . time(); $sql .= ' WHERE id = ' . $id; my $sth = $dbh->prepare($sql); $sth->execute; }else{ my $sql = 'INSERT INTO hunnypot (timestamp,mf,rt,ip,times) VALUES (' . time(); $sql .= ',' . $dbh->quote($client{mf}{$fh}) . ',' . $dbh->quote($client{rt}{$fh}); $sql .= ',' . $dbh->quote($client{ip}{$fh}) . ',' . '1)'; my $sth = $dbh->prepare($sql); $sth->execute; } }else{ $mux->write($fh, "451 try later\r\n"); delete $client{rt}{$fh}; print "$client{ip}{$fh} -> Ignoring chatter: primary MXs off-line\n"; } }else{ $mux->write($fh, "503 MAIL first\r\n"); } }elsif($line =~ /^DATA$/i){ if(exists($client{mf}{$fh})){ if(exists($client{rt}{$fh})){ # give 400 instead of 500 in case main mail server really is off-line $mux->write($fh, "450 gotchya :)\r\n"); }else{ $mux->write($fh, "503 RCPT first\r\n"); } }else{ $mux->write($fh, "503 MAIL first\r\n"); } }elsif($line =~ /^QUIT$/i){ $mux->write($fh, "221 ${heloname}\r\n"); $mux->shutdown($fh,1); }elsif($line =~ /^RSET$/i){ delete $client{mf}{$fh}; delete $client{rt}{$fh}; $mux->write($fh, "250 flushed\r\n"); }elsif($line =~ /^NOOP$/i){ $mux->write($fh, "250 ok\r\n"); }else{ $mux->write($fh, "502 unimplemented\r\n"); } } sub getlines { my $sock = shift; my @lines; while(<$sock>){ if(/^\d+\s/){ chomp; push @lines, $_; last; }else{ push @lines, $_; } } return(@lines); }