【问题标题】:Setting up standard Go net/smtp with Office 365 fails with "Error tls: first record does not look like a TLS handshake"使用 Office 365 设置标准 Go net/smtp 失败并显示“错误 tls:第一条记录看起来不像 TLS 握手”
【发布时间】:2019-11-11 16:06:23
【问题描述】:

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

我需要有关配置 tls/server 设置以使用 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 客户端的示例都将受到赞赏,或者任何人都可以发现似乎可疑的任何东西都会很棒

谢谢

【问题讨论】:

  • 此服务器似乎不支持完全加密的连接。试试StartTLS
  • 你搞定了吗,以下答案都不适合我!

标签: email go smtp


【解决方案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,顾名思义,它是不安全的,与您面临的错误无关。

【讨论】:

  • 谢谢,我实际上在 startTLS 方法中看到了同样的错误,并从 tlsConfig 对象中删除了 InscureSkipVerify 行:
  • c, err := smtp.NewClient(conn, host) if err != nil { fmt.Println("smtp.NewClient Error: %s", err) } c.StartTLS(tlsconfig)
  • @SuperSecretAndHiddenFromWork,仔细检查您是否将 tls.Dial 替换为 net.Dial。
  • 感谢你们到目前为止的帮助,我现在正处于这个烂摊子的 Auth 麻烦阶段。我相信 O365 不支持 smtp.PlainAuth() 所以我正在寻找我应该在这里设置的内容
【解决方案2】:
  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

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
}

【讨论】:

    【解决方案3】:

    以下对我来说很好用:

    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>
    

    【讨论】:

      【解决方案4】:

      所以问题都是关于授权的。首先要求我在客户端使用 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()
      }
      

      【讨论】:

      • host 未声明!!
      猜你喜欢
      • 1970-01-01
      • 2021-06-06
      • 1970-01-01
      • 2019-06-07
      • 1970-01-01
      • 2020-09-06
      • 1970-01-01
      • 2017-01-17
      • 2020-05-13
      相关资源
      最近更新 更多