OSEC

Neohapsis is currently accepting applications for employment. For more information, please visit our website www.neohapsis.com or email hr@neohapsis.com
 
Re: selective greylisting?

From: Mark Martinec (Mark.Martinec+postfixijs.si)
Date: Thu Nov 09 2006 - 20:11:41 CST


On Wednesday November 1 2006 22:49, Folkert van Heusden wrote:
> > > Thank you, but that is not entirely what I would like to do: I would
> > > like postfix to check an rbl-list and if the host is in that list, then
> > > execute the greylisting-part. So ONLY do greylisting if it is in an
> > > RBL-list.
> >
> > That's only possible in the policy daemon itself, so patch the daemon
> > that implements the greylisting.
>
> I did some further googling and found this:
> http://www.orangegroove.net/code/marbl/
> which is exactly what you describe!
> Seems to work...

Some time ago I suggested to Cami to incorporate remote fingerprinting
intelligence into his policy daemon. Somehow this didn't catch,
perhaps because modifying a C program is not as easy as doing it
in Perl.

The maRBL by Ian Campbell is just such a cute programming pearl
and perfect for this job, so I added a bit of code to it to let it
trigger greylisting not only on RBL hits, but also if the remote
host is running Windows (more than 95% of mail coming from Windows
is spam from zombies, so this information by itself is worth
triggering greylisting).

Patched maRBL queries a daemon running p0f-analyzer.pm, which comes
with amavisd-new package, but is a standalone service, covered by
a BSD license. If p0f-analyzer.pm is already in use with amavisd,
the marbl may query the same daemon. Queries/responses are almost
instantaneous, cost is almost nil. The hard work is done by a p0f
service, which inspects IP packets and feeds its information to
p0f-analyzer.pm, which just caches it for a couple of minutes and
answers queries from this cache. Detail in RELEASE_NOTES of
amavisd-new ( http://www.ijs.si/software/amavisd/release-notes.txt ),
search for "p0f" (it was introduced with amavisd-new-2.4.0).

Below is a patch to be applied to marbl. I hope Ian will consider
incorporating it into his code. Happy selective greylisting!

  Mark

--- maRBL-1.0.pl.orig Mon Nov 6 00:48:54 2006
+++ marbl Fri Nov 10 02:32:05 2006
-7,4 +7,6
 use Net::Server::Multiplex;
 use Net::RBLClient;
+use IO::Socket::INET;
+use Time::HiRes ();
 use vars qw(ISA);
 ISA = qw(Net::Server::Multiplex);
-52,4 +54,9
  
 $config{'127.0.0.1:2552'} = {
+ p0f => { # queries daemon p0f-analyzer.pl (comes with amavisd-new)
+ p0f_service => 'inet:127.0.0.1:2345', # where is p0f-analyzer.pl?
+ matching_regexp => qr{^Windows\b},
+ # matching_regexp => qr{^Windows XP},
+ },
     rbls => [
             'sbl-xbl.spamhaus.org',
-71,4 +78,52
 };
 
+# Prepares an UDP socket and sends a query with SMTP client IP address
+# information to a daemon p0f-analyzer.pl, which is keeping a cache of
+# gathered intelligence on recent incoming TCP connections to our MTA.
+sub p0f_init($$$$$) {
+ my($self, $hostport,$timeout,$query,$nonce) = _;
+ local($1,$2,$3);
+ $hostport =~ /^(?: inet: )? (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*) /six
+ or die "Bad p0f method syntax: $hostport";
+ my($host,$port) = ($1.$2, $3); my($sock);
+ $self->{net_server}->log(2, "p0f query: %s port=%s %s %s",
+ $host,$port,$query,$nonce);
+ $sock = IO::Socket::INET->new(Type=>SOCK_DGRAM, Proto=>'udp')
+ or die "Can't create INET socket: $!";
+ my($hisiaddr) = inet_aton($host) or die "p0f - unknown host: $host";
+ my($hispaddr) = scalar(sockaddr_in($port, $hisiaddr));
+ defined($sock->send("$query $nonce", 0, $hispaddr)) or die "p0f - send: $!";
+ { sock=>$sock, wait_until=>(Time::HiRes::time + $timeout),
+ query=>$query, nonce=>$nonce };
+}
+
+# Collect a response from p0f-analyzer.pl which provides best guess about
+# remote host operating system, based on passive OS fingerprinting (p0f);
+# The p0f-analyzer.pl comes with amavisd-new, but is a standalone daemon and
+# is covered by a liberal BSD license (see RELEASE_NOTES of amavisd-new for
+# details on p0f usage).
+sub p0f_collect_response($$) {
+ my($self, $obj) = _;
+ my($timeout) = $obj->{wait_until} - Time::HiRes::time;
+ if ($timeout < 0) { $timeout = 0 };
+ my($sock) = $obj->{sock};
+ my($resp,$nfound); my($rin,$rout);
+ $rin = ''; vec($rin,fileno($sock),1) = 1;
+ while ($nfound=select($rout=$rin, undef,undef,$timeout)) {
+ my($inbuf); my($rv) = $sock->recv($inbuf,1024,0);
+ defined $rv or die "p0f - error receiving from socket: $!";
+ if ($inbuf =~ /^([^ ]*) ([^ ]*) (.*)\015\012\z/) {
+ my($r_query,$r_nonce,$r_resp) = ($1,$2,$3);
+ if ($r_query eq $obj->{query} && $r_nonce eq $obj->{nonce})
+ { $resp = $r_resp };
+ }
+ $self->{net_server}->log(2, "p0f collect: max_wait=%.3f, %.35s... => %s",
+ $timeout,$inbuf,$resp);
+ $timeout = 0;
+ }
+ defined $nfound or die "p0f - select on socket failed: $!";
+ $resp;
+}
+
 # Save the ip/port the connection came in on. This gets used to decide
 # which RBLs to use, and which actions to take.
-122,8 +177,26
     my ($self, $attr) = _;
     my $hostport = $self->{addr} . ':' . $self->{port};
+ my $cl_ip = $attr->{client_address};
     my $cfg = $config{$hostport};
- my $rbl = Net::RBLClient->new('lists' => $cfg->{rbls});
 
- $rbl->lookup($attr->{client_address});
+ my $p0f = $cfg->{p0f};
+ my $p0f_service = !ref($p0f) ? undef : $p0f->{p0f_service};
+ my $p0f_regexp = !ref($p0f) ? undef : $p0f->{matching_regexp};
+ if (defined($p0f_service) && defined($p0f_regexp) &&
+ $cl_ip ne '' && $cl_ip ne '0.0.0.0' && $cl_ip ne '::') {
+ my $nonce = int(rand(1000000000)); # not too clever, but good enough
+ my $os_fingerprint_obj = $self->p0f_init($p0f_service, 0.050,
+ $cl_ip, $nonce);
+ if (defined($os_fingerprint_obj)) {
+ my $os_fingerprint = $self->p0f_collect_response($os_fingerprint_obj);
+ # >95% of mail from remote Windows hosts is spam coming from zombized
+ # machines, so it is worth to greylist
+ return $cfg->{hit} if $os_fingerprint ne '' &&
+ $os_fingerprint =~ /$p0f_regexp/;
+ }
+ }
+
+ my $rbl = Net::RBLClient->new('lists' => $cfg->{rbls});
+ $rbl->lookup($cl_ip);
     my listed_by = $rbl->listed_by;
     if (listed_by != 0) {
-158,3 +231,3
 }
 
-main;
+main();

  Mark