PHPMailer二三事

PHPMailer是使用人数最多的PHP邮件库,然而我却遇到了吊诡的事情。

起因是这样的,ATSAST在上个月经历了一次服务器迁移,从原来位于香港的服务器迁移到了我们的新加披服务器,这次迁移后,陆续有用户反映找回密码失败。当时想既然大多数用户成功,可能是用户使用的问题。

后来,一位同学表示多次失败,为了证明没有问题,我示范了一次。结果失败了……败了……了……

初步分析,刁老板没有修改这块代码(排除作案嫌疑);代码在本地也正常。翻来覆去,遂联想到服务器迁移,会不会是服务器缺少so文件?抱着这种想法,我编译了四五个旧服务器有的链接库,结果问题照旧。

冷静分析片刻,我!想!到!了!

什么都没想到……

但是我发现PHPMailer是可以输出SMTP Error的具体信息的,于是我打开了Debug开关。

$mail->SMTPDebug = true;

结果?什么?报了一个不能连接?

SMTP Error: Could not connect to SMTP host

这不是废话吗……

随后的几个小时,我想到了所有的可能性。最终,夜里1点,我在和刁老板说的时候,为了证明没有问题,我们想到了用telnet去试试。

结果?学校的25端口竟然no response?那为什么别的服务器没问题?我试了4G网,校园网,旧服务器,结果都能连25端口,而新服务器连别的IMAP服务端口,HTTP端口都正常??????啥??????

好的,问题找到了,学校邮件服务沙雕……

可是这样子还是不能解决我的问题,如果不能用SMTP的话,就不能发送邮件。POP3和IMAP只能接受邮件,这可怎么办?

绝望之余,我想起来之前25端口也是学校不公开的,其实学校没有公开任何端口,完全是试了默认SMTP端口结果中了。

那么,不如试试SMTP over SSL?可是尴尬的是,SMTP over SSL的端口并没有统一标准,各家都是按照自己的心情去设置。

好在学校的邮件服务提供商CoreMail是个心大的厂商,说明文档说了默认SMTP over SSL端口是465。那么我们试试吧?

telnet一下,通了!只不过没有回显,略担心。

好的,现在改成465,然后协议选择SSL。可惜,还是发送失败了。

这又是怎么了?明明有465啊,就在我以为学校的465上只是空端口准备放弃的时候,试了一下本地邮件服务竟然成功跑上465了!

那么465就是真的有SMTP服务了!一定是代码有问题!

遂打开SMTPDebug,得到如下提示:

SMTP ERROR: Failed to connect to server: (0)

不一样!有没有!两个是不一样的提示!

OK,详细查阅资料,这个提示似乎和SSL离不开关系?难道是学校的SSL证书验证出了问题?那么关掉就好了吧?

查找错误文本在代码位置,终于锁定出错的位置:

    $this->smtp_conn = @fsockopen($host,    // the host of the server
                                  $port,    // the port to use
                                  $errno,   // error number if any
                                  $errstr,  // error message if any
                                  $tval);   // give up after ? secs

    if(empty($this->smtp_conn)) {
      $this->error = array("error" => "Failed to connect to server",
                           "errno" => $errno,
                           "errstr" => $errstr);
      if($this->do_debug >= 1) {
        echo "SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '<br />';
      }
      return false;
    }

$this->smtp_conn则是通过fsockopen去连接指定端口的,如果不通,就直接报错退出了。

现在,只要关闭fsockopen的证书验证,问题就有可能解决了吧?

可是失望的是,查阅资料发现fsockopen没有办法关闭证书验证……

但是!

不用担心,我们还有stream_socket_client,这和fsockopen功能几乎一样,但是提供了更加丰富的选项,其中就有SSL证书相关的设置。

替换后,代码如下:

    $context = stream_context_create([
        'ssl' => [
            'verify_peer' => false,
            'verify_peer_name' => false
        ]
    ]);
    $hostname = $host.":".$port;
    $this->smtp_conn = @$socket = stream_socket_client($hostname, $errno, $errstr, ini_get("default_socket_timeout"), STREAM_CLIENT_CONNECT, $context);


    // verify we connected properly
    if(empty($this->smtp_conn)) {
      $this->error = array("error" => "Failed to connect to server",
                           "errno" => $errno,
                           "errstr" => $errstr);
      if($this->do_debug >= 1) {
        echo "SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '<br />';
      }
      return false;
    }

来跑一下,成功啦!

到这里,就是邮件问题解决的全过程了,我和刁老板调试了一整晚,才解决了这个问题。

最后,学校的邮件服务的25端口(而且只有这一个端口)为什么要屏蔽我的服务器…………………………………………

后记

我登录了GitHub,准备提issue……

嗯?有人问了stream_socket_client?

嗯?他的代码是什么?

嗯????

翻开PHPMailer,最新版本6.0.6早已更新了这里的逻辑,现在,PHPMailer的逻辑是先判断stream_socket_client不行再退回fsockopen。

那么对于最新版本的用户,直接调用的时候传入一个verify_peer为false等的设置就成。

留下你的评论呗...

电子邮件地址不会被公开。 必填项已用*标注