在下再嘗試使用 fsockopen 發送電子郵件
使用 PHP 下,一般都會使用內建的 mail() 功能
需要修改 php.ini 的 SMTP 資料,修改後還要重新啟動伺服器才能應用
若需要測試不同的 SMTP 伺服器,經常這樣修改比較麻煩,利用 ini_set 可以暫時修改 php.ini
但有些情況是閣下並不是伺服器擁有者,不能隨意修改 php.ini 、 重新啟動伺服器 、 ini_set 的修改受限制
若使用 fsockopen 便可以直接使用像 Telnet 連接 SMTP 伺服器的指令來發送電子郵件
以 Gmail 作例子
Gmail 的 SMTP 位置為 smtp.gmail.com
由於 Gmail 需要使用安全通道加密資料,因此使用 fsockopen 時需要以 ssl:// 連接至 465 port 或以 tls:// 連接至 587 port
$handle = fsockopen('ssl://smtp.gmail.com', 465);或
$handle = fsockopen('tls://smtp.gmail.com', 587);連接後使用
$response = fgets($handle); echo $response;傳回成功回應的內容
220 smtp.gmail.com ESMTP ID - gsmtp或傳回失敗回應,其回應碼為 421
留意:
回應內容,前端必定有一組 三位數 的 SMTP回應碼
另在由於 SMTP 完成整個發送程序前,都會以互動形容操作
若當前操作為使用者給予指令,但使用如 fgets 等獲取伺服器的回應,便會變成不斷等待伺服器
而判斷是否是使用者給予指令的狀態,可以以 第四字元 是否為 空白 作判斷
若伺服器的回應不只一行, SMTP回應碼 會以 - 減號連接
例如
連接到 Gmail 的 SMTP 後編寫
fwrite($handle, "EHLO gmail.smtp.com\r\n");需要使用數次 fgets 來獲取完整的回應
如
250-smtp.gmail.com at your service, [IP] 250-SIZE 35882577 250-8BITMIME 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH 250-ENHANCEDSTATUSCODES 250-PIPELINING 250-CHUNKING 250 SMTPUTF8但每個伺服器傳回傳的內容行數都不一定固定,若未獲取完整回應便不能向伺服器給予指令,若過度獲取資料便會不斷等待伺服器
function check_response($handle) { $response = fgets($handle); echo $response; if (substr($response, 3, 1) != ' ') { check_response($handle); } }使用 迴遞 (recursion) 的方式檢查回應,直至 第四字元 是 空白 便會離開 功能
完成 EHLO 後,編寫
fwrite($handle, "AUTH LOGIN\r\n"); check_response($handle);傳回回應
334 VXNlcm5hbWU6VXNlcm5hbWU6 其實是經 base64加密 後的 Username:
編寫
fwrite($handle, base64_encode($username) . "\r\n"); check_response($handle);傳回回應
334 UGFzc3dvcmQ6UGFzc3dvcmQ6 其實是經 base64加密 後的 Password:
編寫
fwrite($handle, base64_encode($password) . "\r\n"); check_response($handle);若登入成功傳回回應
235 2.7.0 Accepted若登入失敗傳回回應
535 5.7.8 https://support.google.com/mail/?p=BadCredentials ID - gsmtp另外若使用兩步認證,此方法則不能通過登入過程
登入 SMTP 後,可以正式編寫郵件資料及內容
編寫
fwrite($handle, "MAIL FROM: <sender@gmail.com>\r\n"); check_response($handle);若成功傳回回應
250 2.1.0 OK ID - gsmtpMAIL FROM 的內容是可以留空,會自動以登入的帳戶資料顯示
編寫
fwrite($handle, "RCPT TO: <recipient@gmail.com>\r\n"); check_response($handle);若成功傳回回應
250 2.1.0 OK ID - gsmtpRCPT TO 可以增加更多,但每加入 RCPT TO 都需要使用 fgets
編寫
fwrite($handle, "DATA\r\n"); check_response($handle);傳回回應
354 Go ahead ID - gsmtp之後可以編寫郵件的內容資料
使用 DATA 後的內容資料可以當作遵從 HTTP Request 的寫法,例如
$request = ''; $request .= "header-1: data-1\r\n"; $request .= "header-2: data-2\r\n"; $request .= "header-3: data-3\r\n"; $request .= "\r\n"; $request .= "message body\r\n"; $request .= ".\r\n"; fwrite($handle, $request); check_response($handle);
若需要加入附件,便需要在加入
Content-Type: multipart/mixed; boundary="END_OF_PART"表示多重結構
內容需要使用
Content-Type: text/plain; charset=UTF-8; format=flowed附件需要使用
Content-Type: MIME Type; charset=UTF-8 Content-transfer-encoding: Encryption Method Content-Disposition: attachment; filename="filename.ext"若果附件的內容不是 純文字 ,則需要將內容以 7bit 、 8bit 、 base64 等加密後才能加入
例如
$request = ''; $request .= "header-1: data-1\r\n"; $request .= "header-2: data-2\r\n"; $request .= "header-3: data-3\r\n"; $request .= 'Content-Type: multipart/mixed; boundary="END_OF_PART"' . "\r\n"; $request .= "MIME-Version: 1.0\r\n"; $request .= "\r\n"; $request .= "--END_OF_PART\r\n"; $request .= "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n"; $request .= "\r\n"; $request .= "message body\r\n"; $request .= "--END_OF_PART\r\n"; $request .= "Content-Type: image/jpeg\r\n"; $request .= "Content-Transfer-Encoding: base64\r\n"; $request .= 'Content-Disposition: attachment; filename="filename.jpg"' . "\r\n"; $request .= "\r\n"; $request .= base64_encode($file_contents) . "\r\n"; $request .= "--END_OF_PART\r\n"; $request .= ".\r\n"; fwrite($handle, $request); check_response($handle);兩者都要留意, 最後有一點及一次換行
傳回回應
250 2.0.0 OK timestamp ID - gsmtp若沒有設定 MAIL FROM 傳送時會傳回回應
503 5.5.1 MAIL first. ID - gsmtp若沒有設定 RCPT TO 傳送時會傳回回應
503 5.5.1 RCPT first. ID - gsmtp
最後完成後編寫
fwrite($handle, "QUIT\r\n"); check_response($handle);終止 SMTP 伺服器的連接,傳回回應
221 2.0.0 closing connection ID - gsmtp
以下為測試程式
function check_response($handle, $code) { $response = fgets($handle); echo $response; if (substr($response, 3, 1) != ' ') { check_response($handle, $code); } else { if (substr($response, 0, 3) != $code) { die(); } } } function send_request($handle, $request, $code) { echo $request; fwrite($handle, $request); check_response($handle, $code); } $host = 'smtp.gmail.com'; $errno = 0; $error = ''; if ($handle = @fsockopen('ssl://' . $host, 465, $errno, $error)) { check_response($handle, 220); send_request($handle, 'EHLO ' . $host . "\r\n", 250); send_request($handle, "AUTH LOGIN\r\n", 334); echo "***** Hidden Request *****\r\n"; fwrite($handle, base64_encode($username) . "\r\n"); check_response($handle, 334); echo "***** Hidden Request *****\r\n"; fwrite($handle, base64_encode($password) . "\r\n"); check_response($handle, 235); send_request($handle, 'MAIL FROM: <' . $sender_email . ">\r\n", 250); send_request($handle, 'RCPT TO: <' . $recipient_email . ">\r\n", 250); send_request($handle, "DATA\n", 354); $request = ''; $request .= "Subject: This is subject\r\n"; $request .= 'From: "' . $sender_name . '" <' . $sender_email . ">\r\n"; $request .= 'To: "' . $recipient_name . '" <' . $recipient_email . ">\r\n"; $request .= 'Content-Type: multipart/mixed; boundary="END_OF_PART"' . "\r\n"; $request .= "MIME-Version: 1.0\r\n"; $request .= "\r\n"; $request .= "--END_OF_PART\r\n"; $request .= "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n"; $request .= "\r\n"; $request .= "This is body\r\n"; $request .= "--END_OF_PART\r\n"; $request .= "Content-Type: text/plain; charset=UTF-8\r\n"; $request .= "Content-Transfer-Encoding: base64\r\n"; $request .= 'Content-Disposition: attachment; filename="filename.txt"' . "\r\n"; $request .= "\r\n"; $request .= base64_encode('This is attachment') . "\r\n"; $request .= "--END_OF_PART--\r\n"; $request .= ".\r\n"; send_request($handle, $request, 250); send_request($handle, "QUIT\r\n", 221); } else { printf('[%d] %s', $errno, $error); }
得到如下的指令及回應
220 smtp.gmail.com ESMTP ***** Hidden ID ***** - gsmtp EHLO smtp.gmail.com 250-smtp.gmail.com at your service, [***** Hidden IP *****] 250-SIZE 35882577 250-8BITMIME 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH 250-ENHANCEDSTATUSCODES 250-PIPELINING 250-CHUNKING 250 SMTPUTF8 AUTH LOGIN 334 VXNlcm5hbWU6 ***** Hidden base64 encoded Username ***** 334 UGFzc3dvcmQ6 ***** Hidden base64 encoded Password ***** 235 2.7.0 Accepted MAIL FROM: <***** Hidden Sender E-Mail *****> 250 2.1.0 OK ***** Hidden ID ***** - gsmtp RCPT TO: <***** Hidden Recipient E-Mail *****> 250 2.1.5 OK ***** Hidden ID ***** - gsmtp DATA 354 Go ahead ***** Hidden ID ***** - gsmtp Subject: This is subject From: "Mr.Z" <***** Hidden Sender E-Mail *****> To: "Mr.A" <***** Hidden Recipient E-Mail *****> Content-Type: multipart/mixed; boundary="END_OF_PART" MIME-Version: 1.0 --END_OF_PART Content-Type: text/plain; charset=UTF-8; format=flowed This is body --END_OF_PART Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="filename.txt" VGhpcyBpcyBhdHRhY2htZW50 --END_OF_PART-- . 250 2.0.0 OK 1505711793 ***** Hidden ID ***** - gsmtp QUIT 221 2.0.0 closing connection ***** Hidden ID ***** - gsmtp
參考結果
在送出 DATA 指令後的標頭資料,例如 From 及 To 不一定要與 MAIL FROM 及 RCPT TO 相對
基本上這些標頭資料都不一定要加上,或胡亂編寫都可以
這些資料是為了方便收件人了解郵件資料,例如 To (收件), Cc (副本), Bcc (密件) 等
關於更多 SMTP 標頭資料可以到 IETF 的 RFC 4021 文件 查看
實際上 PHPMailer 都是使用 fsockopen 的方式發送電子郵件
沒有留言 :
張貼留言