File Coverage

blib/lib/Mail/SpamAssassin/Conf/Parser.pm
Criterion Covered Total %
statement 450 669 67.2
branch 207 338 61.2
condition 58 127 45.6
subroutine 40 58 68.9
pod 0 48 0.0
total 755 1240 60.8


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;