1 |
stephdl |
1.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 |
|
|
|