Index: lib/Mail/SpamAssassin/Conf.pm =================================================================== --- lib/Mail/SpamAssassin/Conf.pm (revision 579957) +++ lib/Mail/SpamAssassin/Conf.pm (working copy) @@ -218,6 +218,10 @@ reports, but assigning a score of 0 to an indirect rule will disable it from running. +If you want to set the score for one rule in terms of other rules, or +modify an existing score by means other than adding or subtracting an +absolute value to/from it, then see C. + =cut push (@cmds, { @@ -2308,6 +2312,107 @@ } }); +=item meta_score SYMBOLIC_TEST_NAME formula + +Assign scores (the number of points for a hit) to a given test via a +formula which refers to the score for other tests (see C for +basic information on scores). The simplest way to use it is + + meta_score TEST_B TEST_A + +which will make the scores for TEST_B identical to TEST_A. If using a +formula, note that the formula is repeated once for each scoreset. +For example: + + score TEST_A 1 2 3 4 + score TEST_B 2 3 4 5 + + meta_score TEST_C (TEST_A + TEST_B) + meta_score TEST_D (TEST_B - TEST_A) + +will give the same results as + + score TEST_C 3 5 7 9 + score TEST_D 1 1 1 1 + +C can also be used to modify the value for an existing +score: + + meta_score TEST_A (TEST_A / 3) + +though if you just want to increase or descrease the value by an +absolute amount you should use the relative score capability of +C. + +The formula is computed relative to the values for the scores as they +are at the time the C statement is read, so if the scores +the formula is dependent on change after the C statement, +the results of the formula will not change. Also note that all test +scores that the formula depends on must already be defined by the time +the statement is read. + +=cut + + push (@cmds, { + setting => 'meta_score', + code => sub { + my ($self, $key, $value, $line) = @_; + my @values = split(/\s+/, $value, 2); + if (@values != 2) { + return $MISSING_REQUIRED_VALUE; + } + + my ($rule, $formula) = @values; + + # Lex the formula into tokens using a rather simple RE method ... + my $lexer = ARITH_EXPRESSION_LEXER; + my @tokens = ($formula =~ m/$lexer/g); + + foreach my $token (@tokens) { + if ($token =~ /[a-z_]/i) { + unless (exists $self->{scoreset}->[0]->{$token}) { + info("config: score for '$token' must be defined before " . + "meta score '$rule' can use it."); + return $INVALID_VALUE; + } + } + } + + my @new_scores; + + for my $set (0 .. 3) { + my $evalstr = ""; + + foreach my $token (@tokens) { + if ($token =~ /[a-z_]/i) { + $evalstr .= "$self->{scoreset}->[$set]->{$token} "; + } + else { + $evalstr .= "$token "; + } + } + $evalstr .= ";"; + + $evalstr = untaint_var($evalstr); + my $score = eval($evalstr); + + if (!defined $score) { + my $err = $@ ne '' ? $@ : "errno=$!"; chomp $err; + + warn("config: invalid expression for forumula $rule: " . + "\"$formula\": $err\n"); + + return $INVALID_VALUE; + } + $new_scores[$set] = $score + 0.0; + } + + for my $set (0 .. 3) { + $self->{scoreset}->[$set]->{$rule} = $new_scores[$set]; + } + } + }); + =item tflags SYMBOLIC_TEST_NAME [ {net|nice|learn|userconf|noautolearn|multiple} ] Used to set flags on a test. These flags are used in the