1 |
From 671a6953b0c9503717bda10dd07f434cbd302c9c Mon Sep 17 00:00:00 2001 |
2 |
From: Matt Simerson <matt@tnpi.net> |
3 |
Date: Tue, 11 May 2010 00:55:53 -0400 |
4 |
Subject: add TCPLOCAL* variables to $qp->connection |
5 |
|
6 |
(patch remade against latest rspier/qpsmtpd) |
7 |
|
8 |
added remote_port, local_ip, local_port, and local_host to $qp->connection, as the p0f plugin relies on it. |
9 |
added notes to TcpServer.pm and the p0f plugin noting the dependence, and the lack of support for models other than tcpserver. |
10 |
|
11 |
Signed-off-by: Robert <rspier@pobox.com> |
12 |
--- |
13 |
lib/Qpsmtpd/TcpServer.pm | 21 ++++++++++++-- |
14 |
plugins/greylisting | 68 ++++++++++++++++++++++++++++++++++++++------- |
15 |
plugins/ident/p0f | 8 +++++ |
16 |
3 files changed, 83 insertions(+), 14 deletions(-) |
17 |
|
18 |
diff --git a/lib/Qpsmtpd/TcpServer.pm b/lib/Qpsmtpd/TcpServer.pm |
19 |
index 3398c3e..07d8d16 100644 |
20 |
--- a/lib/Qpsmtpd/TcpServer.pm |
21 |
+++ b/lib/Qpsmtpd/TcpServer.pm |
22 |
@@ -30,7 +30,10 @@ my $first_0; |
23 |
sub start_connection { |
24 |
my $self = shift; |
25 |
|
26 |
- my ($remote_host, $remote_info, $remote_ip); |
27 |
+ my ( |
28 |
+ $remote_host, $remote_info, $remote_ip, $remote_port, |
29 |
+ $local_ip, $local_port, $local_host |
30 |
+ ); |
31 |
|
32 |
if ($ENV{TCPREMOTEIP}) { |
33 |
# started from tcpserver (or some other superserver which |
34 |
@@ -38,6 +41,10 @@ sub start_connection { |
35 |
$remote_ip = $ENV{TCPREMOTEIP}; |
36 |
$remote_host = $ENV{TCPREMOTEHOST} || "[$remote_ip]"; |
37 |
$remote_info = $ENV{TCPREMOTEINFO} ? "$ENV{TCPREMOTEINFO}\@$remote_host" : $remote_host; |
38 |
+ $remote_port = $ENV{TCPREMOTEPORT}; |
39 |
+ $local_ip = $ENV{TCPLOCALIP}; |
40 |
+ $local_port = $ENV{TCPLOCALPORT}; |
41 |
+ $local_host = $ENV{TCPLOCALHOST}; |
42 |
} else { |
43 |
# Started from inetd or similar. |
44 |
# get info on the remote host from the socket. |
45 |
@@ -48,6 +55,10 @@ sub start_connection { |
46 |
$remote_ip = inet_ntoa($iaddr); |
47 |
$remote_host = gethostbyaddr($iaddr, AF_INET) || "[$remote_ip]"; |
48 |
$remote_info = $remote_host; |
49 |
+### TODO |
50 |
+# set $remote_port, $local_ip, and $local_port. Those values are |
51 |
+# required for the p0f plugin to function. |
52 |
+### /TODO |
53 |
} |
54 |
$self->log(LOGNOTICE, "Connection from $remote_info [$remote_ip]"); |
55 |
|
56 |
@@ -61,8 +72,12 @@ sub start_connection { |
57 |
$0 = "$first_0 [$remote_ip : $remote_host : $now]"; |
58 |
|
59 |
$self->SUPER::connection->start(remote_info => $remote_info, |
60 |
- remote_ip => $remote_ip, |
61 |
- remote_host => $remote_host, |
62 |
+ remote_ip => $remote_ip, |
63 |
+ remote_host => $remote_host, |
64 |
+ remote_port => $remote_port, |
65 |
+ local_ip => $local_ip, |
66 |
+ local_port => $local_port, |
67 |
+ local_host => $local_host, |
68 |
@_); |
69 |
} |
70 |
|
71 |
diff --git a/plugins/greylisting b/plugins/greylisting |
72 |
index 975563c..ebdec8f 100644 |
73 |
--- a/plugins/greylisting |
74 |
+++ b/plugins/greylisting |
75 |
@@ -106,6 +106,23 @@ directories, if determined, supercede I<db_dir>. |
76 |
|
77 |
=back |
78 |
|
79 |
+=item p0f |
80 |
+ |
81 |
+Enable greylisting only when certain p0f criteria is met. The single |
82 |
+required argument is a comma delimited list of key/value pairs. The keys |
83 |
+are the following p0f TCP fingerprint elements: genre, detail, uptime, |
84 |
+link, and distance. |
85 |
+ |
86 |
+To greylist emails from computers whose remote OS is windows, you'd use |
87 |
+this syntax: |
88 |
+ |
89 |
+ p0f genre,windows |
90 |
+ |
91 |
+To greylist only windows computers on DSL links more than 3 network hops |
92 |
+away: |
93 |
+ |
94 |
+ p0f genre,windows,link,dsl,distance,3 |
95 |
+ |
96 |
=head1 BUGS |
97 |
|
98 |
Database locking is implemented using flock, which may not work on |
99 |
@@ -116,6 +133,8 @@ use something like File::NFSLock instead. |
100 |
|
101 |
Written by Gavin Carr <gavin@openfusion.com.au>. |
102 |
|
103 |
+Added p0f section <mattsimerson@cpan.org> (2010-05-03) |
104 |
+ |
105 |
=cut |
106 |
|
107 |
BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) } |
108 |
@@ -123,22 +142,23 @@ use AnyDBM_File; |
109 |
use Fcntl qw(:DEFAULT :flock); |
110 |
use strict; |
111 |
|
112 |
-my $VERSION = '0.07'; |
113 |
+my $VERSION = '0.08'; |
114 |
|
115 |
my $DENYMSG = "This mail is temporarily denied"; |
116 |
my ($QPHOME) = ($0 =~ m!(.*?)/([^/]+)$!); |
117 |
my $DB = "denysoft_greylist.dbm"; |
118 |
my %PERMITTED_ARGS = map { $_ => 1 } qw(per_recipient remote_ip sender recipient |
119 |
- black_timeout grey_timeout white_timeout deny_late mode db_dir); |
120 |
+ black_timeout grey_timeout white_timeout deny_late mode db_dir p0f ); |
121 |
|
122 |
my %DEFAULTS = ( |
123 |
- remote_ip => 1, |
124 |
- sender => 0, |
125 |
- recipient => 0, |
126 |
- black_timeout => 50 * 60, |
127 |
- grey_timeout => 3 * 3600 + 20 * 60, |
128 |
- white_timeout => 36 * 24 * 3600, |
129 |
- mode => 'denysoft', |
130 |
+ remote_ip => 1, |
131 |
+ sender => 0, |
132 |
+ recipient => 0, |
133 |
+ black_timeout => 50 * 60, |
134 |
+ grey_timeout => 3 * 3600 + 20 * 60, |
135 |
+ white_timeout => 36 * 24 * 3600, |
136 |
+ mode => 'denysoft', |
137 |
+ p0f => undef, |
138 |
); |
139 |
|
140 |
sub register { |
141 |
@@ -206,6 +226,9 @@ sub denysoft_greylist { |
142 |
return DECLINED if $self->qp->connection->notes('whitelisthost'); |
143 |
return DECLINED if $transaction->notes('whitelistsender'); |
144 |
|
145 |
+ # do not greylist if p0f matching is selected and message does not match |
146 |
+ return DECLINED if $config->{'p0f'} && !$self->p0f_match( $config ); |
147 |
+ |
148 |
if ($config->{db_dir} && $config->{db_dir} =~ m{^([-a-zA-Z0-9./_]+)$}) { |
149 |
$config->{db_dir} = $1; |
150 |
} |
151 |
@@ -214,8 +237,10 @@ sub denysoft_greylist { |
152 |
my $dbdir = $transaction->notes('per_rcpt_configdir') |
153 |
if $config->{per_recipient_db}; |
154 |
for my $d ($dbdir, $config->{db_dir}, "/var/lib/qpsmtpd/greylisting", |
155 |
- "$QPHOME/var/db", "$QPHOME/config") { |
156 |
- last if $dbdir ||= $d && -d $d && $d; |
157 |
+ "$QPHOME/var/db", "$QPHOME/config", '.' ) { |
158 |
+ last if $dbdir && -d $dbdir; |
159 |
+ next if ( ! $d || ! -d $d ); |
160 |
+ $dbdir = $d; |
161 |
} |
162 |
my $db = "$dbdir/$DB"; |
163 |
$self->log(LOGINFO,"using $db as greylisting database"); |
164 |
@@ -292,5 +317,26 @@ sub denysoft_greylist { |
165 |
return $config->{mode} eq 'testonly' ? DECLINED : DENYSOFT, $DENYMSG; |
166 |
} |
167 |
|
168 |
+sub p0f_match { |
169 |
+ my $self = shift; |
170 |
+ my $config = shift; |
171 |
+ |
172 |
+ my $p0f = $self->connection->notes('p0f'); |
173 |
+ return if !$p0f || !ref $p0f; # p0f fingerprint info not found |
174 |
+ |
175 |
+ my %valid_matches = map { $_ => 1 } qw( genre detail uptime link distance ); |
176 |
+ my %requested_matches = split(/\,/, $config->{'p0f'} ); |
177 |
+ |
178 |
+ foreach my $key (keys %requested_matches) { |
179 |
+ next if !defined $valid_matches{$key}; # discard invalid match keys |
180 |
+ my $value = $requested_matches{$key}; |
181 |
+ return 1 if $key eq 'distance' && $p0f->{$key} > $value; |
182 |
+ return 1 if $key eq 'genre' && $p0f->{$key} =~ /$value/i; |
183 |
+ return 1 if $key eq 'uptime' && $p0f->{$key} < $value; |
184 |
+ return 1 if $key eq 'link' && $p0f->{$key} =~ /$value/i; |
185 |
+ } |
186 |
+ return; |
187 |
+} |
188 |
+ |
189 |
# arch-tag: 6ef5919e-404b-4c87-bcfe-7e9f383f3901 |
190 |
|
191 |
diff --git a/plugins/ident/p0f b/plugins/ident/p0f |
192 |
index 720adca..98b56ec 100644 |
193 |
--- a/plugins/ident/p0f |
194 |
+++ b/plugins/ident/p0f |
195 |
@@ -18,6 +18,14 @@ things based on source OS. |
196 |
|
197 |
All code heavily based upon the p0fq.pl included with the p0f distribution. |
198 |
|
199 |
+=head1 Environment requirements |
200 |
+ |
201 |
+p0f requires four pieces of information to look up the p0f fingerprint: |
202 |
+local_ip, local_port, remote_ip, and remote_port. TcpServer.pm has been |
203 |
+has been updated to provide that information when running under djb's |
204 |
+tcpserver. The async, forkserver, and prefork models will likely require |
205 |
+some additional changes to make sure these fields are populated. |
206 |
+ |
207 |
=cut |
208 |
|
209 |
use IO::Socket; |
210 |
-- |
211 |
1.7.2.2 |
212 |
|