File Coverage

blib/lib/Mail/SpamAssassin/Conf/Parser.pm
Criterion Covered Total %
statement 485 683 71.0
branch 229 366 62.5
condition 59 132 44.7
subroutine 43 57 75.4
pod 0 47 0.0
total 816 1285 63.5


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   282 use Mail::SpamAssassin::Conf;
  40         84  
  40         1483  
138 40     40   218 use Mail::SpamAssassin::Constants qw(:sa);
  40         78  
  40         4501  
139 40     40   262 use Mail::SpamAssassin::Logger;
  40         80  
  40         2067  
140 40     40   223 use Mail::SpamAssassin::Util qw(untaint_var);
  40         100  
  40         3715  
141 40     40   316 use Mail::SpamAssassin::NetSet;
  40         105  
  40         935  
142              
143 40     40   225 use strict;
  40         89  
  40         1169  
144 40     40   214 use warnings;
  40         74  
  40         1415  
145             # use bytes;
146 40     40   247 use re 'taint';
  40         97  
  40         98206  
147              
148             our @ISA = qw();
149              
150             ###########################################################################
151              
152             sub new {
153 81     81 0 269 my $class = shift;
154 81   33     689 $class = ref($class) || $class;
155 81         273 my ($conf) = @_;
156              
157 81         426 my $self = {
158             'conf' => $conf
159             };
160              
161 81         432 $self->{command_luts} = { };
162 81         380 $self->{command_luts}->{frequent} = { };
163 81         410 $self->{command_luts}->{remaining} = { };
164              
165 81         383 bless ($self, $class);
166 81         605 $self;
167             }
168              
169             ###########################################################################
170              
171             sub register_commands {
172 1088     1088 0 2504 my($self, $arrref) = @_;
173 1088         2171 my $conf = $self->{conf};
174              
175 1088         2904 $self->set_defaults_from_command_list($arrref);
176 1088         3167 $self->build_command_luts($arrref);
177 1088         1576 push(@{$conf->{registered_commands}}, @{$arrref});
  1088         1984  
  1088         5510  
178             }
179              
180             sub set_defaults_from_command_list {
181 1088     1088 0 1841 my ($self, $arrref) = @_;
182 1088         1513 my $conf = $self->{conf};
183 1088         1385 foreach my $cmd (@{$arrref}) {
  1088         2628  
184             # note! exists, not defined -- we want to be able to set
185             # "undef" default values.
186 14494 100       23306 if (exists($cmd->{default})) {
187 7405         21880 $conf->{$cmd->{setting}} = $cmd->{default};
188             }
189             }
190             }
191              
192             sub build_command_luts {
193 1088     1088 0 1768 my ($self, $arrref) = @_;
194              
195 1088         1750 my $conf = $self->{conf};
196              
197 1088         1332 my $set;
198 1088         1401 foreach my $cmd (@{$arrref}) {
  1088         2629  
199             # first off, decide what set this is in.
200 14494 100       19811 if ($cmd->{is_frequent}) { $set = 'frequent'; }
  567         698  
201 13927         15762 else { $set = 'remaining'; }
202              
203             # next, its priority (used to ensure frequently-used params
204             # are parsed first)
205 14494   66     30928 my $cmdname = $cmd->{command} || $cmd->{setting};
206 14494         29190 $self->{command_luts}->{$set}->{$cmdname} = $cmd;
207              
208 14494 100 66     27623 if ($cmd->{aliases} && scalar @{$cmd->{aliases}} > 0) {
  162         624  
209 162         251 foreach my $name (@{$cmd->{aliases}}) {
  162         397  
210 162         590 $self->{command_luts}->{$set}->{$name} = $cmd;
211             }
212             }
213             }
214             }
215              
216             ###########################################################################
217              
218             sub parse {
219 79     79 0 280 my ($self, undef, $scoresonly) = @_; # leave $rules in $_[1]
220              
221 79         244 my $conf = $self->{conf};
222 79         376 $self->{scoresonly} = $scoresonly;
223              
224             # Language selection:
225             # See http://www.gnu.org/manual/glibc-2.2.5/html_node/Locale-Categories.html
226             # and http://www.gnu.org/manual/glibc-2.2.5/html_node/Using-gettextized-software.html
227 79         273 my $lang = $ENV{'LANGUAGE'}; # LANGUAGE has the highest precedence but has a
228 79 50       274 if ($lang) { # special format: The user may specify more than
229 0         0 $lang =~ s/:.*$//; # one language here, colon separated. We use the
230             } # first one only (lazy bums we are :o)
231 79   33     733 $lang ||= $ENV{'LC_ALL'};
232 79   33     483 $lang ||= $ENV{'LC_MESSAGES'};
233 79   33     651 $lang ||= $ENV{'LANG'};
234 79   50     489 $lang ||= 'C'; # Nothing set means C/POSIX
235              
236 79 50       697 if ($lang =~ /^(C|POSIX)$/) {
237 79         305 $lang = 'en_US'; # Our default language
238             } else {
239 0         0 $lang =~ s/[@.+,].*$//; # Strip codeset, modifier/audience, etc.
240             } # (eg. .utf8 or @euro)
241              
242             # get fast-access handles on the command lookup tables
243 79         291 my $lut_frequent = $self->{command_luts}->{frequent};
244 79         189 my $lut_remaining = $self->{command_luts}->{remaining};
245 79         434 my %migrated_keys = map { $_ => 1 }
  79         681  
246             @Mail::SpamAssassin::Conf::MIGRATED_SETTINGS;
247              
248 79         419 $self->{currentfile} = '(no file)';
249 79         230 my $skip_parsing = 0;
250 79         192 my @curfile_stack;
251             my @if_stack;
252 79         35515 my @conf_lines = split (/\n/, $_[1]);
253 79         291 my $line;
254 79         284 $self->{if_stack} = \@if_stack;
255 79         284 $self->{file_scoped_attrs} = { };
256              
257 79         279 my $keepmetadata = $conf->{main}->{keep_config_parsing_metadata};
258              
259 79         420 while (defined ($line = shift @conf_lines)) {
260 123056         214223 local ($1); # bug 3838: prevent random taint flagging of $1
261              
262 123056 100       225141 if (index($line,'#') > -1) {
263             # bug 5545: used to support testing rules in the ruleqa system
264 45334 50 33     67625 if ($keepmetadata && $line =~ /^\#testrules/) {
265 0         0 $self->{file_scoped_attrs}->{testrules}++;
266 0         0 next;
267             }
268              
269             # bug 6800: let X-Spam-Checker-Version also show what sa-update we are at
270 45334 50       68651 if ($line =~ /^\# UPDATE version (\d+)$/) {
271 0         0 for ($self->{currentfile}) { # just aliasing, not a loop
272 0 0 0     0 $conf->{update_version}{$_} = $1 if defined $_ && $_ ne '(no file)';
273             }
274             }
275              
276 45334         112777 $line =~ s/(?<!\\)#.*$//; # remove comments
277 45334         59527 $line =~ s/\\#/#/g; # hash chars are escaped, so unescape them
278             }
279              
280 123056 100       202326 if ($line =~ tr{ \t\r\n\f}{}) {
281 63452         117111 $line =~ s/^\s+//; # remove leading whitespace
282 63452         154747 $line =~ s/\s+$//; # remove tailing whitespace
283             }
284 123056 100       248846 next unless($line); # skip empty lines
285              
286             # handle i18n
287 64408 0       100714 if ($line =~ s/^lang\s+(\S+)\s+//) { next if ($lang !~ /^$1/i); }
  0 50       0  
288              
289 64408         183225 my($key, $value) = split(/\s+/, $line, 2);
290 64408         103509 $key = lc $key;
291             # convert all dashes in setting name to underscores.
292 64408         80650 $key =~ tr/-/_/;
293 64408 100       101687 $value = '' unless defined($value);
294              
295             # # Do a better job untainting this info ...
296             # # $value = untaint_var($value);
297             # Do NOT blindly untaint now, do it carefully later when semantics is known!
298              
299 64408         68437 my $parse_error; # undef by default, may be overridden
300              
301             # File/line number assertions
302 64408 100       190602 if ($key eq 'file') {
    50          
    100          
    100          
    50          
    100          
303 3010 100       7821 if ($value =~ /^start\s+(.+)$/) {
304 1505         3342 push (@curfile_stack, $self->{currentfile});
305 1505         3071 $self->{currentfile} = $1;
306 1505         4253 next;
307             }
308              
309 1505 50       4742 if ($value =~ /^end\s/) {
310 1505         3819 $self->{file_scoped_attrs} = { };
311              
312 1505 50       3534 if (scalar @if_stack > 0) {
313 0         0 my $cond = pop @if_stack;
314              
315 0 0       0 if ($cond->{type} eq 'if') {
316             my $msg = "config: unclosed 'if' in ".
317 0         0 $self->{currentfile}.": if ".$cond->{conditional}."\n";
318 0         0 warn $msg;
319 0         0 $self->lint_warn($msg, undef);
320             }
321             else {
322             # die seems a bit excessive here, but this shouldn't be possible
323             # so I suppose it's okay.
324 0         0 die "config: unknown 'if' type: ".$cond->{type}."\n";
325             }
326              
327 0         0 @if_stack = ();
328             }
329 1505         2232 $skip_parsing = 0;
330              
331 1505         2650 my $curfile = pop @curfile_stack;
332 1505 50       3018 if (defined $curfile) {
333 1505         2568 $self->{currentfile} = $curfile;
334             } else {
335 0         0 $self->{currentfile} = '(no file)';
336             }
337 1505         4193 next;
338             }
339             }
340              
341             # now handle the commands.
342             elsif ($key eq 'include') {
343 0         0 $value = $self->fix_path_relative_to_current_file($value);
344 0         0 my $text = $conf->{main}->read_cf($value, 'included file');
345 0         0 unshift (@conf_lines, split (/\n/, $text));
346 0         0 next;
347             }
348              
349             elsif ($key eq 'ifplugin') {
350 756         3922 $self->handle_conditional ($key, "plugin ($value)",
351             \@if_stack, \$skip_parsing);
352 756         4477 next;
353             }
354              
355             elsif ($key eq 'if') {
356 189         1040 $self->handle_conditional ($key, $value,
357             \@if_stack, \$skip_parsing);
358 189         1197 next;
359             }
360              
361             elsif ($key eq 'else') {
362             # TODO: if/else/else won't get flagged here :(
363 0 0       0 if (!@if_stack) {
364 0         0 $parse_error = "config: found else without matching conditional";
365 0         0 goto failed_line;
366             }
367              
368 0         0 $skip_parsing = !$skip_parsing;
369 0         0 next;
370             }
371              
372             # and the endif statement:
373             elsif ($key eq 'endif') {
374 945         1539 my $lastcond = pop @if_stack;
375 945 50       1893 if (!defined $lastcond) {
376 0         0 $parse_error = "config: found endif without matching conditional";
377 0         0 goto failed_line;
378             }
379              
380 945         1514 $skip_parsing = $lastcond->{skip_parsing};
381 945         3607 next;
382             }
383              
384             # preprocessing? skip all other commands
385 59508 100       88711 next if $skip_parsing;
386              
387 58916 50       83330 if ($key eq 'require_version') {
388             # if it wasn't replaced during install, assume current version ...
389 0 0       0 next if ($value eq "\@\@VERSION\@\@");
390              
391 0         0 my $ver = $Mail::SpamAssassin::VERSION;
392              
393             # if we want to allow "require_version 3.0" be good for all
394             # "3.0.x" versions:
395             ## make sure it's a numeric value
396             #$value += 0.0;
397             ## convert 3.000000 -> 3.0, stay backward compatible ...
398             #$ver =~ s/^(\d+)\.(\d{1,3}).*$/sprintf "%d.%d", $1, $2/e;
399             #$value =~ s/^(\d+)\.(\d{1,3}).*$/sprintf "%d.%d", $1, $2/e;
400              
401 0 0       0 if ($ver ne $value) {
402 0         0 my $msg = "config: configuration file \"$self->{currentfile}\" requires ".
403             "version $value of SpamAssassin, but this is code version ".
404             "$ver. Maybe you need to use ".
405             "the -C switch, or remove the old config files? ".
406             "Skipping this file";
407 0         0 warn $msg;
408 0         0 $self->lint_warn($msg, undef);
409 0         0 $skip_parsing = 1;
410             }
411 0         0 next;
412             }
413              
414 58916         84427 my $cmd = $lut_frequent->{$key}; # check the frequent command set
415 58916 100       88680 if (!$cmd) {
416 50882         68923 $cmd = $lut_remaining->{$key}; # no? try the rest
417             }
418              
419             # we've either fallen through with no match, in which case this
420             # if() will fail, or we have a match.
421 58916 100       86766 if ($cmd) {
422 58910 50       91090 if ($self->{scoresonly}) { # reading user config from spamd
423 0 0 0     0 if ($cmd->{is_priv} && !$conf->{allow_user_rules}) {
424 0         0 info("config: not parsing, 'allow_user_rules' is 0: $line");
425 0         0 goto failed_line;
426             }
427 0 0       0 if ($cmd->{is_admin}) {
428 0         0 info("config: not parsing, administrator setting: $line");
429 0         0 goto failed_line;
430             }
431             }
432              
433 58910 100       91325 if (!$cmd->{code}) {
434 827 50       2616 if (! $self->setup_default_code_cb($cmd)) {
435 0         0 goto failed_line;
436             }
437             }
438              
439 58910         71265 my $ret = &{$cmd->{code}} ($conf, $cmd->{setting}, $value, $line);
  58910         119649  
440              
441 58910 50 66     246491 if ($ret && $ret eq $Mail::SpamAssassin::Conf::INVALID_VALUE)
    50 66        
    50 66        
442             {
443 0         0 $parse_error = "config: SpamAssassin failed to parse line, ".
444             "\"$value\" is not valid for \"$key\", ".
445             "skipping: $line";
446 0         0 goto failed_line;
447             }
448             elsif ($ret && $ret eq $Mail::SpamAssassin::Conf::INVALID_HEADER_FIELD_NAME)
449             {
450 0         0 $parse_error = "config: SpamAssassin failed to parse line, ".
451             "it does not specify a valid header field name, ".
452             "skipping: $line";
453 0         0 goto failed_line;
454             }
455             elsif ($ret && $ret eq $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE)
456             {
457 0         0 $parse_error = "config: SpamAssassin failed to parse line, ".
458             "no value provided for \"$key\", ".
459             "skipping: $line";
460 0         0 goto failed_line;
461             }
462             else {
463 58910         180491 next;
464             }
465             }
466              
467             # last ditch: try to see if the plugins know what to do with it
468 6 50       77 if ($conf->{main}->call_plugins("parse_config", {
469             key => $key,
470             value => $value,
471             line => $line,
472             conf => $conf,
473             user_config => $self->{scoresonly}
474             }))
475             {
476             # a plugin dealt with it successfully.
477 0         0 next;
478             }
479              
480             failed_line:
481 6         17 my $msg = $parse_error;
482 6         10 my $is_error = 1;
483 6 50       14 if (!$msg) {
484             # use a default warning, if a more specific one wasn't output
485 6 50       16 if ($migrated_keys{$key}) {
486             # this key was moved into a plugin; non-fatal for lint
487 0         0 $is_error = 0;
488 0         0 $msg = "config: failed to parse, now a plugin, skipping, in \"$self->{currentfile}\": $line";
489             } else {
490             # a real syntax error; this is fatal for --lint
491 6         18 $msg = "config: failed to parse line, skipping, in \"$self->{currentfile}\": $line";
492             }
493             }
494              
495 6         19 $self->lint_warn($msg, undef, $is_error);
496             }
497              
498 79         360 delete $self->{if_stack};
499              
500 79         649 $self->lint_check();
501 79         426 $self->set_default_scores();
502 79         470 $self->check_for_missing_descriptions();
503              
504 79         694 delete $self->{scoresonly};
505             }
506              
507             sub handle_conditional {
508 945     945 0 2134 my ($self, $key, $value, $if_stack_ref, $skip_parsing_ref) = @_;
509 945         1750 my $conf = $self->{conf};
510              
511 945         1315 my $lexer = ARITH_EXPRESSION_LEXER;
512 945         10930 my @tokens = ($value =~ m/($lexer)/og);
513              
514 945         1960 my $eval = '';
515 945         1272 my $bad = 0;
516 945         1798 foreach my $token (@tokens) {
517 4095 100       15170 if ($token =~ /^(?:\W+|[+-]?\d+(?:\.\d+)?)$/) {
    100          
    100          
    50          
    100          
    50          
    50          
518             # using tainted subr. argument may taint the whole expression, avoid
519 2268         5250 my $u = untaint_var($token);
520 2268         4780 $eval .= $u . " ";
521             }
522             elsif ($token eq 'plugin') {
523             # replace with a method call
524 756         1553 $eval .= '$self->cond_clause_plugin_loaded';
525             }
526             elsif ($token eq 'can') {
527             # replace with a method call
528 126         349 $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 63         226 $eval .= $Mail::SpamAssassin::VERSION." ";
536             }
537             elsif ($token eq 'perl_version') {
538 0         0 $eval .= $]." ";
539             }
540             elsif ($token =~ /^\w[\w\:]+$/) { # class name
541 882         1823 my $u = untaint_var($token);
542 882         2353 $eval .= '"' . $u . '" ';
543             }
544             else {
545 0         0 $bad++;
546 0         0 warn "config: unparseable chars in 'if $value': '$token'\n";
547             }
548             }
549              
550 945 50       2020 if ($bad) {
551 0         0 $self->lint_warn("bad 'if' line, in \"$self->{currentfile}\"", undef);
552 0         0 return -1;
553             }
554              
555 945         1194 push (@{$if_stack_ref}, {
  945         4205  
556             type => 'if',
557             conditional => $value,
558             skip_parsing => $$skip_parsing_ref
559             });
560              
561 945 100       65079 if (eval $eval) {
562             # leave $skip_parsing as-is; we may not be parsing anyway in this block.
563             # in other words, support nested 'if's and 'require_version's
564             } else {
565 260 50       1002 warn "config: error in $key - $eval: $@" if $@ ne '';
566 260         887 $$skip_parsing_ref = 1;
567             }
568             }
569              
570             # functions supported in the "if" eval:
571             sub cond_clause_plugin_loaded {
572 756     756 0 8197 return $_[0]->{conf}->{plugins_loaded}->{$_[1]};
573             }
574              
575             sub cond_clause_can {
576 126     126 0 414 my ($self, $method) = @_;
577 126 50       569 if ($self->{currentfile} =~ q!/user_prefs$! ) {
578 0         0 warn "config: 'if can $method' not available in user_prefs";
579 0         0 return 0
580             }
581 126         563 $self->cond_clause_can_or_has('can', $method);
582             }
583              
584             sub cond_clause_has {
585 0     0 0 0 my ($self, $method) = @_;
586 0         0 $self->cond_clause_can_or_has('has', $method);
587             }
588              
589             sub cond_clause_can_or_has {
590 126     126 0 347 my ($self, $fn_name, $method) = @_;
591              
592 126         423 local($1,$2);
593 126 50       967 if (!defined $method) {
    50          
594 0         0 $self->lint_warn("bad 'if' line, no argument to $fn_name(), ".
595             "in \"$self->{currentfile}\"", undef);
596             } elsif ($method =~ /^(.*)::([^:]+)$/) {
597 40     40   350 no strict "refs";
  40         89  
  40         260177  
598 126         446 my($module, $meth) = ($1, $2);
599             return 1 if $module->can($meth) &&
600 126 50 33     2173 ( $fn_name eq 'has' || &{$method}() );
      33        
601             } else {
602 0         0 $self->lint_warn("bad 'if' line, cannot find '::' in $fn_name($method), ".
603             "in \"$self->{currentfile}\"", undef);
604             }
605 0         0 return;
606             }
607              
608             # Let's do some linting here ...
609             # This is called from _parse(), BTW, so we can check for $conf->{tests}
610             # easily before finish_parsing() is called and deletes it.
611             #
612             sub lint_check {
613 79     79 0 241 my ($self) = @_;
614 79         233 my $conf = $self->{conf};
615              
616 79 100       356 if ($conf->{lint_rules}) {
617             # Check for description and score issues in lint fashion
618 32         71 while ( my $k = each %{$conf->{descriptions}} ) {
  864         2568  
619 832 50       2522 if (!exists $conf->{tests}->{$k}) {
620 0         0 $self->lint_warn("config: warning: description exists for non-existent rule $k\n", $k);
621             }
622             }
623              
624 32         81 while ( my($sk) = each %{$conf->{scores}} ) {
  672         1534  
625 640 50       1192 if (!exists $conf->{tests}->{$sk}) {
626             # bug 5514: not a lint warning any more
627 0         0 dbg("config: warning: score set for non-existent rule $sk");
628             }
629             }
630             }
631             }
632              
633             # we should set a default score for all valid rules... Do this here
634             # instead of add_test because mostly 'score' occurs after the rule is
635             # specified, so why set the scores to default, then set them again at
636             # 'score'?
637             #
638             sub set_default_scores {
639 79     79 0 208 my ($self) = @_;
640 79         216 my $conf = $self->{conf};
641              
642 79         183 while ( my $k = each %{$conf->{tests}} ) {
  3648         7481  
643 3569 100       6187 if ( ! exists $conf->{scores}->{$k} ) {
644             # T_ rules (in a testing probationary period) get low, low scores
645 2310 50       3549 my $set_score = ($k =~/^T_/) ? 0.01 : 1.0;
646              
647 2310 100 100     7269 $set_score = -$set_score if ( ($conf->{tflags}->{$k}||'') =~ /\bnice\b/ );
648 2310         3312 for my $index (0..3) {
649 9240         14693 $conf->{scoreset}->[$index]->{$k} = $set_score;
650             }
651             }
652             }
653             }
654              
655             # loop through all the tests and if we are missing a description with debug
656             # set, throw a warning except for testing T_ or meta __ rules.
657             sub check_for_missing_descriptions {
658 79     79 0 228 my ($self) = @_;
659 79         230 my $conf = $self->{conf};
660              
661 79         185 while ( my $k = each %{$conf->{tests}} ) {
  3648         8511  
662 3569 100       8430 if ($k !~ m/^(?:T_|__)/i) {
663 3378 100       9522 if ( ! exists $conf->{descriptions}->{$k} ) {
664 1750         4403 dbg("config: warning: no description set for $k");
665             }
666             }
667             }
668             }
669              
670             ###########################################################################
671              
672             sub setup_default_code_cb {
673 827     827 0 1582 my ($self, $cmd) = @_;
674 827         1666 my $type = $cmd->{type};
675              
676 827 100       3608 if ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_STRING) {
    100          
    100          
    100          
    50          
    100          
    50          
    50          
    50          
    0          
677 126         368 $cmd->{code} = \&set_string_value;
678             }
679             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL) {
680 150         484 $cmd->{code} = \&set_bool_value;
681             }
682             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC) {
683 192         530 $cmd->{code} = \&set_numeric_value;
684             }
685             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE) {
686 189         512 $cmd->{code} = \&set_hash_key_value;
687             }
688             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_ADDRLIST) {
689 0         0 $cmd->{code} = \&set_addrlist_value;
690             }
691             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_TEMPLATE) {
692 126         365 $cmd->{code} = \&set_template_append;
693             }
694             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_NOARGS) {
695 0         0 $cmd->{code} = \&set_no_value;
696             }
697             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST) {
698 0         0 $cmd->{code} = \&set_string_list;
699             }
700             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_IPADDRLIST) {
701 44         184 $cmd->{code} = \&set_ipaddr_list;
702             }
703             elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_DURATION) {
704 0         0 $cmd->{code} = \&set_duration_value;
705             }
706             else {
707 0         0 warn "config: unknown conf type $type!";
708 0         0 return 0;
709             }
710 827         2171 return 1;
711             }
712              
713             sub set_no_value {
714 0     0 0 0 my ($conf, $key, $value, $line) = @_;
715              
716 0 0 0     0 unless (!defined $value || $value eq '') {
717 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
718             }
719             }
720              
721             sub set_numeric_value {
722 192     192 0 612 my ($conf, $key, $value, $line) = @_;
723              
724 192 50 33     1277 unless (defined $value && $value !~ /^$/) {
725 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
726             }
727 192 50       1070 unless ($value =~ /^ [+-]? \d+ (?: \. \d* )? \z/sx) {
728 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
729             }
730             # it is safe to untaint now that we know the syntax is a valid number
731 192         667 $conf->{$key} = untaint_var($value) + 0;
732             }
733              
734             sub set_duration_value {
735 0     0 0 0 my ($conf, $key, $value, $line) = @_;
736              
737 0         0 local ($1,$2);
738 0 0 0     0 unless (defined $value && $value !~ /^$/) {
739 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
740             }
741 0 0       0 unless ($value =~ /^( \+? \d+ (?: \. \d* )? ) (?: \s* ([smhdw]))? \z/sxi) {
742 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
743             }
744 0         0 $value = $1;
745             $value *= { s => 1, m => 60, h => 3600,
746 0 0       0 d => 24*3600, w => 7*24*3600 }->{lc $2} if defined $2;
747             # it is safe to untaint now that we know the syntax is a valid time interval
748 0         0 $conf->{$key} = untaint_var($value) + 0;
749             }
750              
751             sub set_bool_value {
752 212     212 0 744 my ($conf, $key, $value, $line) = @_;
753              
754 212 50 33     1892 unless (defined $value && $value !~ /^$/) {
755 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
756             }
757              
758             # bug 4462: allow yes/1 and no/0 for boolean values
759 212         556 $value = lc $value;
760 212 100 66     1218 if ($value eq 'yes' || $value eq '1') {
    50 33        
761 191         431 $value = 1;
762             }
763             elsif ($value eq 'no' || $value eq '0') {
764 21         47 $value = 0;
765             }
766             else {
767 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
768             }
769              
770 212         693 $conf->{$key} = $value;
771             }
772              
773             sub set_string_value {
774 126     126 0 415 my ($conf, $key, $value, $line) = @_;
775              
776 126 50 33     981 unless (defined $value && $value !~ /^$/) {
777 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
778             }
779              
780 126         465 $conf->{$key} = $value; # keep tainted
781             }
782              
783             sub set_string_list {
784 0     0 0 0 my ($conf, $key, $value, $line) = @_;
785              
786 0 0 0     0 unless (defined $value && $value !~ /^$/) {
787 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
788             }
789              
790 0         0 push(@{$conf->{$key}}, split(' ', $value));
  0         0  
791             }
792              
793             sub set_ipaddr_list {
794 44     44 0 189 my ($conf, $key, $value, $line) = @_;
795              
796 44 50 33     350 unless (defined $value && $value !~ /^$/) {
797 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
798             }
799              
800 44         193 foreach my $net (split(' ', $value)) {
801 60         250 $conf->{$key}->add_cidr($net);
802             }
803 44         233 $conf->{$key.'_configured'} = 1;
804             }
805              
806             sub set_hash_key_value {
807 3589     3589 0 6393 my ($conf, $key, $value, $line) = @_;
808 3589         11166 my($k,$v) = split(/\s+/, $value, 2);
809              
810 3589 50 33     12236 unless (defined $v && $v ne '') {
811 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
812             }
813              
814 3589         13826 $conf->{$key}->{$k} = $v; # keep tainted
815             }
816              
817             sub set_addrlist_value {
818 0     0 0 0 my ($conf, $key, $value, $line) = @_;
819              
820 0 0 0     0 unless (defined $value && $value !~ /^$/) {
821 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
822             }
823 0         0 $conf->{parser}->add_to_addrlist ($key, split (' ', $value)); # keep tainted
824             }
825              
826             sub remove_addrlist_value {
827 0     0 0 0 my ($conf, $key, $value, $line) = @_;
828              
829 0 0 0     0 unless (defined $value && $value !~ /^$/) {
830 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
831             }
832 0         0 $conf->{parser}->remove_from_addrlist ($key, split (' ', $value));
833             }
834              
835             sub set_template_append {
836 1071     1071 0 1867 my ($conf, $key, $value, $line) = @_;
837 1071 100       2439 if ( $value =~ /^"(.*?)"$/ ) { $value = $1; }
  63         234  
838 1071         3095 $conf->{$key} .= $value."\n"; # keep tainted
839             }
840              
841             sub set_template_clear {
842 126     126 0 440 my ($conf, $key, $value, $line) = @_;
843 126 50 33     898 unless (!defined $value || $value eq '') {
844 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
845             }
846 126         588 $conf->{$key} = '';
847             }
848              
849             ###########################################################################
850              
851             sub finish_parsing {
852 79     79 0 267 my ($self, $isuserconf) = @_;
853 79         218 my $conf = $self->{conf};
854              
855             # note: this function is called once for system-wide configuration
856             # with $isuserconf set to 0, then again for user conf with $isuserconf set to 1.
857 79 50       308 if (!$isuserconf) {
858 79         636 $conf->{main}->call_plugins("finish_parsing_start", { conf => $conf });
859             } else {
860 0         0 $conf->{main}->call_plugins("user_conf_parsing_start", { conf => $conf });
861             }
862              
863 79         693 $self->trace_meta_dependencies();
864 79         639 $self->fix_priorities();
865              
866             # don't do this if allow_user_rules is active, since it deletes entries
867             # from {tests}
868 79 50       471 if (!$conf->{allow_user_rules}) {
869 79         399 $self->find_dup_rules(); # must be after fix_priorities()
870             }
871              
872 79         382 dbg("config: finish parsing");
873              
874 79         199 while (my ($name, $text) = each %{$conf->{tests}}) {
  3647         9060  
875 3568         4714 my $type = $conf->{test_types}->{$name};
876 3568   100     8819 my $priority = $conf->{priority}->{$name} || 0;
877 3568         5255 $conf->{priorities}->{$priority}++;
878              
879             # eval type handling
880 3568 100       5349 if (($type & 1) == 1) {
881 2633 50       14811 if (my ($function, $args) = ($text =~ m/(.*?)\s*\((.*?)\)\s*$/)) {
882 2633         5415 my ($packed, $argsref) =
883             $self->pack_eval_method($function, $args, $name, $text);
884              
885 2633 50       6510 if (!$packed) {
    100          
    100          
    100          
    50          
    50          
886             # we've already warned about this
887             }
888             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS) {
889 85         424 $conf->{body_evals}->{$priority}->{$name} = $packed;
890             }
891             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS) {
892 2299         6204 $conf->{head_evals}->{$priority}->{$name} = $packed;
893             }
894             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS) {
895             # We don't do priorities for $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS
896             # we also use the arrayref instead of the packed string
897 1         7 $conf->{rbl_evals}->{$name} = [ $function, @$argsref ];
898             }
899             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_EVALS) {
900 0         0 $conf->{rawbody_evals}->{$priority}->{$name} = $packed;
901             }
902             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_FULL_EVALS) {
903 248         819 $conf->{full_evals}->{$priority}->{$name} = $packed;
904             }
905             #elsif ($type == $Mail::SpamAssassin::Conf::TYPE_URI_EVALS) {
906             # $conf->{uri_evals}->{$priority}->{$name} = $packed;
907             #}
908             else {
909 0         0 $self->lint_warn("unknown type $type for $name: $text", $name);
910             }
911             }
912             else {
913 0         0 $self->lint_warn("syntax error for eval function $name: $text", $name);
914             }
915             }
916             # non-eval tests
917             else {
918 935 100       2140 if ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS) {
    100          
    100          
    50          
    0          
    0          
    0          
919 233         724 $conf->{body_tests}->{$priority}->{$name} = $text;
920             }
921             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS) {
922 567         1441 $conf->{head_tests}->{$priority}->{$name} = $text;
923             }
924             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) {
925 72         296 $conf->{meta_tests}->{$priority}->{$name} = $text;
926             }
927             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_URI_TESTS) {
928 63         348 $conf->{uri_tests}->{$priority}->{$name} = $text;
929             }
930             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS) {
931 0         0 $conf->{rawbody_tests}->{$priority}->{$name} = $text;
932             }
933             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS) {
934 0         0 $conf->{full_tests}->{$priority}->{$name} = $text;
935             }
936             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_EMPTY_TESTS) {
937             }
938             else {
939 0         0 $self->lint_warn("unknown type $type for $name: $text", $name);
940             }
941             }
942             }
943              
944 79         402 $self->lint_trusted_networks();
945              
946 79 50       296 if (!$isuserconf) {
947 79         558 $conf->{main}->call_plugins("finish_parsing_end", { conf => $conf });
948             } else {
949 0         0 $conf->{main}->call_plugins("user_conf_parsing_end", { conf => $conf });
950             }
951              
952 79         1091 $conf->found_any_rules(); # before we might delete {tests}
953              
954 79 50       370 if (!$conf->{allow_user_rules}) {
955             # free up stuff we no longer need
956 79         1162 delete $conf->{tests};
957 79         1026 delete $conf->{priority};
958             #test_types are needed - see bug 5503
959             #delete $conf->{test_types};
960             }
961             }
962              
963             sub trace_meta_dependencies {
964 79     79 0 278 my ($self) = @_;
965 79         225 my $conf = $self->{conf};
966 79         410 $conf->{meta_dependencies} = { };
967              
968 79         188 foreach my $name (keys %{$conf->{tests}}) {
  79         1057  
969 3569 100       6522 next unless ($conf->{test_types}->{$name}
970             == $Mail::SpamAssassin::Conf::TYPE_META_TESTS);
971              
972 72         228 my $deps = [ ];
973 72         185 my $alreadydone = { };
974 72         394 $self->_meta_deps_recurse($conf, $name, $name, $deps, $alreadydone);
975 72         177 $conf->{meta_dependencies}->{$name} = join (' ', @{$deps});
  72         519  
976             }
977             }
978              
979             sub _meta_deps_recurse {
980 269     269   659 my ($self, $conf, $toprule, $name, $deps, $alreadydone) = @_;
981              
982             # Only do each rule once per top-level meta; avoid infinite recursion
983 269 50       620 return if $alreadydone->{$name};
984 269         502 $alreadydone->{$name} = 1;
985              
986             # Obviously, don't trace empty or nonexistent rules
987 269         569 my $rule = $conf->{tests}->{$name};
988 269 50       536 return unless $rule;
989              
990             # Lex the rule into tokens using a rather simple RE method ...
991 269         356 my $lexer = ARITH_EXPRESSION_LEXER;
992 269         4572 my @tokens = ($rule =~ m/$lexer/og);
993              
994             # Go through each token in the meta rule
995 269         535 my $conf_tests = $conf->{tests};
996 269         522 foreach my $token (@tokens) {
997             # has to be an alpha+numeric token
998 3318 100 100     7606 next if $token =~ tr{A-Za-z0-9_}{}c || substr($token,0,1) =~ tr{A-Za-z_}{}c; # even faster
999              
1000             # and has to be a rule name
1001 1416 100       2929 next unless exists $conf_tests->{$token};
1002              
1003             # add and recurse
1004 197         316 push(@{$deps}, untaint_var($token));
  197         661  
1005 197         822 $self->_meta_deps_recurse($conf, $toprule, $token, $deps, $alreadydone);
1006             }
1007             }
1008              
1009             sub fix_priorities {
1010 79     79 0 223 my ($self) = @_;
1011 79         216 my $conf = $self->{conf};
1012              
1013 79 50       293 die unless $conf->{meta_dependencies}; # order requirement
1014 79         235 my $pri = $conf->{priority};
1015              
1016             # sort into priority order, lowest first -- this way we ensure that if we
1017             # rearrange the pri of a rule early on, we cannot accidentally increase its
1018             # priority later.
1019 79         183 foreach my $rule (sort {
1020 7518         9211 $pri->{$a} <=> $pri->{$b}
1021 79         1447 } keys %{$pri})
1022             {
1023             # we only need to worry about meta rules -- they are the
1024             # only type of rules which depend on other rules
1025 3572         4110 my $deps = $conf->{meta_dependencies}->{$rule};
1026 3572 100       5348 next unless (defined $deps);
1027              
1028 72         201 my $basepri = $pri->{$rule};
1029 72         357 foreach my $dep (split ' ', $deps) {
1030 197         359 my $deppri = $pri->{$dep};
1031 197 100       518 if ($deppri > $basepri) {
1032 6         24 dbg("rules: $rule (pri $basepri) requires $dep (pri $deppri): fixed");
1033 6         14 $pri->{$dep} = $basepri;
1034             }
1035             }
1036             }
1037             }
1038              
1039             sub find_dup_rules {
1040 79     79 0 266 my ($self) = @_;
1041 79         229 my $conf = $self->{conf};
1042              
1043 79         180 my %names_for_text;
1044             my %dups;
1045 79         171 while (my ($name, $text) = each %{$conf->{tests}}) {
  3648         9200  
1046 3569         4419 my $type = $conf->{test_types}->{$name};
1047              
1048             # skip eval and empty tests
1049 3569 100 66     7921 next if ($type & 1) ||
1050             ($type eq $Mail::SpamAssassin::Conf::TYPE_EMPTY_TESTS);
1051              
1052 936   100     2344 my $tf = ($conf->{tflags}->{$name}||''); $tf =~ s/\s+/ /gs;
  936         1557  
1053             # ensure similar, but differently-typed, rules are not marked as dups;
1054             # take tflags into account too due to "tflags multiple"
1055 936         1695 $text = "$type\t$text\t$tf";
1056              
1057 936 100       1897 if (defined $names_for_text{$text}) {
1058 1         4 $names_for_text{$text} .= " ".$name;
1059 1         2 $dups{$text} = undef; # found (at least) one
1060             } else {
1061 935         2597 $names_for_text{$text} = $name;
1062             }
1063             }
1064              
1065 79         624 foreach my $text (keys %dups) {
1066 1         2 my $first;
1067             my $first_pri;
1068 1         6 my @names = sort {$a cmp $b} split(' ', $names_for_text{$text});
  1         5  
1069 1         3 foreach my $name (@names) {
1070 2   50     15 my $priority = $conf->{priority}->{$name} || 0;
1071              
1072 2 100 66     14 if (!defined $first || $priority < $first_pri) {
1073 1         6 $first_pri = $priority;
1074 1         3 $first = $name;
1075             }
1076             }
1077             # $first is now the earliest-occurring rule. mark others as dups
1078              
1079 1         3 my @dups;
1080 1         3 foreach my $name (@names) {
1081 2 100       5 next if $name eq $first;
1082 1         2 push @dups, $name;
1083 1         3 delete $conf->{tests}->{$name};
1084             }
1085              
1086 1         8 dbg("rules: $first merged duplicates: ".join(' ', @dups));
1087 1         8 $conf->{duplicate_rules}->{$first} = \@dups;
1088             }
1089             }
1090              
1091             sub pack_eval_method {
1092 2633     2633 0 4471 my ($self, $function, $args, $name, $text) = @_;
1093              
1094 2633         3060 my @args;
1095 2633 50       4183 if (defined $args) {
1096             # bug 4419: Parse quoted strings, unquoted alphanumerics/floats,
1097             # unquoted IPv4 and IPv6 addresses, and unquoted common domain names.
1098             # s// is used so that we can determine whether or not we successfully
1099             # parsed ALL arguments.
1100 2633         6882 local($1,$2,$3);
1101 2633         8119 while ($args =~ s/^\s* (?: (['"]) (.*?) \1 | ( [\d\.:A-Za-z-]+? ) )
1102             \s* (?: , \s* | $ )//x) {
1103 1579 50       3176 if (defined $2) {
1104 1579         5842 push @args, $2;
1105             }
1106             else {
1107 0         0 push @args, $3;
1108             }
1109             }
1110             }
1111              
1112 2633 50       4421 if ($args ne '') {
1113 0         0 $self->lint_warn("syntax error (unparsable argument: $args) for eval function: $name: $text", $name);
1114 0         0 return;
1115             }
1116              
1117 2633         3037 my $argstr = $function;
1118 2633         4235 $argstr =~ s/\s+//gs;
1119              
1120 2633 100       4370 if (@args > 0) {
1121             $argstr .= ',' . join(', ',
1122 1018         1562 map { my $s = $_; $s =~ s/\#/[HASH]/gs; 'q#' . $s . '#' } @args);
  1579         2034  
  1579         2078  
  1579         4495  
1123             }
1124 2633         6139 return ($argstr, \@args);
1125             }
1126              
1127             ###########################################################################
1128              
1129             sub lint_trusted_networks {
1130 79     79 0 179 my ($self) = @_;
1131 79         209 my $conf = $self->{conf};
1132              
1133             # validate trusted_networks and internal_networks, bug 4760.
1134             # check that all internal_networks are listed in trusted_networks
1135             # too. do the same for msa_networks, but check msa_networks against
1136             # internal_networks if trusted_networks aren't defined
1137              
1138 79         157 my ($nt, $matching_against);
1139 79 100       393 if ($conf->{trusted_networks_configured}) {
    100          
1140 28         75 $nt = $conf->{trusted_networks};
1141 28         71 $matching_against = 'trusted_networks';
1142             } elsif ($conf->{internal_networks_configured}) {
1143 1         5 $nt = $conf->{internal_networks};
1144 1         3 $matching_against = 'internal_networks';
1145             } else {
1146 50         130 return;
1147             }
1148              
1149 29         102 foreach my $net_type ('internal_networks', 'msa_networks') {
1150 58 100       219 next unless $conf->{"${net_type}_configured"};
1151 16 100       72 next if $net_type eq $matching_against;
1152              
1153 15         32 my $replace_nets;
1154             my @valid_net_list;
1155 15         52 my $net_list = $conf->{$net_type};
1156              
1157 15         30 foreach my $net (@{$net_list->{nets}}) {
  15         56  
1158             # don't check to see if an excluded network is included - that's senseless
1159 38 100 100     195 if (!$net->{exclude} && !$nt->contains_net($net)) {
1160             my $msg = "$matching_against doesn't contain $net_type entry '".
1161 4         26 ($net->{as_string})."'";
1162              
1163 4         29 $self->lint_warn($msg, undef); # complain
1164 4         15 $replace_nets = 1; # and omit it from the new internal set
1165             }
1166             else {
1167 34         87 push @valid_net_list, $net;
1168             }
1169             }
1170              
1171 15 100       87 if ($replace_nets) {
1172             # something was invalid. replace the old nets list with a fixed version
1173             # (which may be empty)
1174 4         35 $net_list->{nets} = \@valid_net_list;
1175             }
1176             }
1177             }
1178              
1179             ###########################################################################
1180              
1181             sub add_test {
1182 3569     3569 0 7141 my ($self, $name, $text, $type) = @_;
1183 3569         5481 my $conf = $self->{conf};
1184              
1185             # Don't allow invalid names ...
1186 3569 50       10829 if ($name !~ /^[_[:alpha:]]\w*$/) {
1187 0         0 $self->lint_warn("config: error: rule '$name' has invalid characters ".
1188             "(not Alphanumeric + Underscore + starting with a non-digit)\n", $name);
1189 0         0 return;
1190             }
1191              
1192             # Also set a hard limit for ALL rules (rule names longer than 40
1193             # characters throw warnings). Check this separately from the above
1194             # pattern to avoid vague error messages.
1195 3569 50       6968 if (length $name > 100) {
1196 0         0 $self->lint_warn("config: error: rule '$name' is too long ".
1197             "(recommended maximum length is 22 characters)\n", $name);
1198 0         0 return;
1199             }
1200              
1201             # Warn about, but use, long rule names during --lint
1202 3569 100       6804 if ($conf->{lint_rules}) {
1203 1792 0 33     4132 if (length($name) > 40 && $name !~ /^__/ && $name !~ /^T_/) {
      33        
1204 0         0 $self->lint_warn("config: warning: rule name '$name' is over 40 chars ".
1205             "(recommended maximum length is 22 characters)\n", $name);
1206             }
1207             }
1208              
1209             # all of these rule types are regexps
1210 3569 100 66     18317 if ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS ||
      66        
      66        
1211             $type == $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS ||
1212             $type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS ||
1213             $type == $Mail::SpamAssassin::Conf::TYPE_URI_TESTS)
1214             {
1215 297 50       926 return unless $self->is_delimited_regexp_valid($name, $text);
1216             }
1217 3569 100       7414 if ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS)
    100          
1218             {
1219             # RFC 5322 section 3.6.8, ftext printable US-ASCII chars not including ":"
1220             # no re "strict"; # since perl 5.21.8: Ranges of ASCII printables...
1221 567 100       1400 if ($text =~ /^!?defined\([!-9;-\176]+\)$/) {
1222             # fine, implements 'exists:'
1223             } else {
1224 504         6215 my ($pat) = ($text =~ /^\s*\S+\s*(?:\=|\!)\~\s*(\S.*?\S)\s*$/);
1225 504 50       1195 if ($pat) { $pat =~ s/\s+\[if-unset:\s+(.+)\]\s*$//; }
  504         1698  
1226 504 50       1558 return unless $self->is_delimited_regexp_valid($name, $pat);
1227             }
1228             }
1229             elsif ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS)
1230             {
1231 72 50       339 return unless $self->is_meta_valid($name, $text);
1232             }
1233              
1234 3569         10775 $conf->{tests}->{$name} = $text;
1235 3569         6642 $conf->{test_types}->{$name} = $type;
1236              
1237 3569 50       7317 if ($name =~ /AUTOLEARNTEST/i) {
1238 0         0 dbg("config: auto-learn: $name has type $type = $conf->{test_types}->{$name} during add_test\n");
1239             }
1240              
1241            
1242 3569 100       6238 if ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) {
1243 72   50     477 $conf->{priority}->{$name} ||= 500;
1244             }
1245             else {
1246 3497   50     10763 $conf->{priority}->{$name} ||= 0;
1247             }
1248 3569   100     11564 $conf->{priority}->{$name} ||= 0;
1249 3569         7827 $conf->{source_file}->{$name} = $self->{currentfile};
1250              
1251 3569 50       7313 if ($conf->{main}->{keep_config_parsing_metadata}) {
1252 0         0 $conf->{if_stack}->{$name} = $self->get_if_stack_as_string();
1253              
1254 0 0       0 if ($self->{file_scoped_attrs}->{testrules}) {
1255 0         0 $conf->{testrules}->{$name} = 1; # used in build/mkupdates/listpromotable
1256             }
1257             }
1258              
1259             # if we found this rule in a user_prefs file, it's a user rule -- note that
1260             # we may need to recompile the rule code for this type (if they've already
1261             # been compiled, e.g. in spamd).
1262             #
1263             # Note: the want_rebuild_for_type 'flag' is actually a counter; it is decremented
1264             # after each scan. This ensures that we always recompile at least once more;
1265             # once to *define* the rule, and once afterwards to *undefine* the rule in the
1266             # compiled ruleset again.
1267             #
1268             # If two consecutive scans use user rules, that's ok -- the second one will
1269             # reset the counter, and we'll still recompile just once afterwards to undefine
1270             # the rule again.
1271             #
1272 3569 50       13868 if ($self->{scoresonly}) {
1273 0         0 $conf->{want_rebuild_for_type}->{$type} = 2;
1274 0         0 $conf->{user_defined_rules}->{$name} = 1;
1275             }
1276             }
1277              
1278             sub add_regression_test {
1279 0     0 0 0 my ($self, $name, $ok_or_fail, $string) = @_;
1280 0         0 my $conf = $self->{conf};
1281              
1282 0 0       0 if ($conf->{regression_tests}->{$name}) {
1283 0         0 push @{$conf->{regression_tests}->{$name}}, [$ok_or_fail, $string];
  0         0  
1284             }
1285             else {
1286             # initialize the array, and create one element
1287 0         0 $conf->{regression_tests}->{$name} = [ [$ok_or_fail, $string] ];
1288             }
1289             }
1290              
1291             sub is_meta_valid {
1292 72     72 0 264 my ($self, $name, $rule) = @_;
1293              
1294             # $meta is a degenerate translation of the rule, replacing all variables (i.e. rule names) with 0.
1295 72         178 my $meta = '';
1296 72         316 $rule = untaint_var($rule); # must be careful below
1297             # Bug #7557 code injection
1298 72 50       868 if ( $rule =~ /\S(::|->)\S/ ) {
1299 0         0 warn("is_meta_valid: Bogus rule $name: $rule") ;
1300 0         0 return 0;
1301             }
1302              
1303             # Lex the rule into tokens using a rather simple RE method ...
1304 72         208 my $lexer = ARITH_EXPRESSION_LEXER;
1305 72         1206 my @tokens = ($rule =~ m/$lexer/og);
1306 72 50       329 if (length($name) == 1) {
1307 0         0 for (@tokens) {
1308 0 0       0 print "$name $_\n " or die "Error writing token: $!";
1309             }
1310             }
1311             # Go through each token in the meta rule
1312 72         315 foreach my $token (@tokens) {
1313             # If the token is a syntactically legal rule name, make it zero
1314 581 100       1753 if ($token =~ /^[_[:alpha:]]\w+\z/s) {
    50          
1315 215         402 $meta .= "0 ";
1316             }
1317             # if it is a number or a string of 1 or 2 punctuation characters (i.e. operators) tack it onto the degenerate rule
1318             elsif ( $token =~ /^(\d+|[[:punct:]]{1,2})\z/s ) {
1319 366         704 $meta .= "$token ";
1320             }
1321             # WTF is it? Just warn, for now. Bug #7557
1322             else {
1323 0         0 $self->lint_warn("config: Strange rule token: $token", $name);
1324 0         0 $meta .= "$token ";
1325             }
1326             }
1327 72         288 my $evalstr = 'my $x = ' . $meta . '; 1;';
1328 72 50       5371 if (eval $evalstr) {
1329 72         508 return 1;
1330             }
1331 0 0       0 my $err = $@ ne '' ? $@ : "errno=$!"; chomp $err;
  0         0  
1332 0         0 $err =~ s/\s+(?:at|near)\b.*//s;
1333 0         0 $err =~ s/Illegal division by zero/division by zero possible/i;
1334 0         0 $self->lint_warn("config: invalid expression for rule $name: \"$rule\": $err\n", $name);
1335 0         0 return 0;
1336             }
1337              
1338             sub is_delimited_regexp_valid {
1339 1242     1242 0 2421 my ($self, $name, $re) = @_;
1340              
1341 1242 50 33     7627 if (!$re || $re !~ /^\s*m?(\W).*(?:\1|>|}|\)|\])[a-z]*\s*$/) {
1342 0   0     0 $re ||= '';
1343 0         0 $self->lint_warn("config: invalid regexp for rule $name: $re: missing or invalid delimiters\n", $name);
1344 0         0 return 0;
1345             }
1346 1242         2964 return $self->is_regexp_valid($name, $re);
1347             }
1348              
1349             sub is_regexp_valid {
1350 1266     1266 0 2730 my ($self, $name, $re) = @_;
1351              
1352             # OK, try to remove any normal perl-style regexp delimiters at
1353             # the start and end, and modifiers at the end if present,
1354             # so we can validate those too.
1355 1266         1898 my $origre = $re;
1356 1266         1703 my $safere = $re;
1357 1266         1771 my $mods = '';
1358 1266         3661 local ($1,$2);
1359 1266 100       9491 if ($re =~ s/^m\{//) {
    100          
    100          
    100          
    100          
1360 64         400 $re =~ s/\}([a-z]*)\z//; $mods = $1;
  64         207  
1361             }
1362             elsif ($re =~ s/^m\(//) {
1363 1         5 $re =~ s/\)([a-z]*)\z//; $mods = $1;
  1         4  
1364             }
1365             elsif ($re =~ s/^m<//) {
1366 1         5 $re =~ s/>([a-z]*)\z//; $mods = $1;
  1         5  
1367             }
1368             elsif ($re =~ s/^m(\W)//) {
1369 129         1178 $re =~ s/\Q$1\E([a-z]*)\z//; $mods = $1;
  129         397  
1370             }
1371             elsif ($re =~ s{^/(.*)/([a-z]*)\z}{$1}) {
1372 1057         2140 $mods = $2;
1373             }
1374             else {
1375 14         36 $safere = "m#".$re."#";
1376             }
1377              
1378 1266 100 66     4654 if ($self->{conf}->{lint_rules} ||
1379             $self->{conf}->{ignore_always_matching_regexps})
1380             {
1381 608         1284 my $msg = $self->is_always_matching_regexp($name, $re);
1382              
1383 608 50       1218 if (defined $msg) {
1384 0 0       0 if ($self->{conf}->{lint_rules}) {
1385 0         0 $self->lint_warn($msg, $name);
1386             } else {
1387 0         0 warn $msg;
1388 0         0 return 0;
1389             }
1390             }
1391             }
1392              
1393             # now prepend the modifiers, in order to check if they're valid
1394 1266 100       2383 if ($mods) {
1395 907         1994 $re = "(?" . $mods . ")" . $re;
1396             }
1397              
1398             # note: this MUST use m/...${re}.../ in some form or another, ie.
1399             # interpolation of the $re variable into a code regexp, in order to test the
1400             # security of the regexp. simply using ("" =~ $re) will NOT do that, and
1401             # will therefore open a hole!
1402             { # no re "strict"; # since perl 5.21.8: Ranges of ASCII printables...
1403 1266 100       1636 if (eval { ("" =~ m{$re}); 1; }) { return 1 }
  1266         1730  
  1266         40270  
  1262         4158  
  1262         5708  
1404             }
1405 4 50       14 my $err = $@ ne '' ? $@ : "errno=$!"; chomp $err;
  4         12  
1406 4         32 $err =~ s/ at .*? line \d.*$//;
1407 4         31 $self->lint_warn("config: invalid regexp for rule $name: $origre: $err\n", $name);
1408 4         29 return 0;
1409             }
1410              
1411             # check the pattern for some basic errors, and warn if found
1412             sub is_always_matching_regexp {
1413 620     620 0 2475 my ($self, $name, $re) = @_;
1414              
1415 620 100       2416 if ($re =~ /(?<!\\)\|\|/) {
    100          
    100          
1416 2         16 return "config: regexp for rule $name always matches due to '||'";
1417             }
1418             elsif ($re =~ /^\|/) {
1419 1         15 return "config: regexp for rule $name always matches due to " .
1420             "pattern starting with '|'";
1421             }
1422             elsif ($re =~ /\|(?<!\\\|)$/) {
1423 1         10 return "config: regexp for rule $name always matches due to " .
1424             "pattern ending with '|'";
1425             }
1426 616         1156 return;
1427             }
1428              
1429             ###########################################################################
1430              
1431             sub add_to_addrlist {
1432 31     31 0 63 my ($self, $singlelist, @addrs) = @_;
1433 31         40 my $conf = $self->{conf};
1434              
1435 31         50 foreach my $addr (@addrs) {
1436 31         45 $addr = lc $addr;
1437 31         35 my $re = $addr;
1438 31         64 $re =~ s/[\000\\\(]/_/gs; # paranoia
1439 31         193 $re =~ s/([^\*\?_a-zA-Z0-9])/\\$1/g; # escape any possible metachars
1440 31         66 $re =~ tr/?/./; # "?" -> "."
1441 31         53 $re =~ s/\*+/\.\*/g; # "*" -> "any string"
1442 31         136 $conf->{$singlelist}->{$addr} = "^${re}\$";
1443             }
1444             }
1445              
1446             sub add_to_addrlist_rcvd {
1447 0     0 0 0 my ($self, $listname, $addr, $domain) = @_;
1448 0         0 my $conf = $self->{conf};
1449              
1450 0         0 $domain = lc $domain;
1451 0         0 $addr = lc $addr;
1452 0 0       0 if ($conf->{$listname}->{$addr}) {
1453 0         0 push @{$conf->{$listname}->{$addr}{domain}}, $domain;
  0         0  
1454             }
1455             else {
1456 0         0 my $re = $addr;
1457 0         0 $re =~ s/[\000\\\(]/_/gs; # paranoia
1458 0         0 $re =~ s/([^\*\?_a-zA-Z0-9])/\\$1/g; # escape any possible metachars
1459 0         0 $re =~ tr/?/./; # "?" -> "."
1460 0         0 $re =~ s/\*+/\.\*/g; # "*" -> "any string"
1461 0         0 $conf->{$listname}->{$addr}{re} = "^${re}\$";
1462 0         0 $conf->{$listname}->{$addr}{domain} = [ $domain ];
1463             }
1464             }
1465              
1466             sub remove_from_addrlist {
1467 0     0 0 0 my ($self, $singlelist, @addrs) = @_;
1468 0         0 my $conf = $self->{conf};
1469              
1470 0         0 foreach my $addr (@addrs) {
1471 0         0 delete($conf->{$singlelist}->{lc $addr});
1472             }
1473             }
1474              
1475             sub remove_from_addrlist_rcvd {
1476 0     0 0 0 my ($self, $listname, @addrs) = @_;
1477 0         0 my $conf = $self->{conf};
1478              
1479 0         0 foreach my $addr (@addrs) {
1480 0         0 delete($conf->{$listname}->{lc $addr});
1481             }
1482             }
1483              
1484             sub add_to_addrlist_dkim {
1485 0     0 0 0 add_to_addrlist_rcvd(@_);
1486             }
1487              
1488             sub remove_from_addrlist_dkim {
1489 0     0 0 0 my ($self, $listname, $addr, $domain) = @_;
1490 0         0 my $conf = $self->{conf};
1491 0         0 my $conf_lname = $conf->{$listname};
1492              
1493 0         0 $addr = lc $addr;
1494 0 0       0 if ($conf_lname->{$addr}) {
1495 0         0 $domain = lc $domain;
1496 0         0 my $domains_listref = $conf_lname->{$addr}{domain};
1497             # removing $domain from the list
1498 0         0 my @replacement = grep { lc $_ ne $domain } @$domains_listref;
  0         0  
1499 0 0       0 if (!@replacement) { # nothing left, remove the entire addr entry
    0          
1500 0         0 delete($conf_lname->{$addr});
1501             } elsif (@replacement != @$domains_listref) { # anything changed?
1502 0         0 $conf_lname->{$addr}{domain} = \@replacement;
1503             }
1504             }
1505             }
1506              
1507              
1508             ###########################################################################
1509              
1510             sub fix_path_relative_to_current_file {
1511 0     0 0 0 my ($self, $path) = @_;
1512              
1513             # the path may be specified as "~/foo", so deal with that
1514 0         0 $path = $self->{conf}->{main}->sed_path($path);
1515              
1516 0 0       0 if (!File::Spec->file_name_is_absolute ($path)) {
1517 0         0 my ($vol, $dirs, $file) = File::Spec->splitpath ($self->{currentfile});
1518 0         0 $path = File::Spec->catpath ($vol, $dirs, $path);
1519 0         0 dbg("config: fixed relative path: $path");
1520             }
1521 0         0 return $path;
1522             }
1523              
1524             ###########################################################################
1525              
1526             sub lint_warn {
1527 14     14 0 45 my ($self, $msg, $rule, $iserror) = @_;
1528              
1529 14 100       41 if (!defined $iserror) { $iserror = 1; }
  8         19  
1530              
1531 14 100       52 if ($self->{conf}->{main}->{lint_callback}) {
    50          
1532 4         29 $self->{conf}->{main}->{lint_callback}->(
1533             msg => $msg,
1534             rule => $rule,
1535             iserror => $iserror
1536             );
1537             }
1538             elsif ($self->{conf}->{lint_rules}) {
1539 0         0 warn $msg."\n";
1540             }
1541             else {
1542 10         39 info($msg);
1543             }
1544              
1545 14 50       359 if ($iserror) {
1546 14         51 $self->{conf}->{errors}++;
1547             }
1548             }
1549              
1550             ###########################################################################
1551              
1552             sub get_if_stack_as_string {
1553 0     0 0   my ($self) = @_;
1554             return join ' ', map {
1555             $_->{conditional}
1556 0           } @{$self->{if_stack}};
  0            
  0            
1557             }
1558              
1559             ###########################################################################
1560              
1561             1;