感觉都要转行php了…原本只是想安装个wordpress记录一下java开发中遇到的点滴,现在反倒用了wordpressphp的问题遇到一大堆。wordpress运行在php5.6下,在之前我用的是centos7 yum的方式直接安装的apache,版本是5.4,当时在wordpress中邮件发送phpmailer并没有什么问题,所以后来在自行编译php5.6之后也没有去留意过phpmailer是否正常。直到这几天自己随手在后台点了一下邮件发送测试,才发现出问题了。

请注意,本文提及的内容仅仅只是发信失败的部分原因,在后来的排查中发现了另外一个更重要的点,点此查看

环境

  • 阿里云企业邮
  • php5.6
  • openssl 1.0.1d
  • aliyun centos7 x64
  • 测试代码:
    <?php date_default_timezone_set("PRC");//这个是防止时间函数警告的
    if(isset($_POST['submit'])) 
    { 
        include("class.phpmailer.php"); 
        include('class.smtp.php'); 
        $from = $_POST['from']; 
        $to = $_POST['to']; 
        $subject = $_POST['subject']; 
        $send_body = $_POST['send_body']; 
        $smtp = 'smtp.mxhichina.com';//使用阿里云的stmp服务器 
        $port = 465;//端口号 
        $username = '你的邮箱'; 
        $password = '你的邮箱密码'; 
        $mail =new phpmailer();//建立新对象 
        $mail->IsSMTP();//设定使用SMTP方式寄信
        $mail->SMTPAuth = true;//设定SMTP需要验证
        $mail->SMTPSecure = 'ssl';//GMAIL的SMTP需要用SSL连接
        $mail->Host = $smtp;
        $mail->Port = $port;
        $mail->CharSet = "gb2312";//字体
        $mail->Username = $username;
        $mail->Password = $password;
        $mail->From = $from;
        $mail->FromName = $from;
        $mail->Subject = $subject;
        $mail->Body = $send_body;
        $mail->IsHTML(true);
        $mail->AddAddress($to);
        $mail->SMTPDebug = 4;
        /*$mail->SMTPOptions = array(
         'ssl' => array(
         'verify_peer' => false,
         'verify_peer_name' => false,
         'allow_self_signed' => true
         )
        );*/
        if(!$mail->Send())
        {
            echo "发送错误!".$mail->ErrorInfo;
        } else {
            echo "邮件发送成功,请注意查收!";
        }
        die();
    }
    ?>
    <form id="form1" name="form1" method="post" action="">
       <table width="468" border="0">
           <tr>
               <td width="90" height="25">收件人:</td>
               <td width="368"><input type="text" name="to" id="to" /></td>
           </tr>
           <tr>
               <td>主题:</td>
               <td><input type="text" name="subject" id="subject" /></td>
           </tr>
           <tr>
               <td>内容:</td>
               <td><textarea name="send_body" id="send_body" cols="45" rows="5"></textarea></td>
           </tr>
           <tr>
               <td>来自:</td>
               <td><input type="text" name="from" id="from" value="xxxxxasd@gmail.com"/></td>
           </tr>
           <tr>
               <td>&nbsp;</td>
               <td><input type="submit" name="submit" id="submit" value="提交" /></td>
           </tr>
       </table>
    </form>
    <?php echo (extension_loaded('openssl')?'SSL loaded':'SSL not loaded')."\n"; ?>

症状

  1. 在使用SSL送信的时候
    2015-12-15 16:53:43 Connection: opening to ssl://smtp.mxhichina.com:465, timeout=300, options=array ( ) 
    2015-12-15 16:53:43 SMTP ERROR: Failed to connect to server: (0) 2015-12-15 16:53:43 SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting 
    发送错误!SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting

    这个错误什么都不提示,只告诉你connect失败,遇到这个就真的算你运气背到家了,我就是…

  2. 在使用TLS送信的时候
    2015-12-15 16:58:49 CLIENT -> SERVER: STARTTLS
    2015-12-15 16:58:49 SMTP -> get_lines(): $data is ""
    2015-12-15 16:58:49 SMTP -> get_lines(): $str is "220 Ready to start TLS
     "
    2015-12-15 16:58:49 SERVER -> CLIENT: 220 Ready to start TLS
    <br />
    <b>Warning</b>: stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages:
    error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed in <b>/var/www/html/class.smtp.php</b> on line <b>344</b><br />
    2015-12-15 16:58:49 SMTP Error: Could not connect to SMTP host.
    2015-12-15 16:58:49 CLIENT -> SERVER: QUIT
    2015-12-15 16:58:52 SMTP -> get_lines(): $data is ""
    2015-12-15 16:58:52 SMTP -> get_lines(): $str is ""
    2015-12-15 16:58:52 SERVER -> CLIENT: 
    2015-12-15 16:58:52 SMTP ERROR: QUIT command failed: 
    2015-12-15 16:58:52 Connection: closed
    2015-12-15 16:58:52 SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting
    发送错误SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting

    遇到这个问题还好,至少人家提示了你哪里错了…

解决方案

找这个问题找了几天了,国内百度就不用说了,清一色的什么openssl没加载,什么socket模块没开启,扯淡,对开发人员来说还是google大法好,最终在phpmailer主键的github主页上找到了相关内容

You’re probably running into the fact that PHP 5.6 now verifies SSL certificates by default, and if you connect to a server that doesn’t verify, the connection will fail. Diagnose your situation using openssl. if this is what’s happening, you can provide additional params to the SMTP connection to tell it not to verify certificates, though of course that means your connection is no longer secure.

以上引用自:https://github.com/PHPMailer/PHPMailer/issues/359

上方提到php5.6默认开启了SSL证书验证,如果验证失败将连接失败,难道阿里云的邮箱服务器证书是自签的居然会验证失败?于是乎使用以下openssl指令抓取阿里云企业邮服务器的证书:

openssl s_client -connect smtp.mxhichina.com:465 -ssl3
[root@iZ94ufdvy1jZ ~]# openssl s_client -connect smtp.mxhichina.com:465 -ssl3
 CONNECTED(00000003)
 depth=2 C = US, O = "VeriSign, Inc.", OU = VeriSign Trust Network, OU = "(c) 2006 VeriSign, Inc. - For authorized use only", CN = VeriSign Class 3 Public Primary Certification Authority - G5
 verify error:num=20:unable to get local issuer certificate
 ---
 Certificate chain
 0 s:/C=CN/ST=Zhejiang/L=Hangzhou/O=Taobao(China) Software Co., Ltd/CN=*.mxhichina.com
 i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Secure Server CA - G3
 1 s:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Secure Server CA - G3
 i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5
 2 s:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5
 i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
 ---
 Server certificate
 -----BEGIN CERTIFICATE-----
 MIIFITCCBAmgAwIBAgIQfxyTN/SRCYzxzaGscP55LzANBgkqhkiG9w0BAQUFADCB
 tTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
 ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug
 YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMm
 VmVyaVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwHhcNMTUwMzA2
 MDAwMDAwWhcNMTYwNjA0MjM1OTU5WjB3MQswCQYDVQQGEwJDTjERMA8GA1UECBMI
 WmhlamlhbmcxETAPBgNVBAcUCEhhbmd6aG91MSgwJgYDVQQKFB9UYW9iYW8oQ2hp
 bmEpIFNvZnR3YXJlIENvLiwgTHRkMRgwFgYDVQQDFA8qLm14aGljaGluYS5jb20w
 ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxNkENU7rhCX8DO4LGosZN
 XeAjoJ15p0GiWgUxVZJ4oDzCYorVOPwIUIhGz/EE+PbH5I314pNoTfg4cQxdqTiy
 6GU9qJm90muF+RxWy2NJ3bb7dt1rQfFtZn1HDUA7dhNTQs1QNiLVtLdeFk6uHLU4
 u9nkT7biyHifn0cz65reBFPZZJ1Lo7RVTC6y63Ld9rLM+YeU8Y+5rQqDj9ZqLX84
 a5P7jSM+ovmgfB0Ag4gWZ5EukuSocqQ1yrrqcK3+pyzbvUZStax+Mk9inwsUJr79
 zj3EG7EV12lEL021QO/rnaAMftBUf7jnTg4lvLEINWlj87NPdDMfSf1Kk4b0pSHl
 AgMBAAGjggFoMIIBZDAaBgNVHREEEzARgg8qLm14aGljaGluYS5jb20wCQYDVR0T
 BAIwADAOBgNVHQ8BAf8EBAMCBaAwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3Nk
 LnN5bWNiLmNvbS9zZC5jcmwwZQYDVR0gBF4wXDBaBgpghkgBhvhFAQc2MEwwIwYI
 KwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3BzMCUGCCsGAQUFBwICMBkM
 F2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
 BgEFBQcDAjAfBgNVHSMEGDAWgBQNRFwWU0TBgn4dIKsl9AFj2L55pTBXBggrBgEF
 BQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0dHA6Ly9zZC5zeW1jZC5jb20wJgYIKwYB
 BQUHMAKGGmh0dHA6Ly9zZC5zeW1jYi5jb20vc2QuY3J0MA0GCSqGSIb3DQEBBQUA
 A4IBAQBDpYFSuBKfHIlJw43cuYZfYsNNIc0yE+iDxXB1BuOtqHkpCTtWyxuN+RYy
 4SzNmpcw+T6pC2zB7MErvZJn18E5bqlVd/iV8T9E+fIB3qplCoAxfb66NSnM1jXC
 vJTHTgSkY/0UjE7HUhdTNYjdXHNfNaCrgAJVkqs4V533mmsDy0/IG4Hu0Vuz/8uV
 eyvL+FZe+K6EDzahI6dwZoiEpwf10JBX51Mf/WZp7lzcRramePz1m8L8VD8Vp4b8
 SQfdEr4olCAhfJZeKlS3UaMbm3TaXnhiHRrIEa0hhLm/p7PCm/wyYwzEyQHYaMhD
 AA2JdDP/SUhE9hHa/mN6SejLax+D
 -----END CERTIFICATE-----
 subject=/C=CN/ST=Zhejiang/L=Hangzhou/O=Taobao(China) Software Co., Ltd/CN=*.mxhichina.com
 issuer=/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Secure Server CA - G3
 ---
 No client certificate CA names sent
 ---
 SSL handshake has read 4264 bytes and written 498 bytes
 ---
 New, TLSv1/SSLv3, Cipher is AES256-SHA
 Server public key is 2048 bit
 Secure Renegotiation IS supported
 Compression: NONE
 Expansion: NONE
 No ALPN negotiated
 SSL-Session:
 Protocol : SSLv3
 Cipher : AES256-SHA
 Session-ID: D9FE60E0B2CAD484C72506B8FC9C2BA715910877FE3DD5649DB3220B716ECCD0
 Session-ID-ctx:
 Master-Key: 515C26F2DC0F5A16AB4750CF0A2AD1E283C0B6D89F4009C351DB47EDEE7CCFA6FA262CA1EA22140D1203D02C53BBE5E9
 Key-Arg : None
 PSK identity: None
 PSK identity hint: None
 SRP username: None
 Start Time: 1450114132
 Timeout : 7200 (sec)
 Verify return code: 20 (unable to get local issuer certificate)
 ---
 220 smtp.aliyun-inc.com MX AliMail Server(10.147.41.143)
 closed
 [root@iZ94ufdvy1jZ ~]#

内容太多了看着瘆人?没事,新建一个txt文档,拷贝上面

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

之间的文本(包括首位这两行)到文档中,保存,修改文档后缀名为crt,打开看看:

QQ截图20151216013444

QQ截图20151216013458

可以看到,阿里的邮箱证书是有效的,这就奇怪了,有效为何会校验失败呢?在万能的google找到了stackoverflow上的一个提问 PHPMailer – SSL3_GET_SERVER_CERTIFICATE:certificate verify failed,其中有一位哥们的回答:

It’s the same as an SSL cert for a web server – it needs to match the domain, be signed by a trusted CA (i.e. not self-signed), and should have an SHA2 hash and use a 2048-bit key. How you install it depends on your specific server – and it will tell you in its docs.

以上引用自:http://stackoverflow.com/questions/26827192/phpmailer-ssl3-get-server-certificatecertificate-verify-failed

根据这位哥们的说法,有效的证书应该是一个受信任的CA颁发,并且使用了SHA2签名和至少2048位的加密,于是乎我默默的打开了刚刚阿里云企业邮证书的详情页:
QQ截图20151216014449

可以看到,签名算法是sha1,sha1算法在当今天的SSL传输中已经是不安全了,chrome甚至都已经将采用sha1进行ssl握手的站点标注为非安全,如果确实是因为这个导致php5.6证书验证不通过那也有道理,不过我是没找到相关的官方资料…以后再看了,以下是解决方案:

$mail->SMTPOptions = array(
     'ssl' => array(
     'verify_peer' => false,
     'verify_peer_name' => false,
     'allow_self_signed' => true
     )
    );

将示例代码中这段取消注释就好了,关闭自签名证书验证,关闭之后则不会验证证书有效性了,不过在上面的stackoverflow问题中可以看到,如果关闭证书验证通过伪造证书可能导致中间人攻击。

PHP5.6.x SSL3_GET_SERVER_CERTIFICATE:certificate verify failed 解决方案

PHP5.6.x SSL3_GET_SERVER_CERTIFICATE:certificate verify failed 解决方案 在之前更新 PHP5.6+ 的时候邮件的发送出现了一些问题,本站已经对此做过相关介...

阅读全文

PHP7安装的各种梗

昨日看到php7.0.2版本已放出,老衲第一时间抓了个螃蟹,花了两夜,总算是吃了下去,分享一下安装过程与遇到的问题。 安装 Centos7,php安装包的下载解压编译...

阅读全文

1 条评论

  1. 😉 沉迷百度然并卵,终于在谷歌里搜到了大牛你的文章,STMP+SSL发信失败,切换到php5.4果然发出去了
    目前那个邮件插件也很老旧了,我通知了作者让他看看更新下,同时为了防止你的博客挂了,我对关键内容进行摘录并著名出处,感谢

欢迎留言