2023-06-02

[PHP 5.x]PHPMailer升級TLS1.2

這幾天為了替公司省錢,捨身跳進PHP的坑裡幫廠商救火XD 這個案子是用PHP 5.6、YiiMailer 1.6(include PHPMailer 5.2.8)開發,環境為AWS UBUNTU、Nginx,用途是讓指定品牌透過網站問券方式預約免費膚質檢測服務。這次發生的問題是當用戶填完問券,按下確認時系統卻無法發信,並回傳錯誤訊息:SMTP connect() failed.

最直覺當然是直接先把問題丟給ChatGPT,我也按照他的建議逐一排查,雖然沒有直接解決我的問題,但也讓我更認識我們家的Server環境。由於不只一個活動網站架在這個Server上,甚至不同品牌合作的廠商所使用的PHP版本也不一致。為了避免讓同事等待太久又不一定能解決(幫自己留後路XD),所以我首先請同事直接請原廠商為專案升級PHP版本報價。

在他去請廠商報價的同時,我用了以下方法排查:

一、Telnet (正常)

telnet smtp.office365.com 587
顯示正常:
220 SI1PR02CA0000.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 29 May 2023 05:09:46 +0000


二、mail (正常)

echo "This is the body of the email" | mail -s "Test" recipient@example.com
收信正常:

到這邊可以確定防火牆大致上是設定Ok的。


三、使用PHPMailer套件發信


再來我們撰寫sample code直接用廠商的套件來發信。

<?php
// 加入 PHPMailer 函式庫
require_once('path/to/PHPMailer/PHPMailerAutoload.php');

$mail = new PHPMailer();
$mail->isSMTP();
$mail->Host = 'smtp.office365.com';
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->SMTPSecure = 'tls';
$mail->Username = 'campaign@example.com';
$mail->Password = 'password';
$mail->setFrom('campaign@example.com', 'Campaign Name');
$mail->addAddress('recipient@example.com', 'Recipient Name');
$mail->Subject = 'Testing SMTP connection';
$mail->Body = 'This is a test email.';

// 啟用 SMTP 除錯模式
$mail->SMTPDebug = 2;

// 回傳結果
if ($mail->send()) {
    echo '郵件已成功發送!';
} else {
    echo '郵件發送失敗:' . $mail->ErrorInfo;
}
?>

用php指令直接執行,收信正常:


這邊值得一提的是,一開始我是直接拿YiiMailer底層的PHPMailer來用,而第一次發信是失敗的,錯誤訊息如下:
2023-05-29 08:30:37     SERVER -> CLIENT: 421 4.7.66
TLS 1.0 and 1.1 are not supported. Please upgrade/update your client to support TLS 1.2. Visit https://aka.ms/smtp_auth_tls. [SI2PR02CA0000.apcprd02.prod.outlook.com 2023-05-29T08:30:37.338Z 00DB0FA0F0A000A0]
...
2023-05-29 08:30:37     SMTP ERROR: QUIT command failed: SMTP connect() failed.

經過一番在Stack Overflow探險,我依樣將環境切換成PHP 7.3便成功了(Server上的其他專案是使用PHP 7.3開發)


四、驗證


發現SMTP connect() failed.與PHP版本的關聯,為了驗證我寫了一支echo 'PHP version:' . phpversion();分別在Server執行和用HTTP瀏覽:

在Server執行(sudo php info.php):
PHP version:7.3.13-1+ubuntu18.04.1+deb.sury.org+1
用HTTP瀏覽:
PHP version:5.6.40-21+ubuntu18.04.1+deb.sury.org+1

果然,在Web上依然使用source code中include的PHP版本。
除了花錢讓開發商改版,我果斷把問題轉到另外一個方向:讓我的PHPMailer只能用TLS1.2與smtp.office365.com連線。


五、結論


最後的解法實在非常直接明瞭。在PHPMailer有一個class.smtp.php,裡面的public function startTLS(),呼叫內建的stream_socket_enable_crypto。只要將參數crypto_method從原來的STREAM_CRYPTO_METHOD_TLS_CLIENT改為STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,這題就這麼解了^^
/**
 * Initiate a TLS (encrypted) session.
 * @access public
 * @return boolean
 */
public function startTLS()
{
    if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
        return false;
    }
    // Begin encrypted connection
    if (!stream_socket_enable_crypto(
        $this->smtp_conn,
        true,
        STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
    )) {
        return false;
    }
    return true;
}


後記


讓原本還想靠Laravel接案的我澆了一盆冷水XD 但解決完之後也是蠻有成就感,最近有看到John Liu跟HISKIO合作的Laravel課程,是不是該報名一下了(?

沒有留言:

張貼留言