| 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::Conf::Parser - parse SpamAssassin configuration |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
(see Mail::SpamAssassin) |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
Mail::SpamAssassin is a module to identify spam using text analysis and |
|
29
|
|
|
|
|
|
|
several internet-based realtime blacklists. |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
This class is used internally by SpamAssassin to parse its configuration files. |
|
32
|
|
|
|
|
|
|
Please refer to the C<Mail::SpamAssassin> documentation for public interfaces. |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 STRUCTURE OF A CONFIG BLOCK |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
This is the structure of a config-setting block. Each is a hashref which may |
|
37
|
|
|
|
|
|
|
contain these keys: |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=over 4 |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=item setting |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
the name of the setting it modifies, e.g. "required_score". this also doubles |
|
44
|
|
|
|
|
|
|
as the default for 'command' (below). THIS IS REQUIRED. |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=item command |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
The command string used in the config file for this setting. Optional; |
|
49
|
|
|
|
|
|
|
'setting' will be used for the command if this is omitted. |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=item aliases |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
An [aryref] of other aliases for the same command. optional. |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=item type |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
The type of this setting: |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
- $CONF_TYPE_NOARGS: must not have any argument, like "clear_headers" |
|
60
|
|
|
|
|
|
|
- $CONF_TYPE_STRING: string |
|
61
|
|
|
|
|
|
|
- $CONF_TYPE_NUMERIC: numeric value (float or int) |
|
62
|
|
|
|
|
|
|
- $CONF_TYPE_BOOL: boolean (0/no or 1/yes) |
|
63
|
|
|
|
|
|
|
- $CONF_TYPE_TEMPLATE: template, like "report" |
|
64
|
|
|
|
|
|
|
- $CONF_TYPE_ADDRLIST: list of mail addresses, like "whitelist_from" |
|
65
|
|
|
|
|
|
|
- $CONF_TYPE_HASH_KEY_VALUE: hash key/value pair, like "describe" or tflags |
|
66
|
|
|
|
|
|
|
- $CONF_TYPE_STRINGLIST list of strings, stored as an array |
|
67
|
|
|
|
|
|
|
- $CONF_TYPE_IPADDRLIST list of IP addresses, stored as an array of SA::NetSet |
|
68
|
|
|
|
|
|
|
- $CONF_TYPE_DURATION a nonnegative time interval in seconds - a numeric value |
|
69
|
|
|
|
|
|
|
(float or int), optionally suffixed by a time unit (s, m, |
|
70
|
|
|
|
|
|
|
h, d, w), seconds are implied if unit is missing |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
If this is set, and a 'code' block does not already exist, a 'code' block is |
|
73
|
|
|
|
|
|
|
assigned based on the type. |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
In addition, the SpamAssassin test suite will validate that the settings |
|
76
|
|
|
|
|
|
|
do not 'leak' between users. |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
Note that C<$CONF_TYPE_HASH_KEY_VALUE>-type settings require that the |
|
79
|
|
|
|
|
|
|
value be non-empty, otherwise they'll produce a warning message. |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=item code |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
A subroutine to deal with the setting. ONE OF B<code> OR B<type> IS REQUIRED. |
|
84
|
|
|
|
|
|
|
The arguments passed to the function are C<($self, $key, $value, $line)>, |
|
85
|
|
|
|
|
|
|
where $key is the setting (*not* the command), $value is the value string, |
|
86
|
|
|
|
|
|
|
and $line is the entire line. |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
There are two special return values that the B<code> subroutine may return |
|
89
|
|
|
|
|
|
|
to signal that there is an error in the configuration: |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
C<$Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE> -- this setting requires |
|
92
|
|
|
|
|
|
|
that a value be set, but one was not provided. |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
C<$Mail::SpamAssassin::Conf::INVALID_VALUE> -- this setting requires a value |
|
95
|
|
|
|
|
|
|
from a set of 'valid' values, but the user provided an invalid one. |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
C<$Mail::SpamAssassin::Conf::INVALID_HEADER_FIELD_NAME> -- this setting |
|
98
|
|
|
|
|
|
|
requires a syntactically valid header field name, but the user provided |
|
99
|
|
|
|
|
|
|
an invalid one. |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
Any other values -- including C<undef> -- returned from the subroutine are |
|
102
|
|
|
|
|
|
|
considered to mean 'success'. |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
It is good practice to set a 'type', if possible, describing how your settings |
|
105
|
|
|
|
|
|
|
are stored on the Conf object; this allows the SpamAssassin test suite to |
|
106
|
|
|
|
|
|
|
validate that the settings do not 'leak' between users. |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=item default |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
The default value for the setting. may be omitted if the default value is a |
|
111
|
|
|
|
|
|
|
non-scalar type, which should be set in the Conf ctor. note for path types: |
|
112
|
|
|
|
|
|
|
using "__userstate__" is recommended for defaults, as it allows |
|
113
|
|
|
|
|
|
|
Mail::SpamAssassin module users who set that configuration setting, to receive |
|
114
|
|
|
|
|
|
|
the correct values. |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item is_priv |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Set to 1 if this setting requires 'allow_user_rules' when run from spamd. |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=item is_admin |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
Set to 1 if this setting can only be set in the system-wide config when run |
|
123
|
|
|
|
|
|
|
from spamd. (All settings can be used by local programs run directly by the |
|
124
|
|
|
|
|
|
|
user.) |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=item is_frequent |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
Set to 1 if this value occurs frequently in the config. this means it's looked |
|
129
|
|
|
|
|
|
|
up first for speed. |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=back |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=cut |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
package Mail::SpamAssassin::Conf::Parser; |
|
136
|
|
|
|
|
|
|
|
|
137
|
40
|
|
|
40
|
|
291
|
use Mail::SpamAssassin::Conf; |
|
|
40
|
|
|
|
|
93
|
|
|
|
40
|
|
|
|
|
1532
|
|
|
138
|
40
|
|
|
40
|
|
239
|
use Mail::SpamAssassin::Constants qw(:sa); |
|
|
40
|
|
|
|
|
88
|
|
|
|
40
|
|
|
|
|
5422
|
|
|
139
|
40
|
|
|
40
|
|
293
|
use Mail::SpamAssassin::Logger; |
|
|
40
|
|
|
|
|
93
|
|
|
|
40
|
|
|
|
|
2284
|
|
|
140
|
40
|
|
|
40
|
|
243
|
use Mail::SpamAssassin::Util qw(untaint_var compile_regexp); |
|
|
40
|
|
|
|
|
70
|
|
|
|
40
|
|
|
|
|
3767
|
|
|
141
|
40
|
|
|
40
|
|
280
|
use Mail::SpamAssassin::NetSet; |
|
|
40
|
|
|
|
|
80
|
|
|
|
40
|
|
|
|
|
1045
|
|
|
142
|
|
|
|
|
|
|
|
|
143
|
40
|
|
|
40
|
|
238
|
use strict; |
|
|
40
|
|
|
|
|
97
|
|
|
|
40
|
|
|
|
|
1235
|
|
|
144
|
40
|
|
|
40
|
|
223
|
use warnings; |
|
|
40
|
|
|
|
|
72
|
|
|
|
40
|
|
|
|
|
1562
|
|
|
145
|
|
|
|
|
|
|
# use bytes; |
|
146
|
40
|
|
|
40
|
|
241
|
use re 'taint'; |
|
|
40
|
|
|
|
|
93
|
|
|
|
40
|
|
|
|
|
105816
|
|
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
our @ISA = qw(); |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
my $ARITH_EXPRESSION_LEXER = ARITH_EXPRESSION_LEXER; |
|
151
|
|
|
|
|
|
|
my $META_RULES_MATCHING_RE = META_RULES_MATCHING_RE; |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
########################################################################### |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub new { |
|
156
|
91
|
|
|
91
|
0
|
275
|
my $class = shift; |
|
157
|
91
|
|
33
|
|
|
674
|
$class = ref($class) || $class; |
|
158
|
91
|
|
|
|
|
301
|
my ($conf) = @_; |
|
159
|
|
|
|
|
|
|
|
|
160
|
91
|
|
|
|
|
328
|
my $self = { |
|
161
|
|
|
|
|
|
|
'conf' => $conf |
|
162
|
|
|
|
|
|
|
}; |
|
163
|
|
|
|
|
|
|
|
|
164
|
91
|
|
|
|
|
364
|
$self->{command_luts} = { }; |
|
165
|
91
|
|
|
|
|
349
|
$self->{command_luts}->{frequent} = { }; |
|
166
|
91
|
|
|
|
|
404
|
$self->{command_luts}->{remaining} = { }; |
|
167
|
|
|
|
|
|
|
|
|
168
|
91
|
|
|
|
|
458
|
bless ($self, $class); |
|
169
|
91
|
|
|
|
|
699
|
$self; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
########################################################################### |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub register_commands { |
|
175
|
1621
|
|
|
1621
|
0
|
3698
|
my($self, $arrref) = @_; |
|
176
|
1621
|
|
|
|
|
3102
|
my $conf = $self->{conf}; |
|
177
|
|
|
|
|
|
|
|
|
178
|
1621
|
|
|
|
|
4393
|
$self->set_defaults_from_command_list($arrref); |
|
179
|
1621
|
|
|
|
|
5306
|
$self->build_command_luts($arrref); |
|
180
|
1621
|
|
|
|
|
2643
|
push(@{$conf->{registered_commands}}, @{$arrref}); |
|
|
1621
|
|
|
|
|
3192
|
|
|
|
1621
|
|
|
|
|
8410
|
|
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub set_defaults_from_command_list { |
|
184
|
1621
|
|
|
1621
|
0
|
2725
|
my ($self, $arrref) = @_; |
|
185
|
1621
|
|
|
|
|
2371
|
my $conf = $self->{conf}; |
|
186
|
1621
|
|
|
|
|
2326
|
foreach my $cmd (@{$arrref}) { |
|
|
1621
|
|
|
|
|
4350
|
|
|
187
|
|
|
|
|
|
|
# note! exists, not defined -- we want to be able to set |
|
188
|
|
|
|
|
|
|
# "undef" default values. |
|
189
|
18335
|
100
|
|
|
|
32854
|
if (exists($cmd->{default})) { |
|
190
|
9790
|
|
|
|
|
32154
|
$conf->{$cmd->{setting}} = $cmd->{default}; |
|
191
|
|
|
|
|
|
|
} |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub build_command_luts { |
|
196
|
1621
|
|
|
1621
|
0
|
3186
|
my ($self, $arrref) = @_; |
|
197
|
|
|
|
|
|
|
|
|
198
|
1621
|
|
|
|
|
2578
|
my $conf = $self->{conf}; |
|
199
|
|
|
|
|
|
|
|
|
200
|
1621
|
|
|
|
|
2252
|
my $set; |
|
201
|
1621
|
|
|
|
|
2088
|
foreach my $cmd (@{$arrref}) { |
|
|
1621
|
|
|
|
|
2930
|
|
|
202
|
|
|
|
|
|
|
# first off, decide what set this is in. |
|
203
|
18335
|
100
|
|
|
|
28071
|
if ($cmd->{is_frequent}) { $set = 'frequent'; } |
|
|
728
|
|
|
|
|
1066
|
|
|
204
|
17607
|
|
|
|
|
21516
|
else { $set = 'remaining'; } |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# next, its priority (used to ensure frequently-used params |
|
207
|
|
|
|
|
|
|
# are parsed first) |
|
208
|
18335
|
|
66
|
|
|
42798
|
my $cmdname = $cmd->{command} || $cmd->{setting}; |
|
209
|
18335
|
|
|
|
|
40639
|
$self->{command_luts}->{$set}->{$cmdname} = $cmd; |
|
210
|
|
|
|
|
|
|
|
|
211
|
18335
|
100
|
66
|
|
|
41289
|
if ($cmd->{aliases} && scalar @{$cmd->{aliases}} > 0) { |
|
|
182
|
|
|
|
|
681
|
|
|
212
|
182
|
|
|
|
|
309
|
foreach my $name (@{$cmd->{aliases}}) { |
|
|
182
|
|
|
|
|
477
|
|
|
213
|
182
|
|
|
|
|
651
|
$self->{command_luts}->{$set}->{$name} = $cmd; |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
} |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
########################################################################### |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub parse { |
|
222
|
90
|
|
|
90
|
0
|
413
|
my ($self, undef, $scoresonly) = @_; # leave $rules in $_[1] |
|
223
|
|
|
|
|
|
|
|
|
224
|
90
|
|
|
|
|
310
|
my $conf = $self->{conf}; |
|
225
|
90
|
|
|
|
|
446
|
$self->{scoresonly} = $scoresonly; |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# Language selection: |
|
228
|
|
|
|
|
|
|
# See http://www.gnu.org/manual/glibc-2.2.5/html_node/Locale-Categories.html |
|
229
|
|
|
|
|
|
|
# and http://www.gnu.org/manual/glibc-2.2.5/html_node/Using-gettextized-software.html |
|
230
|
90
|
|
|
|
|
304
|
my $lang = $ENV{'LANGUAGE'}; # LANGUAGE has the highest precedence but has a |
|
231
|
90
|
50
|
|
|
|
296
|
if ($lang) { # special format: The user may specify more than |
|
232
|
0
|
|
|
|
|
0
|
$lang =~ s/:.*$//; # one language here, colon separated. We use the |
|
233
|
|
|
|
|
|
|
} # first one only (lazy bums we are :o) |
|
234
|
90
|
|
33
|
|
|
791
|
$lang ||= $ENV{'LC_ALL'}; |
|
235
|
90
|
|
33
|
|
|
629
|
$lang ||= $ENV{'LC_MESSAGES'}; |
|
236
|
90
|
|
33
|
|
|
652
|
$lang ||= $ENV{'LANG'}; |
|
237
|
90
|
|
50
|
|
|
628
|
$lang ||= 'C'; # Nothing set means C/POSIX |
|
238
|
|
|
|
|
|
|
|
|
239
|
90
|
50
|
|
|
|
897
|
if ($lang =~ /^(C|POSIX)$/) { |
|
240
|
90
|
|
|
|
|
352
|
$lang = 'en_US'; # Our default language |
|
241
|
|
|
|
|
|
|
} else { |
|
242
|
0
|
|
|
|
|
0
|
$lang =~ s/[@.+,].*$//; # Strip codeset, modifier/audience, etc. |
|
243
|
|
|
|
|
|
|
} # (eg. .utf8 or @euro) |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# get fast-access handles on the command lookup tables |
|
246
|
90
|
|
|
|
|
304
|
my $lut_frequent = $self->{command_luts}->{frequent}; |
|
247
|
90
|
|
|
|
|
245
|
my $lut_remaining = $self->{command_luts}->{remaining}; |
|
248
|
90
|
|
|
|
|
472
|
my %migrated_keys = map { $_ => 1 } |
|
|
90
|
|
|
|
|
724
|
|
|
249
|
|
|
|
|
|
|
@Mail::SpamAssassin::Conf::MIGRATED_SETTINGS; |
|
250
|
|
|
|
|
|
|
|
|
251
|
90
|
|
|
|
|
504
|
$self->{currentfile} = '(no file)'; |
|
252
|
90
|
|
|
|
|
212
|
my $skip_parsing = 0; |
|
253
|
90
|
|
|
|
|
231
|
my @curfile_stack; |
|
254
|
|
|
|
|
|
|
my @if_stack; |
|
255
|
90
|
|
|
|
|
78455
|
my @conf_lines = split (/\n/, $_[1]); |
|
256
|
90
|
|
|
|
|
442
|
my $line; |
|
257
|
90
|
|
|
|
|
334
|
$self->{if_stack} = \@if_stack; |
|
258
|
90
|
|
|
|
|
361
|
$self->{file_scoped_attrs} = { }; |
|
259
|
|
|
|
|
|
|
|
|
260
|
90
|
|
|
|
|
291
|
my $keepmetadata = $conf->{main}->{keep_config_parsing_metadata}; |
|
261
|
|
|
|
|
|
|
|
|
262
|
90
|
|
|
|
|
602
|
while (defined ($line = shift @conf_lines)) { |
|
263
|
138546
|
|
|
|
|
297033
|
local ($1); # bug 3838: prevent random taint flagging of $1 |
|
264
|
|
|
|
|
|
|
|
|
265
|
138546
|
100
|
|
|
|
419185
|
if (index($line,'#') > -1) { |
|
266
|
|
|
|
|
|
|
# bug 5545: used to support testing rules in the ruleqa system |
|
267
|
50346
|
50
|
33
|
|
|
97522
|
if ($keepmetadata && $line =~ /^\#testrules/) { |
|
268
|
0
|
|
|
|
|
0
|
$self->{file_scoped_attrs}->{testrules}++; |
|
269
|
0
|
|
|
|
|
0
|
next; |
|
270
|
|
|
|
|
|
|
} |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# bug 6800: let X-Spam-Checker-Version also show what sa-update we are at |
|
273
|
50346
|
50
|
|
|
|
89311
|
if ($line =~ /^\# UPDATE version (\d+)$/) { |
|
274
|
0
|
|
|
|
|
0
|
for ($self->{currentfile}) { # just aliasing, not a loop |
|
275
|
0
|
0
|
0
|
|
|
0
|
$conf->{update_version}{$_} = $1 if defined $_ && $_ ne '(no file)'; |
|
276
|
|
|
|
|
|
|
} |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
|
|
279
|
50346
|
|
|
|
|
150381
|
$line =~ s/(?<!\\)#.*$//; # remove comments |
|
280
|
50346
|
|
|
|
|
87504
|
$line =~ s/\\#/#/g; # hash chars are escaped, so unescape them |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
|
|
283
|
138546
|
|
|
|
|
285248
|
$line =~ s/^\s+//; # remove leading whitespace |
|
284
|
138546
|
|
|
|
|
278080
|
$line =~ s/\s+$//; # remove tailing whitespace |
|
285
|
138546
|
100
|
|
|
|
383450
|
next unless($line); # skip empty lines |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# handle i18n |
|
288
|
74549
|
0
|
|
|
|
153110
|
if ($line =~ s/^lang\s+(\S+)\s+//) { next if ($lang !~ /^$1/i); } |
|
|
0
|
50
|
|
|
|
0
|
|
|
289
|
|
|
|
|
|
|
|
|
290
|
74549
|
|
|
|
|
310199
|
my($key, $value) = split(/\s+/, $line, 2); |
|
291
|
74549
|
|
|
|
|
176247
|
$key = lc $key; |
|
292
|
|
|
|
|
|
|
# convert all dashes in setting name to underscores. |
|
293
|
74549
|
|
|
|
|
140064
|
$key =~ tr/-/_/; |
|
294
|
74549
|
100
|
|
|
|
165400
|
$value = '' unless defined($value); |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# # Do a better job untainting this info ... |
|
297
|
|
|
|
|
|
|
# # $value = untaint_var($value); |
|
298
|
|
|
|
|
|
|
# Do NOT blindly untaint now, do it carefully later when semantics is known! |
|
299
|
|
|
|
|
|
|
|
|
300
|
74549
|
|
|
|
|
98395
|
my $parse_error; # undef by default, may be overridden |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# File/line number assertions |
|
303
|
74549
|
100
|
|
|
|
329056
|
if ($key eq 'file') { |
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
304
|
3264
|
100
|
|
|
|
11161
|
if ($value =~ /^start\s+(.+)$/) { |
|
305
|
1632
|
|
|
|
|
4234
|
push (@curfile_stack, $self->{currentfile}); |
|
306
|
1632
|
|
|
|
|
4332
|
$self->{currentfile} = $1; |
|
307
|
1632
|
|
|
|
|
8043
|
next; |
|
308
|
|
|
|
|
|
|
} |
|
309
|
|
|
|
|
|
|
|
|
310
|
1632
|
50
|
|
|
|
6332
|
if ($value =~ /^end\s/) { |
|
311
|
1632
|
|
|
|
|
4809
|
$self->{file_scoped_attrs} = { }; |
|
312
|
|
|
|
|
|
|
|
|
313
|
1632
|
50
|
|
|
|
4028
|
if (scalar @if_stack > 0) { |
|
314
|
0
|
|
|
|
|
0
|
my $cond = pop @if_stack; |
|
315
|
|
|
|
|
|
|
|
|
316
|
0
|
0
|
|
|
|
0
|
if ($cond->{type} eq 'if') { |
|
317
|
|
|
|
|
|
|
my $msg = "config: unclosed 'if' in ". |
|
318
|
0
|
|
|
|
|
0
|
$self->{currentfile}.": if ".$cond->{conditional}."\n"; |
|
319
|
0
|
|
|
|
|
0
|
warn $msg; |
|
320
|
0
|
|
|
|
|
0
|
$self->lint_warn($msg, undef); |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
else { |
|
323
|
|
|
|
|
|
|
# die seems a bit excessive here, but this shouldn't be possible |
|
324
|
|
|
|
|
|
|
# so I suppose it's okay. |
|
325
|
0
|
|
|
|
|
0
|
die "config: unknown 'if' type: ".$cond->{type}."\n"; |
|
326
|
|
|
|
|
|
|
} |
|
327
|
|
|
|
|
|
|
|
|
328
|
0
|
|
|
|
|
0
|
@if_stack = (); |
|
329
|
|
|
|
|
|
|
} |
|
330
|
1632
|
|
|
|
|
2912
|
$skip_parsing = 0; |
|
331
|
|
|
|
|
|
|
|
|
332
|
1632
|
|
|
|
|
3074
|
my $curfile = pop @curfile_stack; |
|
333
|
1632
|
50
|
|
|
|
3633
|
if (defined $curfile) { |
|
334
|
1632
|
|
|
|
|
3463
|
$self->{currentfile} = $curfile; |
|
335
|
|
|
|
|
|
|
} else { |
|
336
|
0
|
|
|
|
|
0
|
$self->{currentfile} = '(no file)'; |
|
337
|
|
|
|
|
|
|
} |
|
338
|
1632
|
|
|
|
|
7912
|
next; |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
} |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# now handle the commands. |
|
343
|
|
|
|
|
|
|
elsif ($key eq 'include') { |
|
344
|
0
|
|
|
|
|
0
|
$value = $self->fix_path_relative_to_current_file($value); |
|
345
|
0
|
|
|
|
|
0
|
my $text = $conf->{main}->read_cf($value, 'included file'); |
|
346
|
0
|
|
|
|
|
0
|
unshift (@conf_lines, split (/\n/, $text)); |
|
347
|
0
|
|
|
|
|
0
|
next; |
|
348
|
|
|
|
|
|
|
} |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
elsif ($key eq 'ifplugin') { |
|
351
|
734
|
|
|
|
|
4958
|
$self->handle_conditional ($key, "plugin ($value)", |
|
352
|
|
|
|
|
|
|
\@if_stack, \$skip_parsing); |
|
353
|
734
|
|
|
|
|
6383
|
next; |
|
354
|
|
|
|
|
|
|
} |
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
elsif ($key eq 'if') { |
|
357
|
236
|
|
|
|
|
1437
|
$self->handle_conditional ($key, $value, |
|
358
|
|
|
|
|
|
|
\@if_stack, \$skip_parsing); |
|
359
|
236
|
|
|
|
|
2055
|
next; |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
elsif ($key eq 'else') { |
|
363
|
|
|
|
|
|
|
# TODO: if/else/else won't get flagged here :( |
|
364
|
0
|
0
|
|
|
|
0
|
if (!@if_stack) { |
|
365
|
0
|
|
|
|
|
0
|
$parse_error = "config: found else without matching conditional"; |
|
366
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
367
|
|
|
|
|
|
|
} |
|
368
|
|
|
|
|
|
|
|
|
369
|
0
|
|
|
|
|
0
|
$skip_parsing = !$skip_parsing; |
|
370
|
0
|
|
|
|
|
0
|
next; |
|
371
|
|
|
|
|
|
|
} |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
# and the endif statement: |
|
374
|
|
|
|
|
|
|
elsif ($key eq 'endif') { |
|
375
|
970
|
|
|
|
|
2034
|
my $lastcond = pop @if_stack; |
|
376
|
970
|
50
|
|
|
|
2336
|
if (!defined $lastcond) { |
|
377
|
0
|
|
|
|
|
0
|
$parse_error = "config: found endif without matching conditional"; |
|
378
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
|
|
381
|
970
|
|
|
|
|
1810
|
$skip_parsing = $lastcond->{skip_parsing}; |
|
382
|
970
|
|
|
|
|
5851
|
next; |
|
383
|
|
|
|
|
|
|
} |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
# preprocessing? skip all other commands |
|
386
|
69345
|
100
|
|
|
|
129373
|
next if $skip_parsing; |
|
387
|
|
|
|
|
|
|
|
|
388
|
69187
|
50
|
|
|
|
134870
|
if ($key eq 'require_version') { |
|
389
|
|
|
|
|
|
|
# if it wasn't replaced during install, assume current version ... |
|
390
|
0
|
0
|
|
|
|
0
|
next if ($value eq "\@\@VERSION\@\@"); |
|
391
|
|
|
|
|
|
|
|
|
392
|
0
|
|
|
|
|
0
|
my $ver = $Mail::SpamAssassin::VERSION; |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
# if we want to allow "require_version 3.0" be good for all |
|
395
|
|
|
|
|
|
|
# "3.0.x" versions: |
|
396
|
|
|
|
|
|
|
## make sure it's a numeric value |
|
397
|
|
|
|
|
|
|
#$value += 0.0; |
|
398
|
|
|
|
|
|
|
## convert 3.000000 -> 3.0, stay backward compatible ... |
|
399
|
|
|
|
|
|
|
#$ver =~ s/^(\d+)\.(\d{1,3}).*$/sprintf "%d.%d", $1, $2/e; |
|
400
|
|
|
|
|
|
|
#$value =~ s/^(\d+)\.(\d{1,3}).*$/sprintf "%d.%d", $1, $2/e; |
|
401
|
|
|
|
|
|
|
|
|
402
|
0
|
0
|
|
|
|
0
|
if ($ver ne $value) { |
|
403
|
0
|
|
|
|
|
0
|
my $msg = "config: configuration file \"$self->{currentfile}\" requires ". |
|
404
|
|
|
|
|
|
|
"version $value of SpamAssassin, but this is code version ". |
|
405
|
|
|
|
|
|
|
"$ver. Maybe you need to use ". |
|
406
|
|
|
|
|
|
|
"the -C switch, or remove the old config files? ". |
|
407
|
|
|
|
|
|
|
"Skipping this file"; |
|
408
|
0
|
|
|
|
|
0
|
warn $msg; |
|
409
|
0
|
|
|
|
|
0
|
$self->lint_warn($msg, undef); |
|
410
|
0
|
|
|
|
|
0
|
$skip_parsing = 1; |
|
411
|
|
|
|
|
|
|
} |
|
412
|
0
|
|
|
|
|
0
|
next; |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
|
|
415
|
69187
|
|
|
|
|
121150
|
my $cmd = $lut_frequent->{$key}; # check the frequent command set |
|
416
|
69187
|
100
|
|
|
|
114208
|
if (!$cmd) { |
|
417
|
61046
|
|
|
|
|
98355
|
$cmd = $lut_remaining->{$key}; # no? try the rest |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# we've either fallen through with no match, in which case this |
|
421
|
|
|
|
|
|
|
# if() will fail, or we have a match. |
|
422
|
69187
|
100
|
|
|
|
117307
|
if ($cmd) { |
|
423
|
69174
|
50
|
|
|
|
123453
|
if ($self->{scoresonly}) { # reading user config from spamd |
|
424
|
0
|
0
|
0
|
|
|
0
|
if ($cmd->{is_priv} && !$conf->{allow_user_rules}) { |
|
425
|
0
|
|
|
|
|
0
|
info("config: not parsing, 'allow_user_rules' is 0: $line"); |
|
426
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
427
|
|
|
|
|
|
|
} |
|
428
|
0
|
0
|
|
|
|
0
|
if ($cmd->{is_admin}) { |
|
429
|
0
|
|
|
|
|
0
|
info("config: not parsing, administrator setting: $line"); |
|
430
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
} |
|
433
|
|
|
|
|
|
|
|
|
434
|
69174
|
100
|
|
|
|
120398
|
if (!$cmd->{code}) { |
|
435
|
776
|
50
|
|
|
|
2595
|
if (! $self->setup_default_code_cb($cmd)) { |
|
436
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
|
|
440
|
69174
|
|
|
|
|
92165
|
my $ret = &{$cmd->{code}} ($conf, $cmd->{setting}, $value, $line); |
|
|
69174
|
|
|
|
|
153929
|
|
|
441
|
|
|
|
|
|
|
|
|
442
|
69174
|
50
|
66
|
|
|
401355
|
if ($ret && $ret eq $Mail::SpamAssassin::Conf::INVALID_VALUE) |
|
|
|
50
|
66
|
|
|
|
|
|
|
|
50
|
66
|
|
|
|
|
|
443
|
|
|
|
|
|
|
{ |
|
444
|
0
|
|
|
|
|
0
|
$parse_error = "config: SpamAssassin failed to parse line, ". |
|
445
|
|
|
|
|
|
|
"\"$value\" is not valid for \"$key\", ". |
|
446
|
|
|
|
|
|
|
"skipping: $line"; |
|
447
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
448
|
|
|
|
|
|
|
} |
|
449
|
|
|
|
|
|
|
elsif ($ret && $ret eq $Mail::SpamAssassin::Conf::INVALID_HEADER_FIELD_NAME) |
|
450
|
|
|
|
|
|
|
{ |
|
451
|
0
|
|
|
|
|
0
|
$parse_error = "config: SpamAssassin failed to parse line, ". |
|
452
|
|
|
|
|
|
|
"it does not specify a valid header field name, ". |
|
453
|
|
|
|
|
|
|
"skipping: $line"; |
|
454
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
455
|
|
|
|
|
|
|
} |
|
456
|
|
|
|
|
|
|
elsif ($ret && $ret eq $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE) |
|
457
|
|
|
|
|
|
|
{ |
|
458
|
0
|
|
|
|
|
0
|
$parse_error = "config: SpamAssassin failed to parse line, ". |
|
459
|
|
|
|
|
|
|
"no value provided for \"$key\", ". |
|
460
|
|
|
|
|
|
|
"skipping: $line"; |
|
461
|
0
|
|
|
|
|
0
|
goto failed_line; |
|
462
|
|
|
|
|
|
|
} |
|
463
|
|
|
|
|
|
|
else { |
|
464
|
69174
|
|
|
|
|
395069
|
next; |
|
465
|
|
|
|
|
|
|
} |
|
466
|
|
|
|
|
|
|
} |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
# last ditch: try to see if the plugins know what to do with it |
|
469
|
13
|
50
|
|
|
|
131
|
if ($conf->{main}->call_plugins("parse_config", { |
|
470
|
|
|
|
|
|
|
key => $key, |
|
471
|
|
|
|
|
|
|
value => $value, |
|
472
|
|
|
|
|
|
|
line => $line, |
|
473
|
|
|
|
|
|
|
conf => $conf, |
|
474
|
|
|
|
|
|
|
user_config => $self->{scoresonly} |
|
475
|
|
|
|
|
|
|
})) |
|
476
|
|
|
|
|
|
|
{ |
|
477
|
|
|
|
|
|
|
# a plugin dealt with it successfully. |
|
478
|
0
|
|
|
|
|
0
|
next; |
|
479
|
|
|
|
|
|
|
} |
|
480
|
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
failed_line: |
|
482
|
13
|
|
|
|
|
38
|
my $msg = $parse_error; |
|
483
|
13
|
|
|
|
|
21
|
my $is_error = 1; |
|
484
|
13
|
50
|
|
|
|
26
|
if (!$msg) { |
|
485
|
|
|
|
|
|
|
# use a default warning, if a more specific one wasn't output |
|
486
|
13
|
50
|
|
|
|
29
|
if ($migrated_keys{$key}) { |
|
487
|
|
|
|
|
|
|
# this key was moved into a plugin; non-fatal for lint |
|
488
|
0
|
|
|
|
|
0
|
$is_error = 0; |
|
489
|
0
|
|
|
|
|
0
|
$msg = "config: failed to parse, now a plugin, skipping, in \"$self->{currentfile}\": $line"; |
|
490
|
|
|
|
|
|
|
} else { |
|
491
|
|
|
|
|
|
|
# a real syntax error; this is fatal for --lint |
|
492
|
13
|
|
|
|
|
43
|
$msg = "config: failed to parse line, skipping, in \"$self->{currentfile}\": $line"; |
|
493
|
|
|
|
|
|
|
} |
|
494
|
|
|
|
|
|
|
} |
|
495
|
|
|
|
|
|
|
|
|
496
|
13
|
|
|
|
|
68
|
$self->lint_warn($msg, undef, $is_error); |
|
497
|
|
|
|
|
|
|
} |
|
498
|
|
|
|
|
|
|
|
|
499
|
90
|
|
|
|
|
377
|
delete $self->{if_stack}; |
|
500
|
|
|
|
|
|
|
|
|
501
|
90
|
|
|
|
|
543
|
$self->lint_check(); |
|
502
|
90
|
|
|
|
|
396
|
$self->set_default_scores(); |
|
503
|
90
|
|
|
|
|
478
|
$self->check_for_missing_descriptions(); |
|
504
|
|
|
|
|
|
|
|
|
505
|
90
|
|
|
|
|
905
|
delete $self->{scoresonly}; |
|
506
|
|
|
|
|
|
|
} |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
sub handle_conditional { |
|
509
|
970
|
|
|
970
|
0
|
3757
|
my ($self, $key, $value, $if_stack_ref, $skip_parsing_ref) = @_; |
|
510
|
970
|
|
|
|
|
2238
|
my $conf = $self->{conf}; |
|
511
|
|
|
|
|
|
|
|
|
512
|
970
|
|
|
|
|
13733
|
my @tokens = ($value =~ /($ARITH_EXPRESSION_LEXER)/og); |
|
513
|
|
|
|
|
|
|
|
|
514
|
970
|
|
|
|
|
2755
|
my $eval = ''; |
|
515
|
970
|
|
|
|
|
1486
|
my $bad = 0; |
|
516
|
970
|
|
|
|
|
2383
|
foreach my $token (@tokens) { |
|
517
|
3960
|
100
|
|
|
|
20573
|
if ($token =~ /^(?:\W{1,5}|[+-]?\d+(?:\.\d+)?)$/) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
# using tainted subr. argument may taint the whole expression, avoid |
|
519
|
2100
|
|
|
|
|
6232
|
my $u = untaint_var($token); |
|
520
|
2100
|
|
|
|
|
5408
|
$eval .= $u . " "; |
|
521
|
|
|
|
|
|
|
} |
|
522
|
|
|
|
|
|
|
elsif ($token eq 'plugin') { |
|
523
|
|
|
|
|
|
|
# replace with a method call |
|
524
|
734
|
|
|
|
|
2045
|
$eval .= '$self->cond_clause_plugin_loaded'; |
|
525
|
|
|
|
|
|
|
} |
|
526
|
|
|
|
|
|
|
elsif ($token eq 'can') { |
|
527
|
|
|
|
|
|
|
# replace with a method call |
|
528
|
156
|
|
|
|
|
481
|
$eval .= '$self->cond_clause_can'; |
|
529
|
|
|
|
|
|
|
} |
|
530
|
|
|
|
|
|
|
elsif ($token eq 'has') { |
|
531
|
|
|
|
|
|
|
# replace with a method call |
|
532
|
0
|
|
|
|
|
0
|
$eval .= '$self->cond_clause_has'; |
|
533
|
|
|
|
|
|
|
} |
|
534
|
|
|
|
|
|
|
elsif ($token eq 'version') { |
|
535
|
80
|
|
|
|
|
430
|
$eval .= $Mail::SpamAssassin::VERSION." "; |
|
536
|
|
|
|
|
|
|
} |
|
537
|
|
|
|
|
|
|
elsif ($token eq 'perl_version') { |
|
538
|
0
|
|
|
|
|
0
|
$eval .= $]." "; |
|
539
|
|
|
|
|
|
|
} |
|
540
|
|
|
|
|
|
|
elsif ($token =~ /^\w[\w\:]+$/) { # class name |
|
541
|
|
|
|
|
|
|
# Strictly controlled form: |
|
542
|
890
|
50
|
|
|
|
4515
|
if ($token =~ /^(?:\w+::){0,10}\w+$/) { |
|
543
|
890
|
|
|
|
|
2321
|
my $u = untaint_var($token); |
|
544
|
890
|
|
|
|
|
3152
|
$eval .= "'$u'"; |
|
545
|
|
|
|
|
|
|
} else { |
|
546
|
0
|
|
|
|
|
0
|
warn "config: illegal name '$token' in 'if $value'\n"; |
|
547
|
0
|
|
|
|
|
0
|
$bad++; |
|
548
|
0
|
|
|
|
|
0
|
last; |
|
549
|
|
|
|
|
|
|
} |
|
550
|
|
|
|
|
|
|
} |
|
551
|
|
|
|
|
|
|
else { |
|
552
|
0
|
|
|
|
|
0
|
$bad++; |
|
553
|
0
|
|
|
|
|
0
|
warn "config: unparseable chars in 'if $value': '$token'\n"; |
|
554
|
0
|
|
|
|
|
0
|
last; |
|
555
|
|
|
|
|
|
|
} |
|
556
|
|
|
|
|
|
|
} |
|
557
|
|
|
|
|
|
|
|
|
558
|
970
|
50
|
|
|
|
2201
|
if ($bad) { |
|
559
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: bad 'if' line, in \"$self->{currentfile}\"", undef); |
|
560
|
0
|
|
|
|
|
0
|
return -1; |
|
561
|
|
|
|
|
|
|
} |
|
562
|
|
|
|
|
|
|
|
|
563
|
970
|
|
|
|
|
1477
|
push (@{$if_stack_ref}, { |
|
|
970
|
|
|
|
|
5332
|
|
|
564
|
|
|
|
|
|
|
type => 'if', |
|
565
|
|
|
|
|
|
|
conditional => $value, |
|
566
|
|
|
|
|
|
|
skip_parsing => $$skip_parsing_ref |
|
567
|
|
|
|
|
|
|
}); |
|
568
|
|
|
|
|
|
|
|
|
569
|
970
|
100
|
|
|
|
65233
|
if (eval $eval) { |
|
570
|
|
|
|
|
|
|
# leave $skip_parsing as-is; we may not be parsing anyway in this block. |
|
571
|
|
|
|
|
|
|
# in other words, support nested 'if's and 'require_version's |
|
572
|
|
|
|
|
|
|
} else { |
|
573
|
75
|
50
|
|
|
|
335
|
warn "config: error in $key - $eval: $@" if $@ ne ''; |
|
574
|
75
|
|
|
|
|
378
|
$$skip_parsing_ref = 1; |
|
575
|
|
|
|
|
|
|
} |
|
576
|
|
|
|
|
|
|
} |
|
577
|
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
# functions supported in the "if" eval: |
|
579
|
|
|
|
|
|
|
sub cond_clause_plugin_loaded { |
|
580
|
734
|
|
|
734
|
0
|
9716
|
return $_[0]->{conf}->{plugins_loaded}->{$_[1]}; |
|
581
|
|
|
|
|
|
|
} |
|
582
|
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
sub cond_clause_can { |
|
584
|
156
|
|
|
156
|
0
|
611
|
my ($self, $method) = @_; |
|
585
|
156
|
50
|
|
|
|
722
|
if ($self->{currentfile} =~ q!\buser_prefs$! ) { |
|
586
|
0
|
|
|
|
|
0
|
warn "config: 'if can $method' not available in user_prefs"; |
|
587
|
0
|
|
|
|
|
0
|
return 0 |
|
588
|
|
|
|
|
|
|
} |
|
589
|
156
|
|
|
|
|
716
|
$self->cond_clause_can_or_has('can', $method); |
|
590
|
|
|
|
|
|
|
} |
|
591
|
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
sub cond_clause_has { |
|
593
|
0
|
|
|
0
|
0
|
0
|
my ($self, $method) = @_; |
|
594
|
0
|
|
|
|
|
0
|
$self->cond_clause_can_or_has('has', $method); |
|
595
|
|
|
|
|
|
|
} |
|
596
|
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
sub cond_clause_can_or_has { |
|
598
|
156
|
|
|
156
|
0
|
473
|
my ($self, $fn_name, $method) = @_; |
|
599
|
|
|
|
|
|
|
|
|
600
|
156
|
|
|
|
|
663
|
local($1,$2); |
|
601
|
156
|
50
|
|
|
|
1398
|
if (!defined $method) { |
|
|
|
50
|
|
|
|
|
|
|
602
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: bad 'if' line, no argument to $fn_name(), ". |
|
603
|
|
|
|
|
|
|
"in \"$self->{currentfile}\"", undef); |
|
604
|
|
|
|
|
|
|
} elsif ($method =~ /^(.*)::([^:]+)$/) { |
|
605
|
40
|
|
|
40
|
|
371
|
no strict "refs"; |
|
|
40
|
|
|
|
|
91
|
|
|
|
40
|
|
|
|
|
249875
|
|
|
606
|
156
|
|
|
|
|
690
|
my($module, $meth) = ($1, $2); |
|
607
|
|
|
|
|
|
|
return 1 if $module->can($meth) && |
|
608
|
156
|
50
|
33
|
|
|
2641
|
( $fn_name eq 'has' || &{$method}() ); |
|
|
|
|
33
|
|
|
|
|
|
609
|
|
|
|
|
|
|
} else { |
|
610
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: bad 'if' line, cannot find '::' in $fn_name($method), ". |
|
611
|
|
|
|
|
|
|
"in \"$self->{currentfile}\"", undef); |
|
612
|
|
|
|
|
|
|
} |
|
613
|
0
|
|
|
|
|
0
|
return; |
|
614
|
|
|
|
|
|
|
} |
|
615
|
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
# Let's do some linting here ... |
|
617
|
|
|
|
|
|
|
# This is called from _parse(), BTW, so we can check for $conf->{tests} |
|
618
|
|
|
|
|
|
|
# easily before finish_parsing() is called and deletes it. |
|
619
|
|
|
|
|
|
|
# |
|
620
|
|
|
|
|
|
|
sub lint_check { |
|
621
|
90
|
|
|
90
|
0
|
293
|
my ($self) = @_; |
|
622
|
90
|
|
|
|
|
269
|
my $conf = $self->{conf}; |
|
623
|
|
|
|
|
|
|
|
|
624
|
90
|
100
|
|
|
|
401
|
if ($conf->{lint_rules}) { |
|
625
|
|
|
|
|
|
|
# Check for description and score issues in lint fashion |
|
626
|
32
|
|
|
|
|
84
|
while ( my $k = each %{$conf->{descriptions}} ) { |
|
|
928
|
|
|
|
|
2300
|
|
|
627
|
896
|
50
|
|
|
|
2044
|
if (!exists $conf->{tests}->{$k}) { |
|
628
|
0
|
|
|
|
|
0
|
dbg("config: warning: description exists for non-existent rule $k"); |
|
629
|
|
|
|
|
|
|
} |
|
630
|
|
|
|
|
|
|
} |
|
631
|
|
|
|
|
|
|
|
|
632
|
32
|
|
|
|
|
120
|
while ( my($sk) = each %{$conf->{scores}} ) { |
|
|
672
|
|
|
|
|
1674
|
|
|
633
|
640
|
50
|
|
|
|
1320
|
if (!exists $conf->{tests}->{$sk}) { |
|
634
|
|
|
|
|
|
|
# bug 5514: not a lint warning any more |
|
635
|
0
|
|
|
|
|
0
|
dbg("config: warning: score set for non-existent rule $sk"); |
|
636
|
|
|
|
|
|
|
} |
|
637
|
|
|
|
|
|
|
} |
|
638
|
|
|
|
|
|
|
} |
|
639
|
|
|
|
|
|
|
} |
|
640
|
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
# we should set a default score for all valid rules... Do this here |
|
642
|
|
|
|
|
|
|
# instead of add_test because mostly 'score' occurs after the rule is |
|
643
|
|
|
|
|
|
|
# specified, so why set the scores to default, then set them again at |
|
644
|
|
|
|
|
|
|
# 'score'? |
|
645
|
|
|
|
|
|
|
# |
|
646
|
|
|
|
|
|
|
sub set_default_scores { |
|
647
|
90
|
|
|
90
|
0
|
260
|
my ($self) = @_; |
|
648
|
90
|
|
|
|
|
258
|
my $conf = $self->{conf}; |
|
649
|
|
|
|
|
|
|
|
|
650
|
90
|
|
|
|
|
187
|
while ( my $k = each %{$conf->{tests}} ) { |
|
|
3728
|
|
|
|
|
9015
|
|
|
651
|
3638
|
100
|
|
|
|
6880
|
if ( ! exists $conf->{scores}->{$k} ) { |
|
652
|
|
|
|
|
|
|
# T_ rules (in a testing probationary period) get low, low scores |
|
653
|
2419
|
50
|
|
|
|
4312
|
my $set_score = ($k =~/^T_/) ? 0.01 : 1.0; |
|
654
|
|
|
|
|
|
|
|
|
655
|
2419
|
100
|
100
|
|
|
10087
|
$set_score = -$set_score if ( ($conf->{tflags}->{$k}||'') =~ /\bnice\b/ ); |
|
656
|
2419
|
|
|
|
|
4313
|
for my $index (0..3) { |
|
657
|
9676
|
|
|
|
|
19836
|
$conf->{scoreset}->[$index]->{$k} = $set_score; |
|
658
|
|
|
|
|
|
|
} |
|
659
|
|
|
|
|
|
|
} |
|
660
|
|
|
|
|
|
|
} |
|
661
|
|
|
|
|
|
|
} |
|
662
|
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
# loop through all the tests and if we are missing a description with debug |
|
664
|
|
|
|
|
|
|
# set, throw a warning except for testing T_ or meta __ rules. |
|
665
|
|
|
|
|
|
|
sub check_for_missing_descriptions { |
|
666
|
90
|
|
|
90
|
0
|
265
|
my ($self) = @_; |
|
667
|
90
|
|
|
|
|
242
|
my $conf = $self->{conf}; |
|
668
|
|
|
|
|
|
|
|
|
669
|
90
|
|
|
|
|
205
|
while ( my $k = each %{$conf->{tests}} ) { |
|
|
3728
|
|
|
|
|
8466
|
|
|
670
|
3638
|
100
|
|
|
|
7953
|
if ($k !~ m/^(?:T_|__)/i) { |
|
671
|
3453
|
100
|
|
|
|
6695
|
if ( ! exists $conf->{descriptions}->{$k} ) { |
|
672
|
1757
|
|
|
|
|
4103
|
dbg("config: warning: no description set for $k"); |
|
673
|
|
|
|
|
|
|
} |
|
674
|
|
|
|
|
|
|
} |
|
675
|
|
|
|
|
|
|
} |
|
676
|
|
|
|
|
|
|
} |
|
677
|
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
########################################################################### |
|
679
|
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
sub setup_default_code_cb { |
|
681
|
776
|
|
|
776
|
0
|
1652
|
my ($self, $cmd) = @_; |
|
682
|
776
|
|
|
|
|
1586
|
my $type = $cmd->{type}; |
|
683
|
|
|
|
|
|
|
|
|
684
|
776
|
100
|
|
|
|
3531
|
if ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_STRING) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
685
|
122
|
|
|
|
|
604
|
$cmd->{code} = \&set_string_value; |
|
686
|
|
|
|
|
|
|
} |
|
687
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL) { |
|
688
|
96
|
|
|
|
|
455
|
$cmd->{code} = \&set_bool_value; |
|
689
|
|
|
|
|
|
|
} |
|
690
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC) { |
|
691
|
210
|
|
|
|
|
620
|
$cmd->{code} = \&set_numeric_value; |
|
692
|
|
|
|
|
|
|
} |
|
693
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE) { |
|
694
|
182
|
|
|
|
|
560
|
$cmd->{code} = \&set_hash_key_value; |
|
695
|
|
|
|
|
|
|
} |
|
696
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_ADDRLIST) { |
|
697
|
0
|
|
|
|
|
0
|
$cmd->{code} = \&set_addrlist_value; |
|
698
|
|
|
|
|
|
|
} |
|
699
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_TEMPLATE) { |
|
700
|
122
|
|
|
|
|
376
|
$cmd->{code} = \&set_template_append; |
|
701
|
|
|
|
|
|
|
} |
|
702
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_NOARGS) { |
|
703
|
0
|
|
|
|
|
0
|
$cmd->{code} = \&set_no_value; |
|
704
|
|
|
|
|
|
|
} |
|
705
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST) { |
|
706
|
0
|
|
|
|
|
0
|
$cmd->{code} = \&set_string_list; |
|
707
|
|
|
|
|
|
|
} |
|
708
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_IPADDRLIST) { |
|
709
|
44
|
|
|
|
|
155
|
$cmd->{code} = \&set_ipaddr_list; |
|
710
|
|
|
|
|
|
|
} |
|
711
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_DURATION) { |
|
712
|
0
|
|
|
|
|
0
|
$cmd->{code} = \&set_duration_value; |
|
713
|
|
|
|
|
|
|
} |
|
714
|
|
|
|
|
|
|
else { |
|
715
|
0
|
|
|
|
|
0
|
warn "config: unknown conf type $type!"; |
|
716
|
0
|
|
|
|
|
0
|
return 0; |
|
717
|
|
|
|
|
|
|
} |
|
718
|
776
|
|
|
|
|
2331
|
return 1; |
|
719
|
|
|
|
|
|
|
} |
|
720
|
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
sub set_no_value { |
|
722
|
0
|
|
|
0
|
0
|
0
|
my ($conf, $key, $value, $line) = @_; |
|
723
|
|
|
|
|
|
|
|
|
724
|
0
|
0
|
0
|
|
|
0
|
unless (!defined $value || $value eq '') { |
|
725
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
|
726
|
|
|
|
|
|
|
} |
|
727
|
|
|
|
|
|
|
} |
|
728
|
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
sub set_numeric_value { |
|
730
|
210
|
|
|
210
|
0
|
886
|
my ($conf, $key, $value, $line) = @_; |
|
731
|
|
|
|
|
|
|
|
|
732
|
210
|
50
|
33
|
|
|
1762
|
unless (defined $value && $value !~ /^$/) { |
|
733
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
734
|
|
|
|
|
|
|
} |
|
735
|
210
|
50
|
|
|
|
1246
|
unless ($value =~ /^ [+-]? \d+ (?: \. \d* )? \z/sx) { |
|
736
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
|
737
|
|
|
|
|
|
|
} |
|
738
|
|
|
|
|
|
|
# it is safe to untaint now that we know the syntax is a valid number |
|
739
|
210
|
|
|
|
|
789
|
$conf->{$key} = untaint_var($value) + 0; |
|
740
|
|
|
|
|
|
|
} |
|
741
|
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
sub set_duration_value { |
|
743
|
0
|
|
|
0
|
0
|
0
|
my ($conf, $key, $value, $line) = @_; |
|
744
|
|
|
|
|
|
|
|
|
745
|
0
|
|
|
|
|
0
|
local ($1,$2); |
|
746
|
0
|
0
|
0
|
|
|
0
|
unless (defined $value && $value !~ /^$/) { |
|
747
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
748
|
|
|
|
|
|
|
} |
|
749
|
0
|
0
|
|
|
|
0
|
unless ($value =~ /^( \+? \d+ (?: \. \d* )? ) (?: \s* ([smhdw]))? \z/sxi) { |
|
750
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
|
751
|
|
|
|
|
|
|
} |
|
752
|
0
|
|
|
|
|
0
|
$value = $1; |
|
753
|
|
|
|
|
|
|
$value *= { s => 1, m => 60, h => 3600, |
|
754
|
0
|
0
|
|
|
|
0
|
d => 24*3600, w => 7*24*3600 }->{lc $2} if defined $2; |
|
755
|
|
|
|
|
|
|
# it is safe to untaint now that we know the syntax is a valid time interval |
|
756
|
0
|
|
|
|
|
0
|
$conf->{$key} = untaint_var($value) + 0; |
|
757
|
|
|
|
|
|
|
} |
|
758
|
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
sub set_bool_value { |
|
760
|
96
|
|
|
96
|
0
|
589
|
my ($conf, $key, $value, $line) = @_; |
|
761
|
|
|
|
|
|
|
|
|
762
|
96
|
50
|
33
|
|
|
1336
|
unless (defined $value && $value !~ /^$/) { |
|
763
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
764
|
|
|
|
|
|
|
} |
|
765
|
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
# bug 4462: allow yes/1 and no/0 for boolean values |
|
767
|
96
|
|
|
|
|
423
|
$value = lc $value; |
|
768
|
96
|
100
|
66
|
|
|
1076
|
if ($value eq 'yes' || $value eq '1') { |
|
|
|
50
|
33
|
|
|
|
|
|
769
|
63
|
|
|
|
|
230
|
$value = 1; |
|
770
|
|
|
|
|
|
|
} |
|
771
|
|
|
|
|
|
|
elsif ($value eq 'no' || $value eq '0') { |
|
772
|
33
|
|
|
|
|
139
|
$value = 0; |
|
773
|
|
|
|
|
|
|
} |
|
774
|
|
|
|
|
|
|
else { |
|
775
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
|
776
|
|
|
|
|
|
|
} |
|
777
|
|
|
|
|
|
|
|
|
778
|
96
|
|
|
|
|
10321
|
$conf->{$key} = $value; |
|
779
|
|
|
|
|
|
|
} |
|
780
|
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
sub set_string_value { |
|
782
|
122
|
|
|
122
|
0
|
610
|
my ($conf, $key, $value, $line) = @_; |
|
783
|
|
|
|
|
|
|
|
|
784
|
122
|
50
|
33
|
|
|
1213
|
unless (defined $value && $value !~ /^$/) { |
|
785
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
786
|
|
|
|
|
|
|
} |
|
787
|
|
|
|
|
|
|
|
|
788
|
122
|
|
|
|
|
647
|
$conf->{$key} = $value; # keep tainted |
|
789
|
|
|
|
|
|
|
} |
|
790
|
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
sub set_string_list { |
|
792
|
0
|
|
|
0
|
0
|
0
|
my ($conf, $key, $value, $line) = @_; |
|
793
|
|
|
|
|
|
|
|
|
794
|
0
|
0
|
0
|
|
|
0
|
unless (defined $value && $value !~ /^$/) { |
|
795
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
796
|
|
|
|
|
|
|
} |
|
797
|
|
|
|
|
|
|
|
|
798
|
0
|
|
|
|
|
0
|
push(@{$conf->{$key}}, split(' ', $value)); |
|
|
0
|
|
|
|
|
0
|
|
|
799
|
|
|
|
|
|
|
} |
|
800
|
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
sub set_ipaddr_list { |
|
802
|
44
|
|
|
44
|
0
|
227
|
my ($conf, $key, $value, $line) = @_; |
|
803
|
|
|
|
|
|
|
|
|
804
|
44
|
50
|
33
|
|
|
504
|
unless (defined $value && $value !~ /^$/) { |
|
805
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
806
|
|
|
|
|
|
|
} |
|
807
|
|
|
|
|
|
|
|
|
808
|
44
|
|
|
|
|
242
|
foreach my $net (split(' ', $value)) { |
|
809
|
60
|
|
|
|
|
260
|
$conf->{$key}->add_cidr($net); |
|
810
|
|
|
|
|
|
|
} |
|
811
|
44
|
|
|
|
|
256
|
$conf->{$key.'_configured'} = 1; |
|
812
|
|
|
|
|
|
|
} |
|
813
|
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
sub set_hash_key_value { |
|
815
|
3715
|
|
|
3715
|
0
|
11635
|
my ($conf, $key, $value, $line) = @_; |
|
816
|
3715
|
|
|
|
|
17997
|
my($k,$v) = split(/\s+/, $value, 2); |
|
817
|
|
|
|
|
|
|
|
|
818
|
3715
|
50
|
33
|
|
|
18566
|
unless (defined $v && $v ne '') { |
|
819
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
820
|
|
|
|
|
|
|
} |
|
821
|
|
|
|
|
|
|
|
|
822
|
3715
|
|
|
|
|
17228
|
$conf->{$key}->{$k} = $v; # keep tainted |
|
823
|
|
|
|
|
|
|
} |
|
824
|
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
sub set_addrlist_value { |
|
826
|
0
|
|
|
0
|
0
|
0
|
my ($conf, $key, $value, $line) = @_; |
|
827
|
|
|
|
|
|
|
|
|
828
|
0
|
0
|
0
|
|
|
0
|
unless (defined $value && $value !~ /^$/) { |
|
829
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
830
|
|
|
|
|
|
|
} |
|
831
|
0
|
|
|
|
|
0
|
$conf->{parser}->add_to_addrlist ($key, split (' ', $value)); # keep tainted |
|
832
|
|
|
|
|
|
|
} |
|
833
|
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
sub remove_addrlist_value { |
|
835
|
0
|
|
|
0
|
0
|
0
|
my ($conf, $key, $value, $line) = @_; |
|
836
|
|
|
|
|
|
|
|
|
837
|
0
|
0
|
0
|
|
|
0
|
unless (defined $value && $value !~ /^$/) { |
|
838
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; |
|
839
|
|
|
|
|
|
|
} |
|
840
|
0
|
|
|
|
|
0
|
$conf->{parser}->remove_from_addrlist ($key, split (' ', $value)); |
|
841
|
|
|
|
|
|
|
} |
|
842
|
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
sub set_template_append { |
|
844
|
1037
|
|
|
1037
|
0
|
3081
|
my ($conf, $key, $value, $line) = @_; |
|
845
|
1037
|
100
|
|
|
|
3344
|
if ( $value =~ /^"(.*?)"$/ ) { $value = $1; } |
|
|
61
|
|
|
|
|
284
|
|
|
846
|
1037
|
|
|
|
|
4658
|
$conf->{$key} .= $value."\n"; # keep tainted |
|
847
|
|
|
|
|
|
|
} |
|
848
|
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
sub set_template_clear { |
|
850
|
122
|
|
|
122
|
0
|
633
|
my ($conf, $key, $value, $line) = @_; |
|
851
|
122
|
50
|
33
|
|
|
946
|
unless (!defined $value || $value eq '') { |
|
852
|
0
|
|
|
|
|
0
|
return $Mail::SpamAssassin::Conf::INVALID_VALUE; |
|
853
|
|
|
|
|
|
|
} |
|
854
|
122
|
|
|
|
|
548
|
$conf->{$key} = ''; |
|
855
|
|
|
|
|
|
|
} |
|
856
|
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
########################################################################### |
|
858
|
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
sub finish_parsing { |
|
860
|
90
|
|
|
90
|
0
|
304
|
my ($self, $isuserconf) = @_; |
|
861
|
90
|
|
|
|
|
221
|
my $conf = $self->{conf}; |
|
862
|
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
# note: this function is called once for system-wide configuration |
|
864
|
|
|
|
|
|
|
# with $isuserconf set to 0, then again for user conf with $isuserconf set to 1. |
|
865
|
90
|
50
|
|
|
|
327
|
if (!$isuserconf) { |
|
866
|
90
|
|
|
|
|
797
|
$conf->{main}->call_plugins("finish_parsing_start", { conf => $conf }); |
|
867
|
|
|
|
|
|
|
} else { |
|
868
|
0
|
|
|
|
|
0
|
$conf->{main}->call_plugins("user_conf_parsing_start", { conf => $conf }); |
|
869
|
|
|
|
|
|
|
} |
|
870
|
|
|
|
|
|
|
|
|
871
|
90
|
|
|
|
|
628
|
$self->trace_meta_dependencies(); |
|
872
|
90
|
|
|
|
|
685
|
$self->fix_priorities(); |
|
873
|
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
# don't do this if allow_user_rules is active, since it deletes entries |
|
875
|
|
|
|
|
|
|
# from {tests} |
|
876
|
90
|
50
|
|
|
|
533
|
if (!$conf->{allow_user_rules}) { |
|
877
|
90
|
|
|
|
|
599
|
$self->find_dup_rules(); # must be after fix_priorities() |
|
878
|
|
|
|
|
|
|
} |
|
879
|
|
|
|
|
|
|
|
|
880
|
90
|
|
|
|
|
631
|
dbg("config: finish parsing"); |
|
881
|
|
|
|
|
|
|
|
|
882
|
90
|
|
|
|
|
237
|
while (my ($name, $text) = each %{$conf->{tests}}) { |
|
|
3727
|
|
|
|
|
12642
|
|
|
883
|
3637
|
|
|
|
|
5880
|
my $type = $conf->{test_types}->{$name}; |
|
884
|
3637
|
|
100
|
|
|
10764
|
my $priority = $conf->{priority}->{$name} || 0; |
|
885
|
3637
|
|
|
|
|
6177
|
$conf->{priorities}->{$priority}++; |
|
886
|
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
# eval type handling |
|
888
|
3637
|
100
|
|
|
|
6785
|
if (($type & 1) == 1) { |
|
889
|
2669
|
50
|
|
|
|
15823
|
if (my ($function, $args) = ($text =~ /^(\w+)\((.*?)\)$/)) { |
|
890
|
2669
|
|
|
|
|
5637
|
my $argsref = $self->pack_eval_args($args); |
|
891
|
2669
|
50
|
|
|
|
7909
|
if (!defined $argsref) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
892
|
0
|
|
|
|
|
0
|
$self->lint_warn("syntax error for eval function $name: $text"); |
|
893
|
0
|
|
|
|
|
0
|
next; |
|
894
|
|
|
|
|
|
|
} |
|
895
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS) { |
|
896
|
83
|
|
|
|
|
680
|
$conf->{body_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; |
|
897
|
|
|
|
|
|
|
} |
|
898
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS) { |
|
899
|
2285
|
|
|
|
|
12699
|
$conf->{head_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; |
|
900
|
|
|
|
|
|
|
} |
|
901
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS) { |
|
902
|
|
|
|
|
|
|
# We don't do priorities for $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS |
|
903
|
|
|
|
|
|
|
# we also use the arrayref instead of the packed string |
|
904
|
1
|
|
|
|
|
16
|
$conf->{rbl_evals}->{$name} = [ $function, [@$argsref] ]; |
|
905
|
|
|
|
|
|
|
} |
|
906
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_EVALS) { |
|
907
|
0
|
|
|
|
|
0
|
$conf->{rawbody_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; |
|
908
|
|
|
|
|
|
|
} |
|
909
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_FULL_EVALS) { |
|
910
|
300
|
|
|
|
|
1715
|
$conf->{full_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; |
|
911
|
|
|
|
|
|
|
} |
|
912
|
|
|
|
|
|
|
#elsif ($type == $Mail::SpamAssassin::Conf::TYPE_URI_EVALS) { |
|
913
|
|
|
|
|
|
|
# $conf->{uri_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; |
|
914
|
|
|
|
|
|
|
#} |
|
915
|
|
|
|
|
|
|
else { |
|
916
|
0
|
|
|
|
|
0
|
$self->lint_warn("unknown type $type for $name: $text", $name); |
|
917
|
0
|
|
|
|
|
0
|
next; |
|
918
|
|
|
|
|
|
|
} |
|
919
|
|
|
|
|
|
|
} |
|
920
|
|
|
|
|
|
|
else { |
|
921
|
0
|
|
|
|
|
0
|
$self->lint_warn("syntax error for eval function $name: $text", $name); |
|
922
|
0
|
|
|
|
|
0
|
next; |
|
923
|
|
|
|
|
|
|
} |
|
924
|
|
|
|
|
|
|
} |
|
925
|
|
|
|
|
|
|
# non-eval tests |
|
926
|
|
|
|
|
|
|
else { |
|
927
|
968
|
100
|
|
|
|
2630
|
if ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
928
|
228
|
|
|
|
|
1050
|
$conf->{body_tests}->{$priority}->{$name} = $text; |
|
929
|
|
|
|
|
|
|
} |
|
930
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS) { |
|
931
|
549
|
|
|
|
|
1988
|
$conf->{head_tests}->{$priority}->{$name} = $text; |
|
932
|
|
|
|
|
|
|
} |
|
933
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) { |
|
934
|
130
|
|
|
|
|
539
|
$conf->{meta_tests}->{$priority}->{$name} = $text; |
|
935
|
|
|
|
|
|
|
} |
|
936
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_URI_TESTS) { |
|
937
|
61
|
|
|
|
|
386
|
$conf->{uri_tests}->{$priority}->{$name} = $text; |
|
938
|
|
|
|
|
|
|
} |
|
939
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS) { |
|
940
|
0
|
|
|
|
|
0
|
$conf->{rawbody_tests}->{$priority}->{$name} = $text; |
|
941
|
|
|
|
|
|
|
} |
|
942
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS) { |
|
943
|
0
|
|
|
|
|
0
|
$conf->{full_tests}->{$priority}->{$name} = $text; |
|
944
|
|
|
|
|
|
|
} |
|
945
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_EMPTY_TESTS) { |
|
946
|
|
|
|
|
|
|
} |
|
947
|
|
|
|
|
|
|
else { |
|
948
|
0
|
|
|
|
|
0
|
$self->lint_warn("unknown type $type for $name: $text", $name); |
|
949
|
0
|
|
|
|
|
0
|
next; |
|
950
|
|
|
|
|
|
|
} |
|
951
|
|
|
|
|
|
|
} |
|
952
|
|
|
|
|
|
|
} |
|
953
|
|
|
|
|
|
|
|
|
954
|
90
|
|
|
|
|
531
|
$self->lint_trusted_networks(); |
|
955
|
|
|
|
|
|
|
|
|
956
|
90
|
50
|
|
|
|
364
|
if (!$isuserconf) { |
|
957
|
90
|
|
|
|
|
556
|
$conf->{main}->call_plugins("finish_parsing_end", { conf => $conf }); |
|
958
|
|
|
|
|
|
|
} else { |
|
959
|
0
|
|
|
|
|
0
|
$conf->{main}->call_plugins("user_conf_parsing_end", { conf => $conf }); |
|
960
|
|
|
|
|
|
|
} |
|
961
|
|
|
|
|
|
|
|
|
962
|
90
|
|
|
|
|
1714
|
$conf->found_any_rules(); # before we might delete {tests} |
|
963
|
|
|
|
|
|
|
|
|
964
|
90
|
50
|
|
|
|
341
|
if (!$conf->{allow_user_rules}) { |
|
965
|
|
|
|
|
|
|
# free up stuff we no longer need |
|
966
|
90
|
|
|
|
|
1721
|
delete $conf->{tests}; |
|
967
|
90
|
|
|
|
|
991
|
delete $conf->{priority}; |
|
968
|
|
|
|
|
|
|
#test_types are needed - see bug 5503 |
|
969
|
|
|
|
|
|
|
#delete $conf->{test_types}; |
|
970
|
|
|
|
|
|
|
} |
|
971
|
|
|
|
|
|
|
} |
|
972
|
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
sub trace_meta_dependencies { |
|
974
|
90
|
|
|
90
|
0
|
313
|
my ($self) = @_; |
|
975
|
90
|
|
|
|
|
228
|
my $conf = $self->{conf}; |
|
976
|
90
|
|
|
|
|
347
|
$conf->{meta_dependencies} = { }; |
|
977
|
|
|
|
|
|
|
|
|
978
|
90
|
|
|
|
|
235
|
foreach my $name (keys %{$conf->{tests}}) { |
|
|
90
|
|
|
|
|
1130
|
|
|
979
|
3638
|
100
|
|
|
|
7949
|
next unless ($conf->{test_types}->{$name} |
|
980
|
|
|
|
|
|
|
== $Mail::SpamAssassin::Conf::TYPE_META_TESTS); |
|
981
|
130
|
|
|
|
|
306
|
my $alreadydone = {}; |
|
982
|
130
|
|
|
|
|
454
|
$self->_meta_deps_recurse($conf, $name, $name, $alreadydone); |
|
983
|
|
|
|
|
|
|
} |
|
984
|
|
|
|
|
|
|
} |
|
985
|
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
sub _meta_deps_recurse { |
|
987
|
440
|
|
|
440
|
|
1013
|
my ($self, $conf, $toprule, $name, $alreadydone) = @_; |
|
988
|
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
# Avoid recomputing the dependencies of a rule |
|
990
|
440
|
100
|
|
|
|
1104
|
return split(' ', $conf->{meta_dependencies}->{$name}) if defined $conf->{meta_dependencies}->{$name}; |
|
991
|
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
# Obviously, don't trace empty or nonexistent rules |
|
993
|
436
|
|
|
|
|
1182
|
my $rule = $conf->{tests}->{$name}; |
|
994
|
436
|
50
|
|
|
|
1172
|
unless ($rule) { |
|
995
|
0
|
|
|
|
|
0
|
$conf->{meta_dependencies}->{$name} = ''; |
|
996
|
0
|
|
|
|
|
0
|
return ( ); |
|
997
|
|
|
|
|
|
|
} |
|
998
|
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
# Avoid infinite recursion |
|
1000
|
436
|
50
|
|
|
|
1092
|
return ( ) if exists $alreadydone->{$name}; |
|
1001
|
436
|
|
|
|
|
808
|
$alreadydone->{$name} = ( ); |
|
1002
|
|
|
|
|
|
|
|
|
1003
|
436
|
|
|
|
|
599
|
my %deps; |
|
1004
|
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
# Lex the rule into tokens using a rather simple RE method ... |
|
1006
|
436
|
|
|
|
|
11067
|
my @tokens = ($rule =~ /($ARITH_EXPRESSION_LEXER)/og); |
|
1007
|
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
# Go through each token in the meta rule |
|
1009
|
436
|
|
|
|
|
1296
|
my $conf_tests = $conf->{tests}; |
|
1010
|
436
|
|
|
|
|
983
|
foreach my $token (@tokens) { |
|
1011
|
|
|
|
|
|
|
# has to be an alpha+numeric token |
|
1012
|
3799
|
100
|
100
|
|
|
13789
|
next if $token =~ tr{A-Za-z0-9_}{}c || substr($token,0,1) =~ tr{A-Za-z_}{}c; # even faster |
|
1013
|
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
# and has to be a rule name |
|
1015
|
1611
|
100
|
|
|
|
4667
|
next unless exists $conf_tests->{$token}; |
|
1016
|
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
# add and recurse |
|
1018
|
310
|
|
|
|
|
913
|
$deps{untaint_var($token)} = ( ); |
|
1019
|
310
|
|
|
|
|
962
|
my @subdeps = $self->_meta_deps_recurse($conf, $toprule, $token, $alreadydone); |
|
1020
|
310
|
|
|
|
|
818
|
@deps{@subdeps} = ( ); |
|
1021
|
|
|
|
|
|
|
} |
|
1022
|
436
|
|
|
|
|
1518
|
$conf->{meta_dependencies}->{$name} = join (' ', keys %deps); |
|
1023
|
436
|
|
|
|
|
2039
|
return keys %deps; |
|
1024
|
|
|
|
|
|
|
} |
|
1025
|
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
sub fix_priorities { |
|
1027
|
90
|
|
|
90
|
0
|
261
|
my ($self) = @_; |
|
1028
|
90
|
|
|
|
|
248
|
my $conf = $self->{conf}; |
|
1029
|
|
|
|
|
|
|
|
|
1030
|
90
|
50
|
|
|
|
322
|
die unless $conf->{meta_dependencies}; # order requirement |
|
1031
|
90
|
|
|
|
|
231
|
my $pri = $conf->{priority}; |
|
1032
|
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
# sort into priority order, lowest first -- this way we ensure that if we |
|
1034
|
|
|
|
|
|
|
# rearrange the pri of a rule early on, we cannot accidentally increase its |
|
1035
|
|
|
|
|
|
|
# priority later. |
|
1036
|
90
|
|
|
|
|
198
|
foreach my $rule (sort { |
|
1037
|
8613
|
|
|
|
|
14559
|
$pri->{$a} <=> $pri->{$b} |
|
1038
|
90
|
|
|
|
|
1587
|
} keys %{$pri}) |
|
1039
|
|
|
|
|
|
|
{ |
|
1040
|
|
|
|
|
|
|
# we only need to worry about meta rules -- they are the |
|
1041
|
|
|
|
|
|
|
# only type of rules which depend on other rules |
|
1042
|
3641
|
|
|
|
|
5163
|
my $deps = $conf->{meta_dependencies}->{$rule}; |
|
1043
|
3641
|
100
|
|
|
|
6676
|
next unless (defined $deps); |
|
1044
|
|
|
|
|
|
|
|
|
1045
|
436
|
|
|
|
|
664
|
my $basepri = $pri->{$rule}; |
|
1046
|
436
|
|
|
|
|
1050
|
foreach my $dep (split ' ', $deps) { |
|
1047
|
312
|
|
|
|
|
523
|
my $deppri = $pri->{$dep}; |
|
1048
|
312
|
100
|
|
|
|
807
|
if ($deppri > $basepri) { |
|
1049
|
7
|
|
|
|
|
36
|
dbg("rules: $rule (pri $basepri) requires $dep (pri $deppri): fixed"); |
|
1050
|
7
|
|
|
|
|
25
|
$pri->{$dep} = $basepri; |
|
1051
|
|
|
|
|
|
|
} |
|
1052
|
|
|
|
|
|
|
} |
|
1053
|
|
|
|
|
|
|
} |
|
1054
|
|
|
|
|
|
|
} |
|
1055
|
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
sub find_dup_rules { |
|
1057
|
90
|
|
|
90
|
0
|
312
|
my ($self) = @_; |
|
1058
|
90
|
|
|
|
|
254
|
my $conf = $self->{conf}; |
|
1059
|
|
|
|
|
|
|
|
|
1060
|
90
|
|
|
|
|
211
|
my %names_for_text; |
|
1061
|
|
|
|
|
|
|
my %dups; |
|
1062
|
90
|
|
|
|
|
220
|
while (my ($name, $text) = each %{$conf->{tests}}) { |
|
|
3728
|
|
|
|
|
12436
|
|
|
1063
|
3638
|
|
|
|
|
5198
|
my $type = $conf->{test_types}->{$name}; |
|
1064
|
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
# skip eval and empty tests |
|
1066
|
3638
|
100
|
66
|
|
|
9964
|
next if ($type & 1) || |
|
1067
|
|
|
|
|
|
|
($type eq $Mail::SpamAssassin::Conf::TYPE_EMPTY_TESTS); |
|
1068
|
|
|
|
|
|
|
|
|
1069
|
969
|
|
100
|
|
|
2912
|
my $tf = ($conf->{tflags}->{$name}||''); $tf =~ s/\s+/ /gs; |
|
|
969
|
|
|
|
|
1846
|
|
|
1070
|
|
|
|
|
|
|
# ensure similar, but differently-typed, rules are not marked as dups; |
|
1071
|
|
|
|
|
|
|
# take tflags into account too due to "tflags multiple" |
|
1072
|
969
|
|
|
|
|
2725
|
$text = "$type\t$text\t$tf"; |
|
1073
|
|
|
|
|
|
|
|
|
1074
|
969
|
100
|
|
|
|
2244
|
if (defined $names_for_text{$text}) { |
|
1075
|
1
|
|
|
|
|
5
|
$names_for_text{$text} .= " ".$name; |
|
1076
|
1
|
|
|
|
|
5
|
$dups{$text} = undef; # found (at least) one |
|
1077
|
|
|
|
|
|
|
} else { |
|
1078
|
968
|
|
|
|
|
4022
|
$names_for_text{$text} = $name; |
|
1079
|
|
|
|
|
|
|
} |
|
1080
|
|
|
|
|
|
|
} |
|
1081
|
|
|
|
|
|
|
|
|
1082
|
90
|
|
|
|
|
832
|
foreach my $text (keys %dups) { |
|
1083
|
1
|
|
|
|
|
3
|
my $first; |
|
1084
|
|
|
|
|
|
|
my $first_pri; |
|
1085
|
1
|
|
|
|
|
7
|
my @names = sort {$a cmp $b} split(' ', $names_for_text{$text}); |
|
|
1
|
|
|
|
|
7
|
|
|
1086
|
1
|
|
|
|
|
4
|
foreach my $name (@names) { |
|
1087
|
2
|
|
50
|
|
|
23
|
my $priority = $conf->{priority}->{$name} || 0; |
|
1088
|
|
|
|
|
|
|
|
|
1089
|
2
|
100
|
66
|
|
|
16
|
if (!defined $first || $priority < $first_pri) { |
|
1090
|
1
|
|
|
|
|
9
|
$first_pri = $priority; |
|
1091
|
1
|
|
|
|
|
6
|
$first = $name; |
|
1092
|
|
|
|
|
|
|
} |
|
1093
|
|
|
|
|
|
|
} |
|
1094
|
|
|
|
|
|
|
# $first is now the earliest-occurring rule. mark others as dups |
|
1095
|
|
|
|
|
|
|
|
|
1096
|
1
|
|
|
|
|
2
|
my @dups; |
|
1097
|
1
|
|
|
|
|
5
|
foreach my $name (@names) { |
|
1098
|
2
|
100
|
|
|
|
7
|
next if $name eq $first; |
|
1099
|
1
|
|
|
|
|
3
|
push @dups, $name; |
|
1100
|
1
|
|
|
|
|
5
|
delete $conf->{tests}->{$name}; |
|
1101
|
|
|
|
|
|
|
} |
|
1102
|
|
|
|
|
|
|
|
|
1103
|
1
|
|
|
|
|
11
|
dbg("rules: $first merged duplicates: ".join(' ', @dups)); |
|
1104
|
1
|
|
|
|
|
10
|
$conf->{duplicate_rules}->{$first} = \@dups; |
|
1105
|
|
|
|
|
|
|
} |
|
1106
|
|
|
|
|
|
|
} |
|
1107
|
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
# Deprecated function |
|
1109
|
|
|
|
|
|
|
sub pack_eval_method { |
|
1110
|
0
|
|
|
0
|
0
|
0
|
warn "deprecated function pack_eval_method() used\n"; |
|
1111
|
0
|
|
|
|
|
0
|
return ('',undef); |
|
1112
|
|
|
|
|
|
|
} |
|
1113
|
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
sub pack_eval_args { |
|
1115
|
2669
|
|
|
2669
|
0
|
6552
|
my ($self, $args) = @_; |
|
1116
|
|
|
|
|
|
|
|
|
1117
|
2669
|
50
|
|
|
|
6619
|
return [] if $args =~ /^\s+$/; |
|
1118
|
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
# bug 4419: Parse quoted strings, unquoted alphanumerics/floats, |
|
1120
|
|
|
|
|
|
|
# unquoted IPv4 and IPv6 addresses, and unquoted common domain names. |
|
1121
|
|
|
|
|
|
|
# s// is used so that we can determine whether or not we successfully |
|
1122
|
|
|
|
|
|
|
# parsed ALL arguments. |
|
1123
|
2669
|
|
|
|
|
3622
|
my @args; |
|
1124
|
2669
|
|
|
|
|
8054
|
local($1,$2,$3); |
|
1125
|
2669
|
|
|
|
|
8571
|
while ($args =~ s/^\s* (?: (['"]) (.*?) \1 | ( [\d\.:A-Za-z-]+? ) ) |
|
1126
|
|
|
|
|
|
|
\s* (?: , \s* | $ )//x) { |
|
1127
|
|
|
|
|
|
|
# DO NOT UNTAINT THESE ARGS |
|
1128
|
|
|
|
|
|
|
# The eval function that handles these should do that as necessary, |
|
1129
|
|
|
|
|
|
|
# we have no idea what acceptable arguments look like here. |
|
1130
|
1529
|
50
|
|
|
|
8336
|
push @args, defined $2 ? $2 : $3; |
|
1131
|
|
|
|
|
|
|
} |
|
1132
|
|
|
|
|
|
|
|
|
1133
|
2669
|
50
|
|
|
|
5897
|
if ($args ne '') { |
|
1134
|
0
|
|
|
|
|
0
|
return undef; ## no critic (ProhibitExplicitReturnUndef) |
|
1135
|
|
|
|
|
|
|
} |
|
1136
|
|
|
|
|
|
|
|
|
1137
|
2669
|
|
|
|
|
8247
|
return \@args; |
|
1138
|
|
|
|
|
|
|
} |
|
1139
|
|
|
|
|
|
|
|
|
1140
|
|
|
|
|
|
|
########################################################################### |
|
1141
|
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
sub lint_trusted_networks { |
|
1143
|
90
|
|
|
90
|
0
|
274
|
my ($self) = @_; |
|
1144
|
90
|
|
|
|
|
255
|
my $conf = $self->{conf}; |
|
1145
|
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
# validate trusted_networks and internal_networks, bug 4760. |
|
1147
|
|
|
|
|
|
|
# check that all internal_networks are listed in trusted_networks |
|
1148
|
|
|
|
|
|
|
# too. do the same for msa_networks, but check msa_networks against |
|
1149
|
|
|
|
|
|
|
# internal_networks if trusted_networks aren't defined |
|
1150
|
|
|
|
|
|
|
|
|
1151
|
90
|
|
|
|
|
225
|
my ($nt, $matching_against); |
|
1152
|
90
|
100
|
|
|
|
478
|
if ($conf->{trusted_networks_configured}) { |
|
|
|
100
|
|
|
|
|
|
|
1153
|
28
|
|
|
|
|
78
|
$nt = $conf->{trusted_networks}; |
|
1154
|
28
|
|
|
|
|
98
|
$matching_against = 'trusted_networks'; |
|
1155
|
|
|
|
|
|
|
} elsif ($conf->{internal_networks_configured}) { |
|
1156
|
1
|
|
|
|
|
4
|
$nt = $conf->{internal_networks}; |
|
1157
|
1
|
|
|
|
|
4
|
$matching_against = 'internal_networks'; |
|
1158
|
|
|
|
|
|
|
} else { |
|
1159
|
61
|
|
|
|
|
157
|
return; |
|
1160
|
|
|
|
|
|
|
} |
|
1161
|
|
|
|
|
|
|
|
|
1162
|
29
|
|
|
|
|
92
|
foreach my $net_type ('internal_networks', 'msa_networks') { |
|
1163
|
58
|
100
|
|
|
|
241
|
next unless $conf->{"${net_type}_configured"}; |
|
1164
|
16
|
100
|
|
|
|
62
|
next if $net_type eq $matching_against; |
|
1165
|
|
|
|
|
|
|
|
|
1166
|
15
|
|
|
|
|
37
|
my $replace_nets; |
|
1167
|
|
|
|
|
|
|
my @valid_net_list; |
|
1168
|
15
|
|
|
|
|
34
|
my $net_list = $conf->{$net_type}; |
|
1169
|
|
|
|
|
|
|
|
|
1170
|
15
|
|
|
|
|
45
|
foreach my $net (@{$net_list->{nets}}) { |
|
|
15
|
|
|
|
|
57
|
|
|
1171
|
|
|
|
|
|
|
# don't check to see if an excluded network is included - that's senseless |
|
1172
|
38
|
100
|
100
|
|
|
225
|
if (!$net->{exclude} && !$nt->contains_net($net)) { |
|
1173
|
|
|
|
|
|
|
my $msg = "$matching_against doesn't contain $net_type entry '". |
|
1174
|
4
|
|
|
|
|
28
|
($net->{as_string})."'"; |
|
1175
|
|
|
|
|
|
|
|
|
1176
|
4
|
|
|
|
|
26
|
$self->lint_warn($msg, undef); # complain |
|
1177
|
4
|
|
|
|
|
14
|
$replace_nets = 1; # and omit it from the new internal set |
|
1178
|
|
|
|
|
|
|
} |
|
1179
|
|
|
|
|
|
|
else { |
|
1180
|
34
|
|
|
|
|
89
|
push @valid_net_list, $net; |
|
1181
|
|
|
|
|
|
|
} |
|
1182
|
|
|
|
|
|
|
} |
|
1183
|
|
|
|
|
|
|
|
|
1184
|
15
|
100
|
|
|
|
62
|
if ($replace_nets) { |
|
1185
|
|
|
|
|
|
|
# something was invalid. replace the old nets list with a fixed version |
|
1186
|
|
|
|
|
|
|
# (which may be empty) |
|
1187
|
4
|
|
|
|
|
34
|
$net_list->{nets} = \@valid_net_list; |
|
1188
|
|
|
|
|
|
|
} |
|
1189
|
|
|
|
|
|
|
} |
|
1190
|
|
|
|
|
|
|
} |
|
1191
|
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
########################################################################### |
|
1193
|
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
sub add_test { |
|
1195
|
3638
|
|
|
3638
|
0
|
12420
|
my ($self, $name, $text, $type) = @_; |
|
1196
|
3638
|
|
|
|
|
7340
|
my $conf = $self->{conf}; |
|
1197
|
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
# Don't allow invalid names ... |
|
1199
|
3638
|
50
|
|
|
|
12812
|
if ($name !~ IS_RULENAME) { |
|
1200
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: error: rule '$name' has invalid characters ". |
|
1201
|
|
|
|
|
|
|
"(not Alphanumeric + Underscore + starting with a non-digit)\n", $name); |
|
1202
|
0
|
|
|
|
|
0
|
return; |
|
1203
|
|
|
|
|
|
|
} |
|
1204
|
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
# Also set a hard limit for ALL rules (rule names longer than 40 |
|
1206
|
|
|
|
|
|
|
# characters throw warnings). Check this separately from the above |
|
1207
|
|
|
|
|
|
|
# pattern to avoid vague error messages. |
|
1208
|
3638
|
50
|
|
|
|
10456
|
if (length $name > 100) { |
|
1209
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: error: rule '$name' is too long ". |
|
1210
|
|
|
|
|
|
|
"(recommended maximum length is 22 characters)\n", $name); |
|
1211
|
0
|
|
|
|
|
0
|
return; |
|
1212
|
|
|
|
|
|
|
} |
|
1213
|
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
# Warn about, but use, long rule names during --lint |
|
1215
|
3638
|
100
|
|
|
|
8520
|
if ($conf->{lint_rules}) { |
|
1216
|
1888
|
0
|
33
|
|
|
5369
|
if (length($name) > 40 && $name !~ /^__/ && $name !~ /^T_/) { |
|
|
|
|
33
|
|
|
|
|
|
1217
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: warning: rule name '$name' is over 40 chars ". |
|
1218
|
|
|
|
|
|
|
"(recommended maximum length is 22 characters)\n", $name); |
|
1219
|
|
|
|
|
|
|
} |
|
1220
|
|
|
|
|
|
|
} |
|
1221
|
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
# parameter to compile_regexp() |
|
1223
|
|
|
|
|
|
|
my $ignore_amre = |
|
1224
|
|
|
|
|
|
|
$self->{conf}->{lint_rules} || |
|
1225
|
3638
|
|
66
|
|
|
10235
|
$self->{conf}->{ignore_always_matching_regexps}; |
|
1226
|
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
# all of these rule types are regexps |
|
1228
|
3638
|
100
|
66
|
|
|
23474
|
if ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS || |
|
|
|
100
|
66
|
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
$type == $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS || |
|
1230
|
|
|
|
|
|
|
$type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS || |
|
1231
|
|
|
|
|
|
|
$type == $Mail::SpamAssassin::Conf::TYPE_URI_TESTS) |
|
1232
|
|
|
|
|
|
|
{ |
|
1233
|
290
|
|
|
|
|
1184
|
my ($rec, $err) = compile_regexp($text, 1, $ignore_amre); |
|
1234
|
290
|
50
|
|
|
|
895
|
if (!$rec) { |
|
1235
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: invalid regexp for $name '$text': $err", $name); |
|
1236
|
0
|
|
|
|
|
0
|
return; |
|
1237
|
|
|
|
|
|
|
} |
|
1238
|
290
|
|
|
|
|
1189
|
$conf->{test_qrs}->{$name} = $rec; |
|
1239
|
|
|
|
|
|
|
} |
|
1240
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS) |
|
1241
|
|
|
|
|
|
|
{ |
|
1242
|
549
|
|
|
|
|
1844
|
local($1,$2,$3); |
|
1243
|
|
|
|
|
|
|
# RFC 5322 section 3.6.8, ftext printable US-ASCII chars not including ":" |
|
1244
|
|
|
|
|
|
|
# no re "strict"; # since perl 5.21.8: Ranges of ASCII printables... |
|
1245
|
549
|
100
|
|
|
|
1772
|
if ($text =~ /^exists:(.*)/) { |
|
1246
|
61
|
|
|
|
|
304
|
my $hdr = $1; |
|
1247
|
|
|
|
|
|
|
# never evaled, so can be quite generous with the name |
|
1248
|
|
|
|
|
|
|
# check :addr etc header options |
|
1249
|
61
|
50
|
|
|
|
544
|
if ($hdr !~ /^[^:\s]+:?$/) { |
|
1250
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: invalid head test $name header: $hdr"); |
|
1251
|
0
|
|
|
|
|
0
|
return; |
|
1252
|
|
|
|
|
|
|
} |
|
1253
|
61
|
|
|
|
|
258
|
$hdr =~ s/:$//; |
|
1254
|
61
|
|
|
|
|
289
|
$conf->{test_opt_header}->{$name} = $hdr; |
|
1255
|
61
|
|
|
|
|
446
|
$conf->{test_opt_exists}->{$name} = 1; |
|
1256
|
|
|
|
|
|
|
} else { |
|
1257
|
488
|
50
|
|
|
|
3326
|
if ($text !~ /^([^:\s]+(?:\:|(?:\:[a-z]+){1,2})?)\s*([=!]~)\s*(.+)$/) { |
|
1258
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: invalid head test $name: $text"); |
|
1259
|
0
|
|
|
|
|
0
|
return; |
|
1260
|
|
|
|
|
|
|
} |
|
1261
|
488
|
|
|
|
|
2521
|
my ($hdr, $op, $pat) = ($1, $2, $3); |
|
1262
|
488
|
|
|
|
|
1062
|
$hdr =~ s/:$//; |
|
1263
|
488
|
100
|
|
|
|
2050
|
if ($pat =~ s/\s+\[if-unset:\s+(.+)\]$//) { |
|
1264
|
122
|
|
|
|
|
760
|
$conf->{test_opt_unset}->{$name} = $1; |
|
1265
|
|
|
|
|
|
|
} |
|
1266
|
488
|
|
|
|
|
1944
|
my ($rec, $err) = compile_regexp($pat, 1, $ignore_amre); |
|
1267
|
488
|
50
|
|
|
|
1326
|
if (!$rec) { |
|
1268
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: invalid regexp for $name '$pat': $err", $name); |
|
1269
|
0
|
|
|
|
|
0
|
return; |
|
1270
|
|
|
|
|
|
|
} |
|
1271
|
488
|
|
|
|
|
1635
|
$conf->{test_qrs}->{$name} = $rec; |
|
1272
|
488
|
|
|
|
|
1713
|
$conf->{test_opt_header}->{$name} = $hdr; |
|
1273
|
488
|
100
|
|
|
|
3201
|
$conf->{test_opt_neg}->{$name} = 1 if $op eq '!~'; |
|
1274
|
|
|
|
|
|
|
} |
|
1275
|
|
|
|
|
|
|
} |
|
1276
|
|
|
|
|
|
|
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) |
|
1277
|
|
|
|
|
|
|
{ |
|
1278
|
130
|
50
|
|
|
|
677
|
if ($self->is_meta_valid($name, $text)) { |
|
1279
|
|
|
|
|
|
|
# Untaint now once and not repeatedly later |
|
1280
|
130
|
|
|
|
|
436
|
$text = untaint_var($text); |
|
1281
|
|
|
|
|
|
|
} else { |
|
1282
|
0
|
|
|
|
|
0
|
return; |
|
1283
|
|
|
|
|
|
|
} |
|
1284
|
|
|
|
|
|
|
} |
|
1285
|
|
|
|
|
|
|
|
|
1286
|
3638
|
|
|
|
|
13420
|
$conf->{tests}->{$name} = $text; |
|
1287
|
3638
|
|
|
|
|
8348
|
$conf->{test_types}->{$name} = $type; |
|
1288
|
|
|
|
|
|
|
|
|
1289
|
3638
|
50
|
|
|
|
8740
|
if ($name =~ /AUTOLEARNTEST/i) { |
|
1290
|
0
|
|
|
|
|
0
|
dbg("config: auto-learn: $name has type $type = $conf->{test_types}->{$name} during add_test\n"); |
|
1291
|
|
|
|
|
|
|
} |
|
1292
|
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
|
|
1294
|
3638
|
100
|
|
|
|
6671
|
if ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) { |
|
1295
|
130
|
|
50
|
|
|
971
|
$conf->{priority}->{$name} ||= 500; |
|
1296
|
|
|
|
|
|
|
} |
|
1297
|
|
|
|
|
|
|
else { |
|
1298
|
3508
|
|
50
|
|
|
15920
|
$conf->{priority}->{$name} ||= 0; |
|
1299
|
|
|
|
|
|
|
} |
|
1300
|
3638
|
|
100
|
|
|
16384
|
$conf->{priority}->{$name} ||= 0; |
|
1301
|
3638
|
|
|
|
|
18598
|
$conf->{source_file}->{$name} = $self->{currentfile}; |
|
1302
|
|
|
|
|
|
|
|
|
1303
|
3638
|
50
|
|
|
|
8996
|
if ($conf->{main}->{keep_config_parsing_metadata}) { |
|
1304
|
0
|
|
|
|
|
0
|
$conf->{if_stack}->{$name} = $self->get_if_stack_as_string(); |
|
1305
|
|
|
|
|
|
|
|
|
1306
|
0
|
0
|
|
|
|
0
|
if ($self->{file_scoped_attrs}->{testrules}) { |
|
1307
|
0
|
|
|
|
|
0
|
$conf->{testrules}->{$name} = 1; # used in build/mkupdates/listpromotable |
|
1308
|
|
|
|
|
|
|
} |
|
1309
|
|
|
|
|
|
|
} |
|
1310
|
|
|
|
|
|
|
|
|
1311
|
|
|
|
|
|
|
# if we found this rule in a user_prefs file, it's a user rule -- note that |
|
1312
|
|
|
|
|
|
|
# we may need to recompile the rule code for this type (if they've already |
|
1313
|
|
|
|
|
|
|
# been compiled, e.g. in spamd). |
|
1314
|
|
|
|
|
|
|
# |
|
1315
|
|
|
|
|
|
|
# Note: the want_rebuild_for_type 'flag' is actually a counter; it is decremented |
|
1316
|
|
|
|
|
|
|
# after each scan. This ensures that we always recompile at least once more; |
|
1317
|
|
|
|
|
|
|
# once to *define* the rule, and once afterwards to *undefine* the rule in the |
|
1318
|
|
|
|
|
|
|
# compiled ruleset again. |
|
1319
|
|
|
|
|
|
|
# |
|
1320
|
|
|
|
|
|
|
# If two consecutive scans use user rules, that's ok -- the second one will |
|
1321
|
|
|
|
|
|
|
# reset the counter, and we'll still recompile just once afterwards to undefine |
|
1322
|
|
|
|
|
|
|
# the rule again. |
|
1323
|
|
|
|
|
|
|
# |
|
1324
|
3638
|
50
|
|
|
|
22061
|
if ($self->{scoresonly}) { |
|
1325
|
0
|
|
|
|
|
0
|
$conf->{want_rebuild_for_type}->{$type} = 2; |
|
1326
|
0
|
|
|
|
|
0
|
$conf->{user_defined_rules}->{$name} = 1; |
|
1327
|
|
|
|
|
|
|
} |
|
1328
|
|
|
|
|
|
|
} |
|
1329
|
|
|
|
|
|
|
|
|
1330
|
|
|
|
|
|
|
sub add_regression_test { |
|
1331
|
0
|
|
|
0
|
0
|
0
|
my ($self, $name, $ok_or_fail, $string) = @_; |
|
1332
|
0
|
|
|
|
|
0
|
my $conf = $self->{conf}; |
|
1333
|
|
|
|
|
|
|
|
|
1334
|
0
|
0
|
|
|
|
0
|
if ($conf->{regression_tests}->{$name}) { |
|
1335
|
0
|
|
|
|
|
0
|
push @{$conf->{regression_tests}->{$name}}, [$ok_or_fail, $string]; |
|
|
0
|
|
|
|
|
0
|
|
|
1336
|
|
|
|
|
|
|
} |
|
1337
|
|
|
|
|
|
|
else { |
|
1338
|
|
|
|
|
|
|
# initialize the array, and create one element |
|
1339
|
0
|
|
|
|
|
0
|
$conf->{regression_tests}->{$name} = [ [$ok_or_fail, $string] ]; |
|
1340
|
|
|
|
|
|
|
} |
|
1341
|
|
|
|
|
|
|
} |
|
1342
|
|
|
|
|
|
|
|
|
1343
|
|
|
|
|
|
|
sub is_meta_valid { |
|
1344
|
130
|
|
|
130
|
0
|
613
|
my ($self, $name, $rule) = @_; |
|
1345
|
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
# $meta is a degenerate translation of the rule, replacing all variables (i.e. rule names) with 0. |
|
1347
|
130
|
|
|
|
|
335
|
my $meta = ''; |
|
1348
|
|
|
|
|
|
|
|
|
1349
|
|
|
|
|
|
|
# Paranoid check (Bug #7557) |
|
1350
|
130
|
50
|
|
|
|
630
|
if ($rule =~ /(?:\:\:|->)/) { |
|
1351
|
0
|
|
|
|
|
0
|
warn("config: invalid meta $name rule: $rule") ; |
|
1352
|
0
|
|
|
|
|
0
|
return 0; |
|
1353
|
|
|
|
|
|
|
} |
|
1354
|
|
|
|
|
|
|
|
|
1355
|
|
|
|
|
|
|
# Process expandable functions before lexing |
|
1356
|
130
|
|
|
|
|
876
|
$rule =~ s/${META_RULES_MATCHING_RE}/ 0 /g; |
|
1357
|
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
# Lex the rule into tokens using a rather simple RE method ... |
|
1359
|
130
|
|
|
|
|
5307
|
my @tokens = ($rule =~ /($ARITH_EXPRESSION_LEXER)/og); |
|
1360
|
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
# Go through each token in the meta rule |
|
1362
|
130
|
|
|
|
|
689
|
foreach my $token (@tokens) { |
|
1363
|
|
|
|
|
|
|
# If the token is a syntactically legal rule name, make it zero |
|
1364
|
805
|
100
|
|
|
|
3030
|
if ($token =~ IS_RULENAME) { |
|
|
|
50
|
|
|
|
|
|
|
1365
|
329
|
|
|
|
|
826
|
$meta .= "0 "; |
|
1366
|
|
|
|
|
|
|
} |
|
1367
|
|
|
|
|
|
|
# if it is a (decimal) number or a string of 1 or 2 punctuation |
|
1368
|
|
|
|
|
|
|
# characters (i.e. operators) tack it onto the degenerate rule |
|
1369
|
|
|
|
|
|
|
elsif ($token =~ /^(\d+(?:\.\d+)?|[[:punct:]]{1,2})\z/s) { |
|
1370
|
476
|
|
|
|
|
1414
|
$meta .= "$token "; |
|
1371
|
|
|
|
|
|
|
} |
|
1372
|
|
|
|
|
|
|
# Skip anything unknown (Bug #7557) |
|
1373
|
|
|
|
|
|
|
else { |
|
1374
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: invalid meta $name token: $token", $name); |
|
1375
|
0
|
|
|
|
|
0
|
return 0; |
|
1376
|
|
|
|
|
|
|
} |
|
1377
|
|
|
|
|
|
|
} |
|
1378
|
|
|
|
|
|
|
|
|
1379
|
130
|
|
|
|
|
559
|
$meta = untaint_var($meta); # was carefully checked |
|
1380
|
130
|
|
|
|
|
519
|
my $evalstr = 'my $x = '.$meta.'; 1;'; |
|
1381
|
130
|
50
|
|
|
|
8388
|
if (eval $evalstr) { |
|
1382
|
130
|
|
|
|
|
949
|
return 1; |
|
1383
|
|
|
|
|
|
|
} |
|
1384
|
0
|
0
|
|
|
|
0
|
my $err = $@ ne '' ? $@ : "errno=$!"; chomp $err; |
|
|
0
|
|
|
|
|
0
|
|
|
1385
|
0
|
|
|
|
|
0
|
$err =~ s/\s+(?:at|near)\b.*//s; |
|
1386
|
0
|
|
|
|
|
0
|
$err =~ s/Illegal division by zero/division by zero possible/i; |
|
1387
|
0
|
|
|
|
|
0
|
$self->lint_warn("config: invalid expression for rule $name: \"$rule\": $err\n", $name); |
|
1388
|
0
|
|
|
|
|
0
|
return 0; |
|
1389
|
|
|
|
|
|
|
} |
|
1390
|
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
# Deprecated functions, leave just in case.. |
|
1392
|
|
|
|
|
|
|
sub is_delimited_regexp_valid { |
|
1393
|
0
|
|
|
0
|
0
|
0
|
my ($self, $rule, $re) = @_; |
|
1394
|
0
|
|
|
|
|
0
|
warn "deprecated is_delimited_regexp_valid() called, use compile_regexp()\n"; |
|
1395
|
0
|
|
|
|
|
0
|
my ($rec, $err) = compile_regexp($re, 1, 1); |
|
1396
|
0
|
|
|
|
|
0
|
return $rec; |
|
1397
|
|
|
|
|
|
|
} |
|
1398
|
|
|
|
|
|
|
sub is_regexp_valid { |
|
1399
|
0
|
|
|
0
|
0
|
0
|
my ($self, $rule, $re) = @_; |
|
1400
|
0
|
|
|
|
|
0
|
warn "deprecated is_regexp_valid() called, use compile_regexp()\n"; |
|
1401
|
0
|
|
|
|
|
0
|
my ($rec, $err) = compile_regexp($re, 1, 1); |
|
1402
|
0
|
|
|
|
|
0
|
return $rec; |
|
1403
|
|
|
|
|
|
|
} |
|
1404
|
|
|
|
|
|
|
sub is_always_matching_regexp { |
|
1405
|
0
|
|
|
0
|
0
|
0
|
warn "deprecated is_always_matching_regexp() called\n"; |
|
1406
|
0
|
|
|
|
|
0
|
return; |
|
1407
|
|
|
|
|
|
|
} |
|
1408
|
|
|
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
########################################################################### |
|
1410
|
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
sub add_to_addrlist { |
|
1412
|
33
|
|
|
33
|
0
|
66
|
my ($self, $singlelist, @addrs) = @_; |
|
1413
|
33
|
|
|
|
|
46
|
my $conf = $self->{conf}; |
|
1414
|
|
|
|
|
|
|
|
|
1415
|
33
|
|
|
|
|
51
|
foreach my $addr (@addrs) { |
|
1416
|
33
|
|
|
|
|
53
|
$addr = lc $addr; |
|
1417
|
33
|
|
|
|
|
45
|
my $re = $addr; |
|
1418
|
33
|
|
|
|
|
60
|
$re =~ s/[\000\\\(]/_/gs; # paranoia |
|
1419
|
33
|
|
|
|
|
199
|
$re =~ s/([^\*\?_a-zA-Z0-9])/\\$1/g; # escape any possible metachars |
|
1420
|
33
|
|
|
|
|
66
|
$re =~ tr/?/./; # "?" -> "." |
|
1421
|
33
|
|
|
|
|
55
|
$re =~ s/\*+/\.\*/g; # "*" -> "any string" |
|
1422
|
33
|
|
|
|
|
153
|
$conf->{$singlelist}->{$addr} = "^${re}\$"; |
|
1423
|
|
|
|
|
|
|
} |
|
1424
|
|
|
|
|
|
|
} |
|
1425
|
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
sub add_to_addrlist_rcvd { |
|
1427
|
0
|
|
|
0
|
0
|
0
|
my ($self, $listname, $addr, $domain) = @_; |
|
1428
|
0
|
|
|
|
|
0
|
my $conf = $self->{conf}; |
|
1429
|
|
|
|
|
|
|
|
|
1430
|
0
|
|
|
|
|
0
|
$domain = lc $domain; |
|
1431
|
0
|
|
|
|
|
0
|
$addr = lc $addr; |
|
1432
|
0
|
0
|
|
|
|
0
|
if ($conf->{$listname}->{$addr}) { |
|
1433
|
0
|
|
|
|
|
0
|
push @{$conf->{$listname}->{$addr}{domain}}, $domain; |
|
|
0
|
|
|
|
|
0
|
|
|
1434
|
|
|
|
|
|
|
} |
|
1435
|
|
|
|
|
|
|
else { |
|
1436
|
0
|
|
|
|
|
0
|
my $re = $addr; |
|
1437
|
0
|
|
|
|
|
0
|
$re =~ s/[\000\\\(]/_/gs; # paranoia |
|
1438
|
0
|
|
|
|
|
0
|
$re =~ s/([^\*\?_a-zA-Z0-9])/\\$1/g; # escape any possible metachars |
|
1439
|
0
|
|
|
|
|
0
|
$re =~ tr/?/./; # "?" -> "." |
|
1440
|
0
|
|
|
|
|
0
|
$re =~ s/\*+/\.\*/g; # "*" -> "any string" |
|
1441
|
0
|
|
|
|
|
0
|
$conf->{$listname}->{$addr}{re} = "^${re}\$"; |
|
1442
|
0
|
|
|
|
|
0
|
$conf->{$listname}->{$addr}{domain} = [ $domain ]; |
|
1443
|
|
|
|
|
|
|
} |
|
1444
|
|
|
|
|
|
|
} |
|
1445
|
|
|
|
|
|
|
|
|
1446
|
|
|
|
|
|
|
sub remove_from_addrlist { |
|
1447
|
0
|
|
|
0
|
0
|
0
|
my ($self, $singlelist, @addrs) = @_; |
|
1448
|
0
|
|
|
|
|
0
|
my $conf = $self->{conf}; |
|
1449
|
|
|
|
|
|
|
|
|
1450
|
0
|
|
|
|
|
0
|
foreach my $addr (@addrs) { |
|
1451
|
0
|
|
|
|
|
0
|
delete($conf->{$singlelist}->{lc $addr}); |
|
1452
|
|
|
|
|
|
|
} |
|
1453
|
|
|
|
|
|
|
} |
|
1454
|
|
|
|
|
|
|
|
|
1455
|
|
|
|
|
|
|
sub remove_from_addrlist_rcvd { |
|
1456
|
0
|
|
|
0
|
0
|
0
|
my ($self, $listname, @addrs) = @_; |
|
1457
|
0
|
|
|
|
|
0
|
my $conf = $self->{conf}; |
|
1458
|
|
|
|
|
|
|
|
|
1459
|
0
|
|
|
|
|
0
|
foreach my $addr (@addrs) { |
|
1460
|
0
|
|
|
|
|
0
|
delete($conf->{$listname}->{lc $addr}); |
|
1461
|
|
|
|
|
|
|
} |
|
1462
|
|
|
|
|
|
|
} |
|
1463
|
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
sub add_to_addrlist_dkim { |
|
1465
|
0
|
|
|
0
|
0
|
0
|
add_to_addrlist_rcvd(@_); |
|
1466
|
|
|
|
|
|
|
} |
|
1467
|
|
|
|
|
|
|
|
|
1468
|
|
|
|
|
|
|
sub remove_from_addrlist_dkim { |
|
1469
|
0
|
|
|
0
|
0
|
0
|
my ($self, $listname, $addr, $domain) = @_; |
|
1470
|
0
|
|
|
|
|
0
|
my $conf = $self->{conf}; |
|
1471
|
0
|
|
|
|
|
0
|
my $conf_lname = $conf->{$listname}; |
|
1472
|
|
|
|
|
|
|
|
|
1473
|
0
|
|
|
|
|
0
|
$addr = lc $addr; |
|
1474
|
0
|
0
|
|
|
|
0
|
if ($conf_lname->{$addr}) { |
|
1475
|
0
|
|
|
|
|
0
|
$domain = lc $domain; |
|
1476
|
0
|
|
|
|
|
0
|
my $domains_listref = $conf_lname->{$addr}{domain}; |
|
1477
|
|
|
|
|
|
|
# removing $domain from the list |
|
1478
|
0
|
|
|
|
|
0
|
my @replacement = grep { lc $_ ne $domain } @$domains_listref; |
|
|
0
|
|
|
|
|
0
|
|
|
1479
|
0
|
0
|
|
|
|
0
|
if (!@replacement) { # nothing left, remove the entire addr entry |
|
|
|
0
|
|
|
|
|
|
|
1480
|
0
|
|
|
|
|
0
|
delete($conf_lname->{$addr}); |
|
1481
|
|
|
|
|
|
|
} elsif (@replacement != @$domains_listref) { # anything changed? |
|
1482
|
0
|
|
|
|
|
0
|
$conf_lname->{$addr}{domain} = \@replacement; |
|
1483
|
|
|
|
|
|
|
} |
|
1484
|
|
|
|
|
|
|
} |
|
1485
|
|
|
|
|
|
|
} |
|
1486
|
|
|
|
|
|
|
|
|
1487
|
|
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
########################################################################### |
|
1489
|
|
|
|
|
|
|
|
|
1490
|
|
|
|
|
|
|
sub fix_path_relative_to_current_file { |
|
1491
|
0
|
|
|
0
|
0
|
0
|
my ($self, $path) = @_; |
|
1492
|
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
# the path may be specified as "~/foo", so deal with that |
|
1494
|
0
|
|
|
|
|
0
|
$path = $self->{conf}->{main}->sed_path($path); |
|
1495
|
|
|
|
|
|
|
|
|
1496
|
0
|
0
|
|
|
|
0
|
if (!File::Spec->file_name_is_absolute ($path)) { |
|
1497
|
0
|
|
|
|
|
0
|
my ($vol, $dirs, $file) = File::Spec->splitpath ($self->{currentfile}); |
|
1498
|
0
|
|
|
|
|
0
|
$path = File::Spec->catpath ($vol, $dirs, $path); |
|
1499
|
0
|
|
|
|
|
0
|
dbg("config: fixed relative path: $path"); |
|
1500
|
|
|
|
|
|
|
} |
|
1501
|
0
|
|
|
|
|
0
|
return $path; |
|
1502
|
|
|
|
|
|
|
} |
|
1503
|
|
|
|
|
|
|
|
|
1504
|
|
|
|
|
|
|
########################################################################### |
|
1505
|
|
|
|
|
|
|
|
|
1506
|
|
|
|
|
|
|
sub lint_warn { |
|
1507
|
17
|
|
|
17
|
0
|
52
|
my ($self, $msg, $rule, $iserror) = @_; |
|
1508
|
|
|
|
|
|
|
|
|
1509
|
17
|
100
|
|
|
|
55
|
if (!defined $iserror) { $iserror = 1; } |
|
|
4
|
|
|
|
|
11
|
|
|
1510
|
|
|
|
|
|
|
|
|
1511
|
17
|
100
|
|
|
|
103
|
if ($self->{conf}->{main}->{lint_callback}) { |
|
|
|
50
|
|
|
|
|
|
|
1512
|
4
|
|
|
|
|
32
|
$self->{conf}->{main}->{lint_callback}->( |
|
1513
|
|
|
|
|
|
|
msg => $msg, |
|
1514
|
|
|
|
|
|
|
rule => $rule, |
|
1515
|
|
|
|
|
|
|
iserror => $iserror |
|
1516
|
|
|
|
|
|
|
); |
|
1517
|
|
|
|
|
|
|
} |
|
1518
|
|
|
|
|
|
|
elsif ($self->{conf}->{lint_rules}) { |
|
1519
|
0
|
|
|
|
|
0
|
warn $msg."\n"; |
|
1520
|
|
|
|
|
|
|
} |
|
1521
|
|
|
|
|
|
|
else { |
|
1522
|
13
|
|
|
|
|
39
|
info($msg); |
|
1523
|
|
|
|
|
|
|
} |
|
1524
|
|
|
|
|
|
|
|
|
1525
|
17
|
50
|
|
|
|
309
|
if ($iserror) { |
|
1526
|
17
|
|
|
|
|
86
|
$self->{conf}->{errors}++; |
|
1527
|
|
|
|
|
|
|
} |
|
1528
|
|
|
|
|
|
|
} |
|
1529
|
|
|
|
|
|
|
|
|
1530
|
|
|
|
|
|
|
########################################################################### |
|
1531
|
|
|
|
|
|
|
|
|
1532
|
|
|
|
|
|
|
sub get_if_stack_as_string { |
|
1533
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
1534
|
|
|
|
|
|
|
return join ' ', map { |
|
1535
|
|
|
|
|
|
|
$_->{conditional} |
|
1536
|
0
|
|
|
|
|
|
} @{$self->{if_stack}}; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
1537
|
|
|
|
|
|
|
} |
|
1538
|
|
|
|
|
|
|
|
|
1539
|
|
|
|
|
|
|
########################################################################### |
|
1540
|
|
|
|
|
|
|
|
|
1541
|
|
|
|
|
|
|
1; |