line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# <@LICENSE> |
2
|
|
|
|
|
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more |
3
|
|
|
|
|
|
|
# contributor license agreements. See the NOTICE file distributed with |
4
|
|
|
|
|
|
|
# this work for additional information regarding copyright ownership. |
5
|
|
|
|
|
|
|
# The ASF licenses this file to you under the Apache License, Version 2.0 |
6
|
|
|
|
|
|
|
# (the "License"); you may not use this file except in compliance with |
7
|
|
|
|
|
|
|
# the License. You may obtain a copy of the License at: |
8
|
|
|
|
|
|
|
# |
9
|
|
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
10
|
|
|
|
|
|
|
# |
11
|
|
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software |
12
|
|
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, |
13
|
|
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14
|
|
|
|
|
|
|
# See the License for the specific language governing permissions and |
15
|
|
|
|
|
|
|
# limitations under the License. |
16
|
|
|
|
|
|
|
# </@LICENSE> |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 NAME |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Mail::SpamAssassin::Plugin::Razor2 - perform Razor check of messages |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 SYNOPSIS |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
loadplugin Mail::SpamAssassin::Plugin::Razor2 |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 DESCRIPTION |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
Vipul's Razor is a distributed, collaborative, spam detection and |
29
|
|
|
|
|
|
|
filtering network based on user submissions of spam. Detection is done |
30
|
|
|
|
|
|
|
with signatures that efficiently spot mutating spam content and user |
31
|
|
|
|
|
|
|
input is validated through reputation assignments. |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
See http://razor.sourceforge.net/ for more information about Razor. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=head1 USER SETTINGS |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=over 4 |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=cut |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
use Mail::SpamAssassin::Plugin; |
43
|
22
|
|
|
22
|
|
162
|
use Mail::SpamAssassin::Logger; |
|
22
|
|
|
|
|
48
|
|
|
22
|
|
|
|
|
666
|
|
44
|
22
|
|
|
22
|
|
115
|
use Mail::SpamAssassin::Timeout; |
|
22
|
|
|
|
|
47
|
|
|
22
|
|
|
|
|
1230
|
|
45
|
22
|
|
|
22
|
|
1787
|
use strict; |
|
22
|
|
|
|
|
44
|
|
|
22
|
|
|
|
|
530
|
|
46
|
22
|
|
|
22
|
|
121
|
use warnings; |
|
22
|
|
|
|
|
35
|
|
|
22
|
|
|
|
|
534
|
|
47
|
22
|
|
|
22
|
|
115
|
# use bytes; |
|
22
|
|
|
|
|
83
|
|
|
22
|
|
|
|
|
697
|
|
48
|
|
|
|
|
|
|
use re 'taint'; |
49
|
22
|
|
|
22
|
|
132
|
|
|
22
|
|
|
|
|
61
|
|
|
22
|
|
|
|
|
45070
|
|
50
|
|
|
|
|
|
|
our @ISA = qw(Mail::SpamAssassin::Plugin); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
my $class = shift; |
53
|
|
|
|
|
|
|
my $mailsaobject = shift; |
54
|
63
|
|
|
63
|
1
|
201
|
|
55
|
63
|
|
|
|
|
119
|
$class = ref($class) || $class; |
56
|
|
|
|
|
|
|
my $self = $class->SUPER::new($mailsaobject); |
57
|
63
|
|
33
|
|
|
388
|
bless ($self, $class); |
58
|
63
|
|
|
|
|
343
|
|
59
|
63
|
|
|
|
|
135
|
# figure out if razor is even available or not ... |
60
|
|
|
|
|
|
|
$self->{razor2_available} = 0; |
61
|
|
|
|
|
|
|
if ($mailsaobject->{local_tests_only}) { |
62
|
63
|
|
|
|
|
240
|
dbg("razor2: local tests only, skipping Razor"); |
63
|
63
|
100
|
|
|
|
226
|
} |
64
|
62
|
|
|
|
|
212
|
else { |
65
|
|
|
|
|
|
|
if (eval { require Razor2::Client::Agent; }) { |
66
|
|
|
|
|
|
|
$self->{razor2_available} = 1; |
67
|
1
|
50
|
|
|
|
2
|
dbg("razor2: razor2 is available, version " . $Razor2::Client::Version::VERSION . "\n"); |
|
1
|
|
|
|
|
157
|
|
68
|
0
|
|
|
|
|
0
|
} |
69
|
0
|
|
|
|
|
0
|
else { |
70
|
|
|
|
|
|
|
dbg("razor2: razor2 is not available"); |
71
|
|
|
|
|
|
|
} |
72
|
1
|
|
|
|
|
6
|
} |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
$self->register_eval_rule("check_razor2"); |
75
|
|
|
|
|
|
|
$self->register_eval_rule("check_razor2_range"); |
76
|
63
|
|
|
|
|
300
|
|
77
|
63
|
|
|
|
|
207
|
$self->set_config($mailsaobject->{conf}); |
78
|
|
|
|
|
|
|
|
79
|
63
|
|
|
|
|
285
|
return $self; |
80
|
|
|
|
|
|
|
} |
81
|
63
|
|
|
|
|
573
|
|
82
|
|
|
|
|
|
|
my ($self, $conf) = @_; |
83
|
|
|
|
|
|
|
my @cmds; |
84
|
|
|
|
|
|
|
|
85
|
63
|
|
|
63
|
0
|
143
|
=item use_razor2 (0|1) (default: 1) |
86
|
63
|
|
|
|
|
130
|
|
87
|
|
|
|
|
|
|
Whether to use Razor2, if it is available. |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=cut |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
push(@cmds, { |
92
|
|
|
|
|
|
|
setting => 'use_razor2', |
93
|
|
|
|
|
|
|
default => 1, |
94
|
63
|
|
|
|
|
328
|
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, |
95
|
|
|
|
|
|
|
}); |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=back |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=head1 ADMINISTRATOR SETTINGS |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=over 4 |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=item razor_timeout n (default: 5) |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
How many seconds you wait for Razor to complete before you go on without |
106
|
|
|
|
|
|
|
the results |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=cut |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
push(@cmds, { |
111
|
|
|
|
|
|
|
setting => 'razor_timeout', |
112
|
|
|
|
|
|
|
is_admin => 1, |
113
|
63
|
|
|
|
|
313
|
default => 5, |
114
|
|
|
|
|
|
|
type => $Mail::SpamAssassin::Conf::CONF_TYPE_DURATION, |
115
|
|
|
|
|
|
|
}); |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=item razor_config filename |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Define the filename used to store Razor's configuration settings. |
120
|
|
|
|
|
|
|
Currently this is left to Razor to decide. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=cut |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
push(@cmds, { |
125
|
|
|
|
|
|
|
setting => 'razor_config', |
126
|
|
|
|
|
|
|
is_admin => 1, |
127
|
63
|
|
|
|
|
208
|
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, |
128
|
|
|
|
|
|
|
}); |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
$conf->{parser}->register_commands(\@cmds); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
63
|
|
|
|
|
313
|
my ($self, $fulltext, $type, $deadline) = @_; |
134
|
|
|
|
|
|
|
my $timeout = $self->{main}->{conf}->{razor_timeout}; |
135
|
|
|
|
|
|
|
my $return = 0; |
136
|
|
|
|
|
|
|
my @results; |
137
|
0
|
|
|
0
|
0
|
0
|
|
138
|
0
|
|
|
|
|
0
|
my $debug = $type eq 'check' ? 'razor2' : 'reporter'; |
139
|
0
|
|
|
|
|
0
|
|
140
|
0
|
|
|
|
|
0
|
# razor also debugs to stdout. argh. fix it to stderr... |
141
|
|
|
|
|
|
|
if (would_log('dbg', $debug)) { |
142
|
0
|
0
|
|
|
|
0
|
open(OLDOUT, ">&STDOUT"); |
143
|
|
|
|
|
|
|
open(STDOUT, ">&STDERR"); |
144
|
|
|
|
|
|
|
} |
145
|
0
|
0
|
|
|
|
0
|
|
146
|
0
|
|
|
|
|
0
|
Mail::SpamAssassin::PerMsgStatus::enter_helper_run_mode($self); |
147
|
0
|
|
|
|
|
0
|
|
148
|
|
|
|
|
|
|
my $rnd = rand(0x7fffffff); # save entropy before Razor clobbers it |
149
|
|
|
|
|
|
|
|
150
|
0
|
|
|
|
|
0
|
my $timer = Mail::SpamAssassin::Timeout->new( |
151
|
|
|
|
|
|
|
{ secs => $timeout, deadline => $deadline }); |
152
|
0
|
|
|
|
|
0
|
my $err = $timer->run_and_catch(sub { |
153
|
|
|
|
|
|
|
|
154
|
0
|
|
|
|
|
0
|
local ($^W) = 0; # argh, warnings in Razor |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
# everything's in the module! |
157
|
|
|
|
|
|
|
my $rc = Razor2::Client::Agent->new("razor-$type"); |
158
|
0
|
|
|
0
|
|
0
|
|
159
|
|
|
|
|
|
|
if ($rc) { |
160
|
|
|
|
|
|
|
$rc->{opt} = { |
161
|
0
|
|
|
|
|
0
|
debug => (would_log('dbg', $debug) > 1), |
162
|
|
|
|
|
|
|
foreground => 1, |
163
|
0
|
0
|
|
|
|
0
|
config => $self->{main}->{conf}->{razor_config} |
164
|
|
|
|
|
|
|
}; |
165
|
|
|
|
|
|
|
# no facility prefix on this die |
166
|
|
|
|
|
|
|
$rc->do_conf() or die "$debug: " . $rc->errstr; |
167
|
|
|
|
|
|
|
|
168
|
0
|
|
|
|
|
0
|
# Razor2 requires authentication for reporting |
169
|
|
|
|
|
|
|
my $ident; |
170
|
0
|
0
|
|
|
|
0
|
if ($type ne 'check') { |
171
|
|
|
|
|
|
|
# no facility prefix on this die |
172
|
|
|
|
|
|
|
$ident = $rc->get_ident |
173
|
0
|
|
|
|
|
0
|
or die("$type requires authentication"); |
174
|
0
|
0
|
|
|
|
0
|
} |
175
|
|
|
|
|
|
|
|
176
|
0
|
0
|
|
|
|
0
|
my @msg = ($fulltext); |
177
|
|
|
|
|
|
|
# no facility prefix on this die |
178
|
|
|
|
|
|
|
my $objects = $rc->prepare_objects(\@msg) |
179
|
|
|
|
|
|
|
or die "$debug: error in prepare_objects"; |
180
|
0
|
|
|
|
|
0
|
unless ($rc->get_server_info()) { |
181
|
|
|
|
|
|
|
my $error = $rc->errprefix("$debug: spamassassin") || "$debug: razor2 had unknown error during get_server_info"; |
182
|
0
|
0
|
|
|
|
0
|
die $error; |
183
|
|
|
|
|
|
|
} |
184
|
0
|
0
|
|
|
|
0
|
|
185
|
0
|
|
0
|
|
|
0
|
# let's reset the alarm since get_server_info() calls |
186
|
0
|
|
|
|
|
0
|
# nextserver() which calls discover() which very likely will |
187
|
|
|
|
|
|
|
# reset the alarm for us ... how polite. :( |
188
|
|
|
|
|
|
|
$timer->reset(); |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
# no facility prefix on this die |
191
|
|
|
|
|
|
|
my $sigs = $rc->compute_sigs($objects) |
192
|
0
|
|
|
|
|
0
|
or die "$debug: error in compute_sigs"; |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# if mail isn't whitelisted, check it out |
195
|
0
|
0
|
|
|
|
0
|
# see 'man razor-whitelist' |
196
|
|
|
|
|
|
|
if ($type ne 'check' || ! $rc->local_check($objects->[0])) { |
197
|
|
|
|
|
|
|
# provide a better error message when servers are unavailable, |
198
|
|
|
|
|
|
|
# than "Bad file descriptor Died". |
199
|
|
|
|
|
|
|
$rc->connect() or die "$debug: could not connect to any servers\n"; |
200
|
0
|
0
|
0
|
|
|
0
|
|
201
|
|
|
|
|
|
|
# Talk to the Razor server and do work |
202
|
|
|
|
|
|
|
if ($type eq 'check') { |
203
|
0
|
0
|
|
|
|
0
|
unless ($rc->check($objects)) { |
204
|
|
|
|
|
|
|
my $error = $rc->errprefix("$debug: spamassassin") || "$debug: razor2 had unknown error during check"; |
205
|
|
|
|
|
|
|
die $error; |
206
|
0
|
0
|
|
|
|
0
|
} |
207
|
0
|
0
|
|
|
|
0
|
} |
208
|
0
|
|
0
|
|
|
0
|
else { |
209
|
0
|
|
|
|
|
0
|
unless ($rc->authenticate($ident)) { |
210
|
|
|
|
|
|
|
my $error = $rc->errprefix("$debug: spamassassin") || "$debug: razor2 had unknown error during authenticate"; |
211
|
|
|
|
|
|
|
die $error; |
212
|
|
|
|
|
|
|
} |
213
|
0
|
0
|
|
|
|
0
|
unless ($rc->report($objects)) { |
214
|
0
|
|
0
|
|
|
0
|
my $error = $rc->errprefix("$debug: spamassassin") || "$debug: razor2 had unknown error during report"; |
215
|
0
|
|
|
|
|
0
|
die $error; |
216
|
|
|
|
|
|
|
} |
217
|
0
|
0
|
|
|
|
0
|
} |
218
|
0
|
|
0
|
|
|
0
|
|
219
|
0
|
|
|
|
|
0
|
unless ($rc->disconnect()) { |
220
|
|
|
|
|
|
|
my $error = $rc->errprefix("$debug: spamassassin") || "$debug: razor2 had unknown error during disconnect"; |
221
|
|
|
|
|
|
|
die $error; |
222
|
|
|
|
|
|
|
} |
223
|
0
|
0
|
|
|
|
0
|
} |
224
|
0
|
|
0
|
|
|
0
|
|
225
|
0
|
|
|
|
|
0
|
# Razor 2.14 says that if we get here, we did ok. |
226
|
|
|
|
|
|
|
$return = 1; |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# figure out if we have a log file we need to close... |
229
|
|
|
|
|
|
|
if (ref($rc->{logref}) && exists $rc->{logref}->{fd}) { |
230
|
0
|
|
|
|
|
0
|
# the fd can be stdout or stderr, so we need to find out if it is |
231
|
|
|
|
|
|
|
# so we don't close them by accident. Note: we can't just |
232
|
|
|
|
|
|
|
# undef the fd here (like the IO::Handle manpage says we can) |
233
|
0
|
0
|
0
|
|
|
0
|
# because it won't actually close, unfortunately. :( |
234
|
|
|
|
|
|
|
my $untie = 1; |
235
|
|
|
|
|
|
|
foreach my $log (*STDOUT{IO}, *STDERR{IO}) { |
236
|
|
|
|
|
|
|
if ($log == $rc->{logref}->{fd}) { |
237
|
|
|
|
|
|
|
$untie = 0; |
238
|
0
|
|
|
|
|
0
|
last; |
239
|
0
|
|
|
|
|
0
|
} |
240
|
0
|
0
|
|
|
|
0
|
} |
241
|
0
|
|
|
|
|
0
|
if ($untie) { |
242
|
0
|
|
|
|
|
0
|
close($rc->{logref}->{fd}) or die "error closing log: $!"; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
} |
245
|
0
|
0
|
|
|
|
0
|
|
246
|
0
|
0
|
|
|
|
0
|
if ($type eq 'check') { |
247
|
|
|
|
|
|
|
# so $objects->[0] is the first (only) message, and ->{spam} is a general yes/no |
248
|
|
|
|
|
|
|
push(@results, { result => $objects->[0]->{spam} }); |
249
|
|
|
|
|
|
|
|
250
|
0
|
0
|
|
|
|
0
|
# great for debugging, but leave this off! |
251
|
|
|
|
|
|
|
#use Data::Dumper; |
252
|
0
|
|
|
|
|
0
|
#print Dumper($objects),"\n"; |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# ->{p} is for each part of the message |
255
|
|
|
|
|
|
|
# so go through each part, taking the highest cf we find |
256
|
|
|
|
|
|
|
# of any part that isn't contested (ct). This helps avoid false |
257
|
|
|
|
|
|
|
# positives. equals logic_method 4. |
258
|
|
|
|
|
|
|
# |
259
|
|
|
|
|
|
|
# razor-agents < 2.14 have a different object format, so we now support both. |
260
|
|
|
|
|
|
|
# $objects->[0]->{resp} vs $objects->[0]->{p}->[part #]->{resp} |
261
|
|
|
|
|
|
|
my $part = 0; |
262
|
|
|
|
|
|
|
my $arrayref = $objects->[0]->{p} || $objects; |
263
|
|
|
|
|
|
|
if (defined $arrayref) { |
264
|
|
|
|
|
|
|
foreach my $cf (@{$arrayref}) { |
265
|
0
|
|
|
|
|
0
|
if (exists $cf->{resp}) { |
266
|
0
|
|
0
|
|
|
0
|
for (my $response=0; $response<@{$cf->{resp}}; $response++) { |
267
|
0
|
0
|
|
|
|
0
|
my $tmp = $cf->{resp}->[$response]; |
268
|
0
|
|
|
|
|
0
|
my $tmpcf = $tmp->{cf}; # Part confidence |
|
0
|
|
|
|
|
0
|
|
269
|
0
|
0
|
|
|
|
0
|
my $tmpct = $tmp->{ct}; # Part contested? |
270
|
0
|
|
|
|
|
0
|
my $engine = $cf->{sent}->[$response]->{e}; |
|
0
|
|
|
|
|
0
|
|
271
|
0
|
|
|
|
|
0
|
|
272
|
0
|
|
|
|
|
0
|
# These should always be set, but just in case ... |
273
|
0
|
|
|
|
|
0
|
$tmpcf = 0 unless defined $tmpcf; |
274
|
0
|
|
|
|
|
0
|
$tmpct = 0 unless defined $tmpct; |
275
|
|
|
|
|
|
|
$engine = 0 unless defined $engine; |
276
|
|
|
|
|
|
|
|
277
|
0
|
0
|
|
|
|
0
|
push(@results, |
278
|
0
|
0
|
|
|
|
0
|
{ part => $part, engine => $engine, contested => $tmpct, confidence => $tmpcf }); |
279
|
0
|
0
|
|
|
|
0
|
} |
280
|
|
|
|
|
|
|
} |
281
|
0
|
|
|
|
|
0
|
else { |
282
|
|
|
|
|
|
|
push(@results, { part => $part, noresponse => 1 }); |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
$part++; |
285
|
|
|
|
|
|
|
} |
286
|
0
|
|
|
|
|
0
|
} |
287
|
|
|
|
|
|
|
else { |
288
|
0
|
|
|
|
|
0
|
# If we have some new $objects format that isn't close to |
289
|
|
|
|
|
|
|
# the current razor-agents 2.x version, we won't FP but we |
290
|
|
|
|
|
|
|
# should alert in debug. |
291
|
|
|
|
|
|
|
dbg("$debug: it looks like the internal Razor object has changed format!"); |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
} |
295
|
0
|
|
|
|
|
0
|
else { |
296
|
|
|
|
|
|
|
warn "$debug: undefined Razor2::Client::Agent\n"; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
}); |
300
|
0
|
|
|
|
|
0
|
|
301
|
|
|
|
|
|
|
# OK, that's enough Razor stuff. now, reset all that global |
302
|
|
|
|
|
|
|
# state it futzes with :( |
303
|
0
|
|
|
|
|
0
|
# work around serious brain damage in Razor2 (constant seed) |
304
|
|
|
|
|
|
|
$rnd ^= int(rand(0xffffffff)); # mix old acc with whatever came out of razor |
305
|
|
|
|
|
|
|
srand; # let Perl give it a try ... |
306
|
|
|
|
|
|
|
$rnd ^= int(rand(0xffffffff)); # ... and mix-in that too |
307
|
|
|
|
|
|
|
srand($rnd & 0x7fffffff); # reseed, keep it unsigned 32-bit just in case |
308
|
0
|
|
|
|
|
0
|
|
309
|
0
|
|
|
|
|
0
|
Mail::SpamAssassin::PerMsgStatus::leave_helper_run_mode($self); |
310
|
0
|
|
|
|
|
0
|
|
311
|
0
|
|
|
|
|
0
|
if ($timer->timed_out()) { |
312
|
|
|
|
|
|
|
dbg("$debug: razor2 $type timed out after $timeout seconds"); |
313
|
0
|
|
|
|
|
0
|
} |
314
|
|
|
|
|
|
|
|
315
|
0
|
0
|
|
|
|
0
|
if ($err) { |
316
|
0
|
|
|
|
|
0
|
chomp $err; |
317
|
|
|
|
|
|
|
if ($err =~ /(?:could not connect|network is unreachable)/) { |
318
|
|
|
|
|
|
|
# make this a dbg(); SpamAssassin will still continue, |
319
|
0
|
0
|
|
|
|
0
|
# but without Razor checking. otherwise there may be |
320
|
0
|
|
|
|
|
0
|
# DSNs and errors in syslog etc., yuck |
321
|
0
|
0
|
|
|
|
0
|
dbg("$debug: razor2 $type could not connect to any servers"); |
|
|
0
|
|
|
|
|
|
322
|
|
|
|
|
|
|
} elsif ($err =~ /timeout/i) { |
323
|
|
|
|
|
|
|
dbg("$debug: razor2 $type timed out connecting to servers"); |
324
|
|
|
|
|
|
|
} else { |
325
|
0
|
|
|
|
|
0
|
warn("$debug: razor2 $type failed: $! $err"); |
326
|
|
|
|
|
|
|
} |
327
|
0
|
|
|
|
|
0
|
} |
328
|
|
|
|
|
|
|
|
329
|
0
|
|
|
|
|
0
|
# razor also debugs to stdout. argh. fix it to stderr... |
330
|
|
|
|
|
|
|
if (would_log('dbg', $debug)) { |
331
|
|
|
|
|
|
|
open(STDOUT, ">&OLDOUT"); |
332
|
|
|
|
|
|
|
close OLDOUT; |
333
|
|
|
|
|
|
|
} |
334
|
0
|
0
|
|
|
|
0
|
|
335
|
0
|
|
|
|
|
0
|
return wantarray ? ($return, @results) : $return; |
336
|
0
|
|
|
|
|
0
|
} |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
my ($self, $options) = @_; |
339
|
0
|
0
|
|
|
|
0
|
|
340
|
|
|
|
|
|
|
return unless $self->{razor2_available}; |
341
|
|
|
|
|
|
|
return if $self->{main}->{local_tests_only}; |
342
|
|
|
|
|
|
|
return unless $self->{main}->{conf}->{use_razor2}; |
343
|
0
|
|
|
0
|
1
|
0
|
return if $options->{report}->{options}->{dont_report_to_razor}; |
344
|
|
|
|
|
|
|
|
345
|
0
|
0
|
|
|
|
0
|
if ($self->razor2_access($options->{text}, 'report', undef)) { |
346
|
0
|
0
|
|
|
|
0
|
$options->{report}->{report_available} = 1; |
347
|
0
|
0
|
|
|
|
0
|
info('reporter: spam reported to Razor'); |
348
|
0
|
0
|
|
|
|
0
|
$options->{report}->{report_return} = 1; |
349
|
|
|
|
|
|
|
} |
350
|
0
|
0
|
|
|
|
0
|
else { |
351
|
0
|
|
|
|
|
0
|
info('reporter: could not report spam to Razor'); |
352
|
0
|
|
|
|
|
0
|
} |
353
|
0
|
|
|
|
|
0
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
my ($self, $options) = @_; |
356
|
0
|
|
|
|
|
0
|
|
357
|
|
|
|
|
|
|
return unless $self->{razor2_available}; |
358
|
|
|
|
|
|
|
return if $self->{main}->{local_tests_only}; |
359
|
|
|
|
|
|
|
return unless $self->{main}->{conf}->{use_razor2}; |
360
|
|
|
|
|
|
|
return if $options->{revoke}->{options}->{dont_report_to_razor}; |
361
|
0
|
|
|
0
|
1
|
0
|
|
362
|
|
|
|
|
|
|
if ($self->razor2_access($options->{text}, 'revoke', undef)) { |
363
|
0
|
0
|
|
|
|
0
|
$options->{revoke}->{revoke_available} = 1; |
364
|
0
|
0
|
|
|
|
0
|
info('reporter: spam revoked from Razor'); |
365
|
0
|
0
|
|
|
|
0
|
$options->{revoke}->{revoke_return} = 1; |
366
|
0
|
0
|
|
|
|
0
|
} |
367
|
|
|
|
|
|
|
else { |
368
|
0
|
0
|
|
|
|
0
|
info('reporter: could not revoke spam from Razor'); |
369
|
0
|
|
|
|
|
0
|
} |
370
|
0
|
|
|
|
|
0
|
} |
371
|
0
|
|
|
|
|
0
|
|
372
|
|
|
|
|
|
|
my ($self, $permsgstatus, $full) = @_; |
373
|
|
|
|
|
|
|
|
374
|
0
|
|
|
|
|
0
|
return $permsgstatus->{razor2_result} if (defined $permsgstatus->{razor2_result}); |
375
|
|
|
|
|
|
|
$permsgstatus->{razor2_result} = 0; |
376
|
|
|
|
|
|
|
$permsgstatus->{razor2_cf_score} = { '4' => 0, '8' => 0 }; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
return unless $self->{razor2_available}; |
379
|
4
|
|
|
4
|
0
|
8
|
return unless $self->{main}->{conf}->{use_razor2}; |
380
|
|
|
|
|
|
|
|
381
|
4
|
50
|
|
|
|
13
|
my $timer = $self->{main}->time_method("check_razor2"); |
382
|
4
|
|
|
|
|
8
|
|
383
|
4
|
|
|
|
|
13
|
my $return; |
384
|
|
|
|
|
|
|
my @results; |
385
|
4
|
50
|
|
|
|
60
|
|
386
|
0
|
0
|
|
|
|
|
# TODO: check for cache header, set results appropriately |
387
|
|
|
|
|
|
|
|
388
|
0
|
|
|
|
|
|
# do it this way to make it easier to get out the results later from the |
389
|
|
|
|
|
|
|
# netcache plugin |
390
|
0
|
|
|
|
|
|
($return, @results) = |
391
|
|
|
|
|
|
|
$self->razor2_access($full, 'check', $permsgstatus->{master_deadline}); |
392
|
|
|
|
|
|
|
$self->{main}->call_plugins ('process_razor_result', |
393
|
|
|
|
|
|
|
{ results => \@results, permsgstatus => $permsgstatus } |
394
|
|
|
|
|
|
|
); |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
foreach my $result (@results) { |
397
|
|
|
|
|
|
|
if (exists $result->{result}) { |
398
|
0
|
|
|
|
|
|
$permsgstatus->{razor2_result} = $result->{result} if $result->{result}; |
399
|
0
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
elsif ($result->{noresponse}) { |
401
|
|
|
|
|
|
|
dbg('razor2: part=' . $result->{part} . ' noresponse'); |
402
|
|
|
|
|
|
|
} |
403
|
0
|
|
|
|
|
|
else { |
404
|
0
|
0
|
|
|
|
|
dbg('razor2: part=' . $result->{part} . |
|
|
0
|
|
|
|
|
|
405
|
0
|
0
|
|
|
|
|
' engine=' . $result->{engine} . |
406
|
|
|
|
|
|
|
' contested=' . $result->{contested} . |
407
|
|
|
|
|
|
|
' confidence=' . $result->{confidence}); |
408
|
0
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
next if $result->{contested}; |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
my $cf = $permsgstatus->{razor2_cf_score}->{$result->{engine}} || 0; |
412
|
|
|
|
|
|
|
if ($result->{confidence} > $cf) { |
413
|
|
|
|
|
|
|
$permsgstatus->{razor2_cf_score}->{$result->{engine}} = $result->{confidence}; |
414
|
0
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
} |
416
|
0
|
0
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
0
|
|
0
|
|
|
|
dbg("razor2: results: spam? " . $permsgstatus->{razor2_result}); |
419
|
0
|
0
|
|
|
|
|
while(my ($engine, $cf) = each %{$permsgstatus->{razor2_cf_score}}) { |
420
|
0
|
|
|
|
|
|
dbg("razor2: results: engine $engine, highest cf score: $cf"); |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
return $permsgstatus->{razor2_result}; |
424
|
|
|
|
|
|
|
} |
425
|
0
|
|
|
|
|
|
|
426
|
0
|
|
|
|
|
|
# Check the cf value of a given message and return if it's within the |
|
0
|
|
|
|
|
|
|
427
|
0
|
|
|
|
|
|
# given range |
428
|
|
|
|
|
|
|
my ($self, $permsgstatus, $body, $engine, $min, $max) = @_; |
429
|
|
|
|
|
|
|
|
430
|
0
|
|
|
|
|
|
# If Razor2 isn't available, or the general test is disabled, don't |
431
|
|
|
|
|
|
|
# continue. |
432
|
|
|
|
|
|
|
return unless $self->{razor2_available}; |
433
|
|
|
|
|
|
|
return unless $self->{main}->{conf}->{use_razor2}; |
434
|
|
|
|
|
|
|
return unless $self->{main}->{conf}->{scores}->{'RAZOR2_CHECK'}; |
435
|
|
|
|
|
|
|
|
436
|
0
|
|
|
0
|
0
|
|
# If Razor2 hasn't been checked yet, go ahead and run it. |
437
|
|
|
|
|
|
|
unless (defined $permsgstatus->{razor2_result}) { |
438
|
|
|
|
|
|
|
$self->check_razor2($permsgstatus, $body); |
439
|
|
|
|
|
|
|
} |
440
|
0
|
0
|
|
|
|
|
|
441
|
0
|
0
|
|
|
|
|
my $cf = 0; |
442
|
0
|
0
|
|
|
|
|
if ($engine) { |
443
|
|
|
|
|
|
|
$cf = $permsgstatus->{razor2_cf_score}->{$engine}; |
444
|
|
|
|
|
|
|
return unless defined $cf; |
445
|
0
|
0
|
|
|
|
|
} |
446
|
0
|
|
|
|
|
|
else { |
447
|
|
|
|
|
|
|
# If no specific engine was given to the rule, find the highest cf |
448
|
|
|
|
|
|
|
# determined and use that |
449
|
0
|
|
|
|
|
|
while(my ($engine, $ecf) = each %{$permsgstatus->{razor2_cf_score}}) { |
450
|
0
|
0
|
|
|
|
|
if ($ecf > $cf) { |
451
|
0
|
|
|
|
|
|
$cf = $ecf; |
452
|
0
|
0
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
} |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
if ($cf >= $min && $cf <= $max) { |
457
|
0
|
|
|
|
|
|
$permsgstatus->test_log(sprintf("cf: %3d", $cf)); |
|
0
|
|
|
|
|
|
|
458
|
0
|
0
|
|
|
|
|
return 1; |
459
|
0
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
return; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
0
|
0
|
0
|
|
|
|
1; |
465
|
0
|
|
|
|
|
|
|
466
|
0
|
|
|
|
|
|
=back |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |