使用Office 365设置标准Go net / smtp失败,并显示“错误tls:第一条记录看起来不像TLS握手”

时间:2019-11-11 16:06:23

标签: email go smtp

我正在尝试使用默认的Go包net / smtp创建一个简单的Go电子邮件服务-我知道有gomailer,但我想使用标准库

我需要配置tls /服务器设置以与 Office365

一起使用时需要帮助

我相信我的主机正确:

smtp.office365.com:587

但是,通过复制Microsoft提供的smtp文档,运行以下代码时,控制台中出现以下错误:

错误:tls:第一条记录看起来不像TLS握手 紧急:运行时错误:无效的内存地址或nil指针取消引用

package main

import (
"fmt"
"net"
mail "net/mail"
smtp "net/smtp"
)

func main() {

from := mail.Address{"", "example@example.com"}
to := mail.Address{"", "example@example.com"}
subject := "My test subject"
body := "Test email body"

// Setup email headers
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject

message := ""
for k, v := range headers {
    message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body

servername := "smtp.office365.com:587"
host, _, _ := net.SplitHostPort(servername)

auth := smtp.PlainAuth("", "example@example.com", "password", host)

tlsconfig := &tls.Config{
    InsecureSkipVerify: true,
    ServerName:         host,
}

conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
    fmt.Println("tls.Dial Error: %s", err)
}

c, err := smtp.NewClient(conn, host)
if err != nil {
    fmt.Println("smtp.NewClient Error: %s", err)
}

if err = c.Auth(auth); err != nil {
    fmt.Println("c.Auth Error: %s", err)
}

if err = c.Mail(from.Address); err != nil {
    fmt.Println("c.Mail Error: %s", err)
}

if err = c.Rcpt(to.Address); err != nil {
    fmt.Println("c.Rcpt Error: %s", err)
}

w, err := c.Data()
if err != nil {
    fmt.Println("c.Data Error: %s", err)
}

_, err = w.Write([]byte(message))
if err != nil {
    fmt.Println("Error: %s", err)
}

err = w.Close()
if err != nil {
    fmt.Println("reader Error: %s", err)
}

c.Quit()
}

任何O365客户端的示例都将受到赞赏,或者任何人都可以发现的看起来可疑的东西都是很棒的

谢谢

4 个答案:

答案 0 :(得分:1)

错误消息Error: tls: first record does not look like a TLS handshake告诉您问题出在哪里:-)。如果尝试连接到服务器,您将看到(与任何SMTP服务器一样)它使用纯文本:

telnet smtp.office365.com 587
Trying 2603:1026:c0b:10::2...
Connected to zrh-efz.ms-acdc.office.com.
Escape character is '^]'.
220 ZRAP278CA0003.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 11 Nov 2019 17:13:50 +0000
...

您需要使用STARTTLS命令,请参见https://en.wikipedia.org/wiki/Opportunistic_TLS(以及该Wiki页面所指向的RFC)。

在Go中,它是https://golang.org/pkg/net/smtp/#Client.StartTLS

在您的代码中,我注意到了

tlsconfig := &tls.Config{
    InsecureSkipVerify: true,   <== REMOVE THIS
    ServerName:         host,
}

请删除InsecureSkipVerify,顾名思义,它是不安全的,与您面临的错误无关。

答案 1 :(得分:1)

  1. Outlook.com自2017年8月起不再支持AUTH PLAIN身份验证。

https://support.microsoft.com/en-us/office/outlook-com-no-longer-supports-auth-plain-authentication-07f7d5e9-1697-465f-84d2-4513d4ff0145?ui=en-us&rs=en-us&ad=us

  1. 使用AUTH LOGIN

以下代码实现了AUTH LOGIN

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", []byte(a.username), nil
}


func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
    if more {
        switch string(fromServer) {
        case "Username:":
            return []byte(a.username), nil
        case "Password:":
            return []byte(a.password), nil
        default:
            return nil, errors.New("Unknown from server")
        }
    }
    return nil, nil
}
  1. 删除“ InsecureSkipVerify:true”
tlsconfig := &tls.Config {
    ServerName: host,
}
  1. 请勿使用tsl.Dial(),而应使用net.Dial()
conn, err := net.Dial("tcp", "smtp.office365.com:587")
if err != nil {
    return err
}
  1. 在smtp.NewClient()之后调用StartTLS()
c, err := smtp.NewClient(conn, host)
if err != nil {
    return err
}

if err = c.StartTLS(tlsconfig); err != nil {
    return err
}
  1. 使用AUTH LOGIN
auth := LoginAuth(fromAddress, password) 

if err = c.Auth(auth); err != nil {
    return err
}

答案 2 :(得分:1)

以下与我合作很好:

package main

import (
    "bytes"
    "crypto/tls"
    "errors"
    "fmt"
    "net"
    "net/smtp"
    "text/template"
)

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", []byte(a.username), nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
    if more {
        switch string(fromServer) {
        case "Username:":
            return []byte(a.username), nil
        case "Password:":
            return []byte(a.password), nil
        default:
            return nil, errors.New("Unknown from server")
        }
    }
    return nil, nil
}

func main() {

    // Sender data.
    from := "O365 logging name"
    password := "O365 logging pasword"

    // Receiver email address.
    to := []string{
        "receiver email",
    }

    // smtp server configuration.
    smtpHost := "smtp.office365.com"
    smtpPort := "587"

    conn, err := net.Dial("tcp", "smtp.office365.com:587")
    if err != nil {
        println(err)
    }

    c, err := smtp.NewClient(conn, smtpHost)
    if err != nil {
        println(err)
    }

    tlsconfig := &tls.Config{
        ServerName: smtpHost,
    }

    if err = c.StartTLS(tlsconfig); err != nil {
        println(err)
    }

    auth := LoginAuth(from, password)

    if err = c.Auth(auth); err != nil {
        println(err)
    }

    t, _ := template.ParseFiles("template.html")

    var body bytes.Buffer

    mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
    body.Write([]byte(fmt.Sprintf("Subject: This is a test subject \n%s\n\n", mimeHeaders)))

    t.Execute(&body, struct {
        Name    string
        Message string
    }{
        Name:    "Hasan Yousef",
        Message: "This is a test message in a HTML template",
    })

    // Sending email.
    err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes())
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("Email Sent!")
}

使用以下模板作为奖励:)

<!-- template.html -->
<!DOCTYPE html>
<html>
<body>
    <h3>Name:</h3><span>{{.Name}}</span><br/><br/>
    <h3>Email:</h3><span>{{.Message}}</span><br/>
</body>
</html>

答案 3 :(得分:0)

所以问题全在授权上。首先要求我在客户端上使用StartTLS方法,还要求我编写一个函数和方法来支持LOGIN,而标准Go库则不支持(出于某种原因)

请参阅main()上方的函数和结构

这是带有帮助程序功能的完整代码,现在可以通过我的O365帐户成功发送电子邮件:

package main

import (
"fmt"
"net"
"errors"
mail "net/mail"
smtp "net/smtp"
)

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", []byte{}, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
    if more {
        switch string(fromServer) {
        case "Username:":
            return []byte(a.username), nil
        case "Password:":
            return []byte(a.password), nil
        default:
            return nil, errors.New("Unknown fromServer")
        }
    }
    return nil, nil
}

func main() {

from := mail.Address{"", "example@example.com"}
to := mail.Address{"", "example@example.com"}
subject := "My test subject"
body := "Test email body"

headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject

message := ""
for k, v := range headers {
    message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body

tlsconfig := &tls.Config{
    ServerName:         host,
}

conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
    fmt.Println("tls.Dial Error: ", err)
}

c, err := smtp.NewClient(conn, host)
if err != nil {
    fmt.Println("smtp.NewClient Error: ", err)
}


if err = c.Auth(LoginAuth("example@example.com", "password")); err != nil {
        fmt.Println("c.Auth Error: ", err)
        return
}

if err = c.Mail(from.Address); err != nil {
    fmt.Println("c.Mail Error: ", err)
}

if err = c.Rcpt(to.Address); err != nil {
    fmt.Println("c.Rcpt Error: ", err)
}

w, err := c.Data()
if err != nil {
    fmt.Println("c.Data Error: ", err)
}

_, err = w.Write([]byte(message))
if err != nil {
    fmt.Println("Error: ", err)
}

err = w.Close()
if err != nil {
    fmt.Println("reader Error: ", err)
}

c.Quit()
}