Cover V12, I10
oct2003.tar

Listing 2 Mail::Archive::Account

package Mail::Archive::Account;

use File::Basename;
use File::Path;
use File::Spec;
use Mail::Audit;
use Mail::Address;
use Mail::POP3Client;


sub new(@) {
    my $class = shift;
    my %params = @_;
    my $self = {};

    map {
        die "Cannot create account without $_" unless (exists($params{$_}));
    } qw/user password host address/;

    map {
    $self->{$_} = $params{$_};
    } keys %params;

    $self->{maildir}    = File::Spec->catfile("$ENV{HOME}", "mail")
    unless $self->{maildir};

    $self->{auth_mode}  = "PASS"              unless $self->{auth_mode};
    $self->{log}        = "log"               unless $self->{log};
    $self->{archive}    = "Archive"           unless $self->{archive};
    $self->{inbox}      = "Inbox"             unless $self->{inbox}; 
    $self->{junk}       = "Junk"              unless $self->{junk};
    $self->{filter}     = \&filter            unless $self->{filter};
    $self->{safe}       = 0                   unless $self->{safe};
    $self->{mode}       = 0700                unless $self->{mode}; 

    (my $username, my $domain) = split(/\@/, $self->{address});
    $self->{maildir} .= "/$domain" if $opts{f};
    $self->{domain} = $domain;

    for (qw(archive log junk inbox)) { 
    $self->{$_} = File::Spec->catfile($self->{maildir}, $self->{$_})
        unless ($self->{$_} =~ /^\//);
    }

    $self->{spam} = File::Spec->catfile($self->{maildir}, $self->{spam})
        if ($self->{spam} && $self->{spam} !~ /^\//);

    bless($self, $class);

    return $self;
}


sub fetch($;) {
    my $self = shift;
    my $filter = shift;

    do {print "Skipping $self->{address}\n"; return;} if ($self->{skip});

    $self->_m_mkpath($self->{maildir}) unless -d $self->{maildir};
    $self->_popread($filter);
}


sub _popread($;) {
    my $self = shift;
    my $filter = shift || $self->{filter};
    my %account = {};
    my %midcache;
    my $cache = File::Spec->catfile($ENV{HOME}, ".msgidcache");

    if (-f $cache) {%midcache = map {chomp; $_ => 1} `tail -50 $cache`};

    print "Connecting to $self->{host}...";

    for (user, password, host, auth_mode) {
        (my $upper = $_) =~ tr/a-z/A-Z/;
        $account{$upper} = $self->{$_};
    }

    my $pop = new Mail::POP3Client(%account);
    unless ($pop) { warn "Couldn't connect\n"; next; }

    my $count = $pop->Count;
    if ($count <0) { warn "Authorization failed ($$_{host})"; next; }
    print "\n";
    print "New messages: $count\n";

    my %down = map {$_ => 1} (1..$count); 
    my @mails;
    for my $num (1..$count) {
    print "\n";
    my @head = $pop->Head($num);
    for (@head) {
         /^(From|Subject):\s+(.*)/i and do {
        print "$1\t$2\n";
        $mails[$num]->{$1} = $2;
         };
         /^Message-Id:\s+(\S+)/i and do {
        if (exists $midcache{$1}) {
            print "(Duplicate)\n";
            delete $down{$num};
            $mails[$num]->{mid} = $1;
            $pop->Delete($num) unless $self->{safe};
        }
        $midcache{$1}++;
         }
    }
    }

    next unless keys %down;

    my @tocome = sort {$a <=> $b} keys %down;
    print "Downloading: @tocome\n";
    for my $num (@tocome) {
    print "Downloading message $num (", $mails[$num]->{From}, ":",
        $mails[$num]->{Subject}, ")...";

    my @mail = $pop->Retrieve($num);
    $_ .= "\n" for @mail;
    my $now = scalar localtime;
    $mail[0] =~ s/Return-Path:\s+<([^>]+)>/From $1 $now/;

    print "\n";

    if (!@mail) { 
        print "Ugh, something went wrong!\n"; 
        delete $midcache{$mails[$num]->{mid}};
        next;
    }

    my $item = Mail::Audit->new(
        data      => \@mail, 
        emergency => $self->{junk},
        log       => $self->{log},
        loglevel  => 2,
        noexit    => 1,   
        nomime    => 1,   
    ); 

        next unless $item;

        print "From: ", $item->from, "\nTo: ", $item->to, "\n";

        $self->deliver($item, $self->{junk}) 
        unless &$filter($self, $item);

    $pop->Delete($num) 
        unless $self->{safe};
    }

    $pop->Close;

    open OUT, ">$cache" or die $!;
    print OUT "$_\n" for keys %midcache;
    close OUT;
}


sub filter($$) {
    my $self = shift;
    my $item = shift;

    my $spamtest= $self->{spamtest};
    my $address    = $self->{address};
    my $domain     = $self->{domain};
    my $inbox     = $self->{inbox};
    my $maildir = $self->{maildir};
    my $archive = $self->{archive};
    my $spam     = $self->{spam};
    my $junk    = $self->{junk};

    my $to = $item->to;
    my $cc = $item->cc;
    my $from = $item->from;
    
    my $isspam = 0;
     
    if ($spam && $spamtest) {
    my $status = $spamtest->check($item);
    if ($status) {
        $isspam = $status->is_spam();
        $status->finish();
    }
    }

    return eval {
    if ($from =~ /$address/i) {             # mail from me? 
        if ($spamtest) {
        my $learner = $spamtest->learn($item);
        $learner->finish();
        }

        # if i am cc'd at the same address, deliver it, too
        $self->deliver($item, $inbox) 
        if ($to =~ /$address/i || $cc =~ /$address/i);
        $self->file($item, $archive, Mail::Address->parse($to, $cc)); 
    } 
    elsif ($to =~ /$address/i || $cc =~ /$address/i) {# directly to me, either to or cc 
        if ($isspam) {
        $self->deliver($item, $spam); 
        } else {
        $self->file($item, $archive, Mail::Address->parse($from));
        $self->deliver($item, $inbox);
        }
    } 
    elsif (($to =~ /$domain/ || $cc =~ /$domain/)) {# mail not directly to me... 
        # check if email originated from within my domain
        if ($from =~ /$domain/) {  
        $self->file($item, $archive, Mail::Address->parse($from));
        $self->deliver($item, $inbox);
        } elsif ($isspam) {
        $self->deliver($item, $spam); 
        } else {
        $self->deliver($item, $junk);
        }
    } 
    elsif ($isspam) {            # spam
        $self->deliver($item, $spam);
    } 
    else {
        0;                       # something else
    }
    };
}


sub inbox($;)     {$self = shift; $self->get("inbox", @_)};
sub junk($;)      {$self = shift; $self->get("junk", @_)};
sub spam($;)      {$self = shift; $self->get("spam", @_)};
sub maildir($;)   {$self = shift; $self->get("maildir", @_)};
sub archive($;)   {$self = shift; $self->get("archive", @_)};
sub host($;)      {$self = shift; $self->get("host", @_)};
sub address($;)   {$self = shift; $self->get("address", @_)};
sub auth_mode($;) {$self = shift; $self->get("auth_mode", @_)};
sub password($;)  {$self = shift; $self->get("password", @_)};
sub user($;)      {$self = shift; $self->get("user", @_)};
sub safe($;)      {$self = shift; $self->get("safe", @_)};
sub code($;)      {$self = shift; $self->get("code", @_)};
sub mode($;)      {$self = shift; $self->get("mode", @_)};


sub get($$;) {
    my $self = shift;
    my $parameter = shift;
    my $setting = shift;

    my $string = $self->{$parameter}; 

    chomp($string=(defined $string && length $string) ? $string : "") 
        unless ($parameter =~ /(c|m)ode/);     # don't convert mode or code

    $self->{$parameter} = $setting if ($setting); 

    return $string; 
}


sub isspam($) {
    my $self = shift;
    my $item = shift;

    return 0 unless (exists $self->{spamtest} && exists $self->{spam});

    my $status = $self->{spamtest}->check($item);
    if ($status) {
    my $isspam = $status->is_spam();
    $status->finish();
    return $isspam;
    }

    return 0;
}


sub deliver($$;$;) {
    my $self = shift;
    (my $item, my $folder, my $mbox) = @_;

    return unless $item;

    $folder = $self->{inbox} unless $folder;

    my $to = $item->to;
    my $cc = $item->cc;
    my $from = $item->from;

    my $drop = eval {
    if ($mbox) {
        $self->_m_mkpath($folder) unless -d $folder; 
        $mbox = File::Spec->catfile($folder, $mbox);
    } 
    elsif (-f $folder || -d $folder) {
        $folder;
    } 
    else {
        my $dirname = dirname($folder);
        $self->_m_mkpath($dirname) unless (-f $dirname || -d $dirname);
        $folder;
    } 
    };

    print "Delivering to mailbox: $drop\n";

    return $item->accept($drop);
}


sub file($$@) {
    my $self = shift;
    my ($item, $subfolder) = (shift, shift);
    my @names = map {_explode($_);} @_;
    my $filed = 0;

    for my $ref (@names) {
    my $box = ''; my $username = ${$ref}{username};
    my $firstname = ${$ref}{firstname};
    my $lastname = ${$ref}{lastname};
    my $fullname = ${$ref}{fullname};
    my $domain = ${$ref}{domain};

        next if ($username =~ /-(help|subscribe)$/);

        if ($opts{s} && $lastname) {
        $box = $lastname;
        do {
            $box .= "_$firstname";
        } if $firstname; 
    } elsif ($firstname) {
        $box = $firstname;
        $box .= "_$lastname" if $lastname; 
    } elsif ($fullname) {
        $box =$fullname;
    } elsif ($username) {
        $box = $username;
    } else {
        print "... exception ... \n";
        $self->deliver($item, $self->{junk});
        $filed++;
        next;
    }

    $box = $1 if ($box =~ /(.*)@/); 
    $box =~ s/\s+/_/g;
    my ($letter) = split(//, $box);
    next if ($letter !~ /[a-z0-9_-]/);

        $filed += $self->deliver($item, $subfolder, 
        File::Spec->catfile($letter, $box));
    }

    return $filed;
}


use constant esc        => '\\\\';               
use constant Period     => '\.';
use constant space      => '\040';               
use constant tab        => '\t';
use constant OpenBR     => '\[';                 
use constant CloseBR    => '\]';
use constant OpenParen  => '\(';                 
use constant CloseParen => '\)';
use constant NonASCII   => '\x80-\xff';          
use constant ctrl       => '\000-\037';
use constant CRlist     => '\n\015'; 
use constant atom_char  => qq/[^<>()spacetab\@,;:\"escOpenBRCloseBRctrlNonASCII]/;

sub _explode {
    my $address = shift;
    return undef if (!defined($address));


    my @subdomains = split(Period, $address->host);
    my $fdomain = pop @subdomains;

    if ($fdomain =~ /atom_char{3}/) {
    $fdomain = pop @subdomains;
    } elsif ($fdomain =~ /atom_char{2}/) {
    $fdomain = pop @subdomains;
    $fdomain = pop @subdomains if ($fdomain =~ /^atom_char{2}$/);
    } 

    my $username = $address->user;
    my $fullname = $address->name;
    my $lastname = my $firstname = '';

    if ($fullname =~ /
                       ^(atom_char+) [spacetab]
                (atom_char+ [spacetab])* 
            (atom_char+)$
             /x) {
    $firstname = $1;
    $lastname = $3;
    }

    tr/A-Z/a-z/ for ($username, $fullname, $firstname, $lastname, $fdomain);

    return {
    username => $username,
    domain => $fdomain,
    fullname => $fullname,
    firstname => $firstname,
    lastname => $lastname,
    };
}

sub _exception($) {
    my $self = shift;
    my $item = shift;

    return unless ($item);

    $item->accept($self->{junk});
}


sub _m_mkpath($$;$;) {
    my $self = shift;
    (my $paths, my $echo, my $mode) = @_;

    mkpath($paths, $echo || 0, $mode || $self->{mode});
}

1;