golang筆記:net/smtp


跟go語言的net/smtp斗爭了一天,記錄下歷程。

 
先用最標准的例子
host := net.JoinHostPort(hostname, port)
auth := smtp.PlainAuth("", username, password, hostname)
to := []string{address}
msg := []byte("To: " + 
        address +
        "\r\n" +
        "Subject:" +
        title +
        "\r\n" +
        "\r\n" +        
        content +      
        "\r\n")            
err := smtp.SendMail(host, auth, from, to, msg)
 
         
程序持續報一個 unencrypted connection 的錯誤。原來新版本的smtp為了防止密碼以明文傳輸,強制以SSL連接發送郵件。但我手上的服務器沒有SSL連接,只好去庫里看在哪兒做的判斷,找到auth.go里面func Start()中的這樣一段話
 
         
 
         
if !server.TLS {
    advertised := false
    for _, mechanism := range server.Auth {
        if mechanism == "PLAIN" {
            advertised = true
            break
        }
    }
    if !advertised {
        return "", nil, errors.New("unencrypted connection")
    }
}
 
         
看樣子判斷是在這里進行的了。在網上找到一個偽裝TLS鏈接的方法。
首先,在代碼里加上
 
         
 
         
/*use unSSL to link mail server*/
type unencryptedAuth struct {
    smtp.Auth
}
 
         
func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
    s := *server
    s.TLS = true    
    login, resp, th := a.Auth.Start(&s)
    return "LOGIN", resp, th
}
 
         
將TLS的值設為true, 發郵件部分這樣寫
 
         
 
         
auth := unencryptedAuth {
     smtp.PlainAuth(
         "",
         username,
         password,
         hostname,
     )
}
 
err := smtp.SendMail(host, auth, from, to, msg)
 
         
這樣鏈接成立了,報的錯誤變成 unrecognized authentication type. 查到func Start() 的返回值為
 
         
    return "PLAIN", resp, nil
 
         
原來這里強制以plain登陸。參考前人的方法修改思路,重寫Start方法
 
         
type loginAuth struct {
    username, password string
}
 
         
 
         
func LoginAuth(username, password string) smtp.Auth {
    return &loginAuth{username, password}
}
 
         
 
         
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {    
    return "LOGIN", nil, nil
}
 
         
 
         
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
    command := string(fromServer)
    command = strings.TrimSpace(command)
    command = strings.TrimSuffix(command, ":")
    command = strings.ToLower(command)
 
         
    if more {
        if (command == "username") {
            return []byte(fmt.Sprintf("%s", a.username)), nil
        } else if (command == "password") {
            return []byte(fmt.Sprintf("%s", a.password)), nil
        } else {
            // We've already sent everything.
            return nil, fmt.Errorf("unexpected server challenge: %s", command)
        }
    }
    return nil, nil
}
 
         
Login的認證方式協議和Plain不同,所以Next方法也重寫了,不然報那個unexpected server challenge的錯誤,這樣就能順利地使用用戶名和密碼認證,發郵件的認證部分這樣寫:
 
         
 
         
   auth := LoginAuth("username, password)
 
         
如此一來就可以成功發送郵件了。
 
         
但是當我換用另一台郵件服務器時,又出現了certificate signed by unknown authority
部署到服務器上時,錯誤顯示為cannot validate certificate for 10.11.64.80 because it doesn't contain any IP SANS
總之都是類似於認證的問題。這兩台服務器的區別是第一台使用465端口,即smtps,而第二台使用25端口。
 
         
查看smtp.go發現func SendMail()中有這樣一段
if ok, _ := c.Extension("STARTTLS"); ok {
config := &tls.Config{ServerName: c.serverName}
     if testHookStartTLS != nil {
         testHookStartTLS(config)
     }
     if err = c.StartTLS(config); err != nil {
         return err
     }
}
我干脆把SendMail方法拷出來,去掉這一段判斷,同時smtp里面涉及到的func和struct都拷出來,寫了一個新的.go,在發郵件的時候直接使用這個新的SendMail。部分原有的公共方法和結構不用拷出,直接以smtp.調用,如此一來就能直接用端口25的那台服務器發郵件了。
 
         
雖然郵件發送成功,但是查看日志里總輸出一個錯誤250 Mail OK queued as XXXX,看着很不爽,但這輸出又不像錯誤。按照telnet hostname port后的操作對照SendMail的執行過程。發現在發送DATA指令之后,會收到一個回復碼354,接收輸入郵件內容,以句號回車結尾后,會再收到一個250的回復。在代碼中,發送了DATA,收到354,接着發送郵件內容,代碼並未接收這個250。最后發送QUIT,這里收到的是上一個回復碼250,和QUIT的正常回復碼221作比較,程序就會返回error。我也不知道哪個函數可以只接收回復,簡單起見,干脆在Quit函數里發了兩遍QUIT,判斷第一個返回250,第二個返回221,終於不再報錯。
 
         
研究完這個函數,對smtp就從一無所知到相當了解了。另外,要從根本上解決問題,還是升級為SSL吧!

 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM