繁体   English   中英

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

[英]Setting up standard Go net/smtp with Office 365 fails with “Error tls: first record does not look like a TLS handshake”

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

我需要帮助配置 tls/server 设置以使用Office365

我相信我有正确的主机:

smtp.office365.com:587

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

错误:tls:第一条记录看起来不像 TLS 握手恐慌:运行时错误:无效的 memory 地址或 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 客户端的示例都将受到赞赏,或者任何人都可以发现似乎可疑的任何东西都会很棒

谢谢

错误消息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. 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-我们&ad=我们

  1. 使用授权登录

以下代码实现 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 := LoginAuth(fromAddress, password) 

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

下面对我来说很好:

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>

所以问题都是关于授权的。 首先要求我在客户端上使用 StartTLS 方法,并且我编写了一个 function 和方法来支持 LOGIN,标准 Go 库不支持的东西(无论出于何种原因)

查看 main() 上面的函数和结构

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

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()
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM