#!/usr/bin/perl package Mail::SpamAssassin::POP3Client; use strict; use vars qw($VERSION); use Net::POP3; use IPC::Open2; use File::Spec; $VERSION = sprintf("%d.%02d", q$Revision: 1.3 $ =~ /(\d+)\.(\d+)/); sub popspam { my(%args) = @_; my($pop, $pop_handled_outside, $user, $pw, $host); if ($args{pop3}) { $pop = delete $args{pop3}; $pop_handled_outside = 1; } else { $host = delete $args{host} || die "Argument host missing"; $user = delete $args{user} || die "Argument user missing"; $pw = delete $args{pw} || die "Argument pw missing"; my $timeout = delete $args{timeout}; $pop = Net::POP3->new($host, defined $timeout ? (Timeout => $timeout) : (), ); if (!$pop) { die "Can't construct Net::POP3 object"; } } my $v = delete $args{v}; my $headeronly = delete $args{headeronly}; my $action = delete $args{action} || "report"; my $seenmsgid = delete $args{seenmsgid}; if (keys %args) { die "Unrecognized arguments: " . join ", ", keys %args; } if (!$pop_handled_outside) { if (!defined $pop->login($user, $pw)) { die "Can't login to $host as $user"; } } my $msgnums = $pop->list; my $uidl = $pop->uidl; my $devnull = File::Spec->can("devnull") ? File::Spec->devnull : "/dev/null"; open(DEVNULL, ">$devnull") or die $!; foreach my $msgnum (sort { $a <=> $b } keys %$msgnums) { if ($seenmsgid && exists $seenmsgid->{$uidl->{$msgnum}}) { if ($v) { print STDERR "Skipping message $msgnum ...\n" } next; } if ($v) { print STDERR "Check message $msgnum ...\n" } my $buf; if ($headeronly) { $buf = join "", @{ $pop->top($msgnum, 0) }; } else { $buf = join "", @{ $pop->get($msgnum) }; } my $pid = open2(">&DEVNULL", my $wtrfh, "spamassassin", "-e"); print $wtrfh $buf; close $wtrfh; waitpid $pid, 0; my $is_spam = ($?/256 != 0); if ($is_spam) { warn "Mail number $msgnum is SPAM\n"; if ($buf =~ /^from:\s*(.*)/im) { warn "From: $1\n"; } if ($buf =~ /^subject:\s*(.*)/im) { warn "Subject: $1\n"; } } if ($is_spam) { if ($action eq 'delete') { warn "Deleting message $msgnum...\n"; $pop->delete($msgnum); } } if ($seenmsgid) { $seenmsgid->{$uidl->{$msgnum}} = 1; } } close DEVNULL; if (!$pop_handled_outside) { $pop->quit; } } return 1 if caller; require Getopt::Long; my %opt; Getopt::Long::GetOptions(\%opt, "host=s", "user=s", "pw=s", "timeout=i", "headeronly!", "action=s", "seenmsgid=s", "v!") or die "usage!"; if (!defined $opt{pw}) { print STDERR "Password for $opt{user}\@$opt{host}: "; require Term::ReadKey; Term::ReadKey::ReadMode('noecho'); $opt{pw} = Term::ReadKey::ReadLine(0); Term::ReadKey::ReadMode('restore'); print STDERR "\n"; } if (defined $opt{seenmsgid}) { require DB_File; require Fcntl; tie my %seen, 'DB_File', $opt{seenmsgid}, &Fcntl::O_RDWR|&Fcntl::O_CREAT, 0600 or die $!; $opt{seenmsgid} = \%seen; } popspam(%opt); =head1 NAME popspam - apply spamassassin rules on remote POP3 mailboxes =head1 SYNOPSIS popspam -user -host [-pw ] [-timeout ] [-headeronly] [-action ] [-v] [-seenmsgid ] =head1 DESCRIPTION B applies L rules to a POP3 mailbox, optionally deleting mails classified as spam. =head2 OPTIONS =over =item -user User name of POP3 mailbox. =item -host Host name of POP3 mailbox. =item -pw Supply password on command line. This is optional, otherwise the password is asked interactively. Note that supplying the password on command line is a security risk, as command line arguments may be visible in programs like C or C. =item -headeronly Only fetch mail headers, not the mail body. =item -action What to do with spam: if I is C, then the mail will be deleted, otherwise it will only be reported on the console. =item -v Be verbose. =item -seenmsgid Path to a file where to store a hash of seen messages. This may improve performance. =back =head1 README popspam applies Mail::SpamAssassin rules to a POP3 mailbox, optionally deleting mails classified as spam. =head1 PREREQUISITES Mail::SpamAssassin, Net::POP3, Term::ReadKey =head1 COREQUISITES DB_File =head1 OSNAMES Platform independent, but only tested on Linux and FreeBSD =head1 SCRIPT CATEGORIES Mail =head1 CAVEAT This script is quite new and not well tested. Use at your own risk, especially when using the B action! =head1 AUTHOR Slaven Rezic =head1 SEE ALSO L =cut