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

« back to all changes in this revision

Viewing changes to lib/Mail/SpamAssassin/ArchiveIterator.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:
22
22
use strict;
23
23
use warnings;
24
24
use bytes;
 
25
use re 'taint';
25
26
 
 
27
use Errno qw(ENOENT EACCES EBADF);
26
28
use Mail::SpamAssassin::Util;
27
29
use Mail::SpamAssassin::Constants qw(:sa);
28
30
use Mail::SpamAssassin::Logger;
68
70
 
69
71
The Mail::SpamAssassin::ArchiveIterator module will go through a set
70
72
of mbox files, mbx files, and directories (with a single message per
71
 
file) and generate a list of messages.  It will then call the wanted
72
 
and results functions appropriately per message.
 
73
file) and generate a list of messages.  It will then call the C<wanted_sub>
 
74
and C<result_sub> functions appropriately per message.
73
75
 
74
76
=head1 METHODS
75
77
 
123
125
it's a good idea to set this to 0 if you can, as it imposes a performance
124
126
hit.
125
127
 
 
128
=item opt_skip_empty_messages
 
129
 
 
130
Set to 1 if you want to skip corrupt, 0-byte messages.  The default is 0.
 
131
 
126
132
=item opt_cache
127
133
 
128
134
Set to 0 (default) if you don't want to use cached information to help speed
323
329
    return;
324
330
  }
325
331
 
326
 
  # skip too-big mails
327
 
  if (! $self->{opt_all} && -s INPUT > BIG_BYTES) {
 
332
  my $stat_errn = stat(INPUT) ? 0 : 0+$!;
 
333
  if ($stat_errn == ENOENT) {
 
334
    dbg("archive-iterator: no such input ($where)");
 
335
    return;
 
336
  }
 
337
  elsif ($stat_errn != 0) {
 
338
    warn "archive-iterator: no access to input ($where): $!";
 
339
    return;
 
340
  }
 
341
  elsif (!-f _ && !-c _ && !-p _) {
 
342
    warn "archive-iterator: not a plain file (or char.spec. or pipe) ($where)";
 
343
    return;
 
344
  }
 
345
 
 
346
  if ($self->{opt_all}) {
 
347
    # process any size
 
348
  } elsif (!-f _) {
 
349
    # must check size while reading
 
350
  } elsif (-s _ > BIG_BYTES) {
 
351
    # skip too-big mails
 
352
    # note that -s can only deal with files, it returns 0 on char.spec. STDIN
328
353
    info("archive-iterator: skipping large message\n");
329
 
    close INPUT;
 
354
    close INPUT  or die "error closing input file: $!";
330
355
    return;
331
356
  }
 
357
 
332
358
  my @msg;
333
359
  my $header;
334
 
  while (<INPUT>) {
335
 
    push(@msg, $_);
336
 
    if (!defined $header && /^\015?$/) {
337
 
      $header = $#msg;
 
360
  my $len = 0;
 
361
  my $str = '';
 
362
  my($inbuf,$nread);
 
363
  while ( $nread=read(INPUT,$inbuf,16384) ) {
 
364
    $len += $nread;
 
365
    if (($len > BIG_BYTES) && !$self->{opt_all}) {
 
366
      info("archive-iterator: skipping large message\n");
 
367
      close INPUT  or die "error closing input file: $!";
 
368
      return;
338
369
    }
339
 
  }
340
 
  close INPUT;
 
370
    $str .= $inbuf;
 
371
  }
 
372
  defined $nread  or die "error reading: $!";
 
373
  undef $inbuf;
 
374
  @msg = split(/^/m, $str, -1);  undef $str;
 
375
  for my $j (0..$#msg) {
 
376
    if ($msg[$j] =~ /^\015?$/) { $header = $j; last }
 
377
  }
 
378
  close INPUT  or die "error closing input file: $!";
341
379
 
342
380
  if ($date == AI_TIME_UNKNOWN && $self->{determine_receive_date}) {
343
381
    $date = Mail::SpamAssassin::Util::receive_date(join('', splice(@msg, 0, $header)));
356
394
    $self->{access_problem} = 1;
357
395
    return;
358
396
  }
359
 
  seek(INPUT,$offset,0);
360
 
  while (<INPUT>) {
361
 
    last if (substr($_,0,5) eq "From " && @msg);
 
397
  seek(INPUT,$offset,0)  or die "cannot reposition file to $offset: $!";
 
398
  for ($!=0; <INPUT>; $!=0) {
 
399
    last if (substr($_,0,5) eq "From " && @msg && /^From \S+  ?\S\S\S \S\S\S .\d .\d:\d\d:\d\d \d{4}/);
362
400
    push (@msg, $_);
363
401
 
364
402
    # skip too-big mails
365
403
    if (! $self->{opt_all} && @msg > BIG_LINES) {
366
404
      info("archive-iterator: skipping large message\n");
367
 
      close INPUT;
 
405
      close INPUT  or die "error closing input file: $!";
368
406
      return;
369
407
    }
370
408
 
372
410
      $header = $#msg;
373
411
    }
374
412
  }
375
 
  close INPUT;
 
413
  defined $_ || $!==0  or
 
414
    $!==EBADF ? dbg("archive-iterator: error reading: $!")
 
415
              : die "error reading: $!";
 
416
  close INPUT  or die "error closing input file: $!";
376
417
 
377
418
  if ($date == AI_TIME_UNKNOWN && $self->{determine_receive_date}) {
378
419
    $date = Mail::SpamAssassin::Util::receive_date(join('', splice(@msg, 0, $header)));
393
434
    return;
394
435
  }
395
436
 
396
 
  seek(INPUT, $offset, 0);
 
437
  seek(INPUT,$offset,0)  or die "cannot reposition file to $offset: $!";
397
438
    
398
 
  while (<INPUT>) {
 
439
  for ($!=0; <INPUT>; $!=0) {
399
440
    last if ($_ =~ MBX_SEPARATOR);
400
441
    push (@msg, $_);
401
442
 
402
443
    # skip mails that are too big
403
444
    if (! $self->{opt_all} && @msg > BIG_LINES) {
404
445
      info("archive-iterator: skipping large message\n");
405
 
      close INPUT;
 
446
      close INPUT  or die "error closing input file: $!";
406
447
      return;
407
448
    }
408
449
 
410
451
      $header = $#msg;
411
452
    }
412
453
  }
413
 
  close INPUT;
 
454
  defined $_ || $!==0  or
 
455
    $!==EBADF ? dbg("archive-iterator: error reading: $!")
 
456
              : die "error reading: $!";
 
457
  close INPUT  or die "error closing input file: $!";
414
458
 
415
459
  if ($date == AI_TIME_UNKNOWN && $self->{determine_receive_date}) {
416
460
    $date = Mail::SpamAssassin::Util::receive_date(join('', splice(@msg, 0, $header)));
436
480
      next;
437
481
    }
438
482
 
439
 
    my %opts = ();
 
483
    my %opts;
440
484
    if (ref $target eq 'HASH') {
441
485
      # e.g. { target => $target, opt_foo => 1, opt_bar => 0.4 ... }
442
486
      foreach my $k (keys %{$target}) {
443
 
        next unless ($k =~ /^opt_/);
444
 
        my $v = $target->{$k};
445
 
        next unless defined $v;
446
 
        $opts{$k} = $v;
 
487
        if ($k =~ /^opt_/) {
 
488
          $opts{$k} = $target->{$k};
 
489
        }
447
490
      }
448
491
      $target = $target->{target};
449
492
    }
484
527
      my $method;
485
528
 
486
529
      # for this location only; 'detect' means they can differ for each location
487
 
      my $thisformat = $format;     
 
530
      my $thisformat = $format;
488
531
 
489
532
      if ($format eq 'detect') {
490
533
        # detect the format
491
 
        if (!-d $location && $location =~ /\.mbox/i) {
 
534
        my $stat_errn = stat($location) ? 0 : 0+$!;
 
535
        if ($stat_errn == ENOENT) {
 
536
          $thisformat = 'file';  # actually, no file - to be detected later
 
537
        }
 
538
        elsif ($stat_errn != 0) {
 
539
          warn "archive-iterator: no access to $location: $!";
 
540
          $thisformat = 'file';
 
541
        }
 
542
        elsif (-d _) {
 
543
          # it's a directory
 
544
          $thisformat = 'dir';
 
545
        }
 
546
        elsif ($location =~ /\.mbox/i) {
492
547
          # filename indicates mbox
493
548
          $thisformat = 'mbox';
494
549
        } 
495
 
        elsif (!(-d $location)) {
496
 
          $thisformat = 'file';
497
 
        }
498
550
        else {
499
 
          # it's a directory
500
 
          $thisformat = 'dir';
 
551
          $thisformat = 'file';
501
552
        }
502
553
      }
503
554
 
553
604
  }
554
605
 
555
606
  # bug 5249: mail could have 8-bit data, need this on some platforms
556
 
  binmode INPUT;
 
607
  binmode INPUT  or die "cannot set input file to binmode: $!";
557
608
 
558
609
  return 1;
559
610
}
636
687
sub _scan_directory {
637
688
  my ($self, $class, $folder, $bkfunc) = @_;
638
689
 
639
 
  my @files;
640
 
 
641
 
  opendir(DIR, $folder) || die "archive-iterator: can't open '$folder' dir: $!\n";
642
 
  if (-f "$folder/cyrus.header") {
 
690
  my(@files,@subdirs);
 
691
 
 
692
  if (-d "$folder/new" && -d "$folder/cur" && -d "$folder/tmp") {
 
693
    # Maildir format: bug 3003
 
694
    for my $sub ("new", "cur") {
 
695
      opendir (DIR, "$folder/$sub")
 
696
            or die "Can't open '$folder/$sub' dir: $!\n";
 
697
      # Don't learn from messages marked as deleted
 
698
      # Or files starting with a leading dot
 
699
      push @files, map { "$sub/$_" } grep { !/^\.|:2,.*T/ } readdir(DIR);
 
700
      closedir(DIR)  or die "error closing directory $folder: $!";
 
701
    } 
 
702
  }
 
703
  elsif (-f "$folder/cyrus.header") {
 
704
    opendir(DIR, $folder)
 
705
      or die "archive-iterator: can't open '$folder' dir: $!\n";
 
706
 
643
707
    # cyrus metadata: http://unix.lsa.umich.edu/docs/imap/imap-lsa-srv_3.html
644
 
    @files = grep { /^\S+$/ && !/^cyrus\.(?:index|header|cache|seen)/ }
645
 
                        readdir(DIR);
 
708
    @files = grep { $_ ne '.' && $_ ne '..' &&
 
709
                    /^\S+$/ && !/^cyrus\.(?:index|header|cache|seen)/ }
 
710
                  readdir(DIR);
 
711
    closedir(DIR)  or die "error closing directory $folder: $!";
646
712
  }
647
713
  else {
 
714
    opendir(DIR, $folder)
 
715
      or die "archive-iterator: can't open '$folder' dir: $!\n";
 
716
 
648
717
    # ignore ,234 (deleted or refiled messages) and MH metadata dotfiles
649
718
    @files = grep { !/^[,.]/ } readdir(DIR);
 
719
    closedir(DIR)  or die "error closing directory $folder: $!";
650
720
  }
651
 
  closedir(DIR);
652
721
 
653
 
  @files = grep { -f } map { "$folder/$_" } @files;
 
722
  $_ = "$folder/$_"  for @files;
654
723
 
655
724
  if (!@files) {
656
725
    # this is not a problem; no need to warn about it
660
729
 
661
730
  $self->_create_cache('dir', $folder);
662
731
 
663
 
  foreach my $mail (@files) {
664
 
    $self->_scan_file($class, $mail, $bkfunc);
 
732
  foreach my $file (@files) {
 
733
    my $stat_errn = stat($file) ? 0 : 0+$!;
 
734
    if ($stat_errn == ENOENT) {
 
735
      # no longer there?
 
736
    }
 
737
    elsif ($stat_errn != 0) {
 
738
      warn "archive-iterator: no access to $file: $!";
 
739
    }
 
740
    elsif (-f _ || -c _ || -p _) {
 
741
      $self->_scan_file($class, $file, $bkfunc);
 
742
    }
 
743
    elsif (-d _) {
 
744
      push(@subdirs, $file);
 
745
    }
 
746
    else {
 
747
      warn "archive-iterator: $file is not a plain file or directory: $!";
 
748
    }
 
749
  }
 
750
  @files = ();  # release storage
 
751
 
 
752
  # recurse into directories
 
753
  foreach my $dir (@subdirs) {
 
754
    $self->_scan_directory($class, $dir, $bkfunc);
665
755
  }
666
756
 
667
757
  if (defined $AICache) {
674
764
 
675
765
  $self->_bump_scan_progress();
676
766
 
677
 
  my @s = stat($mail);
678
 
  return unless $self->_message_is_useful_by_file_modtime($s[9]);
 
767
  # only perform these stat() operations if we're not using a cache;
 
768
  # it's faster to perform lookups in the cache, and more accurate
 
769
  if (!defined $AICache) {
 
770
    my @s = stat($mail);
 
771
    @s  or warn "archive-iterator: no access to $mail: $!";
 
772
    return unless $self->_message_is_useful_by_file_modtime($s[9]);
 
773
  }
679
774
 
680
775
  my $date = AI_TIME_UNKNOWN;
681
 
 
682
776
  if ($self->{determine_receive_date}) {
683
777
    unless (defined $AICache and $date = $AICache->check($mail)) {
684
 
      my $header;
 
778
      # silently skip directories/non-files; some folders may
 
779
      # contain extraneous dirs etc.
 
780
      my $stat_errn = stat($mail) ? 0 : 0+$!;
 
781
      if ($stat_errn != 0) {
 
782
        warn "archive-iterator: no access to $mail: $!";
 
783
        return;
 
784
      }
 
785
      elsif (!-f _) {
 
786
        return;
 
787
      }
 
788
 
 
789
      my $header = '';
685
790
      if (!_mail_open($mail)) {
686
791
        $self->{access_problem} = 1;
687
792
        return;
688
793
      }
689
 
      while (<INPUT>) {
 
794
      for ($!=0; <INPUT>; $!=0) {
690
795
        last if /^\015?$/s;
691
796
        $header .= $_;
692
797
      }
693
 
      close(INPUT);
 
798
      defined $_ || $!==0  or
 
799
        $!==EBADF ? dbg("archive-iterator: error reading: $!")
 
800
                  : die "error reading: $!";
 
801
      close INPUT  or die "error closing input file: $!";
 
802
 
 
803
      return if ($self->{opt_skip_empty_messages} && $header eq '');
 
804
 
694
805
      $date = Mail::SpamAssassin::Util::receive_date($header);
695
806
      if (defined $AICache) {
696
807
        $AICache->update($mail, $date);
700
811
    return if !$self->_message_is_useful_by_date($date);
701
812
    return if !$self->_scanprob_says_scan();
702
813
  }
 
814
  else {
 
815
    return if ($self->{opt_skip_empty_messages} && (-z $mail));
 
816
  }
703
817
 
704
818
  &{$bkfunc}($self, $date, $class, 'f', $mail);
705
819
 
710
824
  my ($self, $class, $folder, $bkfunc) = @_;
711
825
  my @files;
712
826
 
713
 
  if (-d $folder) {
 
827
  my $stat_errn = stat($folder) ? 0 : 0+$!;
 
828
  if ($stat_errn == ENOENT) {
 
829
    # no longer there?
 
830
  }
 
831
  elsif ($stat_errn != 0) {
 
832
    warn "archive-iterator: no access to $folder: $!";
 
833
  }
 
834
  elsif (-f _) {
 
835
    push(@files, $folder);
 
836
  }
 
837
  elsif (-d _) {
714
838
    # passed a directory of mboxes
715
839
    $folder =~ s/\/\s*$//; #Remove trailing slash, if there
716
840
    if (!opendir(DIR, $folder)) {
718
842
      $self->{access_problem} = 1;
719
843
      return;
720
844
    }
721
 
 
722
845
    while ($_ = readdir(DIR)) {
723
 
      if(/^[^\.]\S*$/ && ! -d "$folder/$_") {
 
846
      next if $_ eq '.' || $_ eq '..' || !/^[^\.]\S*$/;
 
847
      # hmmm, ignores folders with spaces in the name???
 
848
      $stat_errn = stat("$folder/$_") ? 0 : 0+$!;
 
849
      if ($stat_errn == ENOENT) {
 
850
        # no longer there?
 
851
      }
 
852
      elsif ($stat_errn != 0) {
 
853
        warn "archive-iterator: no access to $folder/$_: $!";
 
854
      }
 
855
      elsif (-f _) {
724
856
        push(@files, "$folder/$_");
725
857
      }
726
858
    }
727
 
    closedir(DIR);
 
859
    closedir(DIR)  or die "error closing directory $folder: $!";
728
860
  }
729
861
  else {
730
 
    push(@files, $folder);
 
862
    warn "archive-iterator: $folder is not a plain file or directory: $!";
731
863
  }
732
864
 
733
865
  foreach my $file (@files) {
739
871
    }
740
872
 
741
873
    my @s = stat($file);
 
874
    @s  or warn "archive-iterator: no access to $file: $!";
742
875
    next unless $self->_message_is_useful_by_file_modtime($s[9]);
743
876
 
744
877
    my $info = {};
762
895
      my $where = 0;            # current byte offset
763
896
      my $first = '';           # first line of message
764
897
      my $header = '';          # header text
765
 
      my $in_header = 0;                # are in we a header?
 
898
      my $in_header = 0;        # are in we a header?
766
899
      while (!eof INPUT) {
767
900
        my $offset = $start;    # byte offset of this message
768
901
        my $header = $first;    # remember first line
769
 
        while (<INPUT>) {
 
902
        for ($!=0; <INPUT>; $!=0) {
770
903
          if ($in_header) {
771
904
            if (/^\015?$/s) {
772
905
              $in_header = 0;
775
908
              $header .= $_;
776
909
            }
777
910
          }
778
 
          if (substr($_,0,5) eq "From ") {
 
911
          if (substr($_,0,5) eq "From " &&
 
912
              /^From \S+  ?\S\S\S \S\S\S .\d .\d:\d\d:\d\d \d{4}/) {
779
913
            $in_header = 1;
780
914
            $first = $_;
781
915
            $start = $where;
782
916
            $where = tell INPUT;
 
917
            $where >= 0  or die "cannot obtain file position: $!";
783
918
            last;
784
919
          }
785
920
          $where = tell INPUT;
 
921
          $where >= 0  or die "cannot obtain file position: $!";
786
922
        }
787
 
        if ($header) {
 
923
        defined $_ || $!==0  or
 
924
          $!==EBADF ? dbg("archive-iterator: error reading: $!")
 
925
                    : die "error reading: $!";
 
926
        if ($header ne '') {
 
927
        # next if ($self->{opt_skip_empty_messages} && $header eq '');
788
928
          $self->_bump_scan_progress();
789
929
          $info->{$offset} = Mail::SpamAssassin::Util::receive_date($header);
790
930
        }
791
931
      }
792
 
      close INPUT;
 
932
      close INPUT  or die "error closing input file: $!";
793
933
    }
794
934
 
795
935
    while(my($k,$v) = each %{$info}) {
815
955
  my ($self, $class, $folder, $bkfunc) = @_;
816
956
  my (@files, $fp);
817
957
 
818
 
  if (-d $folder) {
 
958
  my $stat_errn = stat($folder) ? 0 : 0+$!;
 
959
  if ($stat_errn == ENOENT) {
 
960
    # no longer there?
 
961
  }
 
962
  elsif ($stat_errn != 0) {
 
963
    warn "archive-iterator: no access to $folder: $!";
 
964
  }
 
965
  elsif (-f _) {
 
966
    push(@files, $folder);
 
967
  }
 
968
  elsif (-d _) {
819
969
    # got passed a directory full of mbx folders.
820
970
    $folder =~ s/\/\s*$//; # remove trailing slash, if there is one
821
971
    if (!opendir(DIR, $folder)) {
823
973
      $self->{access_problem} = 1;
824
974
      return;
825
975
    }
826
 
 
827
976
    while ($_ = readdir(DIR)) {
828
 
      if(/^[^\.]\S*$/ && ! -d "$folder/$_") {
 
977
      next if $_ eq '.' || $_ eq '..' || !/^[^\.]\S*$/;
 
978
      # hmmm, ignores folders with spaces in the name???
 
979
      $stat_errn = stat("$folder/$_") ? 0 : 0+$!;
 
980
      if ($stat_errn == ENOENT) {
 
981
        # no longer there?
 
982
      }
 
983
      elsif ($stat_errn != 0) {
 
984
        warn "archive-iterator: no access to $folder/$_: $!";
 
985
      }
 
986
      elsif (-f _) {
829
987
        push(@files, "$folder/$_");
830
988
      }
831
989
    }
832
 
    closedir(DIR);
 
990
    closedir(DIR)  or die "error closing directory $folder: $!";
833
991
  }
834
992
  else {
835
 
    push(@files, $folder);
 
993
    warn "archive-iterator: $folder is not a plain file or directory: $!";
836
994
  }
837
995
 
838
996
  foreach my $file (@files) {
845
1003
    }
846
1004
 
847
1005
    my @s = stat($file);
 
1006
    @s  or warn "archive-iterator: no access to $file: $!";
848
1007
    next unless $self->_message_is_useful_by_file_modtime($s[9]);
849
1008
 
850
1009
    my $info = {};
865
1024
      }
866
1025
 
867
1026
      # check the mailbox is in mbx format
868
 
      $fp = <INPUT>;
869
 
      if ($fp !~ /\*mbx\*/) {
 
1027
      $! = 0; $fp = <INPUT>;
 
1028
      defined $fp || $!==0  or
 
1029
        $!==EBADF ? dbg("archive-iterator: error reading: $!")
 
1030
                  : die "error reading: $!";
 
1031
      if (!defined $fp) {
 
1032
        die "archive-iterator: error: mailbox not in mbx format - empty!\n";
 
1033
      } elsif ($fp !~ /\*mbx\*/) {
870
1034
        die "archive-iterator: error: mailbox not in mbx format!\n";
871
1035
      }
872
1036
 
873
1037
      # skip mbx headers to the first email...
874
 
      seek(INPUT, 2048, 0);
875
 
 
 
1038
      seek(INPUT,2048,0)  or die "cannot reposition file to 2048: $!";
876
1039
      my $sep = MBX_SEPARATOR;
877
1040
 
878
 
      while (<INPUT>) {
 
1041
      for ($!=0; <INPUT>; $!=0) {
879
1042
        if ($_ =~ /$sep/) {
880
1043
          my $offset = tell INPUT;
 
1044
          $offset >= 0  or die "cannot obtain file position: $!";
881
1045
          my $size = $2;
882
1046
 
883
1047
          # gather up the headers...
884
1048
          my $header = '';
885
 
          while (<INPUT>) {
 
1049
          for ($!=0; <INPUT>; $!=0) {
886
1050
            last if (/^\015?$/s);
887
1051
            $header .= $_;
888
1052
          }
889
 
 
890
 
          $self->_bump_scan_progress();
891
 
          $info->{$offset} = Mail::SpamAssassin::Util::receive_date($header);
 
1053
          defined $_ || $!==0  or
 
1054
            $!==EBADF ? dbg("archive-iterator: error reading: $!")
 
1055
                      : die "error reading: $!";
 
1056
          if (!($self->{opt_skip_empty_messages} && $header eq '')) {
 
1057
            $self->_bump_scan_progress();
 
1058
            $info->{$offset} = Mail::SpamAssassin::Util::receive_date($header);
 
1059
          }
892
1060
 
893
1061
          # go onto the next message
894
 
          seek(INPUT, $offset + $size, 0);
 
1062
          seek(INPUT, $offset + $size, 0)
 
1063
            or die "cannot reposition file to $offset + $size: $!";
895
1064
        }
896
1065
        else {
897
1066
          die "archive-iterator: error: failure to read message body!\n";
898
1067
        }
899
1068
      }
900
 
      close INPUT;
 
1069
      defined $_ || $!==0  or
 
1070
        $!==EBADF ? dbg("archive-iterator: error reading: $!")
 
1071
                  : die "error reading: $!";
 
1072
      close INPUT  or die "error closing input file: $!";
901
1073
    }
902
1074
 
903
1075
    while(my($k,$v) = each %{$info}) {