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吧!
相關文章
相關標籤/搜索