级别: 初级 Teodor Zlatanov (tzz@bu.edu), 程序员, Gold Software Systems
2005 年 6 月 06 日 Ted 回到了使用 Mail::IMAPClient 来访问 IMAP 的主题,他将 ifrom.pl 当作是另外一个检查 IMAP 和 POP3 邮件的替代工具。这次 Ted 会介绍使用隧道(有时称为“端口转发”),并将这个脚本应用于 Maildir 邮件存储格式。
本文是“通过 Perl 使用 IMAP,第 1 部分” 的续篇,再次讨论了 ifrom.pl 工具。在阅读本文之前,请首先阅读上一篇文章中的一些介绍材料,从而能够很好地理解 ifrom.ipl 的一些特有机制。本文中的新主题包括 Maildir 邮件存储格式和隧道(有时也称为端口转发)。
对 ifrom.pl 的扩展已经满足了我作为一个 IMAP 用户的需求,因此我希望能证明它对您来说也非常有用。
 |
Stunnel,通用的 SSL 封装程序
Stunnel 是一个程序,它允许您使用 SSL 对任意的 TCP 连接进行加密,这在 UNIX® 和 Windows® 上都可以使用。使用 Stunnel,您可以通过让 Stunnel 提供加密来增强没有使用 SSL 的守护进程和协议(POP、IMAP、LDAP)的安全性,而不需要对守护进程的代码进行任何修改。源代码(遵守 GNU GPL)并不是一个完整的产品;您将仍然需要一个 SSL 函数库来编译 stunnel(这就是说 stunnel 只能支持您的 SSL 库,除非您希望对源代码进行修改)。
OpenSSH 对您的报告进行加密
OpenSSH 是一个免费的 SSH 协议网络连接工具包;它对所有的网络通信(包括密码)进行加密,从而有效地消除网络窃听、连接窃取以及其他类型的网络攻击,这些攻击在通过不加密的 Internet(例如 telnet、rlogin、ftp 以及其他程序)传输用户密码时都会发生。OpenSSH 还提供了很多加密隧道能力,以及很多认证方法。OpenSSH 主要是由 OpenBSD Project 开发的,支持 SSH 协议 1.3、1.5 和 2.0,包括 ssh (取代 rlogin 和 telnet)、scp(取代 rcp)、sftp(取代 ftp)、sshd(该包的服务器端),以及一些基本的工具,例如 ssh-add、ssh-agent、ssh-keysign、ssh-keyscan、ssh-keygen 和 sftp-server。
|
|
隧道,也称为端口转发
网络连接的
隧道(也称为 端口转发,不过从技术上来讲,二者并不完全相同)是计算环境中的一种常见技术。要对一个连接使用隧道,就是要将其重定向到一个或多个目的地(这并不需要是远程机器)。隧道应用程序通常并不知道自己所携带的是什么数据。使用隧道的程序也不知道。常见的 UNIX 隧道应用程序有 stunnel 和 OpenSSH 等。(OpenSSH 也有一些其他的用法,但是隧道非常适合它。)任何使用一个网络端口的程序都可以再使用一个隧道网络端口。您可以认为它就像是一个可以到达下一个门的电话连接,这就像它可以到达世界上的各个角落一样简单。
除 OpenSSH 之外,其他 SSH 版本也都支持隧道,但是由于 OpenSSH 的广泛流行和无限制的可用性,本文中我们就使用 OpenSSH 进行讨论。
在限制性环境中,隧道会非常有用。假设不允许您连接到一个外部 IMAP 服务器,因为防火墙配置限制了这种行为。那么您可以以端口转发模式运行 OpenSSH,这样它就可以将外部 IMAP 服务器的端口 143(IMAP)转发到您的本地机器上的 XYZ 端口上。您甚至并不需要登录到 IMAP 服务器上就可以实现这种功能!实际上,您只需要一个中间跳板,它可以接受 SSH 连接,然后连接到 IMAP 服务器。当然,如果您可以直接 SSH 到 IMAP 服务器上,那么连接的速度会更快,也更可靠,因为这样就消除了这个中间跳板的影响。
您也可以对整个连接过程进行反向,从 IMAP 服务器使用隧道连接到您自己的机器。
隧道的另外一种用途是对连接进行加密。当您使用 OpenSSH 启用隧道时,如果 IMAP 服务器没有提供安全连接,那么您可以使用它的加密能力来对 IMAP 报文进行加密。如果 IMAP 服务器早已提供了安全连接,那您仍然可以这样做,这样就对连接加密了两次。有些疯狂,不是吗?
为了提供隧道的功能,我在 ifrom.pl 中增加了 TUNNEL 选项。这非常简单。实际上,我 10 个月大的女儿在一半时间内就可以编写一个更好的程序(如果她不先啃键盘的话)。我只是在 ifrom.pl 中添加了一个 -tunnel 选项,在这个选项出现时,我会使用 system() 来运行它。
清单 1. 在 ifrom.pl 中使用 TUNNEL 选项
# use -tunnel like so with OpenSSH:
# ifrom.pl -tunnel "ssh REMOTEHOST.COM -N -T -n -L 2002:127.0.0.1:143 &"
# [other options to follow...]
# This forwards the REMOTE port 143 on REMOTEHOST.COM to the LOCAL
# port 2002. See the OpenSSH documentation for details and more
# examples. There is no intermediate jump point.
if ($config->TUNNEL())
{
system($config->TUNNEL());
}
|
然后,我实现了 ifrom.pl 应该完成的事情。也就是说,我连接到 IMAP 服务器上并检查邮件。这就是为什么我们会说隧道非常方便 —— 使用隧道的程序并不要做什么不常见的事情,因为所有的魔力都来自于数据传输层。在我的例子中,ifrom.pl 必须要稍加修改,但是整个程序的主要逻辑并没有发生变化。
Maildir 支持
Maildir 是一种邮件存储格式。Courier IMAP、qmail 以及其他一些服务器都使用这种格式来存储用户的邮件。Maildir 包含了一个目录,其中有子目录 cur、new 和 tmp。可能还有其他一些子目录,不过都可以忽略。为了简单起见,我将 Maildir 目录称为“Maildir”。
在上一段中所提到的 3 个子目录中,只有“new”目录直接与我们有关。在每个子目录中,Maildir 都会存储一些信息,每个文件一条。这些文件的名字都是惟一的。
“new”子目录中包含了 Maildir 中的新消息。何为“新”这一语义要取决于特定的邮件传输代理(MDA)和其他使用 Maildir 的程序。例如,Courier IMAP 通过将消息移出 Maildir 的 “new” 子目录而将这些消息标记为已查看的。
首先,我在 ifrom.pl 中添加了 -maildir 开关。这只是一个标量,告诉我不要尝试 IMAP 连接,而是执行 Maildir 逻辑。
Maildir 处理是通过一个 glob() 调用进行的。这意味着使用了本地 shell 的工具进行通配符“Maildir/new/*”的匹配。我还可以使用 opendir() 和 readdir(),但是 glob() 调用要简单得多。
对于使用 glob() 调用找到的每个文件,我会从中搜索发送者和主题并打印这些信息。然后,如果还指定了 -dump 或 -print 开关,就通过逐一打印相关的文件来打印所请求的消息。文件的顺序是由 shell 的 glob() 函数决定的。我并没有对日期进行排序,因为 shell 对 Maildir 文件的排序已经很好了(如果这些文件都是由 qmail 传输的,那么它们早就根据日期进行了排序)。在使用更多邮件传输代理时,需要对日期进行排序。
清单 2. ifrom.pl 对 Maildir 的支持
if ($config->MAILDIR())
{
my $count = 0;
foreach my $file (glob($config->MAILDIR() . '/new/*'))
{
$count++;
open M, "<$file";
my $address = 'UNKNOWN';
my $subject = 'UNKNOWN';
while (<M>)
{
$address = $1 if m/^From: (.*)/; # the sender of the message
$subject = $1 if m/^Subject: (.*)/; # the subject of the message
last if $_ eq "\n";
}
printf "%5d %-35.35s %s\n", $count, $address, $subject;
if ($config->DUMP || grep {$_ == $count} @{$config->PRINT})
{
close M;
open M, "<$file";
print MARKER();
print foreach <M>;
print MARKER();
}
}
}
|
导入邮件
作为一个邮件服务器的管理员,我需要将用户的邮件从所谓的 mbox 格式(一个大文件,其中包含消息)转换成 IMAP 服务器格式。由于 ifrom.pl 早已具有了必需的 IMAP 逻辑,因此我向其中添加了 -import 开关,并使用 Mail::Box 模块来处理从 mbox 文件读取邮件的过程。
这是一个速度很慢的方法,因为每条消息都必须通过网络连接进行传输。为了真正能够批量导入很多用户的邮件,您可能需要考虑一种更为复杂的方法,它需要对您的站点进行一些定制和裁剪。例如,如果您的站点使用 Maildirs 来存储邮件,并且您现在正从 mbox 格式迁移过来,那就可以使用 mbox 到 Maildir 的转换程序,例如 safecat(顺便说一下,safecat 还有很多其他的用途)。有关 mbox 到 Maildir 的转换,请参阅 safecat 的 Web 站点(参阅 参考资料)。
-mailbox 的 auto_mailbox 参数(保存在 MAILBOX_AUTO 常量中)会告诉 ifrom.pl 根据文件名使用邮箱名。与 PREFIX 参数一起,我在 -import 开关的值上使用 basename() 函数。如果没有指定 auto_mailbox,那么不论 -mailbox 说什么,新邮箱就叫什么。因此,-import A/B/C/FILE -mailbox auto_mailbox 会创建并生成一个名为“FILE”的 IMAP 文件夹,而 -import A/B/C/FILE -mailbox XYZ 则会创建一个名为“XYZ”的文件夹。
此外,如果看到一个文件名为“XYZ.msf”(XYZ 代表任意的名称,.msf 是 Mozilla 的 mbox 索引文件扩展名),那么就只有“XYZ”会被用作文件名。这会让您实现类似于 find DIRECTORY -name "*.msf" -exec ifrom.pl -import {} ... \; 的功能,这会将 find 的结果逐一传递给 ifrom.pl。它会发现所有的 Mozilla 索引文件,然后 ifrom.pl 会逐一导入相应的邮箱。
PREFIX 向 ifrom.pl 告知 IMAP 服务器的前缀。这个前缀在 IMAP 服务器上可以使用 namespace() 函数真正地得到,但是获得这个前缀的逻辑对于这样一个简单的 ifrom.pl 脚本来说太过复杂了。如果您需要 IMAP 前缀或分隔符,可以用 namespace() 得到。作为一个例子,通用的 UW IMAP 前缀和分隔符是 "" 和 "/",因此邮箱就类似于“a/b/c”(注意文件名和目录名是类似的)。Courier IMAP 服务器前缀和分隔符是“INBOX.”和“.”,因此邮箱就类似于“INBOX.a.b.c”(注意结构 —— Courier 文件夹全都位于顶层的 Maildir 之下,而没有子目录)。
ifrom.pl 的 -dryrun 选项非常有用。它让您可以在 ifrom.pl 导入邮件时看到详细的信息,除非实际的导入操作并不发生。
清单 3. 导入邮件
elsif ($config->IMPORT)
{
eval { require Mail::Box::Manager; };
die "You need to install the Mail::Box module, exiting" if $!;
my $file = $config->IMPORT;
if ($file =~ m/^(.*)\.msf$/i)
{
$file = $1;
print "MSF file detected, using $file as the file name\n";
}
die "Can't access import file $file" unless -r $file;
if ($config->MAILBOX eq MAILBOX_AUTO)
{
$box = $config->PREFIX . basename($file);
}
my $mgr = Mail::Box::Manager->new;
my $folder = $mgr->open(folder => $file);
my $i;
if ($config->DRYRUN)
{
print "Skipping folder check and creation because a dry run was requested\n";
}
else
{
if ($imap->select($box))
{
print "Selected folder successfully, ready to import.\n"
if $config->VERBOSE;
}
else
{
print "Could not select folder $box, trying to create it...\n";
$imap->create($box)
or die "Could not create import folder: $@\n";
}
}
# Iterate over the messages.
foreach ($folder->messages)
{
printf "Appending to mailbox %s, message %d: ID %s, %d lines\n",
$box, ++$i, $_->messageId, $_->nrLines;
if ($config->DRYRUN)
{
print "Skipping import because a dry run was requested\n";
}
else
{
$imap->append($box, $_->string);
}
}
}
|

 |

|
其他改进和注意事项
我编写了一些帮助信息。它非常简短,但却信息丰富,您可以使用 ifrom.pl -help 来查看这些信息。
我添加了一个 MARKER() 常量,因为会频繁用到“\n===\n\n”。
对于 Maildirs 来说,我并没有添加 IMAP 中所有的特有代码,例如 -backup 或 -delete_mailbox_really 开关。因为 Maildir 是一个本地特性,这就是说您可以通过本地挂载的文件系统来访问 Maildir 中的数据,备份和邮箱的删除都是非常简单的一些 shell 操作。例如,要备份一个 Maildir,请参考下面的例子:
清单 4. 备份本地的 Maildir
rsync -avP --delete MaildirLocation Destination
# so, for example, to back up /var/qmail/maildirs/tzz
# to /home/tzz/backups
rsync -avP --delete /var/qmail/maildirs/tzz /home/tzz/backups
|
请阅读 rsync 的文档 —— 它是一个常见的很好的备份和目录同步工具。如果您不使用这个工具,就忽略了目前最好的一个 UNIX 工具。
Maildir 和 IMAP 循环的逻辑看起来非常类似,但是它们的相似度还不足以进行合并。如果有另外一个邮件源被加入 ifrom.pl 中,那么可能就有意义将所有的循环都合并成一个循环了。现在,我并不认为值得合并这两个循环,因为这会使得整个程序的逻辑更容易产生混淆,但却没有得到什么好处。
我还添加了调用 -print 时可以从命令行中只接受数字的能力,这样就不用使用 ifrom.pl -print 1 -print 2,而是只要使用 ifrom.pl 1 2 即可 —— 我认为这对于用户来说会更好。代码非常简单,您只需要首先调用 $config->args() 即可。
清单 5. 为 -print 开关传递命令行参数
# all non-switch command-line arguments are implied -print requests
if (scalar @ARGV)
{
$config->print($_) foreach @ARGV;
}
|
结束语
ifrom.pl 是我曾经编写的工具中功能最为强大的工具之一。它非常简单,却速度很快,可以实现我需要的一些功能。我希望您对本文满意,并希望 ifrom.pl 可以很好地为您服务。而且,我还希望您从本文中获得的有关 IMAP 和 Maildirs 的知识将来会有用。
您可以自由地使用 ifrom.pl,并将您的改进建议发送给我。
参考资料
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- “通过 Perl 使用 IMAP,第 1 部分”(developerWorks,2003 年 6 月)向您简介了使用
Mail::IMAPClient CPAN 模块和 ifrom 工具来访问 IMAP 的知识。
- 下载本文中使用的 ifrom.pl 脚本。
- 阅读 developerWorks 上 Ted 撰写的
功能丰富的 Perl 系列 文章。
- 访问 CPAN,这是 CPAN 模块包。
- 在 Mail::IMAPClient 手册页中查找更多有关
Mail::IMAPClient 的信息。
- IMAP4rev1 RFC 2060 文档中定义了当前版本的 IMAP 协议。这是一篇必读物。
-
TAoUP 或 The Art of Unix Programming,是由 Eric Raymond 撰写的一本非常好的书,它可以帮助您理解为什么
-tunnel 选项是如此简单,但在 UNIX 环境中的功能却是如此强大。
-
safecat 是一个优秀的工具,用于将 mboxes 转换为 Maildirs。
-
Stunnel 是一个程序,可以让您使用 SSL 来加密任意的 TCP 连接,这在 UNIX 和 Windows 上都可以使用。
-
OpenSSH 是一个免费的 SSH 协议的网路连接工具,它可以对所有的网络通信(包括密码)进行加密,从而有效的消除网络窃听、连接窃取以及其他类型的网络攻击。
- 在 developerWorks Linux 专区 中可以找到更多为 Linux 开发者准备的参考资料。
- 通过参与 developerWorks blogs 加入 developerWorks 社区。
- 在 Developer Bookstore 的 Linux 专区购买 Linux 的打折书籍。
-
定购免费的 SEK for Linux,这是两张 DVD,其中包含了 IBM 在 Linux 平台上的最新试用软件,包括 DB2®、Lotus®、Rational®、Tivoli® 以及 WebSphere®。
- 在您的下一个 Linux 开发项目中使用 IBM 试用软件,它们可以从 developerWorks 的 下载目录中获得。
关于作者  | 
|  | Teodor Zlatanov 1999 年毕业于波士顿大学计算机工程专业,获硕士学位。自从 1992 以来,他一直从事编程工作,使用的语言包括 Perl、Java、C 和 C++。他的主要兴趣在于文本解析、三层客户机-服务器数据库体系结构、UNIX 系统管理、CORBA 以及项目管理方面的开放源码工作。任何有关本文的建议和纠正都可以发往 tzz-at-bu.edu。 |
对本文的评价
|