~ubuntu-branches/ubuntu/utopic/spamassassin/utopic-updates

« back to all changes in this revision

Viewing changes to lib/Mail/SpamAssassin/PerMsgStatus.pm

  • Committer: Package Import Robot
  • Author(s): Noah Meyerhans
  • Date: 2014-02-14 22:45:15 UTC
  • mfrom: (0.8.1) (0.6.2) (5.1.22 sid)
  • Revision ID: package-import@ubuntu.com-20140214224515-z1es2twos8xh7n2y
Tags: 3.4.0-1
* New upstream version! (Closes: 738963, 738872, 738867)
* Scrub the environment when switching to the debian-spamd user in
  postinst and cron.daily. (Closes: 738951)
* Enhancements to postinst to better manage ownership of
  /var/lib/spamassassin, via Iain Lane <iain.lane@canonical.com>
  (Closes: 738974)

Show diffs side-by-side

added added

removed removed

Lines of Context:
53
53
use warnings;
54
54
use re 'taint';
55
55
 
 
56
use Errno qw(ENOENT);
56
57
use Time::HiRes qw(time);
57
58
 
58
59
use Mail::SpamAssassin::Constants qw(:sa);
77
78
 
78
79
###########################################################################
79
80
 
 
81
use vars qw( %common_tags );
 
82
 
 
83
BEGIN {
 
84
  %common_tags = (
 
85
 
 
86
    YESNO => sub {
 
87
      my $pms = shift;
 
88
      $pms->_get_tag_value_for_yesno(@_);
 
89
    },
 
90
  
 
91
    YESNOCAPS => sub {
 
92
      my $pms = shift;
 
93
      uc $pms->_get_tag_value_for_yesno(@_);
 
94
    },
 
95
 
 
96
    SCORE => sub {
 
97
      my $pms = shift;
 
98
      $pms->_get_tag_value_for_score(@_);
 
99
    },
 
100
 
 
101
    HITS => sub {
 
102
      my $pms = shift;
 
103
      $pms->_get_tag_value_for_score(@_);
 
104
    },
 
105
 
 
106
    REQD => sub {
 
107
      my $pms = shift;
 
108
      $pms->_get_tag_value_for_required_score(@_);
 
109
    },
 
110
 
 
111
    VERSION => \&Mail::SpamAssassin::Version,
 
112
 
 
113
    SUBVERSION => sub { $Mail::SpamAssassin::SUB_VERSION },
 
114
 
 
115
    RULESVERSION => sub {
 
116
      my $pms = shift;
 
117
      my $conf = $pms->{conf};
 
118
      my @fnames;
 
119
      @fnames =
 
120
        keys %{$conf->{update_version}}  if $conf->{update_version};
 
121
      @fnames = sort @fnames  if @fnames > 1;
 
122
      join(',', map($conf->{update_version}{$_}, @fnames));
 
123
    },
 
124
 
 
125
    HOSTNAME => sub {
 
126
      my $pms = shift;
 
127
      $pms->{conf}->{report_hostname} ||
 
128
        Mail::SpamAssassin::Util::fq_hostname();
 
129
    },
 
130
 
 
131
    REMOTEHOSTNAME => sub {
 
132
      my $pms = shift;
 
133
      $pms->{tag_data}->{'REMOTEHOSTNAME'} || "localhost";
 
134
    },
 
135
 
 
136
    REMOTEHOSTADDR => sub {
 
137
      my $pms = shift;
 
138
      $pms->{tag_data}->{'REMOTEHOSTADDR'} || "127.0.0.1";
 
139
    },
 
140
 
 
141
    LASTEXTERNALIP => sub {
 
142
      my $pms = shift;
 
143
      my $lasthop = $pms->{relays_external}->[0];
 
144
      $lasthop ? $lasthop->{ip} : '';
 
145
    },
 
146
 
 
147
    LASTEXTERNALRDNS => sub {
 
148
      my $pms = shift;
 
149
      my $lasthop = $pms->{relays_external}->[0];
 
150
      $lasthop ? $lasthop->{rdns} : '';
 
151
    },
 
152
 
 
153
    LASTEXTERNALHELO => sub {
 
154
      my $pms = shift;
 
155
      my $lasthop = $pms->{relays_external}->[0];
 
156
      $lasthop ? $lasthop->{helo} : '';
 
157
    },
 
158
 
 
159
    CONTACTADDRESS => sub {
 
160
      my $pms = shift;
 
161
      $pms->{conf}->{report_contact};
 
162
    },
 
163
 
 
164
    BAYES => sub {
 
165
      my $pms = shift;
 
166
      defined $pms->{bayes_score} ? sprintf("%3.4f", $pms->{bayes_score})
 
167
                                   : "0.5";
 
168
    },
 
169
 
 
170
    DATE => \&Mail::SpamAssassin::Util::time_to_rfc822_date,
 
171
 
 
172
    STARS => sub {
 
173
      my $pms = shift;
 
174
      my $arg = (shift || "*");
 
175
      my $length = int($pms->{score});
 
176
      $length = 50 if $length > 50;
 
177
      $arg x $length;
 
178
    },
 
179
 
 
180
    AUTOLEARN => sub {
 
181
      my $pms = shift;
 
182
      $pms->get_autolearn_status();
 
183
    },
 
184
 
 
185
    AUTOLEARNSCORE => sub {
 
186
      my $pms = shift;
 
187
      $pms->get_autolearn_points();
 
188
    },
 
189
 
 
190
    TESTS => sub {
 
191
      my $pms = shift;
 
192
      my $arg = (shift || ',');
 
193
      join($arg, sort(@{$pms->{test_names_hit}})) || "none";
 
194
    },
 
195
 
 
196
    SUBTESTS => sub {
 
197
      my $pms = shift;
 
198
      my $arg = (shift || ',');
 
199
      join($arg, sort(@{$pms->{subtest_names_hit}})) || "none";
 
200
    },
 
201
 
 
202
    TESTSSCORES => sub {
 
203
      my $pms = shift;
 
204
      my $arg = (shift || ",");
 
205
      my $line = '';
 
206
      foreach my $test (sort @{$pms->{test_names_hit}}) {
 
207
        my $score = $pms->{conf}->{scores}->{$test};
 
208
        $score = '0'  if !defined $score;
 
209
        $line .= $arg  if $line ne '';
 
210
        $line .= $test . "=" . $score;
 
211
      }
 
212
      $line ne '' ? $line : 'none';
 
213
    },
 
214
 
 
215
    PREVIEW => sub {
 
216
      my $pms = shift;
 
217
      $pms->get_content_preview();
 
218
    },
 
219
 
 
220
    REPORT => sub {
 
221
      my $pms = shift;
 
222
      "\n" . ($pms->{tag_data}->{REPORT} || "");
 
223
    },
 
224
 
 
225
    HEADER => sub {
 
226
      my $pms = shift;
 
227
      my $hdr = shift;
 
228
      return if !$hdr;
 
229
      $pms->get($hdr,undef);
 
230
    },
 
231
 
 
232
    TIMING => sub {
 
233
      my $pms = shift;
 
234
      $pms->{main}->timer_report();
 
235
    },
 
236
 
 
237
    ADDEDHEADERHAM => sub {
 
238
      my $pms = shift;
 
239
      $pms->_get_added_headers('headers_ham');
 
240
    },
 
241
 
 
242
    ADDEDHEADERSPAM => sub {
 
243
      my $pms = shift;
 
244
      $pms->_get_added_headers('headers_spam');
 
245
    },
 
246
 
 
247
    ADDEDHEADER => sub {
 
248
      my $pms = shift;
 
249
      $pms->_get_added_headers(
 
250
                $pms->{is_spam} ? 'headers_spam' : 'headers_ham');
 
251
    },
 
252
 
 
253
  );
 
254
}
 
255
 
80
256
sub new {
81
257
  my $class = shift;
82
258
  $class = ref($class) || $class;
86
262
    'main'              => $main,
87
263
    'msg'               => $msg,
88
264
    'score'             => 0,
89
 
    'test_logs'         => '',
 
265
    'test_log_msgs'     => { },
90
266
    'test_names_hit'    => [ ],
91
267
    'subtest_names_hit' => [ ],
92
268
    'spamd_result_log_items' => [ ],
96
272
    'rule_errors'       => 0,
97
273
    'disable_auto_learning' => 0,
98
274
    'auto_learn_status' => undef,
 
275
    'auto_learn_force_status' => undef,
99
276
    'conf'              => $main->{conf},
100
277
    'async'             => Mail::SpamAssassin::AsyncLoop->new($main),
101
278
    'master_deadline'   => $msg->{master_deadline},  # dflt inherited from msg
137
314
  $self;
138
315
}
139
316
 
 
317
sub DESTROY {
 
318
  my ($self) = shift;
 
319
  local $@;
 
320
  eval { $self->delete_fulltext_tmpfile() };  # Bug 5808
 
321
}
 
322
 
140
323
###########################################################################
141
324
 
142
325
=item $status->check ()
169
352
  $self->{head_only_points} = 0;
170
353
  $self->{score} = 0;
171
354
 
 
355
  # clear NetSet cache before every check to prevent it growing too large
 
356
  foreach my $nset_name (qw(internal_networks trusted_networks msa_networks)) {
 
357
    my $netset = $self->{conf}->{$nset_name};
 
358
    $netset->ditch_cache()  if $netset;
 
359
  }
 
360
 
172
361
  $self->{main}->call_plugins ("check_start", { permsgstatus => $self });
173
362
 
174
363
  # in order of slowness; fastest first, slowest last.
265
454
    return;
266
455
  }
267
456
 
268
 
  my $isspam = $self->{main}->call_plugins ("autolearn_discriminator", {
 
457
  my ($isspam, $force_autolearn, $force_autolearn_names, $arrayref);
 
458
  $arrayref = $self->{main}->call_plugins ("autolearn_discriminator", {
269
459
      permsgstatus => $self
270
460
    });
271
461
 
 
462
  $isspam = $arrayref->[0];
 
463
  $force_autolearn = $arrayref->[1];
 
464
  $force_autolearn_names = $arrayref->[2];
 
465
 
 
466
  #AUTOLEARN_FORCE FLAG INFORMATION
 
467
  if (defined $force_autolearn and $force_autolearn > 0) {
 
468
    $self->{auto_learn_force_status} = "yes";
 
469
    if (defined $force_autolearn_names) {
 
470
      $self->{auto_learn_force_status} .= " ($force_autolearn_names)";
 
471
     }
 
472
  } else {
 
473
    $self->{auto_learn_force_status} = "no";
 
474
  }
 
475
 
272
476
  if (!defined $isspam) {
273
477
    $self->{auto_learn_status} = 'no';
274
478
    return;
275
479
  }
276
480
 
 
481
 
277
482
  my $timer = $self->{main}->time_method("learn");
278
483
 
279
484
  $self->{main}->call_plugins ("autolearn", {
373
578
  return $self->{body_only_points};
374
579
}
375
580
 
 
581
=item $score = $status->get_autolearn_force_status()
 
582
 
 
583
Return whether a message's score included any rules that are flagged as 
 
584
autolearn_force.
 
585
  
 
586
=cut
 
587
 
 
588
sub get_autolearn_force_status {
 
589
  my ($self) = @_;
 
590
  $self->_get_autolearn_points();
 
591
  return $self->{autolearn_force};
 
592
 
593
 
 
594
=item $rule_names = $status->get_autolearn_force_names()
 
595
 
 
596
Return a list of comma separated list of rule names if a message's 
 
597
score included any rules that are flagged as autolearn_force.
 
598
  
 
599
=cut
 
600
 
 
601
sub get_autolearn_force_names {
 
602
  my ($self) = @_;
 
603
  my ($names);
 
604
 
 
605
  $self->_get_autolearn_points();
 
606
  $names = $self->{autolearn_force_names};
 
607
 
 
608
  if (defined $names) { 
 
609
    #remove trailing comma
 
610
    $names =~ s/,$//;
 
611
  } else {
 
612
    $names = "";
 
613
  }
 
614
 
 
615
  return $names;
 
616
}
 
617
 
376
618
sub _get_autolearn_points {
377
619
  my ($self) = @_;
378
620
 
403
645
  $self->{learned_points} = 0;
404
646
  $self->{body_only_points} = 0;
405
647
  $self->{head_only_points} = 0;
 
648
  $self->{autolearn_force} = 0;
406
649
 
407
650
  foreach my $test (@{$self->{test_names_hit}}) {
408
651
    # According to the documentation, noautolearn, userconf, and learn
418
661
        $self->{learned_points} += $self->{conf}->{scoreset}->[$orig_scoreset]->{$test};
419
662
        next;
420
663
      }
 
664
    
 
665
      #IF ANY RULES ARE AUTOLEARN FORCE, SET THAT FLAG  
 
666
      if ($tflags->{$test} =~ /\bautolearn_force\b/) {
 
667
        $self->{autolearn_force}++;
 
668
        #ADD RULE NAME TO LIST
 
669
        $self->{autolearn_force_names}.="$test,";
 
670
      }
421
671
    }
422
672
 
423
673
    # ignore tests with 0 score (or undefined) in this scoreset
424
674
    next if !$scores->{$test};
425
675
 
426
 
    # Go ahead and add points to the proper locations
427
 
    if (!$self->{conf}->maybe_header_only ($test)) {
 
676
    # Go ahead and add points to the proper locations 
 
677
    # Changed logic because in testing, I was getting both head and body. Bug 5503
 
678
    if ($self->{conf}->maybe_header_only ($test)) {
 
679
      $self->{head_only_points} += $scores->{$test};
 
680
      dbg("learn: auto-learn: adding head_only points $scores->{$test}");
 
681
    } elsif ($self->{conf}->maybe_body_only ($test)) {
428
682
      $self->{body_only_points} += $scores->{$test};
429
 
    }
430
 
    if (!$self->{conf}->maybe_body_only ($test)) {
431
 
      $self->{head_only_points} += $scores->{$test};
 
683
      dbg("learn: auto-learn: adding body_only points $scores->{$test}");
 
684
    } else {
 
685
      dbg("learn: auto-learn: not considered head or body scores: $scores->{$test}");
432
686
    }
433
687
 
434
688
    $points += $scores->{$test};
539
793
return one of the following strings depending on whether the mail was
540
794
auto-learned or not: "ham", "no", "spam", "disabled", "failed", "unavailable".
541
795
 
 
796
It also returns is flagged with auto_learn_force, it will also include the status
 
797
and the rules hit.  For example: "autolearn_force=yes (AUTOLEARNTEST_BODY)"
 
798
 
542
799
=cut
543
800
 
544
801
sub get_autolearn_status {
545
802
  my ($self) = @_;
546
 
  return ($self->{auto_learn_status} || "unavailable");
 
803
  my ($status) = $self->{auto_learn_status} || "unavailable";
 
804
 
 
805
  if (defined $self->{auto_learn_force_status}) {
 
806
    $status .= " autolearn_force=".$self->{auto_learn_force_status};
 
807
  }
 
808
 
 
809
  return $status;
547
810
}
548
811
 
549
812
###########################################################################
703
966
  }
704
967
}
705
968
 
706
 
sub _get_added_headers($) {
 
969
sub _get_added_headers {
707
970
  my ($self, $which) = @_;
708
971
  my $str = '';
709
972
  # use string appends to put this back together -- I finally benchmarked it.
1029
1292
 
1030
1293
  # default to leaving the original string in place, if we cannot find
1031
1294
  # a tag for it (bug 4793)
1032
 
  my $t;
1033
 
  my $v;
1034
1295
  local($1,$2,$3);
1035
1296
  $text =~ s{(_(\w+?)(?:\((.*?)\))?_)}{
1036
1297
        my $full = $1;
1040
1301
          # Bug 6278: break infinite recursion through _get_added_headers and
1041
1302
          # _get_tag on an attempt to use such tag in add_header template
1042
1303
        } else {
1043
 
          $result = $self->_get_tag($tag,$3);
 
1304
          $result = $self->get_tag_raw($tag,$3);
 
1305
          $result = join(' ',@$result)  if ref $result eq 'ARRAY';
1044
1306
        }
1045
1307
        defined $result ? $result : $full;
1046
1308
      }ge;
1052
1314
 
1053
1315
# public API for plugins
1054
1316
 
 
1317
=item $status->action_depends_on_tags($tags, $code, @args)
 
1318
 
 
1319
Enqueue the supplied subroutine reference C<$code>, to become runnable when
 
1320
all the specified tags become available. The C<$tags> may be a simple
 
1321
scalar - a tag name, or a listref of tag names. The subroutine C<&$code>
 
1322
when called will be passed a C<permessagestatus> object as its first argument,
 
1323
followed by the supplied (optional) list C<@args> .
 
1324
 
 
1325
=cut
 
1326
 
 
1327
sub action_depends_on_tags {
 
1328
  my($self, $tags, $code, @args) = @_;
 
1329
 
 
1330
  ref $code eq 'CODE'
 
1331
    or die "action_depends_on_tags: argument must be a subroutine ref";
 
1332
 
 
1333
  # tag names on which the given action depends
 
1334
  my @dep_tags = !ref $tags ? uc $tags : map(uc($_),@$tags);
 
1335
 
 
1336
  # @{$self->{tagrun_subs}}            list of all submitted subroutines
 
1337
  # @{$self->{tagrun_actions}{$tag}}   bitmask of action indices blocked by tag
 
1338
  # $self->{tagrun_tagscnt}[$action_ind]  count of tags still pending
 
1339
 
 
1340
  # store action details, obtain its index
 
1341
  push(@{$self->{tagrun_subs}}, [$code,@args]);
 
1342
  my $action_ind = $#{$self->{tagrun_subs}};
 
1343
 
 
1344
  # list dependency tag names which are not already satistied
 
1345
  my @blocking_tags =
 
1346
    grep(!defined $self->{tag_data}{$_} || $self->{tag_data}{$_} eq '',
 
1347
         @dep_tags);
 
1348
 
 
1349
  $self->{tagrun_tagscnt}[$action_ind] = scalar @blocking_tags;
 
1350
  $self->{tagrun_actions}{$_}[$action_ind] = 1  for @blocking_tags;
 
1351
 
 
1352
  if (@blocking_tags) {
 
1353
    dbg("check: tagrun - action %s blocking on tags %s",
 
1354
        $action_ind, join(', ',@blocking_tags));
 
1355
  } else {
 
1356
    dbg("check: tagrun - tag %s was ready, action %s runnable immediately: %s",
 
1357
        join(', ',@dep_tags), $action_ind, join(', ',$code,@args));
 
1358
    &$code($self, @args);
 
1359
  }
 
1360
}
 
1361
 
 
1362
# tag_is_ready() will be called by set_tag(), indicating that a given
 
1363
# tag just received its value, possibly unblocking an action routine
 
1364
# as declared by action_depends_on_tags().
 
1365
#
 
1366
# Well-behaving plugins should call set_tag() once when a tag is fully
 
1367
# assembled and ready. Multiple calls to set the same tag value are handled
 
1368
# gracefully, but may result in premature activation of a pending action.
 
1369
# Setting tag values by plugins should not be done directly but only through
 
1370
# the public API set_tag(), otherwise a pending action release may be missed.
 
1371
#
 
1372
sub tag_is_ready {
 
1373
  my($self, $tag) = @_;
 
1374
  $tag = uc $tag;
 
1375
 
 
1376
  if (would_log('dbg', 'check')) {
 
1377
    my $tag_val = $self->{tag_data}{$tag};
 
1378
    dbg("check: tagrun - tag %s is now ready, value: %s",
 
1379
         $tag, !defined $tag_val ? '<UNDEF>'
 
1380
               : ref $tag_val ne 'ARRAY' ? $tag_val
 
1381
               : 'ARY:[' . join(',',@$tag_val) . ']' );
 
1382
  }
 
1383
  if (ref $self->{tagrun_actions}{$tag}) {  # any action blocking on this tag?
 
1384
    my $action_ind = 0;
 
1385
    foreach my $action_pending (@{$self->{tagrun_actions}{$tag}}) {
 
1386
      if ($action_pending) {
 
1387
        $self->{tagrun_actions}{$tag}[$action_ind] = 0;
 
1388
        if ($self->{tagrun_tagscnt}[$action_ind] <= 0) {
 
1389
          # should not happen, warn and ignore
 
1390
          warn "tagrun error: count for $action_ind is ".
 
1391
                $self->{tagrun_tagscnt}[$action_ind]."\n";
 
1392
        } elsif (! --($self->{tagrun_tagscnt}[$action_ind])) {
 
1393
          my($code,@args) = @{$self->{tagrun_subs}[$action_ind]};
 
1394
          dbg("check: tagrun - tag %s unblocking the action %s: %s",
 
1395
              $tag, $action_ind, join(', ',$code,@args));
 
1396
          &$code($self, @args);
 
1397
        }
 
1398
      }
 
1399
      $action_ind++;
 
1400
    }
 
1401
  }
 
1402
}
 
1403
 
 
1404
# debugging aid: show actions that are still pending, waiting for their
 
1405
# tags to receive a value
 
1406
#
 
1407
sub report_unsatisfied_actions {
 
1408
  my($self) = @_;
 
1409
  my @tags;
 
1410
  @tags = keys %{$self->{tagrun_actions}}  if ref $self->{tagrun_actions};
 
1411
  for my $tag (@tags) {
 
1412
    my @pending_actions = grep($self->{tagrun_actions}{$tag}[$_],
 
1413
                               (0 .. $#{$self->{tagrun_actions}{$tag}}));
 
1414
    dbg("check: tagrun - tag %s is still blocking action %s",
 
1415
        $tag, join(', ', @pending_actions))  if @pending_actions;
 
1416
  }
 
1417
}
 
1418
 
1055
1419
=item $status->set_tag($tagname, $value)
1056
1420
 
1057
 
Set a template tag, as used in C<add_header>, report templates, etc. This API
1058
 
is intended for use by plugins.   Tag names will be converted to an
1059
 
all-uppercase representation internally.
1060
 
 
1061
 
C<$value> can be a subroutine reference, which will be evaluated each time
1062
 
the template is expanded.  Note that perl supports closures, which means
1063
 
that variables set in the caller's scope can be accessed inside this C<sub>.
1064
 
For example:
 
1421
Set a template tag, as used in C<add_header>, report templates, etc.
 
1422
This API is intended for use by plugins.  Tag names will be converted
 
1423
to an all-uppercase representation internally.
 
1424
 
 
1425
C<$value> can be a simple scalar (string or number), or a reference to an
 
1426
array, in which case the public method get_tag will join array elements
 
1427
using a space as a separator, returning a single string for backward
 
1428
compatibility.
 
1429
 
 
1430
C<$value> can also be a subroutine reference, which will be evaluated
 
1431
each time the template is expanded. The first argument passed by get_tag
 
1432
to a called subroutine will be a PerMsgStatus object (this module's object),
 
1433
followed by optional arguments provided a caller to get_tag.
 
1434
 
 
1435
Note that perl supports closures, which means that variables set in the
 
1436
caller's scope can be accessed inside this C<sub>. For example:
1065
1437
 
1066
1438
    my $text = "hello world!";
1067
1439
    $status->set_tag("FOO", sub {
 
1440
              my $pms = shift;
1068
1441
              return $text;
1069
1442
            });
1070
1443
 
1071
 
See C<Mail::SpamAssassin::Conf>'s C<TEMPLATE TAGS> section for more details on
1072
 
how template tags are used.
 
1444
See C<Mail::SpamAssassin::Conf>'s C<TEMPLATE TAGS> section for more details
 
1445
on how template tags are used.
1073
1446
 
1074
1447
C<undef> will be returned if a tag by that name has not been defined.
1075
1448
 
1076
1449
=cut
1077
1450
 
1078
1451
sub set_tag {
1079
 
  my $self = shift;
1080
 
  my $tag  = uc shift;
1081
 
  my $val  = shift;
1082
 
 
1083
 
  $self->{tag_data}->{$tag} = $val;
 
1452
  my($self,$tag,$val) = @_;
 
1453
  $self->{tag_data}->{uc $tag} = $val;
 
1454
  $self->tag_is_ready($tag);
1084
1455
}
1085
1456
 
1086
1457
# public API for plugins
1098
1469
=cut
1099
1470
 
1100
1471
sub get_tag {
1101
 
  # expose this previously-private API
1102
 
  return shift->_get_tag(uc shift);
 
1472
  my($self, $tag, @args) = @_;
 
1473
 
 
1474
  return if !defined $tag;
 
1475
  $tag = uc $tag;
 
1476
  my $data;
 
1477
  if (exists $common_tags{$tag}) {
 
1478
    # tag data from traditional pre-defined tag subroutines
 
1479
    $data = $common_tags{$tag};
 
1480
    $data = $data->($self,@args)  if ref $data eq 'CODE';
 
1481
    $data = join(' ',@$data)  if ref $data eq 'ARRAY';
 
1482
    $data = ""  if !defined $data;
 
1483
  } elsif (exists $self->{tag_data}->{$tag}) {
 
1484
    # tag data comes from $self->{tag_data}->{TAG}, typically from plugins
 
1485
    $data = $self->{tag_data}->{$tag};
 
1486
    $data = $data->($self,@args)  if ref $data eq 'CODE';
 
1487
    $data = join(' ',@$data)  if ref $data eq 'ARRAY';
 
1488
    $data = ""  if !defined $data;
 
1489
  }
 
1490
  return $data;
 
1491
}
 
1492
 
 
1493
=item $string = $status->get_tag_raw($tagname, @args)
 
1494
 
 
1495
Similar to C<get_tag>, but keeps a tag name unchanged (does not uppercase it),
 
1496
and does not convert arrayref tag values into a single string.
 
1497
 
 
1498
=cut
 
1499
 
 
1500
sub get_tag_raw {
 
1501
  my($self, $tag, @args) = @_;
 
1502
 
 
1503
  return if !defined $tag;
 
1504
  my $data;
 
1505
  if (exists $common_tags{$tag}) {
 
1506
    # tag data from traditional pre-defined tag subroutines
 
1507
    $data = $common_tags{$tag};
 
1508
    $data = $data->($self,@args)  if ref $data eq 'CODE';
 
1509
    $data = ""  if !defined $data;
 
1510
  } elsif (exists $self->{tag_data}->{$tag}) {
 
1511
    # tag data comes from $self->{tag_data}->{TAG}, typically from plugins
 
1512
    $data = $self->{tag_data}->{$tag};
 
1513
    $data = $data->($self,@args)  if ref $data eq 'CODE';
 
1514
    $data = ""  if !defined $data;
 
1515
  }
 
1516
  return $data;
1103
1517
}
1104
1518
 
1105
1519
###########################################################################
1153
1567
}
1154
1568
 
1155
1569
sub _get_tag_value_for_score {
1156
 
  #$pad parameter never used.  removed.
1157
 
  my ($self) = @_;
 
1570
  my ($self, $pad) = @_;
1158
1571
 
1159
1572
  my $score  = sprintf("%2.1f", $self->{score});
1160
1573
  my $rscore = $self->_get_tag_value_for_required_score();
1161
1574
 
1162
1575
  #Change due to bug 6419 to use Util function for consistency with spamd
1163
1576
  #and PerMessageStatus
1164
 
  return Mail::SpamAssassin::Util::get_tag_value_for_score($score, $rscore, $self->{is_spam});
 
1577
  $score = Mail::SpamAssassin::Util::get_tag_value_for_score($score, $rscore, $self->{is_spam});
 
1578
 
 
1579
  #$pad IS PROVIDED BY THE _SCORE(PAD)_ tag
 
1580
  if (defined $pad && $pad =~ /^(0+| +)$/) {
 
1581
    my $count = length($1) + 3 - length($score);
 
1582
    $score = (substr($pad, 0, $count) . $score) if $count > 0;
 
1583
  }
 
1584
  return $score;
 
1585
 
1165
1586
}
1166
1587
 
1167
1588
sub _get_tag_value_for_required_score {
1168
 
  my $self  = shift;
 
1589
  my $self = shift;
1169
1590
  return sprintf("%2.1f", $self->{conf}->{required_score});
1170
1591
}
1171
1592
 
1172
 
sub _get_tag {
1173
 
  my $self = shift;
1174
 
  my $tag = shift;
1175
 
  my %tags;
1176
 
 
1177
 
  # tag data also comes from $self->{tag_data}->{TAG}
1178
 
 
1179
 
  $tag = "" unless defined $tag; # can be "0", so use a defined test
1180
 
 
1181
 
  %tags = ( YESNO     => sub {    $self->_get_tag_value_for_yesno(@_) },
1182
 
  
1183
 
            YESNOCAPS => sub { uc $self->_get_tag_value_for_yesno(@_) },
1184
 
 
1185
 
            SCORE => sub { $self->_get_tag_value_for_score(shift) },
1186
 
            HITS  => sub { $self->_get_tag_value_for_score(shift) },
1187
 
 
1188
 
            REQD  => sub { $self->_get_tag_value_for_required_score() },
1189
 
 
1190
 
            VERSION => \&Mail::SpamAssassin::Version,
1191
 
 
1192
 
            SUBVERSION => sub { $Mail::SpamAssassin::SUB_VERSION },
1193
 
 
1194
 
            HOSTNAME => sub {
1195
 
              $self->{conf}->{report_hostname} ||
1196
 
              Mail::SpamAssassin::Util::fq_hostname();
1197
 
            },
1198
 
 
1199
 
            REMOTEHOSTNAME => sub {
1200
 
              $self->{tag_data}->{'REMOTEHOSTNAME'} || "localhost";
1201
 
            },
1202
 
            REMOTEHOSTADDR => sub {
1203
 
              $self->{tag_data}->{'REMOTEHOSTADDR'} || "127.0.0.1";
1204
 
            },
1205
 
 
1206
 
            LASTEXTERNALIP => sub {
1207
 
              my $lasthop = $self->{relays_external}->[0];
1208
 
              $lasthop ? $lasthop->{ip} : '';
1209
 
            },
1210
 
 
1211
 
            LASTEXTERNALRDNS => sub {
1212
 
              my $lasthop = $self->{relays_external}->[0];
1213
 
              $lasthop ? $lasthop->{rdns} : '';
1214
 
            },
1215
 
 
1216
 
            LASTEXTERNALHELO => sub {
1217
 
              my $lasthop = $self->{relays_external}->[0];
1218
 
              $lasthop ? $lasthop->{helo} : '';
1219
 
            },
1220
 
 
1221
 
            CONTACTADDRESS => sub { $self->{conf}->{report_contact} },
1222
 
 
1223
 
            BAYES => sub {
1224
 
              defined($self->{bayes_score}) ?
1225
 
                        sprintf("%3.4f", $self->{bayes_score}) : "0.5"
1226
 
            },
1227
 
 
1228
 
            DATE => \&Mail::SpamAssassin::Util::time_to_rfc822_date,
1229
 
 
1230
 
            STARS => sub {
1231
 
              my $arg = (shift || "*");
1232
 
              my $length = int($self->{score});
1233
 
              $length = 50 if $length > 50;
1234
 
              $arg x $length;
1235
 
            },
1236
 
 
1237
 
            AUTOLEARN => sub { $self->get_autolearn_status() },
1238
 
 
1239
 
            AUTOLEARNSCORE => sub { $self->get_autolearn_points() },
1240
 
 
1241
 
            TESTS => sub {
1242
 
              my $arg = (shift || ',');
1243
 
              join($arg, sort(@{$self->{test_names_hit}})) || "none";
1244
 
            },
1245
 
 
1246
 
            SUBTESTS => sub {
1247
 
              my $arg = (shift || ',');
1248
 
              join($arg, sort(@{$self->{subtest_names_hit}})) || "none";
1249
 
            },
1250
 
 
1251
 
            TESTSSCORES => sub {
1252
 
              my $arg = (shift || ",");
1253
 
              my $line = '';
1254
 
              foreach my $test (sort @{$self->{test_names_hit}}) {
1255
 
                my $score = $self->{conf}->{scores}->{$test};
1256
 
                $score = '0'  if !defined $score;
1257
 
                $line .= $arg  if $line ne '';
1258
 
                $line .= $test . "=" . $score;
1259
 
              }
1260
 
              $line ne '' ? $line : 'none';
1261
 
            },
1262
 
 
1263
 
            PREVIEW => sub { $self->get_content_preview() },
1264
 
 
1265
 
            REPORT => sub { "\n" . ($self->{tag_data}->{REPORT} || "") },
1266
 
 
1267
 
            HEADER => sub {
1268
 
              my $hdr = shift || return;
1269
 
              $self->get($hdr,undef);
1270
 
            },
1271
 
 
1272
 
            TIMING => sub { $self->{main}->timer_report() },
1273
 
 
1274
 
            ADDEDHEADERHAM => sub { $self->_get_added_headers('headers_ham') },
1275
 
 
1276
 
            ADDEDHEADERSPAM=> sub { $self->_get_added_headers('headers_spam') },
1277
 
 
1278
 
            ADDEDHEADER => sub {
1279
 
              $self->_get_added_headers(
1280
 
                        $self->{is_spam} ? 'headers_spam' : 'headers_ham');
1281
 
            },
1282
 
 
1283
 
          );
1284
 
 
1285
 
  my $data;
1286
 
  if (exists $tags{$tag}) {
1287
 
    $data = $tags{$tag};
1288
 
    $data = $data->(@_)  if ref $data eq 'CODE';
1289
 
    $data = ""  if !defined $data;
1290
 
  } elsif (exists $self->{tag_data}->{$tag}) {
1291
 
    $data = $self->{tag_data}->{$tag};
1292
 
    $data = $data->(@_)  if ref $data eq 'CODE';
1293
 
    $data = ""  if !defined $data;
1294
 
  }
1295
 
  return $data;
1296
 
}
1297
1593
 
1298
1594
###########################################################################
1299
1595
 
1314
1610
          permsgstatus => $self
1315
1611
        });
1316
1612
 
 
1613
  $self->report_unsatisfied_actions;
 
1614
 
1317
1615
  # Delete out all of the members of $self.  This will remove any direct
1318
1616
  # circular references and let the memory get reclaimed while also being more
1319
1617
  # efficient than a foreach() loop over the keys.
1366
1664
    $self->{$item} = $self->{msg}->{metadata}->{$item};
1367
1665
  }
1368
1666
 
1369
 
  $self->{tag_data}->{RELAYSTRUSTED} = $self->{relays_trusted_str};
1370
 
  $self->{tag_data}->{RELAYSUNTRUSTED} = $self->{relays_untrusted_str};
1371
 
  $self->{tag_data}->{RELAYSINTERNAL} = $self->{relays_internal_str};
1372
 
  $self->{tag_data}->{RELAYSEXTERNAL} = $self->{relays_external_str};
1373
 
  $self->{tag_data}->{LANGUAGES} = $self->{msg}->get_metadata("X-Languages");
 
1667
  $self->set_tag('RELAYSTRUSTED',   $self->{relays_trusted_str});
 
1668
  $self->set_tag('RELAYSUNTRUSTED', $self->{relays_untrusted_str});
 
1669
  $self->set_tag('RELAYSINTERNAL',  $self->{relays_internal_str});
 
1670
  $self->set_tag('RELAYSEXTERNAL',  $self->{relays_external_str});
 
1671
  $self->set_tag('LANGUAGES', $self->{msg}->get_metadata("X-Languages"));
1374
1672
 
1375
1673
  # This should happen before we get called, but just in case.
1376
1674
  if (!defined $self->{msg}->{metadata}->{html}) {
1429
1727
Appending C<:raw> to the header name will inhibit decoding of quoted-printable
1430
1728
or base-64 encoded strings.
1431
1729
 
1432
 
Appending C<:addr> to the header name will cause everything except
1433
 
the first email address to be removed from the header.  For example,
1434
 
all of the following will result in "example@foo":
 
1730
Appending a modifier C<:addr> to a header field name will cause everything
 
1731
except the first email address to be removed from the header field.  It is
 
1732
mainly applicable to header fields 'From', 'Sender', 'To', 'Cc' along with
 
1733
their 'Resent-*' counterparts, and the 'Return-Path'. For example, all of
 
1734
the following will result in "example@foo":
1435
1735
 
1436
1736
=over 4
1437
1737
 
1451
1751
 
1452
1752
=back
1453
1753
 
1454
 
Appending C<:name> to the header name will cause everything except
1455
 
the first display name to be removed from the header.  For example,
1456
 
all of the following will result in "Foo Blah"
 
1754
Appending a modifier C<:name> to a header field name will cause everything
 
1755
except the first display name to be removed from the header field. It is
 
1756
mainly applicable to header fields containing a single mail address: 'From',
 
1757
'Sender', along with their 'Resent-From' and 'Resent-Sender' counterparts.
 
1758
For example, all of the following will result in "Foo Blah". One level of
 
1759
single quotes is stripped too, as it is often seen.
1457
1760
 
1458
1761
=over 4
1459
1762
 
1647
1950
      $result =~ s/,.*$//;
1648
1951
    }
1649
1952
    elsif ($getname) {
1650
 
      # Get the real name out of the header
 
1953
      # Get the display name out of the header
1651
1954
      # All of these should result in "Foo Blah":
1652
1955
      #
1653
1956
      # jm@foo (Foo Blah)
 
1957
      # (Foo Blah) jm@foo
1654
1958
      # jm@foo (Foo Blah), jm@bar
1655
1959
      # display: jm@foo (Foo Blah), jm@bar ;
1656
1960
      # Foo Blah <jm@foo>
1658
1962
      # "'Foo Blah'" <jm@foo>
1659
1963
      #
1660
1964
      local $1;
1661
 
      $result =~ s/^[\'\"]*(.*?)[\'\"]*\s*<.+>\s*$/$1/g
1662
 
          or $result =~ s/^.+\s\((.*?)\)\s*$/$1/g; # jm@foo (Foo Blah)
 
1965
      # does not handle mailbox-list or address-list well, to be improved
 
1966
      if ($result =~ /^ \s* (.*?) \s* < [^<>]* >/sx) {
 
1967
        $result = $1;  # display-name, RFC 5322
 
1968
        # name-addr    = [display-name] angle-addr
 
1969
        # display-name = phrase
 
1970
        # phrase       = 1*word / obs-phrase
 
1971
        # word         = atom / quoted-string
 
1972
        # obs-phrase   = word *(word / "." / CFWS)
 
1973
        $result =~ s{ " ( (?: [^"\\] | \\. )* ) " }
 
1974
                { my $s=$1; $s=~s{\\(.)}{$1}gs; $s }gsxe;
 
1975
      } elsif ($result =~ /^ [^(,]*? \( (.*?) \) /sx) {  # legacy form
 
1976
        # nested comments are not handled, to be improved
 
1977
        $result = $1;
 
1978
      } else {  # no display name
 
1979
        $result = '';
 
1980
      }
 
1981
      $result =~ s/^ \s* ' \s* (.*?) \s* ' \s* \z/$1/sx;
1663
1982
    }
1664
1983
  }
1665
1984
  return $result;
1694
2013
# The goals are to find URIs in plain text spam that are intended to be clicked on or copy/pasted, but
1695
2014
# ignore random strings that might look like URIs, for example in uuencoded files, and to ignore
1696
2015
# URIs that spammers might seed in spam in ways not visible or clickable to add work to spam filters.
1697
 
# When we extract a domain and look it up in an RBL, an FP on decding that the text is a URI is not much
 
2016
# When we extract a domain and look it up in an RBL, an FP on deciding that the text is a URI is not much
1698
2017
# of a problem, as the only cost is an extra RBL lookup. The same FP is worse if the URI is used in matching rule
1699
2018
# because it could lead to a rule FP, as in bug 5780 with WIERD_PORT matching random uuencoded strings.
1700
2019
# The principles of the following code are 1) if ThunderBird or Outlook Express would linkify a string,
1780
2099
  }
1781
2100
 
1782
2101
  $self->{uri_list} = \@uris;
 
2102
# $self->set_tag('URILIST', @uris == 1 ? $uris[0] : \@uris)  if @uris;
1783
2103
 
1784
2104
  return @uris;
1785
2105
}
1818
2138
 
1819
2139
C<domains> is a hash of the domains found in the canonified URIs.
1820
2140
 
 
2141
C<hosts> is a hash of unstripped hostnames found in the canonified URIs
 
2142
as hash keys, with their domain part stored as a value of each hash entry.
 
2143
 
1821
2144
=cut
1822
2145
 
1823
2146
sub get_uri_detail_list {
1835
2158
  # do this so we're sure metadata->html is setup
1836
2159
  my %parsed = map { $_ => 'parsed' } $self->_get_parsed_uri_list();
1837
2160
 
 
2161
 
 
2162
  # This parses of DKIM for URIs disagrees with documentation and bug 6700 votes to disable
 
2163
  # this functionality
 
2164
  # 2013-01-07
 
2165
 
1838
2166
  # Look for the domain in DK/DKIM headers
1839
 
  my $dk = join(" ", grep {defined} ( $self->get('DomainKey-Signature',undef),
1840
 
                                      $self->get('DKIM-Signature',undef) ));
1841
 
  while ($dk =~ /\bd\s*=\s*([^;]+)/g) {
1842
 
    my $dom = $1;
1843
 
    $dom =~ s/\s+//g;
1844
 
    $parsed{$dom} = 'domainkeys';
1845
 
  }
 
2167
  #my $dk = join(" ", grep {defined} ( $self->get('DomainKey-Signature',undef),
 
2168
  #                                    $self->get('DKIM-Signature',undef) ));
 
2169
  #while ($dk =~ /\bd\s*=\s*([^;]+)/g) {
 
2170
  #  my $dom = $1;
 
2171
  #  $dom =~ s/\s+//g;
 
2172
  #  $parsed{$dom} = 'domainkeys';
 
2173
  #}
1846
2174
 
1847
2175
  # get URIs from HTML parsing
1848
2176
  # use the metadata version since $self->{html} may not be setup
1858
2186
    $info->{cleaned} = \@tmp;
1859
2187
 
1860
2188
    foreach (@tmp) {
1861
 
      my $domain = Mail::SpamAssassin::Util::uri_to_domain($_);
1862
 
      if ($domain && !$info->{domains}->{$domain}) {
1863
 
        $info->{domains}->{$domain} = 1;
1864
 
        $self->{uri_domain_count}++;
 
2189
      my($domain,$host) = Mail::SpamAssassin::Util::uri_to_domain($_);
 
2190
      if (defined $host && $host ne '' && !$info->{hosts}->{$host}) {
 
2191
        # unstripped full host name as a key, and its domain part as a value
 
2192
        $info->{hosts}->{$host} = $domain;
 
2193
        if (defined $domain && $domain ne '' && !$info->{domains}->{$domain}) {
 
2194
          $info->{domains}->{$domain} = 1;  # stripped to domain boundary
 
2195
          $self->{uri_domain_count}++;
 
2196
        }
1865
2197
      }
1866
2198
    }
1867
2199
 
1870
2202
      foreach my $nuri (@tmp) {
1871
2203
        dbg("uri: cleaned html uri, $nuri");
1872
2204
      }
1873
 
      if ($info->{domains}) {
1874
 
        foreach my $domain (keys %{$info->{domains}}) {
1875
 
          dbg("uri: html domain, $domain");
 
2205
      if ($info->{hosts} && $info->{domains}) {
 
2206
        for my $host (keys %{$info->{hosts}}) {
 
2207
          dbg("uri: html host %s, domain %s", $host, $info->{hosts}->{$host});
1876
2208
        }
1877
2209
      }
1878
2210
    }
1895
2227
      $info->{cleaned} = \@uris;
1896
2228
 
1897
2229
      foreach (@uris) {
1898
 
        my $domain = Mail::SpamAssassin::Util::uri_to_domain($_);
1899
 
        if ($domain && !$info->{domains}->{$domain}) {
1900
 
          $info->{domains}->{$domain} = 1;
1901
 
          $self->{uri_domain_count}++;
 
2230
        my($domain,$host) = Mail::SpamAssassin::Util::uri_to_domain($_);
 
2231
        if (defined $host && $host ne '' && !$info->{hosts}->{$host}) {
 
2232
          # unstripped full host name as a key, and its domain part as a value
 
2233
          $info->{hosts}->{$host} = $domain;
 
2234
          if (defined $domain && $domain ne '' && !$info->{domains}->{$domain}){
 
2235
            $info->{domains}->{$domain} = 1;
 
2236
            $self->{uri_domain_count}++;
 
2237
          }
1902
2238
        }
1903
2239
      }
1904
2240
    }
1908
2244
      foreach my $nuri (@uris) {
1909
2245
        dbg("uri: cleaned parsed uri, $nuri");
1910
2246
      }
1911
 
      if ($info->{domains}) {
1912
 
        foreach my $domain (keys %{$info->{domains}}) {
1913
 
          dbg("uri: parsed domain, $domain");
 
2247
      if ($info->{hosts} && $info->{domains}) {
 
2248
        for my $host (keys %{$info->{hosts}}) {
 
2249
          dbg("uri: parsed host %s, domain %s", $host, $info->{hosts}->{$host});
1914
2250
        }
1915
2251
      }
1916
2252
    }
2114
2450
#
2115
2451
# the clearing of the test state is now inlined as:
2116
2452
#
2117
 
# $self->{test_log_msgs} = ();        # clear test state
 
2453
# %{$self->{test_log_msgs}} = ();        # clear test state
2118
2454
#
2119
2455
# except for this public API for plugin use:
2120
2456
 
2126
2462
 
2127
2463
sub clear_test_state {
2128
2464
    my ($self) = @_;
2129
 
    $self->{test_log_msgs} = ();
 
2465
    %{$self->{test_log_msgs}} = ();
2130
2466
}
2131
2467
 
2132
2468
# internal API, called only by get_hit()
2183
2519
              $self->_wrap_desc($desc,
2184
2520
                  3+length($rule)+length($score)+length($area), " " x 28),
2185
2521
              ($self->{test_log_msgs}->{LONG} || ''));
2186
 
 
2187
 
    $self->{test_log_msgs} = ();        # clear test logs
2188
2522
}
2189
2523
 
2190
2524
sub _wrap_desc {
2249
2583
 
2250
2584
=back
2251
2585
 
2252
 
Backwards compatibility: the two mandatory arguments have been part of this API
 
2586
Backward compatibility: the two mandatory arguments have been part of this API
2253
2587
since SpamAssassin 2.x.  The optional I<name=<gt>value> pairs, however, are a
2254
2588
new addition in SpamAssassin 3.2.0.
2255
2589
 
2271
2605
 
2272
2606
  # adding a hit does nothing if we don't have a score -- we probably
2273
2607
  # shouldn't have run it in the first place
2274
 
  return unless $score;
 
2608
  if (!$score) {
 
2609
    %{$self->{test_log_msgs}} = ();
 
2610
    return;
 
2611
  }
2275
2612
 
2276
2613
  # ensure that rule values always result in an *increase*
2277
2614
  # of $self->{tests_already_hit}->{$rule}:
2288
2625
  my $already_hit = $self->{tests_already_hit}->{$rule} || 0;
2289
2626
  # don't count hits multiple times, unless 'tflags multiple' is on
2290
2627
  if ($already_hit && ($tflags_ref->{$rule}||'') !~ /\bmultiple\b/) {
 
2628
    %{$self->{test_log_msgs}} = ();
2291
2629
    return;
2292
2630
  }
2293
2631
 
2307
2645
  } else {
2308
2646
    $rule_descr = $conf_ref->get_description_for_rule($rule);  # static
2309
2647
  }
2310
 
  $rule_descr = $rule  if !defined $rule_descr || $rule_descr eq '';
 
2648
  # Bug 6880 Set Rule Description to something that says no rule
 
2649
  #$rule_descr = $rule  if !defined $rule_descr || $rule_descr eq '';
 
2650
  $rule_descr = "No description available." if !defined $rule_descr || $rule_descr eq '';
 
2651
 
2311
2652
  $self->_handle_hit($rule,
2312
2653
            $score,
2313
2654
            $area,
2322
2663
    }
2323
2664
  }
2324
2665
 
 
2666
  %{$self->{test_log_msgs}} = ();  # clear test logs
2325
2667
  return 1;
2326
2668
}
2327
2669
 
2330
2672
# TODO: this needs API doc
2331
2673
sub test_log {
2332
2674
  my ($self, $msg) = @_;
 
2675
  local $1;
2333
2676
  while ($msg =~ s/^(.{30,48})\s//) {
2334
2677
    $self->_test_log_line ($1);
2335
2678
  }
2542
2885
  }
2543
2886
 
2544
2887
  my ($tmpf, $tmpfh) = Mail::SpamAssassin::Util::secure_tmpfile();
 
2888
  $tmpfh  or die "failed to create a temporary file";
2545
2889
  print $tmpfh $$fulltext  or die "error writing to $tmpf: $!";
2546
2890
  close $tmpfh  or die "error closing $tmpf: $!";
2547
2891
 
2548
2892
  $self->{fulltext_tmpfile} = $tmpf;
2549
2893
 
 
2894
  dbg("check: create_fulltext_tmpfile, written %d bytes to file %s",
 
2895
      length($$fulltext), $tmpf);
 
2896
 
2550
2897
  return $self->{fulltext_tmpfile};
2551
2898
}
2552
2899
 
2560
2907
sub delete_fulltext_tmpfile {
2561
2908
  my ($self) = @_;
2562
2909
  if (defined $self->{fulltext_tmpfile}) {
2563
 
    unlink $self->{fulltext_tmpfile}
2564
 
      or die "cannot unlink ".$self->{fulltext_tmpfile}.": $!";
 
2910
    if (!unlink $self->{fulltext_tmpfile}) {
 
2911
      my $msg = sprintf("cannot unlink %s: %s", $self->{fulltext_tmpfile}, $!);
 
2912
      # don't fuss too much if file is missing, perhaps it wasn't even created
 
2913
      if ($! == ENOENT) { warn $msg } else { die $msg }
 
2914
    }
2565
2915
    $self->{fulltext_tmpfile} = undef;
2566
2916
  }
2567
2917
}
2608
2958
  return @addrs;
2609
2959
}
2610
2960
 
 
2961
=item all_from_addrs_domains
 
2962
 
 
2963
This function returns all the various from addresses in a message using all_from_addrs() 
 
2964
and then returns only the domain names.  
 
2965
 
 
2966
=cut
 
2967
 
 
2968
sub all_from_addrs_domains {
 
2969
  my ($self) = @_;
 
2970
 
 
2971
  if (exists $self->{all_from_addrs_domains}) { 
 
2972
    return @{$self->{all_from_addrs_domains}};
 
2973
  }
 
2974
 
 
2975
  #TEST POINT - my @addrs = ("test.voipquotes2.net","test.voipquotes2.co.uk"); 
 
2976
  #Start with all the normal from addrs
 
2977
  my @addrs = &all_from_addrs($self);
 
2978
 
 
2979
  dbg("eval: all '*From' addrs domains (before): " . join(" ", @addrs));
 
2980
 
 
2981
  #loop through and limit to just the domain with a dummy address
 
2982
  for (my $i = 0; $i < scalar(@addrs); $i++) {
 
2983
    $addrs[$i] = 'dummy@'.&Mail::SpamAssassin::Util::uri_to_domain($addrs[$i]);
 
2984
  }
 
2985
 
 
2986
  #Remove duplicate domains
 
2987
  my %addrs = map { $_ => 1 } @addrs;
 
2988
  @addrs = keys %addrs;
 
2989
 
 
2990
  dbg("eval: all '*From' addrs domains (after uri to domain): " . join(" ", @addrs));
 
2991
 
 
2992
  $self->{all_from_addrs_domains} = \@addrs;
 
2993
 
 
2994
  return @addrs;
 
2995
}
 
2996
 
2611
2997
sub all_to_addrs {
2612
2998
  my ($self) = @_;
2613
2999