在 Raspberry Pi 上用 Postfix 搭建内网邮件转发服务器
这本来是6月3日的记录,记录的是3日前几天内的测试过程,不过一直忘了写。昨晚翻看日历中的待办事项时发现这一个记录,就决定在一有空的时候就写下来。
原记录:
要点:改NS记录,设置relay而不是利用MX记录直接投递
建设的网站中有一个要邮箱验证的功能,而且要求使用学校的邮箱。所以在注册之后要将验证链接发送到学校的邮箱里;学校的邮箱地址在内网就可以解析。
我们原来的设计是将邮件给126进行转发。不过有一个问题,126提供的是商业服务,为了保证资源可用,肯定要限制账户的发送次数和流量。此外,学校的邮箱系统在接收外网来的邮件时会出现10分钟的延迟。所以,同学们的体验非常不好,我们不断接到“收不到邮件”的投诉。我们也问了学校,即使能使用其提供的服务,同样会有发送限制。
现在问题来了:要找到一种方法,一种没有发送限制的方法。
在剔除了一些明显不可能的解后,我们决定自己搭建一个邮件转发服务器。
硬件上,最好的选择当然是单片机加上板子,小巧而强劲,能hack。我们选了 Raspberry Pi B+,杨彦君往 TF 卡里灌了 Raspbian,进行了初始化的配置。
邮件服务有多种选择,包括 sendmail、Postfix、inbox.py、Apache JAMES。后两个的出现是因为我们想,如果只是基本需求的话我们完全可以自己从端口开始做起写一个邮件服务器。当然,由于是 Linux,没有 IIS,就没有 IIS 自带的邮件服务,而且只能命令行操作——好糟糕。
我尝试了 inbox.py,不过一写 handler 的时候就出现了问题:我们还是要往外面发——这需要在一个邮件服务器上有账号。这样,问题就回到原点了。JAMES 就复杂一些,我们真的要下到端口层面去进行操作吗?要处理各种通讯异常,重复造轮子?
于是回归了 Postfix。(sendmail 直接被否决了……)
第一次安装 Postfix 后,希望该服务器只能通过认证才能使用,上了 Cyrus SASL。结果配置后全都乱了,恢复不可,于是删了之后重新装了一遍 Postfix。
在安装了 Postfix 之后,将系统的邮件服务交给 Postfix,然后用 mail
测试发送,目标是我的学校邮箱。但是等了很久还无法收到邮件。以前在网站服务器上尝试发送给126的时候就失败过,ping 了一下发现地址无法解析。于是想:是不是地址解析失败?于是修改 DNS 解析到学校的解析服务器 NS1 和 NS2。还失败。想到之前学了 nslookup
的 -q
开关,尝试用 nslookup
。
自然是失败了,命令未找到。Google 了一下,原来 Debian(Raspbian 衍生自 Debian)默认并不带 DNS 工具。于是乎
sudo apt-get install dnsutils
在前一步设置了 DNS 服务器,以及设置了合适的 apt-get 源的前提下,apt-get
能快速安装所需的包。安装完成之后可以了。然后开始测试:
nslookup baidu.com
nslookup -q=mx smtp.126.com
看到前者走的是 NS2,后者走的是 NS1(在 NS2 优先的情况下)。在建设这个博客的时候,在 DNSPod 就看到过若干种类的 DNS 记录:A、CNAME、MX、AAAA。当时还不清楚 MX 记录的作用,现在大概能体会到了。在校内的环境下,看来是解析 A 记录首选 NS2,解析 MX 记录首选 NS1。而且很清楚的是,我们的发送目标是一个 SMTP 服务器,携带的肯定有 MX 记录,所以我们应该优先使用 NS1 进行解析。
调整顺序,让 NS1 的权值更大之后再 nslookup
就正常了。
再使用 mail
试验一下,还是失败。怀疑是不是与 SMTP 服务器通信过程中出现了问题。于是使用 telnet
验证。
补充说明:SMTP relay
经过查阅资料得知,SMTP 服务器在互联网还没这么发达的时候是接力传输(中继,relay)的。例如一封邮件要从 A 向 B 发送,而 A 和 B 之间没有直接的联系方式,则 A 向一个已知的服务器 C 发送这封标明要发送到 B 的邮件。C 收到邮件后,打上邮戳(表明该邮件经过了 C)。如果 C 无法联系 B,则将其转交另一个服务器 D,如此直到最后到达 B。当然,转交的前提是 C、D 等等都启用了中继功能。
现在大多数 SMTP 服务器都关闭了中继,因为:
- 现在互联网已经很发达了,两个 SMTP 服务器之间很容易直接连通;
- 中继容易让自己的 SMTP 服务器成为垃圾邮件的助推器。
在发送的时候,SMTP 服务器应该对目标进行检查,如果是发送到本域的,则应该放到终端的收件箱里,结束传递链。
开始一次会话。以下的测试反映了多次尝试的结论:
- 学校的 SMTP 服务器对安全控制比较高,必须使用
EHLO
而不是HELO
,同时需要正确地表明身份。例如,我们要发送到xxx@b.com
,则需要在EHLO
中就告诉服务器我们以xxx@b.com
的身份进行此会话。 - 学校的服务器禁止中继。(很好理解,见上面的说明。)在
RCPT TO:
的时候,必须使用学校的邮箱。 - 登录的时候没有强制验证。
符合我们的需求。毕竟,我们不能去要求用户提供他们的邮箱密码用于 SMTP 会话啊。
telnet smtp.b.com 25
EHLO xxx@b.com
MAIL FROM: yyy@yyy.com
RCPT TO: xxx@b.com
DATA
FROM: xxxxxxx
TO: xxxxxxx
SUBJECT: xxxxxxx
.........
.
↙
最后那个是回车,即结尾为“. + CR-LF + CR-LF”。
吐槽:
- dnsutils 的
telnet
比 Windows 自带的telnet
好用得多,后者甚至会将退格键传输过去,所以一个输入错误都不能有。 - PKGS 很好用。
- 原来此 SMTP 服务器不会对 DATA 里的 FROM: 节与 TO: 节进行校验(126会)……
在本机和 Raspberry Pi 上 telnet
都通过了,确认邮箱能收到邮件。但是,Postfix 还是没有转发成功。将配置文件中的 mydestination
设置为 smtp.b.com
也不行。
这个问题困扰了我一天,这个 Postfix 服务器如何联系学校的 SMTP 服务器呢?现在所有的准备都齐全了,但是最后这个 Postfix 的发送部分瞄准镜不知道指着外面——如果是的话,DNS 记录会引导其走向;不是的话,很可能就是我配置的问题,封死了出路。如何打开出路呢?
后来我仔细读了 Postfix 的各种文档,有一句话引起了我的注意:
By default, Postfix has a moderately restrictive approach to mail relaying.
这说明,用 Postfix 做中继是可能的;查看 Postfix 配置,信任列表中包含 localhost。同时,综合 relay 说明的最后一条,得到结论:如果我是以本机(localhost)身份向本机的 Postfix 服务器发送一封发往 b.com 存在的用户的邮件,同时设置 relay 而且目标就是 b.com,那么此次 relay 和直接往 b.com 发送是等价的(因为 b.com 会直接将这样的邮件放入其终端信箱)。我看到了解决问题的曙光。
修改 Postfix 配置,加入设置项:
relayhost = smtp.b.com
再次在站点上使用邮件测试,SMTP 服务器指定为 Raspberry Pi,发送成功,在学校邮箱里也见到了。
由于保护还是比较弱,为了安全起见添加了一些防护措施。总算解决了。
只不过希望再开放的时候如果有老师查看日志发现从校内某个点有大量往本校 SMTP 服务器走的连接,不要误认为那是垃圾邮件攻击……正常情况下是不会被攻击的……
Postfix 简单配置参考: