用Socket發送電子郵件--續篇 作者:limodou 在前面我曾經寫過一篇文章,介紹了如何利用socket編程來發送郵件,以解決web服務器不支持mail()函數的問題。經過我的測試也是可以使用的。但目前眾多的免費郵件提供商(從263開始,163,新浪網也快開始了)均在smtp功能上增加了認證功能,使得原郵件發送類無法使用。在經過對相應smtp後續rfc的學習之後,經過了多次的試驗,我終於試驗成功了。於是懷著急迫的心情向大家介紹。 SMTP 認證功能介紹 此處不想向你詳細介紹SMTP認證功能,因為我也說不清楚,詳細的請參考[RFC 2554]規范。SMTP的認證功能主要是增加了AUTH命令。AUTH命令有多種用法,而且有多種認證機制。AUTH支持的認證機制主要有LOGIN,CRAM-MD5[注1]等。LOGIN應該是大多數免費郵件服務器都支持的,263與新浪都支持。而新浪還支持CRAM-MD5機制。認證機制一般只在真正發送郵件之前進行,而且只需要執行一次。當認證成功後,即可按原來正常的處理發送郵件。原理是口令-應答(Challenge-Response),即由服務器發送命令要求客戶端回答,客戶端根據服務器發送信息進行回答,如果應答通過了,則認證成功,即可繼續處理。下面對這兩種制作一個簡單介紹。S:表示服務器返回,C:表示客戶端發送。 LOGIN 它應該比較簡單。口令-應答過程如下: 1 C: AUTH LOGIN 2 S: 334 dXNlcm5hbWU6 3 C: dXNlcm5hbWU6 4 S: 334 cGFzc3dvcmQ6 5 C: cGFzc3dvcmQ6 6 S: 235 Authentication successful. 1 為客戶端向服務器發送認證指令。 2 服務端返回base64編碼串,成功碼為334。編碼字符串解碼後為“username:”,說明要求客戶端發送用戶名。 3 客戶端發送用base64編碼的用戶名,此處為“username:”。 4 服務端返回base64編碼串,成功碼為334。編碼字符串解碼後為“password:”,說明要求客戶端發送用戶口令。 5 客戶端發送用base64編碼的口令,此處為“password:”。 6 成功後,服務端返回碼為235,表示認證成功可以發送郵件了。 對於LOGIN方式認證,其實就是將用戶名與口令用base64進行編碼,根據服務器的要求,分別發出即可。(就我看來,由於base64是一種公共的編碼標准,也起不到太大的保護作用。) CRAM-MD5機制 關於CRAM-MD5的機制可以參考[RFC 2195]規范,這裡不詳細說明了。主要就是通過口令-回答機制,由服務端發出一個信息串,這個由隨機數,時間戳,服務器地址構成,並且用base64編碼。客戶端收到後,發送一個由用戶名,加一個空格,再加一個摘要構成的串,並用base64編碼。摘要是通過MD5算法求出。這種機制要求服務端與客戶端有相同的加密串。當客戶端發送摘要後,服務器對其合法性進行驗證,成功後,返回235。 如何得知郵件服務器支持什麼認證? 在smtp的[RFC 821]中,在與郵件服務器連接成功後,第一個命令一般是“HELO”。但是在支持認證的郵件服務器中,第一個命令應改為“EHLO”[注2]。在命令成功後,263的返回可能為: EHLO hello 250-smtp.263.net [注3] 250-PIPELINING 250-SIZE 10240000 250-ETRN 250-AUTH LOGIN 250 8BITMIME 從而可以看到263支持LOGIN方式認證。當然,如果你已經知道郵件服務器是什麼方式,也沒有必要自動進行判斷,但是如果不知道,就需要分析這個返回結果了。不過大部分的郵件服務器都支持最簡單的LOGIN方式。 好了,下面開始對以前所寫的sendmail.class.php3進行修改。你沒有不要緊,本文在最後提供了sendmail.class.php3的打包文件,可以下載。至於例子則自已根據本文進行編寫。 修改sendmail.class.php3 此處只說出修改的重點,而不是全面的分析。 首先回顧一下sendmail.class.php3的思路,讓大家先心中有數。 sendmail.class.php3一共有四個函數,分別為: send_mail 類的構造函數,用於信息的初始化 send 郵件發送函數,執行socket命令,發送郵件 do_command 命令執行函數,執行一條smtp命令,並將處理返回結果 show_debug 顯示調示信息函數 首先用戶應先調用類的構造函數,對必要的參數進行初始化。如smtp服務器地址($smtp),歡迎信息($welcome),及是否顯示調示信息($debug)。同時還要初始化一些內部變量,如最後執行命令($lastact),最後響應信息($lastmessage),及端口號($port=25)。 然後,用戶生成郵件信息,並調用send()函數發送郵件。在send()函數中,根據smtp規范,一條命令接一條命令執行(詳情參見前面的文章)。在執行命令時,是通過調用do_command()來實現的。如果do_command()執行出錯,則程序立即返回,否則繼續向下執行。如果設置了顯示調示信息標志,則do_command()在命令發送和信息響應時會返回調示信息。 好了,大家已經對它的運行有了一個了解,下面就是如何修改了。 修改構造函數(send_mail) 由於以前的send_mail類不支持認證功能,所以先要增加認證信息。增加了三個參數,$auth, $authuser,和$authpasswd。$auth是一個標志,表示是否要使用認證功能。$authuser和$authpasswd是smtp認證的用戶名和口令,根據相應的郵件服務商的要求,例如263是同pop3相一致。大部分應該也是如此。這樣,同時需要在類的內部變量表後面增加三個內部變量:$auth,$user,$passwd。 修改發送函數(send) 將發送命令HELO改為發送EHLO。同時要加入判斷是否要進行認證處理: //改為支持ESMTP EHLO命令 if($this->auth) { $this->lastact="EHLO "; } else $this->lastact="HELO "; 即,如果需要認證處理,則發送EHLO命令,否則還發送HELO命令。 然後,增加認證處理: //2000.02.28 增加認證處理 if($this->auth) { $this->lastact="AUTH LOGIN" . " "; if(!$this->do_command($this->lastact, "334")) { fclose($this->fp); return false; } //回傳用戶名,用base64編碼 $this->lastact=base64_encode($this->user) . " "; if(!$this->do_command($this->lastact, "334")) { fclose($this->fp); return false; } //回傳口令,用base64編碼 $this->lastact=base64_encode($this->passwd) . " "; if(!$this->do_command($this->lastact, "235")) { fclose($this->fp); return false; } } 注意,這裡只實現了AUTH LOGIN機制,CRAM-MD5沒有實現。而且對服務器傳回的信息沒有判斷,默認為第一次要求用戶名,第二次要求口令。 修改命令執行函數(do_command) 原函數不能顯示當響應串為多行的情況。修改為: /* 2000.02.28 修改,將返回信息顯示完全 $this->lastmessage = fgets ( $this->fp, 512 ); $this->show_debug($this->lastmessage, "in"); */ while(true) { $this->lastmessage = fgets ( $this->fp, 512 ); $this->show_debug($this->lastmessage, "in"); if(($this->lastmessage[3]== ) or (empty($this->lastmessage))) break; } 這樣類就改好了。 測試send_mail類 下面是我編寫的一個測試小程序,用於發送一封信,但是為了安全起見,我將用戶名及口令沒有用真實信息,如果大家想要測試請改成你自已的信息。程序如下(send.php): include("sendmail.class.php3"); $sendmail=new send_mail("smtp.263.net", true, "username", "password", "hello", true); $sendmail->send("toemail, "fromemail", "test", "This is a test!"); ?> 結論 對於263的測試很順利,也比較快。但是新浪網則不容易成功,主要是超時,而且發成功也收不著,不知為何? 注意:由於發送smtp需要用戶名及口令,且大部分的smtp認證使用與pop3相同的用戶名和口令。所以如果大家使用這個方法,可能會把用戶名和口令寫入程序,上傳到服務器。但是這樣做是不安全的。加密也不一定好用,因為信息放在服務器上,相應的解密信息也會放到服務器上。我的建議是,再申請一個專門用來發信用的信箱,這樣別人知道了也不怕。 希望這個程序對你有用。sendmail.class.php3下載。 附:相關的RFC RFC 1869 SMTP Service Extensions RFC 2195 IMAP/POP AUTHorize Extension(裡面有關於CRAM-MD5的說明) RFC 2222 Simple Authentication and Security Layer RFC 2554 SMTP Service Extension for Authentication -------------------------------------------------------------------------------- [注1] CRAM=Challenge-Response Authentication Mechanism 口令-應答認證機制 MD5是一種摘要算法,主要用於RSA,PGP中。 [注2] 關於EHLO的說明參見[RFC 1869]。 [注3] 在郵件服務器應答串中,如果應響碼後面跟空格( )表示,應答串只有一行;如果為減號(-)表示有多行,且最後一行響應碼後面為空格( )。 本文所有權屬於limodou。如要轉載請保留此信息。 注意:sendmail.class.php3下載地址: http://www.zphp.com/files/sendmail.class