--- /usr/sbin/postgrey.orig	2007-06-27 11:15:50.000000000 -0300
+++ /usr/sbin/postgrey	2007-06-27 11:15:56.000000000 -0300
@@ -16,6 +16,8 @@
 use Fcntl ':flock'; # import LOCK_* constants
 use Sys::Hostname;
 use POSIX qw(strftime setlocale LC_ALL);
+use IO::Socket::INET;
+use Time::HiRes ();
 
 use vars qw(@ISA);
 @ISA = qw(Net::Server::Multiplex);
@@ -106,6 +108,33 @@
     $self->{postgrey}{whitelist_recipients} = \@whitelist_recipients;
 }
 
+sub read_os_fingerprints_whitelists($)
+{
+    my ($self) = @_;
+
+    my @whitelist_os_fingerprints = ();
+    for my $f (@{$self->{postgrey}{whitelist_os_fingerprints_files}}) {
+        if(open(OS, $f)) {
+            while(<OS>) {
+                s/#.*$//; s/^\s+//; s/\s+$//; next if $_ eq '';
+                if(/^\/(\S+)\/$/) {
+                    # regular expression
+                    push @whitelist_os_fingerprints, qr{$1}i;
+                }
+                elsif(!/^\S+$/) {
+                    warn "WARNING: $f line $.: doesn't look like an address\n";
+                }
+            }
+        }
+        else {
+            # do not warn about .local file: maybe the user just doesn't have one
+            warn "WARNING: can't open $f: $!\n" unless $f =~ /\.local$/;
+        }
+        close(OS);
+    }
+    $self->{postgrey}{whitelist_os_fingerprints} = \@whitelist_os_fingerprints;
+}
+
 sub do_sender_substitutions($$)
 {
     my ($self, $addr) = @_;
@@ -297,6 +326,14 @@
             return 'DUNNO';
         }
     }
+    if($self->{postgrey}{p0f}) {
+        for my $w (@{$self->{postgrey}{whitelist_os_fingerprints}}) {
+            if($attr->{os_fingerprint} =~ $w) {
+                $self->mylog_action($attr, 'pass', 'os-fingerprint whitelist');
+	        return 'DUNNO';
+            }
+        }
+    }
 
     # auto whitelist clients (see below for explanation)
     my ($cawl_db, $cawl_key, $cawl_count, $cawl_last);
@@ -435,9 +472,9 @@
         'verbose|v', 'quiet|q', 'daemonize|d', 'unix|u=s', 'inet|i=s',
         'user=s', 'group=s', 'dbdir=s', 'pidfile=s', 'delay=i', 'max-age=i',
         'lookup-by-subnet', 'lookup-by-host', 'auto-whitelist-clients:s', 
-        'whitelist-clients=s@', 'whitelist-recipients=s@',
+        'whitelist-clients=s@', 'whitelist-recipients=s@', 'whitelist-os-fingerprints=s@',
         'retry-window=s', 'greylist-action=s', 'greylist-text=s', 'privacy',
-        'hostname=s', 'exim'
+        'hostname=s', 'exim', 'p0f', 'p0f-service=s'
     ) or exit(1);
     # note: lookup-by-subnet can be given for compatibility, but it is default
     # so do not do nothing with it...
@@ -513,9 +550,13 @@
                   "$CONFIG_DIR/postgrey_whitelist_clients.local" ],
             whitelist_recipients_files => $opt{'whitelist-recipients'} ||
                 [ "$CONFIG_DIR/postgrey_whitelist_recipients" ],
+            whitelist_os_fingerprints_files => $opt{'whitelist-os-fingerprints'} ||
+                [ "$CONFIG_DIR/postgrey_whitelist_os_fingerprints" ],
             privacy => defined $opt{'privacy'},
             hostname => defined $opt{hostname} ? $opt{hostname} : hostname,
             exim => defined $opt{'exim'},
+            p0f              => defined $opt{'p0f'},
+            p0f_service      => defined $opt{'p0f-service'} ? $opt{'p0f-service'} : 'inet:127.0.0.1:2345'
         },
     }, 'postgrey';
 
@@ -525,6 +566,7 @@
     # read whitelist
     $server->read_clients_whitelists();
     $server->read_recipients_whitelists();
+    $server->read_os_fingerprints_whitelists();
 
     # --privacy requires Digest::SHA1
     if($opt{'privacy'}) {
@@ -545,6 +587,7 @@
     $self->mylog(1, "HUP received: reloading whitelists...");
     $self->read_clients_whitelists();
     $self->read_recipients_whitelists();
+    $self->read_os_fingerprints_whitelists();
 }
 
 sub pre_loop_hook()
@@ -604,6 +647,62 @@
     }
 }
 
+# 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);
+
+    my ($host, $port, $sock);
+    if ($hostport =~ /^(?: inet: )? (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*) /six) {
+       ($host, $port) = ($2, $3);
+    } else {
+       die "Bad p0f method syntax: $hostport";
+    }
+
+    $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;
+            }
+        }
+        $timeout = 0;
+    }
+    defined $nfound or die "p0f - select on socket failed: $!";
+    $resp;
+}
+
 sub mux_input()
 {
     my ($self, $mux, $fh, $in_ref) = @_;
@@ -624,6 +723,13 @@
                 $self->{net_server}->mylog(0, "unrecognized request type: '$attr->{request}'");
             }
             else {
+                # get OS fingerprint ?
+                if ($self->{net_server}->{postgrey}{p0f}) {
+                    my $os_fingerprint_obj = $self->{net_server}->p0f_init($self->{net_server}->{postgrey}{p0f_service}, 0.050, $attr->{client_address}, int(rand(1000000000)));
+                    if (defined($os_fingerprint_obj)) {
+                        $attr->{os_fingerprint} = $self->{net_server}->p0f_collect_response($os_fingerprint_obj);
+                    }
+                }
                 # decide
                 my $action = $self->{net_server}->smtpd_access_policy($attr);
                 # give answer
