关于服务器邮件发信的误区

今天恰巧群中谈论到了这个问题:

封端口究竟能不能发信

我的答案是能,但是很多人的答案都是不能

常见的 SMTP 端口是 25 465 587 为了防止垃圾邮件,通常来说服务商都会封堵这些端口,于是我产生了一个疑问:

发邮件的行为是发送数据包而非对等连接,为什么本地需要开放这些端口才能发信?

要探讨这个问题,首先要区分一个概念,这里的发信指的是通过邮件客户端而非 SMTP 等协议连接现有邮件服务器发信,例如 Gmail Web 中的发信功能就是先将你要发的东西传递给 Google 的服务器,再由 Google 的服务器发送邮件,这之间是肯定不需要本地开放邮件端口的,因为和邮件本身相关的操作都在 Google 的服务器上完成,故不是本次探讨范围

最初的起因

几年前我在玩 WordPress 的时候,偶然间发现我收到了来自我网站的邮件,但是我并没有配置任何 SMTP 服务,我很好奇邮件是怎么来的,甚至认为这东西可能是从内网过来的,因为当初我的服务器是一个旧笔记本电脑改造而来的

于是我便认为发信并不像网上说的需要开放端口才行

但是好景不长,在不就后突然发现又没办法发信了,导致我异常的迷茫,于是便一直在使用 SMTP 连接第三方邮箱来发信

测试的开始

经历了很多类似的讨论后,我决定按照先前成功发信的环境再测试一遍,于是写了一个简单的 PHP 脚本来测试

然而遇到了新的问题

sh: line 1: /usr/sbin/sendmail: No such file or directory

是的,PHP 的 mail() 函数居然不是原生功能,而是使用了 sendmail (还挺符合不重复造轮子的)

于是我给系统装上了这个东西再次测试

image

执行后我发现 PHP 执行时并没有任何报错,那么来等一等邮件吧

惊喜的是我成功收到了邮件

我注意到其中的 由 xxx 代发,这与几年前收到的那封奇怪邮件是一致的,都是服务器的用户+FQDN

我们来琢磨一下这封邮件是何方神圣,直接浏览邮件的 raw 格式

在 raw 格式中可以很清晰的看到发信方 IP

localhost [127.0.0.1]

此时群里一位大佬说的话点到了我

发信只需要出口不 ban 目的地为 25 端口的就行

于是我给服务器装上了 tcpdump 开始抓 25 端口的包并且重新开始发信测试

从抓包结果中看到了完整的 SMTP 流程

220 就绪 -> EHLO 协商 -> STARTTLS 加密 -> 邮件指令交互 -> 连接关闭

我们还需要排除最后一种可能,那就是 UPnP/NAT 帮我们临时开放了邮件端口

然后我发现不光没有映射记录,我甚至没有开这个服务

那么真相大白了,发信不需要外部能连接本机的邮件端口

最终的测试

为了确保结论的准确性,我们需要另一个工具 iptables

确定一下目前的 INPUTUOTPUT 链中目前的内容,可以看到 INPUT 链中是允许全部,而 OUTPUT 链中没有任何规则,并且两个链的默认处理方式都是 ACCEPT

我们 DROP 掉目标端口为 25 的全部地址再次尝试一次

iptables -A OUTPUT -p tcp --dport 25 -j DROP

关闭之前的抓包并且重新开始一次,再次执行测试的 PHP 脚本,发现 PHP 依然提示发信成功,但是 tcpdump 没有抓到任何内容,我也没收到任何邮件

那么可以确定了,发信只需要本地能连接到目标邮箱所属的邮件服务器就可以,并不需要允许传入连接

不过就这么得出结论不严禁,我还需要删除刚刚创建的 iptables 规则,添加一个新的规则来阻止 25 465 和 587 端口的传入连接

iptables -A INPUT -p tcp -m multiport --dports 25,465,587 -j DROP

不过奇怪的是,我不光没有收到新的测试邮件,甚至抓包都没有抓到

不可置信的我再次查看了 iptables 规则

并没有阻止传出连接,不过我很快反应过来貌似是 tcp 握手失败了,但是就算握手失败也至少会有一个 SYN 包出去才对

经过一系列的抓包和测试,我发现新的规则阻断了 127.0.0.1 连接本地的 sendmail 服务,于是我的懒癌犯了,就写这么多吧

虽然但是自建服务器的域名发送多半会被QQ邮箱什么的扔垃圾站,还是用代理转发稳定一点

还好我都开 :melting_face:

雨云的还好吧,不会ban到垃圾,就是香港不能用我十堰自建的挺难受的