从命令行检查 Exchange
多年来,在使用 Linux 的过程中,您往往会积累一套工具、实践和设置。就我而言,这意味着 Mutt 配置非常适合我,就像量身定制的西装一样,而 screen 会话在家中将我重新连接到我的 IRC 会话,在工作中为我提供对电子邮件的快速访问,终端底部会显示 Nagios 警报和收到的电子邮件的通知。多年来,我写过所有这些不同的工具,但在本文中,我将讨论当我的一个脚本不再工作时,我是如何适应的。
我的电子邮件通知脚本相对简单直接。我在本地机器上配置 fetchmail,但实际上并没有抓取电子邮件,我只是运行 fetchmail -c
,它会返回每个邮箱以及未读邮件的数量。我解析它,如果我有任何未读邮件,我会在 screen 的通知区域中显示它。我在 2011 年 2 月的 Hack and / 专栏 “Screen 中的状态消息” 中写过关于它的文章,到目前为止,它对我来说一直运行良好。每当我为新工作设置我的电脑时,我只需配置 fetchmail 并重复使用相同的脚本。
然而,最近,我们在工作中将邮件服务器切换到中央 Exchange 设置,这本身不会是太大的问题——过去我只是配置 Mutt 和 fetchmail 将其视为任何其他 IMAP 主机——但在这种情况下,Exchange 服务器的配置考虑了安全性。因此,除了使用 IMAPS 之外,每个客户端还获得了客户端证书,以便在身份验证期间呈现给服务器。Mutt 能够通过一些配置调整来很好地处理这个问题,但 fetchmail 表现不佳。事实证明,fetchmail 有一些人会称之为配置怪癖,另一些人会称之为错误的东西。当您配置 fetchmail 以使用客户端证书时,它会覆盖您配置的任何用户名,而偏向于客户端证书内指定的用户。就我而言,这两者不匹配,因此 fetchmail 无法登录到 Exchange 服务器,我也不再收到 screen 会话中的新邮件通知。
我忍受这种情况大约一周左右,直到我意识到我真的很想知道我何时有新邮件。我决定一定有其他方法可以从命令行获取未读邮件的计数,所以我开始做研究。最后,对我有效的方法是使用 OpenSSL 的 s_client
模式来处理我和 Exchange 服务器之间的 SSL 会话(包括客户端证书),一旦会话建立,我就能够发送原始 IMAP 命令进行身份验证,然后检查未读邮件。
第一步是建立 OpenSSL s_client
连接。大多数人可能只在需要生成新的自签名证书或从证书内部读取数据时才在命令行与 OpenSSL 交互,但该工具还提供 s_client
模式,您可以使用该模式来排查 SSL 启用服务(如 HTTPS)的故障。使用 s_client
,您可以启动 SSL 连接,并在其输出有关该 SSL 连接的相关信息后,您会看到一个提示符,就像您使用 Telnet 或 Netcat 连接到远程端口一样。从那里,您可以根据您的服务键入原始 HTTP、SMTP 或 IMAP 命令。
s_client
的语法相对简单直接,以下是我如何通过 IMAPS 连接到我的 Exchange 服务器的
$ openssl s_client -cert /home/kyle/.mutt/imaps_cert.pem
↪-crlf -connect imaps.example.com:993
-cert
参数接受我的客户端证书文件的完整路径,我将其与我的其余 Mutt 配置一起存储。-crlf
选项确保我在每次按 Enter 键时都发送正确的换行符——这对于某些敏感的 IMAPS 服务器很重要。最后,-connect
参数让我指定要连接的主机名和端口。
连接后,您将看到很多 SSL 输出,包括服务器呈现的证书,最后,您将看到如下所示的提示符
* OK The Microsoft Exchange IMAP4 service is ready.
从这里,您使用 tag login
IMAP 命令,后跟您的用户名和密码登录,如果登录成功,您应该会收到某种确认。
tag login kyle.rankin supersecretpassword
tag OK LOGIN completed.
既然您已登录,您就可以发送您想要的任何其他 IMAP 命令,包括一些会向您显示邮箱列表、邮件标头甚至邮件完整内容的命令。但在我的情况下,我只想查看我的收件箱中未读邮件的数量,所以我使用 tag STATUS
命令,后跟邮箱,然后是 (UNSEEN)
,告诉它返回未读邮件的数量。
tag STATUS INBOX (UNSEEN)
* STATUS INBOX (UNSEEN 1)
tag OK STATUS completed.
在此示例中,我的收件箱中有一封未读邮件。既然我已经有了这些信息,我可以键入 tag LOGOUT
以注销。
expect
现在这很棒,但我不打算每次想检查新邮件时都执行所有这些步骤。 我需要做的是自动化这个过程。 不幸的是,我尝试仅将我想要的命令作为输入传递并没有很好地工作,因为我需要在命令之间暂停,以便远程服务器接受上一个命令。 当您遇到这种情况时,像 expect
这样的工具是处理它的常用方法之一。 expect
允许您构建非常复杂的程序,这些程序查找特定的输出,然后发送您的输入。 在我的情况下,我只需要几个简单的命令:1) 确认 Exchange 已准备就绪;2) 发送我的登录信息;3) 一旦我通过身份验证,发送 tag STATUS
命令;4) 然后最终注销。 expect
脚本变成了以下内容
set timeout 10
spawn openssl s_client -cert /home/kyle/.mutt/imaps_cert.pem
↪-crlf -connect imaps.example.com:993
expect "* OK"
send "tag login kyle.rankin supersecretpassword\n"
expect "tag OK LOGIN completed."
sleep 1
send "tag STATUS INBOX (UNSEEN)\n"
expect "tag OK"
send "tag LOGOUT\n"
我将其保存到本地文件(并确保只有我的用户可以读取它),然后将其作为 expect
的唯一参数调用。
$ expect .imapsexpectscript
当然,由于此脚本运行整个 IMAPS 会话,它也会将我的身份验证信息输出到屏幕。 无论如何我只需要收件箱状态输出,所以我只是 grep
查找它。
$ expect ~/.imapsexpectscript | egrep '\(UNSEEN [0-9]'
* STATUS INBOX (UNSEEN 1)
对于我的 screen 会话,我只想知道邮箱的名称和未读邮件的数量(如果没有未读邮件则不输出),所以我稍微修改了我的 egrep
,并将整个内容通过管道传输到一个快速的 Perl 单行命令来去除我不想要的输出。 最终脚本如下所示
#!/bin/bash
MAILCOUNT=`expect ~/.imapsexpectscript | egrep '\(UNSEEN [1-9]'
↪| perl -pe 's/.*STATUS \w+.*?(\d+)\).*?$/$1/'`
if [ "$MAILCOUNT" != "" ]; then
echo INBOX:${MAILCOUNT}
fi
现在我只需更新我的 .screenrc,将该脚本的输出加载到我的一个反引号字段中,而不是 fetchmail(更多信息请参见我之前关于 screen 的专栏文章),我就又可以工作了。