~ubuntu-branches/ubuntu/utopic/spamassassin/utopic-proposed

« back to all changes in this revision

Viewing changes to lib/Mail/SpamAssassin.pm

  • Committer: Bazaar Package Importer
  • Author(s): Noah Meyerhans
  • Date: 2010-01-26 22:53:12 UTC
  • mfrom: (1.1.13 upstream) (5.1.7 sid)
  • Revision ID: james.westby@ubuntu.com-20100126225312-wkftb10idc1kz2aq
Tags: 3.3.0-1
* New upstream version.
* Switch to dpkg-source 3.0 (quilt) format

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
 
36
36
  $status->finish();
37
37
  $mail->finish();
 
38
  $spamtest->finish();
38
39
 
39
40
=head1 DESCRIPTION
40
41
 
61
62
use strict;
62
63
use warnings;
63
64
use bytes;
 
65
use re 'taint';
64
66
 
65
67
require 5.006_001;
66
68
 
73
75
use Mail::SpamAssassin::Message;
74
76
use Mail::SpamAssassin::PluginHandler;
75
77
use Mail::SpamAssassin::DnsResolver;
 
78
use Mail::SpamAssassin::Util::ScopedTimer;
76
79
 
77
80
use Errno qw(ENOENT EACCES);
78
81
use File::Basename;
79
82
use File::Path;
80
83
use File::Spec 0.8;
81
84
use File::Copy;
 
85
use Time::HiRes qw(time);
82
86
use Cwd;
83
87
use Config;
84
88
 
85
 
# Load Time::HiRes if it's available
86
 
BEGIN {
87
 
  eval { require Time::HiRes };
88
 
  Time::HiRes->import( qw(time) ) unless $@;
89
 
}
90
 
 
91
89
use vars qw{
92
90
  @ISA $VERSION $SUB_VERSION @EXTRA_VERSION $IS_DEVEL_BUILD $HOME_URL
93
91
  @default_rules_path @default_prefs_path
95
93
  @site_rules_path
96
94
};
97
95
 
98
 
$VERSION = "3.002005";      # update after release (same format as perl $])
 
96
$VERSION = "3.003000";      # update after release (same format as perl $])
99
97
# $IS_DEVEL_BUILD = 1;        # change for release versions
100
98
 
101
99
# Used during the prerelease/release-candidate part of the official release
106
104
@ISA = qw();
107
105
 
108
106
# SUB_VERSION is now just <yyyy>-<mm>-<dd>
109
 
$SUB_VERSION = (split(/\s+/,'$LastChangedDate: 2008-06-10 09:13:55 +0000 (Tue, 10 Jun 2008) $ updated by SVN'))[1];
 
107
$SUB_VERSION = (split(/\s+/,'$LastChangedDate: 2010-01-18 23:42:44 +0000 (Mon, 18 Jan 2010) $ updated by SVN'))[1];
110
108
 
111
109
if (defined $IS_DEVEL_BUILD && $IS_DEVEL_BUILD) {
112
110
  push(@EXTRA_VERSION,
113
 
       ('r' . qw{$LastChangedRevision: 666026 $ updated by SVN}[1]));
 
111
       ('r' . qw{$LastChangedRevision: 900609 $ updated by SVN}[1]));
114
112
}
115
113
 
116
114
sub Version {
194
192
 
195
193
=item site_rules_filename
196
194
 
197
 
The directory to load site-specific spam-identifying rules from. (optional)
 
195
The filename/directory to load site-specific spam-identifying rules from.
 
196
(optional)
198
197
 
199
198
=item userprefs_filename
200
199
 
216
215
override the settings for C<rules_filename>, C<site_rules_filename>,
217
216
and C<userprefs_filename>.
218
217
 
 
218
=item pre_config_text
 
219
 
 
220
Similar to C<config_text>, this text is placed before config_text to allow an
 
221
override of config files.
 
222
 
219
223
=item post_config_text
220
224
 
221
225
Similar to C<config_text>, this text is placed after config_text to allow an
224
228
=item force_ipv4
225
229
 
226
230
If set to 1, DNS tests will not attempt to use IPv6. Use if the existing tests
227
 
for IPv6 availablity produce incorrect results or crashes.
 
231
for IPv6 availability produce incorrect results or crashes.
 
232
 
 
233
=item require_rules
 
234
 
 
235
If set to 1, init() will die if no valid rules could be loaded. This is the
 
236
default behaviour when called by C<spamassassin> or C<spamd>.
228
237
 
229
238
=item languages_filename
230
239
 
239
248
If set to 1, no tests that require internet access will be performed. (default:
240
249
0)
241
250
 
 
251
=item need_tags
 
252
 
 
253
The option provides a way to avoid more expensive processing when it is known
 
254
in advance that some information will not be needed by a caller.
 
255
 
 
256
A value of the option can either be a string (a comma-delimited list of tag
 
257
names), or a reference to a list of individual tag names. A caller may provide
 
258
the list in advance, specifying his intention to later collect the information
 
259
through $pms->get_tag() calls. If a name of a tag starts with a 'NO' (case
 
260
insensitive), it shows that a caller will not be interested in such tag,
 
261
although there is no guarantee it would save any resources, nor that a tag
 
262
value will be empty. Currently no built-in tags start with 'NO'. A later
 
263
entry overrides previous one, e.g. ASN,NOASN,ASN,TIMING,NOASN is equivalent
 
264
to TIMING,NOASN.
 
265
 
 
266
For backwards compatibility, all tags available as of version 3.2.4 will
 
267
be available by default (unless disabled by NOtag), even if not requested
 
268
through need_tags option. Future versions may provide new tags conditionally
 
269
available.
 
270
 
 
271
Currently the only tag that needs to be explicitly requested is 'TIMING'.
 
272
Not requesting it can save a millisecond or two - it mostly serves to
 
273
illustrate the usage of need_tags.
 
274
 
 
275
Example:
 
276
  need_tags =>    'TIMING,noLANGUAGES,RELAYCOUNTRY,ASN,noASNCIDR',
 
277
or:
 
278
  need_tags => [qw(TIMING noLANGUAGES RELAYCOUNTRY ASN noASNCIDR)],
 
279
 
242
280
=item ignore_site_cf_files
243
281
 
244
282
If set to 1, any rule files found in the C<site_rules_filename> directory will
337
375
  $self->{DEF_RULES_DIR}        ||= '@@DEF_RULES_DIR@@';
338
376
  $self->{LOCAL_RULES_DIR}      ||= '@@LOCAL_RULES_DIR@@';
339
377
  $self->{LOCAL_STATE_DIR}      ||= '@@LOCAL_STATE_DIR@@';
 
378
  dbg("generic: Perl %s, %s", $], join(", ", map { $_ . '=' . $self->{$_} } 
 
379
      qw(PREFIX DEF_RULES_DIR LOCAL_RULES_DIR LOCAL_STATE_DIR)));
 
380
 
 
381
  $self->{needed_tags} = {};
 
382
  { my $ntags = $self->{need_tags};
 
383
    if (defined $ntags) {
 
384
      for my $t (ref $ntags ? @$ntags : split(/[, \s]+/,$ntags)) {
 
385
        $self->{needed_tags}->{$2} = !defined($1)  if $t =~ /^(NO)?(.+)\z/si;
 
386
      }
 
387
    }
 
388
  }
 
389
  if (would_log('dbg','timing') || $self->{needed_tags}->{TIMING}) {
 
390
    $self->timer_enable();
 
391
  }
340
392
 
341
393
  $self->{conf} ||= new Mail::SpamAssassin::Conf ($self);
342
394
  $self->{plugins} = Mail::SpamAssassin::PluginHandler->new ($self);
382
434
  eval '
383
435
    use Mail::SpamAssassin::Locker::'.$class.';
384
436
    $self->{locker} = new Mail::SpamAssassin::Locker::'.$class.' ($self);
385
 
  '; ($@) and die $@;
 
437
    1;
 
438
  ' or do {
 
439
    my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
 
440
    die "Mail::SpamAssassin::Locker::$class error: $eval_stat\n";
 
441
  };
386
442
 
387
443
  if (!defined $self->{locker}) { die "locker: oops! no locker"; }
388
444
}
389
445
 
390
446
###########################################################################
391
447
 
392
 
=item parse($message, $parse_now)
 
448
=item parse($message, $parse_now [, $suppl_attrib])
393
449
 
394
450
Parse will return a Mail::SpamAssassin::Message object with just the
395
451
headers parsed.  When calling this function, there are two optional
406
462
needs the pristine header and body which is always parsed and stored
407
463
by this function.
408
464
 
 
465
The optional last argument I<$suppl_attrib> provides a way for a caller
 
466
to pass additional information about a message to SpamAssassin. It is
 
467
either undef, or a ref to a hash where each key/value pair provides some
 
468
supplementary attribute of the message, typically information that cannot
 
469
be deduced from the message itself, or is hard to do so reliably, or would
 
470
represent unnecessary work for SpamAssassin to obtain it. The argument will
 
471
be stored to a Mail::SpamAssassin::Message object as 'suppl_attrib', thus
 
472
made available to the rest of the code as well as to plugins. The exact list
 
473
of attributes will evolve through time, any unknown attribute should be
 
474
ignored. Possible examples are: SMTP envelope information, a flag indicating
 
475
that a message as supplied by a caller was truncated due to size limit, an
 
476
already verified list of DKIM signature objects, or perhaps a list of rule
 
477
hits predetermined by a caller, which makes another possible way for a
 
478
caller to provide meta information (instead of having to insert made-up
 
479
header fields in order to pass information), or maybe just plain rule hits.
 
480
 
409
481
For more information, please see the C<Mail::SpamAssassin::Message>
410
482
and C<Mail::SpamAssassin::Message::Node> POD.
411
483
 
412
484
=cut
413
485
 
414
486
sub parse {
415
 
  my($self, $message, $parsenow) = @_;
 
487
  my($self, $message, $parsenow, $suppl_attrib) = @_;
 
488
 
 
489
  my $start_time = time;
416
490
  $self->init(1);
417
 
  my $msg = Mail::SpamAssassin::Message->new({message=>$message, parsenow=>$parsenow, normalize=>$self->{conf}->{normalize_charset}});
 
491
  my $timer = $self->time_method("parse");
 
492
 
 
493
  my $msg = Mail::SpamAssassin::Message->new({
 
494
    message=>$message, parsenow=>$parsenow,
 
495
    normalize=>$self->{conf}->{normalize_charset},
 
496
    suppl_attrib=>$suppl_attrib });
 
497
 
 
498
  if (ref $suppl_attrib && exists $suppl_attrib->{master_deadline}) {
 
499
    $msg->{master_deadline} = $suppl_attrib->{master_deadline};  # may be undef
 
500
  } elsif ($self->{conf}->{time_limit}) {  # defined and nonzero
 
501
    $msg->{master_deadline} = $start_time + $self->{conf}->{time_limit};
 
502
  }
 
503
  if (defined $msg->{master_deadline}) {
 
504
    dbg("config: time limit %.1f s", $msg->{master_deadline} - $start_time);
 
505
  }
418
506
 
419
507
  # bug 5069: The goal here is to get rendering plugins to do things
420
508
  # like OCR, convert doc and pdf to text, etc, though it could be anything
446
534
  my ($self, $mail_obj) = @_;
447
535
 
448
536
  $self->init(1);
449
 
  my $msg = Mail::SpamAssassin::PerMsgStatus->new($self, $mail_obj);
450
 
  $msg->check();
451
 
  $msg;
 
537
  my $pms = Mail::SpamAssassin::PerMsgStatus->new($self, $mail_obj);
 
538
  $pms->check();
 
539
  dbg("timing: " . $self->timer_report())  if $self->{timer_enabled};
 
540
  $pms;
452
541
}
453
542
 
454
543
=item $status = $f->check_message_text ($mailtext)
620
709
 
621
710
sub finish_learner {
622
711
  my $self = shift;
623
 
  $self->{bayes_scanner}->sanity_check_is_untied(1) if $self->{bayes_scanner};
 
712
  $self->{bayes_scanner}->force_close(1) if $self->{bayes_scanner};
624
713
  1;
625
714
}
626
715
 
673
762
  my $opts = shift;
674
763
  my $set = 0;
675
764
 
 
765
  my $timer = $self->time_method("signal_user_changed");
676
766
  dbg("info: user has changed");
677
767
 
678
768
  if (defined $opts && $opts->{username}) {
708
798
 
709
799
  $self->{conf}->set_score_set ($set);
710
800
 
711
 
  $self->call_plugins ("signal_user_changed", {
 
801
  $self->call_plugins("signal_user_changed", {
712
802
                username => $self->{username},
713
803
                userstate_dir => $self->{userstate_dir},
714
804
                user_dir => $self->{user_dir},
758
848
  local ($_);
759
849
 
760
850
  $self->init(1);
 
851
  my $timer = $self->time_method("report_as_spam");
761
852
 
762
853
  # learn as spam if enabled
763
854
  if ( $self->{conf}->{bayes_learn_during_report} ) {
799
890
  local ($_);
800
891
 
801
892
  $self->init(1);
 
893
  my $timer = $self->time_method("revoke_as_spam");
802
894
 
803
895
  # learn as nonspam
804
896
  $self->learn ($mail, undef, 0, 0);
810
902
 
811
903
###########################################################################
812
904
 
813
 
=item $f->add_address_to_whitelist ($addr)
 
905
=item $f->add_address_to_whitelist ($addr, $cli_p)
814
906
 
815
907
Given a string containing an email address, add it to the automatic
816
908
whitelist database.
817
909
 
 
910
If $cli_p is set then underlying plugin may give visual feedback on additions/failures.
 
911
 
818
912
=cut
819
913
 
820
914
sub add_address_to_whitelist {
821
 
  my ($self, $addr) = @_;
 
915
  my ($self, $addr, $cli_p) = @_;
822
916
 
823
 
  $self->call_plugins("whitelist_address", { address => $addr });
 
917
  $self->call_plugins("whitelist_address", { address => $addr,
 
918
                                             cli_p => $cli_p });
824
919
}
825
920
 
826
921
###########################################################################
827
922
 
828
 
=item $f->add_all_addresses_to_whitelist ($mail)
 
923
=item $f->add_all_addresses_to_whitelist ($mail, $cli_p)
829
924
 
830
925
Given a mail message, find as many addresses in the usual headers (To, Cc, From
831
926
etc.), and the message body, and add them to the automatic whitelist database.
832
927
 
 
928
If $cli_p is set then underlying plugin may give visual feedback on additions/failures.
 
929
 
833
930
=cut
834
931
 
835
932
sub add_all_addresses_to_whitelist {
836
 
  my ($self, $mail_obj) = @_;
 
933
  my ($self, $mail_obj, $cli_p) = @_;
837
934
 
838
935
  foreach my $addr ($self->find_all_addrs_in_mail ($mail_obj)) {
839
 
    $self->call_plugins("whitelist_address", { address => $addr });
 
936
    $self->call_plugins("whitelist_address", { address => $addr,
 
937
                                               cli_p => $cli_p });
840
938
  }
841
939
}
842
940
 
843
941
###########################################################################
844
942
 
845
 
=item $f->remove_address_from_whitelist ($addr)
 
943
=item $f->remove_address_from_whitelist ($addr, $cli_p)
846
944
 
847
945
Given a string containing an email address, remove it from the automatic
848
946
whitelist database.
849
947
 
 
948
If $cli_p is set then underlying plugin may give visual feedback on additions/failures.
 
949
 
850
950
=cut
851
951
 
852
952
sub remove_address_from_whitelist {
853
 
  my ($self, $addr) = @_;
 
953
  my ($self, $addr, $cli_p) = @_;
854
954
 
855
 
  $self->call_plugins("remove_address", { address => $addr });
 
955
  $self->call_plugins("remove_address", { address => $addr,
 
956
                                          cli_p => $cli_p });
856
957
}
857
958
 
858
959
###########################################################################
859
960
 
860
 
=item $f->remove_all_addresses_from_whitelist ($mail)
 
961
=item $f->remove_all_addresses_from_whitelist ($mail, $cli_p)
861
962
 
862
963
Given a mail message, find as many addresses in the usual headers (To, Cc, From
863
964
etc.), and the message body, and remove them from the automatic whitelist
864
965
database.
865
966
 
 
967
If $cli_p is set then underlying plugin may give visual feedback on additions/failures.
 
968
 
866
969
=cut
867
970
 
868
971
sub remove_all_addresses_from_whitelist {
869
 
  my ($self, $mail_obj) = @_;
 
972
  my ($self, $mail_obj, $cli_p) = @_;
870
973
 
871
974
  foreach my $addr ($self->find_all_addrs_in_mail ($mail_obj)) {
872
 
    $self->call_plugins("remove_address", { address => $addr });
 
975
    $self->call_plugins("remove_address", { address => $addr,
 
976
                                            cli_p => $cli_p });
873
977
  }
874
978
}
875
979
 
876
980
###########################################################################
877
981
 
878
 
=item $f->add_address_to_blacklist ($addr)
 
982
=item $f->add_address_to_blacklist ($addr, $cli_p)
879
983
 
880
984
Given a string containing an email address, add it to the automatic
881
985
whitelist database with a high score, effectively blacklisting them.
882
986
 
 
987
If $cli_p is set then underlying plugin may give visual feedback on additions/failures.
 
988
 
883
989
=cut
884
990
 
885
991
sub add_address_to_blacklist {
886
 
  my ($self, $addr) = @_;
887
 
  $self->call_plugins("blacklist_address", { address => $addr });
 
992
  my ($self, $addr, $cli_p) = @_;
 
993
  $self->call_plugins("blacklist_address", { address => $addr,
 
994
                                             cli_p => $cli_p });
888
995
}
889
996
 
890
997
###########################################################################
891
998
 
892
 
=item $f->add_all_addresses_to_blacklist ($mail)
 
999
=item $f->add_all_addresses_to_blacklist ($mail, $cli_p)
893
1000
 
894
1001
Given a mail message, find addresses in the From headers and add them to the
895
1002
automatic whitelist database with a high score, effectively blacklisting them.
896
1003
 
897
1004
Note that To and Cc addresses are not used.
898
1005
 
 
1006
If $cli_p is set then underlying plugin may give visual feedback on additions/failures.
 
1007
 
899
1008
=cut
900
1009
 
901
1010
sub add_all_addresses_to_blacklist {
902
 
  my ($self, $mail_obj) = @_;
 
1011
  my ($self, $mail_obj, $cli_p) = @_;
903
1012
 
904
1013
  $self->init(1);
905
1014
 
906
 
  my @addrlist = ();
907
 
  my @hdrs = $mail_obj->get_header ('From');
 
1015
  my @addrlist;
 
1016
  my @hdrs = $mail_obj->get_header('From');
908
1017
  if ($#hdrs >= 0) {
909
1018
    push (@addrlist, $self->find_all_addrs_in_line (join (" ", @hdrs)));
910
1019
  }
911
1020
 
912
1021
  foreach my $addr (@addrlist) {
913
 
    $self->call_plugins("blacklist_address", { address => $addr });
 
1022
    $self->call_plugins("blacklist_address", { address => $addr,
 
1023
                                               cli_p => $cli_p });
914
1024
  }
915
1025
 
916
1026
}
934
1044
  my ($self, $mail_obj) = @_;
935
1045
  local ($_);
936
1046
 
 
1047
  my $timer = $self->time_method("remove_spamassassin_markup");
937
1048
  my $mbox = $mail_obj->get_mbox_separator() || '';
938
1049
 
939
1050
  dbg("markup: removing markup");
1087
1198
sub read_scoreonly_config {
1088
1199
  my ($self, $filename) = @_;
1089
1200
 
 
1201
  my $timer = $self->time_method("read_scoreonly_config");
 
1202
  local *IN;
1090
1203
  if (!open(IN,"<$filename")) {
1091
1204
    # the file may not exist; this should not be verbose
1092
1205
    dbg("config: read_scoreonly_config: cannot open \"$filename\": $!");
1093
1206
    return;
1094
1207
  }
1095
1208
 
1096
 
  my $text = "file start $filename\n"
1097
 
        . join ('', <IN>)
1098
 
        # add an extra \n in case file did not end in one.
1099
 
        . "\nfile end $filename\n";
 
1209
  my($inbuf,$nread,$text); $text = '';
 
1210
  while ( $nread=read(IN,$inbuf,16384) ) { $text .= $inbuf }
 
1211
  defined $nread  or die "error reading $filename: $!";
 
1212
  close IN  or die "error closing $filename: $!";
 
1213
  undef $inbuf;
1100
1214
 
1101
 
  close IN;
 
1215
  $text = "file start $filename\n" . $text;
 
1216
  # add an extra \n in case file did not end in one.
 
1217
  $text .= "\nfile end $filename\n";
1102
1218
 
1103
1219
  $self->{conf}->{main} = $self;
1104
1220
  $self->{conf}->parse_scores_only ($text);
1124
1240
sub load_scoreonly_sql {
1125
1241
  my ($self, $username) = @_;
1126
1242
 
 
1243
  my $timer = $self->time_method("load_scoreonly_sql");
1127
1244
  my $src = Mail::SpamAssassin::Conf::SQL->new ($self);
1128
1245
  $self->{username} = $username;
1129
1246
  unless ($src->load($username)) {
1151
1268
  my ($self, $username) = @_;
1152
1269
 
1153
1270
  dbg("config: load_scoreonly_ldap($username)");
 
1271
  my $timer = $self->time_method("load_scoreonly_ldap");
1154
1272
  my $src = Mail::SpamAssassin::Conf::LDAP->new ($self);
1155
1273
  $self->{username} = $username;
1156
1274
  $src->load($username);
1202
1320
sub compile_now {
1203
1321
  my ($self, $use_user_prefs, $deal_with_userstate) = @_;
1204
1322
 
1205
 
  # tell plugins we are here
1206
 
  $self->call_plugins("compile_now_start",
1207
 
                      { use_user_prefs => $use_user_prefs,
1208
 
                        keep_userstate => $deal_with_userstate});
1209
 
 
1210
 
  # note: this may incur network access. Good.  We want to make sure
1211
 
  # as much as possible is preloaded!
1212
 
  my @testmsg = ("From: ignore\@compiling.spamassassin.taint.org\n", 
1213
 
    "Message-Id:  <".time."\@spamassassin_spamd_init>\n", "\n",
1214
 
    "I need to make this message body somewhat long so TextCat preloads\n"x20);
1215
 
 
1216
 
  dbg("ignore: test message to precompile patterns and load modules");
 
1323
  my $timer = $self->time_method("compile_now");
1217
1324
 
1218
1325
  # Backup default values which deal with userstate.
1219
1326
  # This is done so we can create any new files in, presumably, a temp dir.
1220
1327
  # see bug 2762 for more details.
1221
 
  my %backup = ();
 
1328
  my %backup;
1222
1329
  if (defined $deal_with_userstate && $deal_with_userstate) {
1223
1330
    while(my($k,$v) = each %{$self->{conf}}) {
1224
1331
      $backup{$k} = $v if (defined $v && !ref($v) && $v =~/__userstate__/);
1240
1347
    }
1241
1348
  }
1242
1349
 
1243
 
  my $mail = $self->parse(\@testmsg, 1);
 
1350
  dbg("ignore: test message to precompile patterns and load modules");
 
1351
 
 
1352
  # tell plugins we are about to send a message for compiling purposes
 
1353
  $self->call_plugins("compile_now_start",
 
1354
                      { use_user_prefs => $use_user_prefs,
 
1355
                        keep_userstate => $deal_with_userstate});
 
1356
 
 
1357
  # note: this may incur network access. Good.  We want to make sure
 
1358
  # as much as possible is preloaded!
 
1359
  my @testmsg = ("From: ignore\@compiling.spamassassin.taint.org\n", 
 
1360
    "Message-Id:  <".time."\@spamassassin_spamd_init>\n", "\n",
 
1361
    "I need to make this message body somewhat long so TextCat preloads\n"x20);
 
1362
 
 
1363
  my $mail = $self->parse(\@testmsg, 1, { master_deadline => undef });
1244
1364
  my $status = Mail::SpamAssassin::PerMsgStatus->new($self, $mail,
1245
1365
                        { disable_auto_learning => 1 } );
1246
1366
 
1266
1386
  }
1267
1387
 
1268
1388
  # make sure things are ready for scanning
1269
 
  $self->{bayes_scanner}->sanity_check_is_untied() if $self->{bayes_scanner};
 
1389
  $self->{bayes_scanner}->force_close() if $self->{bayes_scanner};
1270
1390
  $self->call_plugins("compile_now_finish",
1271
1391
                      { use_user_prefs => $use_user_prefs,
1272
1392
                        keep_userstate => $deal_with_userstate});
1335
1455
  $self->{'conf'}->{'use_auto_whitelist'} = 0;
1336
1456
  $self->{'conf'}->{'bayes_auto_learn'} = 0;
1337
1457
 
1338
 
  my $mail = $self->parse(\@testmsg, 1);
 
1458
  my $mail = $self->parse(\@testmsg, 1, { master_deadline => undef });
1339
1459
  my $status = Mail::SpamAssassin::PerMsgStatus->new($self, $mail,
1340
1460
                        { disable_auto_learning => 1 } );
1341
1461
  $status->check();
1343
1463
  $self->{syntax_errors} += $status->{rule_errors};
1344
1464
  $status->finish();
1345
1465
  $mail->finish();
1346
 
 
 
1466
  dbg("timing: " . $self->timer_report())  if $self->{timer_enabled};
1347
1467
  return ($self->{syntax_errors});
1348
1468
}
1349
1469
 
1360
1480
sub finish {
1361
1481
  my ($self) = @_;
1362
1482
 
 
1483
  $self->timer_start("finish");
1363
1484
  $self->call_plugins("finish_tests", { conf => $self->{conf},
1364
1485
                                        main => $self });
1365
1486
 
1373
1494
 
1374
1495
  $self->{resolver}->finish();
1375
1496
 
 
1497
  $self->timer_end("finish");
1376
1498
  %{$self} = ();
1377
1499
}
1378
1500
 
1379
1501
###########################################################################
 
1502
# timers: bug 5356
 
1503
 
 
1504
sub timer_enable {
 
1505
  my ($self) = @_;
 
1506
  dbg("config: timing enabled")  if !$self->{timer_enabled};
 
1507
  $self->{timer_enabled} = 1;
 
1508
}
 
1509
 
 
1510
sub timer_disable {
 
1511
  my ($self) = @_;
 
1512
  dbg("config: timing disabled")  if $self->{timer_enabled};
 
1513
  $self->{timer_enabled} = 0;
 
1514
}
 
1515
 
 
1516
# discard all timers, start afresh
 
1517
sub timer_reset {
 
1518
  my ($self) = @_;
 
1519
  delete $self->{timers};
 
1520
  delete $self->{timers_order};
 
1521
}
 
1522
 
 
1523
sub timer_start {
 
1524
  my ($self, $name) = @_;
 
1525
 
 
1526
  return unless $self->{timer_enabled};
 
1527
# dbg("timing: '$name' starting");
 
1528
 
 
1529
  if (!exists $self->{timers}->{$name}) {
 
1530
    push @{$self->{timers_order}}, $name;
 
1531
  }
 
1532
  
 
1533
  $self->{timers}->{$name}->{start} = Time::HiRes::time();
 
1534
  # note that this will reset any existing, unstopped timer of that name;
 
1535
  # that's ok
 
1536
}
 
1537
 
 
1538
sub timer_end {
 
1539
  my ($self, $name) = @_;
 
1540
  return unless $self->{timer_enabled};
 
1541
 
 
1542
  my $t = $self->{timers}->{$name};
 
1543
  $t->{end} = time;
 
1544
 
 
1545
  if (!$t->{start}) {
 
1546
    warn "timer_end('$name') with no timer_start";
 
1547
    return;
 
1548
  }
 
1549
 
 
1550
  # add to any existing elapsed time for this event, since
 
1551
  # we may call the same timer name multiple times -- this is ok,
 
1552
  # as long as they are not nested
 
1553
  my $dt = $t->{end} - $t->{start};
 
1554
  $dt = 0  if $dt < 0;  # tolerate clock jumps, just in case
 
1555
  if (defined $t->{elapsed}) { $t->{elapsed} += $dt }
 
1556
  else { $t->{elapsed} = $dt }
 
1557
}
 
1558
 
 
1559
sub time_method {
 
1560
  my ($self, $name) = @_;
 
1561
  return unless $self->{timer_enabled};
 
1562
  return Mail::SpamAssassin::Util::ScopedTimer->new($self, $name);
 
1563
}
 
1564
 
 
1565
sub timer_report {
 
1566
  my ($self) = @_;
 
1567
 
 
1568
  my $earliest;
 
1569
  my $latest;
 
1570
 
 
1571
  while (my($name,$h) = each(%{$self->{timers}})) {
 
1572
  # dbg("timing: %s - %s", $name, join(", ",
 
1573
  #     map { sprintf("%s => %s", $_, $h->{$_}) } keys(%$h)));
 
1574
    my $start = $h->{start};
 
1575
    if (defined $start && (!defined $earliest || $earliest > $start)) {
 
1576
      $earliest = $start;
 
1577
    }
 
1578
    my $end = $h->{end};
 
1579
    if (defined $end && (!defined $latest || $latest < $end)) {
 
1580
      $latest = $end;
 
1581
    }
 
1582
    dbg("timing: start but no end: $name") if defined $start && !defined $end;
 
1583
  }
 
1584
  my $total =
 
1585
    (!defined $latest || !defined $earliest) ? 0 : $latest - $earliest;
 
1586
  my @str;
 
1587
  foreach my $name (@{$self->{timers_order}}) {
 
1588
    my $elapsed = $self->{timers}->{$name}->{elapsed} || 0;
 
1589
    my $pc = $total <= 0 || $elapsed >= $total ? 100 : ($elapsed/$total)*100;
 
1590
    my $fmt = $elapsed >= 0.002 ? "%.0f" : "%.2f";
 
1591
    push @str, sprintf("%s: $fmt (%.1f%%)", $name, $elapsed*1000, $pc);
 
1592
  }
 
1593
 
 
1594
  return sprintf("total %.0f ms - %s", $total*1000, join(", ", @str));
 
1595
}
 
1596
 
 
1597
###########################################################################
1380
1598
# non-public methods.
1381
1599
 
1382
1600
sub init {
1393
1611
    return;
1394
1612
  }
1395
1613
 
 
1614
  my $timer = $self->time_method("init");
1396
1615
  # Note that this PID has run init()
1397
1616
  $self->{_initted} = $$;
1398
1617
 
1427
1646
    }
1428
1647
 
1429
1648
    if ($sysrules) {
1430
 
      $self->{config_text} .= $self->read_cf($sysrules, 'default rules dir');
 
1649
      my $cftext = $self->read_cf($sysrules, 'default rules dir');
 
1650
      if ($self->{require_rules} && $cftext !~ /\S/) {
 
1651
        die "config: no rules were found!  Do you need to run 'sa-update'?\n";
 
1652
      }
 
1653
      $self->{config_text} .= $cftext;
1431
1654
    }
1432
1655
 
1433
1656
    if (!$self->{languages_filename}) {
1450
1673
        # just use the last entry in the array as the default path.
1451
1674
        $fname ||= $self->sed_path($default_userprefs_path[-1]);
1452
1675
 
1453
 
        if (!-f $fname && !$self->create_default_prefs($fname)) {
 
1676
        my $stat_errn = stat($fname) ? 0 : 0+$!;
 
1677
        if ($stat_errn == 0 && -f _) {
 
1678
          # exists and is a regular file, nothing to do
 
1679
        } elsif ($stat_errn == 0) {
 
1680
          warn "config: default user preference file $fname is not a regular file\n";
 
1681
        } elsif ($stat_errn != ENOENT) {
 
1682
          warn "config: default user preference file $fname not accessible: $!\n";
 
1683
        } elsif (!$self->create_default_prefs($fname)) {
1454
1684
          warn "config: failed to create default user preference file $fname\n";
1455
1685
        }
1456
1686
      }
1459
1689
    }
1460
1690
  }
1461
1691
 
1462
 
  $self->{config_text} .= $self->{post_config_text} if ($self->{post_config_text});
 
1692
  if ($self->{pre_config_text}) {
 
1693
    $self->{config_text} = $self->{pre_config_text} . $self->{config_text};
 
1694
  }
 
1695
  if ($self->{post_config_text}) {
 
1696
    $self->{config_text} .= $self->{post_config_text};
 
1697
  }
1463
1698
 
1464
1699
  if ($self->{config_text} !~ /\S/) {
1465
 
    warn "config: no configuration text or files found! please check your setup\n";
 
1700
    my $m = "config: no configuration text or files found! do you need to run 'sa-update'?\n";
 
1701
    if ($self->{require_rules}) {
 
1702
      die $m;
 
1703
    } else {
 
1704
      warn $m;
 
1705
    }
1466
1706
  }
1467
1707
 
1468
1708
  # Go and parse the config!
1469
1709
  $self->{conf}->{main} = $self;
 
1710
  if (would_log('dbg', 'config_text') > 1) {
 
1711
    dbg('config_text: '.$self->{config_text});
 
1712
  }
1470
1713
  $self->{conf}->parse_rules ($self->{config_text});
1471
1714
  $self->{conf}->finish_parsing(0);
1472
1715
  delete $self->{conf}->{main}; # to allow future GC'ing
1474
1717
  undef $self->{config_text};   # ensure it's actually freed
1475
1718
  delete $self->{config_text};
1476
1719
 
 
1720
  if ($self->{require_rules} && !$self->{conf}->found_any_rules()) {
 
1721
    die "config: no rules were found!  Do you need to run 'sa-update'?\n";
 
1722
  }
 
1723
 
1477
1724
  # Initialize the Bayes subsystem
1478
1725
  if ($self->{conf}->{use_bayes}) {
1479
1726
      require Mail::SpamAssassin::Bayes;
1491
1738
    $self->{conf}->trim_rules($self->{only_these_rules});
1492
1739
  }
1493
1740
 
 
1741
  if (!$self->{timer_enabled}) {
 
1742
    # enable timing implicitly if _TIMING_ is used in add_header templates
 
1743
    foreach my $hf_ref (@{$self->{conf}->{'headers_ham'}},
 
1744
                        @{$self->{conf}->{'headers_spam'}}) {
 
1745
      if ($hf_ref->[1] =~ /_TIMING_/) { $self->timer_enable(); last }
 
1746
    }
 
1747
  }
 
1748
 
1494
1749
  # TODO -- open DNS cache etc. if necessary
1495
1750
}
1496
1751
 
1514
1769
  {
1515
1770
    dbg("config: using \"$path\" for $desc");
1516
1771
 
1517
 
    if (-d $path) {
 
1772
    my $stat_errn = stat($path) ? 0 : 0+$!;
 
1773
    if ($stat_errn == ENOENT) {
 
1774
      # no file or directory
 
1775
    } elsif ($stat_errn != 0) {
 
1776
      dbg("config: file or directory $path not accessible: $!");
 
1777
    } elsif (-d _) {
1518
1778
      foreach my $file ($self->$filelistmethod($path)) {
1519
1779
        $txt .= read_cf_file($file);
1520
1780
      }
1521
 
 
1522
 
    } elsif (-f $path && -s _ && -r _) {
 
1781
    } elsif (-f _ && -s _ && -r _) {
1523
1782
      $txt .= read_cf_file($path);
1524
1783
    }
1525
1784
  }
1532
1791
  my($path) = @_;
1533
1792
  my $txt = '';
1534
1793
 
 
1794
  local *IN;
1535
1795
  if (open (IN, "<".$path)) {
1536
 
    $txt = "file start $path\n";
1537
 
    $txt .= join ('', <IN>);
 
1796
 
 
1797
    my($inbuf,$nread); $txt = '';
 
1798
    while ( $nread=read(IN,$inbuf,16384) ) { $txt .= $inbuf }
 
1799
    defined $nread  or die "error reading $path: $!";
 
1800
    close IN  or die "error closing $path: $!";
 
1801
    undef $inbuf;
 
1802
 
 
1803
    $txt = "file start $path\n" . $txt;
1538
1804
    # add an extra \n in case file did not end in one.
1539
1805
    $txt .= "\nfile end $path\n";
1540
 
    close IN;
 
1806
 
1541
1807
    dbg("config: read file $path");
1542
1808
  }
1543
1809
  else {
1574
1840
    dbg("config: using \"$fname\" for user state dir");
1575
1841
  }
1576
1842
 
1577
 
  if (!-d $fname) {
1578
 
    # not being able to create the *dir* is not worth a warning at all times
1579
 
    eval { mkpath($fname, 0, 0700) } or dbg("config: mkdir $fname failed: $@ $!\n");
 
1843
  # if this is not a dir, not readable, or we are unable to create the dir,
 
1844
  # this is not (yet) a serious error; in fact, it's not even worth
 
1845
  # a warning at all times, so use dbg().  see bug 6268
 
1846
  my $stat_errn = stat($fname) ? 0 : 0+$!;
 
1847
  if ($stat_errn == 0 && !-d _) {
 
1848
    dbg("config: $fname exists but is not a directory");
 
1849
  } elsif ($stat_errn != 0 && $stat_errn != ENOENT) {
 
1850
    dbg("config: error accessing $fname: $!");
 
1851
  } else {  # does not exist, create it
 
1852
    eval {
 
1853
      mkpath($fname, 0, 0700);  1;
 
1854
    } or do {
 
1855
      my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
 
1856
      dbg("config: mkdir $fname failed: $eval_stat");
 
1857
    };
1580
1858
  }
1581
1859
 
1582
1860
  $fname;
1595
1873
sub find_rule_support_file {
1596
1874
  my ($self, $filename) = @_;
1597
1875
 
1598
 
  # take a copy to avoid modifying the real one (stupid map { } side-effect)
1599
 
  my @paths = @default_rules_path;
1600
 
  return $self->first_existing_path (map {
1601
 
      s/$/\/${filename}/;
1602
 
      $_;
1603
 
    } @paths);
 
1876
  return $self->first_existing_path(
 
1877
    map { my $p = $_; $p =~ s{$}{/$filename}; $p } @default_rules_path );
1604
1878
}
1605
1879
 
1606
1880
=item $f->create_default_prefs ($filename, $username [ , $userdir ] )
1625
1899
#    warn "config: hooray! user_dirs don't match! '$userdir' vs '$self->{user_dir}'\n";
1626
1900
#  }
1627
1901
 
1628
 
  if (!-f $fname)
1629
 
  {
 
1902
  my $stat_errn = stat($fname) ? 0 : 0+$!;
 
1903
  if ($stat_errn == 0) {
 
1904
    # fine, it already exists
 
1905
  } elsif ($stat_errn != ENOENT) {
 
1906
    dbg("config: cannot access user preferences file $fname: $!");
 
1907
  } else {
1630
1908
    # Pass on the value of $userdir for virtual users in vpopmail
1631
1909
    # otherwise it is empty and the user's normal homedir is used
1632
1910
    $self->get_and_create_userstate_dir($userdir);
1633
1911
 
1634
1912
    # copy in the default one for later editing
1635
 
    my $defprefs = $self->first_existing_path (@Mail::SpamAssassin::default_prefs_path);
1636
 
 
1637
 
    if (defined $defprefs && open (IN, "<$defprefs")) {
1638
 
      $fname = Mail::SpamAssassin::Util::untaint_file_path($fname);
1639
 
      if (open (OUT, ">$fname")) {
1640
 
        while (<IN>) {
1641
 
          /^\#\* / and next;
1642
 
          print OUT;
1643
 
        }
1644
 
        close OUT;
1645
 
        close IN;
1646
 
 
1647
 
        if (($< == 0) && ($> == 0) && defined($user)) { # chown it
1648
 
          my ($uid,$gid) = (getpwnam($user))[2,3];
1649
 
          unless (chown($uid, $gid, $fname)) {
1650
 
            warn "config: couldn't chown $fname to $uid:$gid for $user: $!\n";
1651
 
          }
1652
 
        }
1653
 
        warn "config: created user preferences file: $fname\n";
1654
 
        return(1);
1655
 
      }
1656
 
      else {
1657
 
        warn "config: cannot write to $fname: $!\n";
1658
 
      }
1659
 
    }
1660
 
    elsif (defined $defprefs) {
 
1913
    my $defprefs =
 
1914
      $self->first_existing_path(@Mail::SpamAssassin::default_prefs_path);
 
1915
 
 
1916
    local(*IN,*OUT);
 
1917
    $fname = Mail::SpamAssassin::Util::untaint_file_path($fname);
 
1918
    if (!defined $defprefs) {
 
1919
      warn "config: can not determine default prefs path\n";
 
1920
    } elsif (!open(IN, "<$defprefs")) {
1661
1921
      warn "config: cannot open $defprefs: $!\n";
1662
 
    }
1663
 
    else {
1664
 
      warn "config: can not determine default prefs path\n";
 
1922
    } elsif (!open(OUT, ">$fname")) {
 
1923
      warn "config: cannot create user preferences file $fname: $!\n";
 
1924
    } else {
 
1925
      # former code skipped lines beginning with '#* ', the following copy
 
1926
      # procedure no longer does so, as it avoids reading line-by-line
 
1927
      my($inbuf,$nread);
 
1928
      while ( $nread=read(IN,$inbuf,16384) ) {
 
1929
        print OUT $inbuf  or die "cannot write to $fname: $!";
 
1930
      }
 
1931
      defined $nread  or die "error reading $defprefs: $!";
 
1932
      undef $inbuf;
 
1933
      close OUT or die "error closing $fname: $!";
 
1934
      close IN  or die "error closing $defprefs: $!";
 
1935
 
 
1936
      if (($< == 0) && ($> == 0) && defined($user)) { # chown it
 
1937
        my ($uid,$gid) = (getpwnam($user))[2,3];
 
1938
        unless (chown($uid, $gid, $fname)) {
 
1939
          warn "config: couldn't chown $fname to $uid:$gid for $user: $!\n";
 
1940
        }
 
1941
      }
 
1942
      warn "config: created user preferences file: $fname\n";
 
1943
      return(1);
1665
1944
    }
1666
1945
  }
1667
1946
 
1729
2008
    if (defined $path) {
1730
2009
      my($errn) = stat($path) ? 0 : 0+$!;
1731
2010
      if    ($errn == ENOENT) { }  # does not exist
1732
 
      elsif ($errn) { warn "config: path \"$path\" is inaccessible: $!\n" }
 
2011
      elsif ($errn) {  warn "config: path \"$path\" is inaccessible: $!\n" }
1733
2012
      else { return $path }
1734
2013
    }
1735
2014
  }
1752
2031
  my ($self, $dir, $type) = @_;
1753
2032
 
1754
2033
  if ($self->{config_tree_recurse}) {
1755
 
    my @cfs = ();
 
2034
    my @cfs;
1756
2035
 
1757
2036
    # use "eval" to avoid loading File::Find unless this is specified
1758
 
    eval {
1759
 
      use File::Find qw();
 
2037
    eval ' use File::Find qw();
1760
2038
      File::Find::find(
1761
 
        sub {
1762
 
          return unless (/\.${type}$/i && -f $_);
1763
 
          push @cfs, $File::Find::name;
1764
 
        }, $dir);
 
2039
        { untaint => 1,
 
2040
          follow => 1,
 
2041
          wanted =>
 
2042
            sub { push(@cfs, $File::Find::name) if /\.\Q$type\E$/i && -f $_ }
 
2043
        }, $dir); 1;
 
2044
    ' or do {
 
2045
      my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
 
2046
      die "_get_cf_pre_files_in_dir error: $eval_stat";
1765
2047
    };
1766
2048
    return sort { $a cmp $b } @cfs;
1767
2049
 
1769
2051
  }
1770
2052
  else {
1771
2053
    opendir(SA_CF_DIR, $dir) or warn "config: cannot opendir $dir: $!\n";
1772
 
    my @cfs = grep { /\.${type}$/i && -f "$dir/$_" } readdir(SA_CF_DIR);
 
2054
    my @cfs = grep { $_ ne '.' && $_ ne '..' &&
 
2055
                     /\.${type}$/i && -f "$dir/$_" } readdir(SA_CF_DIR);
1773
2056
    closedir SA_CF_DIR;
1774
2057
 
1775
2058
    return map { "$dir/$_" } sort { $a cmp $b } @cfs;
1793
2076
  # We could potentially get called after a finish(), so just return.
1794
2077
  return unless $self->{plugins};
1795
2078
 
 
2079
  # safety net in case some plugin changes global settings, Bug 6218
 
2080
  local $/ = $/;  # prevent underlying modules from changing the global $/
 
2081
 
1796
2082
  my $subname = shift;
1797
 
  return $self->{plugins}->callback ($subname, @_);
 
2083
  return $self->{plugins}->callback($subname, @_);
1798
2084
}
1799
2085
 
1800
2086
###########################################################################
1804
2090
 
1805
2091
  $self->init(1);
1806
2092
 
1807
 
  my @addrlist = ();
 
2093
  my @addrlist;
1808
2094
  foreach my $header (qw(To From Cc Reply-To Sender
1809
2095
                                Errors-To Mail-Followup-To))
1810
2096
  {
1811
 
    my @hdrs = $mail_obj->get_header ($header);
 
2097
    my @hdrs = $mail_obj->get_header($header);
1812
2098
    if ($#hdrs < 0) { next; }
1813
 
    push (@addrlist, $self->find_all_addrs_in_line (join (" ", @hdrs)));
 
2099
    push (@addrlist, $self->find_all_addrs_in_line(join (" ", @hdrs)));
1814
2100
  }
1815
2101
 
1816
2102
  # find addrs in body, too
1817
2103
  foreach my $line (@{$mail_obj->get_body()}) {
1818
 
    push (@addrlist, $self->find_all_addrs_in_line ($line));
 
2104
    push (@addrlist, $self->find_all_addrs_in_line($line));
1819
2105
  }
1820
2106
 
1821
 
  my @ret = ();
1822
 
  my %done = ();
 
2107
  my @ret;
 
2108
  my %done;
1823
2109
 
1824
2110
  foreach $_ (@addrlist) {
1825
2111
    s/^mailto://;       # from Outlook "forwarded" message
1837
2123
  my $ID_PATTERN   = '[-a-z0-9_\+\:\=\!\#\$\%\&\*\^\?\{\}\|\~\/\.]+';
1838
2124
  my $HOST_PATTERN = '[-a-z0-9_\+\:\/]+';
1839
2125
 
1840
 
  my @addrs = ();
1841
 
  my %seen = ();
 
2126
  my @addrs;
 
2127
  my %seen;
1842
2128
  while ($line =~ s/(?:mailto:)?\s*
1843
2129
              ($ID_PATTERN \@
1844
2130
              $HOST_PATTERN(?:\.$HOST_PATTERN)+)//oix) 
1875
2161
  my $spamtest = Mail::SpamAssassin->new( ... );
1876
2162
 
1877
2163
  # backup configuration to %conf_backup
1878
 
  my %conf_backup = ();
 
2164
  my %conf_backup;
1879
2165
  $spamtest->copy_config(undef, \%conf_backup) ||
1880
2166
    die "config: error returned from copy_config!\n";
1881
2167
 
1900
2186
    return 0;
1901
2187
  }
1902
2188
 
 
2189
  my $timer = $self->time_method("copy_config");
 
2190
 
1903
2191
  # let the Conf object itself do all the heavy lifting.  It's better
1904
2192
  # than having this class know all about that class' internals...
1905
2193
  if (defined $source) {
1928
2216
  return $self->{plugins}->get_loaded_plugins_list();
1929
2217
}
1930
2218
 
1931
 
 
1932
2219
1;
1933
2220
__END__
1934
2221