使用 Mail::Audit 进行邮件过滤
让我们面对现实。 procmail 真的很糟糕。但对大多数人来说,它是唯一合理的邮件过滤方式。我曾经忍受 procmail,因为它那丑陋的语法和不太有帮助的错误信息,因为这是我唯一知道如何分离邮件的方式。然而有一天,我决定不再忍受“投递失败,无法获取锁”或类似的垃圾信息,于是决定坐下来写一个 procmail 的替代品。
这时我意识到,我真正不喜欢 procmail 的地方是它的配方格式。我不想用冒号、零和单个字母命令来处理我的邮件,这些命令让 sendmail.cf
看起来就像莎士比亚的十四行诗;我想要用一种漂亮的高级语言来编写我的邮件路由程序。比如 Perl。
结果是惊人的简单 Mail::Audit 模块。在这篇文章中,我们将探讨 Mail::Audit 可以做什么,以及我们如何使用它来创建邮件过滤器。我们还将查看 News::Gateway 模块,用于将邮件列表转换为新闻组,反之亦然。
这是什么?
Mail::Audit 本身不是一个邮件过滤器 - 它是一个工具包,使您能够轻松地构建邮件过滤器。您编写一个程序来描述您的邮件应该发生什么,然后它将替换您的 .forward
或 .qmail
文件中的 procmail 命令。
Mail::Audit 提供了提取邮件头、退回、接受、拒绝、转发和过滤传入邮件的功能。
一个非常简单的邮件过滤器
以下是使用 Mail::Audit 可以创建的最简单的过滤器程序。
use Mail::Audit;
my $incoming = Mail::Audit->new;
$incoming->reject;
如果您将其保存为 ~/bin/chuckmail
,则可以将以下内容放入 .forward
文件中
|~/bin/chuckmail
或者在 .qmail
文件中
preline ~/bin/chuckmail
您收到的每封邮件现在都会通过这个程序。邮件通过标准输入进入程序,new()
方法将其从那里取出来,并将其转换为一个 Mail::Audit 对象
my $incoming = Mail::Audit->new;
接下来,我们将将其退回为不可投递
$incoming->reject;
我们甚至可以更高级,为退回提供原因
$incoming->reject(<<EOF);
The local user was silly enough to leave chuckmail as his
mail filter. Too bad you can't mail him to let him know.
EOF
这个原因将作为退回消息的一部分发送回发送者。
将邮件分隔到文件夹中
大多数人使用 procmail 的一个原因是将其邮件分离到几个邮件文件夹中。以下是如何做的示例
use Mail::Audit;
my $item = Mail::Audit->new;
if ($item->from =~ /perl5-porters/) {
$item->accept("/home/simon/mail/p5p")
}
$item->accept;
现在任何在 From:
行中有 perl5-porters
的邮件将被添加到我的主目录下的 mail/p5p
文件中。任何其他邮件将正常接受到我的收件箱中。
这里要注意两点
- 一旦邮件通过
accept()
被归档到mail/p5p
,它就会离开程序。游戏结束,故事结束。对其他方法如reject()
、pipe()
和bounce()
也是如此。 - 程序中的最后一行可能是
accept()
调用;如果没有存入邮箱或被拒绝,邮件就会在程序结束时被默默地忽略。(这可能在以后的版本中改为隐式accept()
,以更类似于 procmail。)
如果您有一些要过滤的邮件列表或人,您可以这样做
use Mail::Audit;
my $item = Mail::Audit->new;
my $maildir = "/home/simon/mail/";
my %lists = (
perl5-porters => "p5p",
helixcode => "gnome",
uclinux => "uclinux",
'infobot\.org' => "infobot",
'@dion\.ne\.jp' => "yamachan"
);
for my $pattern (keys %lists) {
$item->accept($maildir.$lists{$pattern})
if $item->from =~ /$pattern/
or $item->to =~ /$pattern/;
}
$item->accept;
这次,我们执行正则表达式匹配,以查看 From:
行或 To:
行是否与我们的哈希键中的任何模式匹配,如果匹配,就将邮件导向相应的文件夹。由于我们使用的是普通的 Perl 正则表达式,我们可以这样做
'\bxxx.*\.com$' => "spam"
(您会惊讶于它能捕获多少垃圾邮件。)
这里有一个简单但非常有效的垃圾邮件陷阱配方。
$item->accept("questionable")
if $item->from !~ /simon/i and $item->cc !~ /simon/i;
我们检查《From:》和《CC:》头信息是否包含我的名字,如果不包含任何一个,那么邮件可能不是给我的。这个方法在过滤掉来自订阅者到通用列表地址的邮件列表消息之后才有效。
邮件和新闻
我更喜欢将邮件列表当作新闻组阅读;虽然像《mutt》这样的好邮局客户端可以显示邮件作为线程讨论,但我个人更喜欢在新闻阅读器中导航。那么,我们如何将邮件列表转到新闻组,再转回来呢?Russ Allbery的《News::Gateway》模块可以帮助我们做到这一点——它提供了一个名为《listgate》的程序,该程序接收一个传入的邮件列表消息,将其重新格式化为有效的新闻文章,然后将其发布到新闻服务器。我们非常容易将其插入到我们的邮件过滤器中;假设我们已经在本地新闻服务器上设置了组《lists.p5p》,并且已经适当地配置了listgate,我们只需说
$item->pipe("listgate p5p") if $item->from =~ /perl5-porters/;
再次,如果我们有多个组,我们可以像上面处理邮件列表那样使用哈希表将模式关联到组。
关于将接收到的邮件发送到新闻组已经说完了,那么如何将发布的文章再发回到邮件列表呢?关键是新闻组审批系统——当你向审批的新闻组发布文章时,文章会被发送给审批者进行审批。如果我们把《lists.p5p》的审批者设置为列表地址,我们就可以将我们的发布的帖子发送到列表。在《/usr/news/etc/moderators》中,你应该这样写
lists.p5p: [email protected]
非常简单。唯一的问题是它不起作用。邮件消息和新闻文章的格式略有不同,一些邮件列表管理器会拒绝看起来像新闻文章的邮件消息。因此,我们需要先进行清理。我们不会将其发送到《[email protected]》,而是发送到《news-outgoing@localhost》
lists.*: news-outgoing@localhost
到达该账户的邮件需要通过另一个Perl程序进行清理和发送出去的文章,这看起来是这样的
#!/usr/bin/perl
use News::Gateway;
my $gw=News::Gateway->new(0);
$gw->modules( 'newstomail', 'headers');
$gw->config_line("newstomail /home/simon/bin/news2mail.h");
$gw->config_line("header newsgroups drop");
$gw->config_line("header organisation drop");
$gw->config_line("header nntp-posting-host drop");
$gw->read(*STDIN) or die $!;
$gw->apply();
$gw->mail();
它从标准输入读取一篇文章,删除《Newsgroups》、《Organisation》和《NNTP-Posting-Host》头信息,使用配置文件《/home/simon/bin/news2mail.h》来找到地址,然后将其重新格式化为邮件消息,并发送。该配置文件只是一个新闻组和它们所属的地址列表
lists.p5p [email protected]
lists.tlug [email protected]
lists.advocacy [email protected]
lists.linux-kernel [email protected]
lists.perl-friends [email protected]
所以,这是过滤新闻到邮件和再次回到新闻的配方
• 接收到的消息
将通过你的邮件过滤器中的一个规则进行捕获,并通过类似以下的方式通过管道传输到《listgate》
$item->pipe("listgate p5p") if $item->from =~ /perl5-porters/;
《listgate》然后将它们发布到你的新闻服务器,到组《lists.p5p》。
• 发送的文章
将发送到审批者地址,《news-outgoing@localhost》进行清理。清理程序将删除不必要的头信息,重新格式化为邮件消息,然后查看配置文件以确定发送的位置。它们将被发送到邮件列表,稍后将以邮件的形式返回给你,以在新闻组中显示,如上所述。
完整的过滤器
在这里,为了展示我使用《Mail::Audit》所做的操作,提供了一个适当的匿名和注释版本的过滤器,我目前用它来处理我的接收到的邮件。
#!/usr/bin/perl
use Mail::Audit;
$folder = "/home/simon/mail/";
任何真正到达我这里的邮件都将被记录下来,这样我就可以在我的一个终端上使用《tail -f》查看接收到的邮件摘要。
open (LOG, ">/home/simon/.audit_log");
读取新的邮件消息,并从中提取重要的头信息
my $item = Mail::Audit->new;
my $from = $item->from();
study $from;
my $to = $item->to();
my $cc = $item->cc();
my $subject = $item->subject();
chomp($from, $to, $subject);
如果我可能会在办公室,我喜欢收到我收到的所有邮件的副本,以防我需要立即处理某些事情。所以我需要《时间控制的》过滤。尝试使用《procmail》来做这件事
my ($hour, $wday) = (localtime)[2,6];
if ($wday !=0 and $wday !=6 # Not Saturday/Sunday
and $hour > 9 and $hour < 18) { # Between 9am and 6pm
print LOG "$subject: $from: Bouncing to work\n";
$item->resend('[email protected]');
# resend is the only action
# which doesn't end the program.
}
我的一个用户有一段时间没有自己的电子邮件地址,所以他们的朋友会给我发送邮件。现在他们有了自己的地址,所以邮件会转发给他们
$item->bounce('[email protected]') if $subject =~ /^For Ei:/;
我维护两个常见问题解答(FAQ):Perl5-porters FAQ和东京高速连接FAQ。邮件来自不同的电子邮件地址,但最终都汇集到我的邮箱中。它们需要分别存放在不同的文件夹中。
$item->accept("$folder/p5p-faq") if $to=~ /p5p-faq/;
$item->accept("$folder/tokyo-faq") if $to=~ /faq/;
我收到一些希腊语的邮件,需要使用metamail
来处理字符集。管道方法将邮件喷射到另一个程序。
$item->pipe("metamail -B -x > $folder/greek")
if $from =~/hri\.org$/;
有些人我非常想收到他们的邮件,因此在这个阶段就接受他们的邮件以节省时间。
for (qw(goodguy dormouse locust)) {
if ($from =~ /$_/) {
print LOG "$from:$subject:Exception,
accepting into inbox\n";
$item->accept;
}
}
有些人我非常不想收到他们的邮件。
for (qw(badguy nasty enemy)) {
if ($from =~ /$_/) {
print LOG "$from:$subject:Dumped\n";
$item->reject("Go away! Stop emailing me!");
}
}
有些人或邮件列表我现在没有时间处理,所以它们被默默地忽略。
for (qw(freshmeat.net microsoft news\@myhost cron)) {
if ($from =~ /$_/) {
print LOG "$from:$subject:Ignored\n";
$item->ignore;
}
}
有些邮件列表我希望保持列表形式。
my %lists = (
"pound.perl.org" => "purl",
"helixcode" => "gnome",
"uclinux" => "uclinux",
"infobot" => "infobot",
"european-" => "yapc",
"tpm\@otherside" => "tpm",
"hellenic" => "greeknews",
);
for my $what (keys %lists) {
next unless $from =~ /$what/i or
$to =~ /$what/i or $cc =~/$what/i;
my $where = $lists{$what};
print LOG "$from:$subject:List,
accepting to folder $where\n";
$item->accept($folder.$where);
}
还有一些我希望将它们通过listgate
作为新闻组。
my %gated = (
"tlug" => "tlug",
"advocacy" => "advocacy",
"security-sig" => "security",
"iss.net" => "security",
"securityfocus" => "security",
"perl5-porters" => "p5p",
"linux-kernel" => "linux-kernel",
"perlsupport" => "perl-friends",
);
for my $what (keys %gated) {
next unless $from =~ /$what/i or
$to =~ /$what/i or $cc =~/$what/i;
my $where=$gated{$what};
print LOG "$from:$subject:Gated to lists.$where\n";
$item->pipe("/usr/local/bin/listgate $where");
}
有些垃圾邮件发送者从不放弃,所以我们实际上拒绝了他们的邮件。我们根据主题进行拒绝,这有点风险,但似乎有效。
for ("Invest", "nude asian")) {
$item->reject("No! Go away!")
if $subject=~/\b$_\b/;
}
在我们让文章进入收件箱之前,程序末尾有一个长列表,其中包含与已知垃圾邮件发送者匹配的模式。我们将收到的邮件与此列表进行比对,并保存以进行分析和报告。
while (<DATA>) {
chomp;
next unless $from =~ /$_/i or $to =~ /$_/i;
print LOG "$from:$subject:Spam?\n";
$item->accept($folder."spam");
}
现在是我们对看似不是给我们的邮件的最终检查。
if ($item->from !~ /simon/i and $item->cc !~ /simon/i) {
print LOG "$from:$subject:Badly addressed mail\n";
$item->accept("questionable")
}
最后,我们让邮件进入。
print LOG "INCOMING MAIL:$from:$subject:
Accepting to inbox\n";
$item->accept();
注意事项
我非常愿意信任Mail::Audit处理我所有的收件箱邮件。一段时间内它和procmail并行运行,但现在它成了主导者。然而,如果你想自己运行它,有些事情你需要注意。
Mail::Audit已在qmail
和postfix
上进行了测试——它应该在其他MTA(消息传输代理)上正常工作,只要它们相信exit 100
表示拒绝。如果不这样,你可以像这样覆盖reject
方法。
$item = Mail::Audit->new(
reject => sub { exit 67; }
);
它还假设默认邮箱是/var/spool/mail/name
,其中name
是当前用户的用户ID。如果不是这样,(我相信mh
不是这样工作的)说accept("Mailbox")
或用自己的子例程覆盖accept
。
最后,Mail::Audit并不复杂。它不过是Mail::Internet的包装。虽然它可能适合你想要编写的绝大多数过滤器,但不要期望它为你做所有事情。
结论
Mail::Audit和News::Gateway都可在CPAN找到;它们一起允许你非常容易地在Perl中构建邮件过滤器和新闻组网关。这是一种使用Perl过滤邮件的极好方式,也是过时的procmail的出色替代品。
版权所有《Perl杂志》。经CPM Media LLC许可转载。保留所有权利。
标签
反馈
这篇文章有什么问题吗?请通过在GitHub上打开问题或拉取请求来帮助我们。