net/smtp

smtp包实现了简单邮件传输协议(SMTP),参见RFC 5321。同时本包还实现了如下扩展

8BITMIME RFC 1652
AUTH RFC 2554
STARTTLS RFC 3207

  • SMPT
来源于百度

我们简单描述了一下发送邮件的过程

  1. 用户A 调用自己的邮件代理程序并提供 用户B 的邮件地址,撰写报文,然后指示用户代理发送该报文
  2. 用户A 的代理程序把报文发送给它的邮件服务器S1,服务器将报文放在报文队列中
  3. S1服务器上的SMTP 客户端发现了这个报文队列中的报文,它就创建了一个与用户B邮件服务器S2上的SMTP服务器的tcp连接,在经过初步握手之后,S1上的SMTP客户端通过tcp连接发送用户A的报文
  4. 在用户B 的邮件服务器S2上,SMTP的服务器接受到用户A发送给用户B的报文之后,就放在了用户B所在的报文邮箱中
  5. 在b方便的时候,就调用用户代理程序阅读该报文

下面我们演示一下linux发送邮件的过程

S : telnet smtp.qq.com 25 // 第一步 和服务器进行握手连接
R: Trying 157.255.174.111... //
Connected to smtp.qq.com.
Escape character is '^]'.
220 smtp.qq.com Esmtp QQ Mail Server // 服务器发送来电
S: HELO XUJIE // 第二步 和服务器打个招呼
R :250 smtp.qq.com
S: AUTH LOGIN // 第三步 输入授权信息
R: 334 VXNlcm5hbWU6
S :MjQ2MTU1NjY4MkBxcS5jb20=
R:334 UGFzc3dvcmQ6
S:anNrY2FubWpycGFrZWFlYg==
R:235 Authentication successful
S:MAIL FROM : [email protected] // 第四步 设置发送方的邮箱
R:250 Ok
S:rcpt to :[email protected] // 第五步 设置接受方的邮箱
R:250 Ok
S:Data // 第六步发送数据
R:354 End data with .
S:hello world
S:. // 第七步 发送完成后告诉服务器一声哈 输入一个.就可以
R:250 Ok: queued as
S:quit // 第八步关闭连接
R:221 Bye
R:Connection closed by foreign host.

smtp使用的端口号是25

  • 邮件格式报文和 MIME

FROM: [email protected]
TO: [email protected]
Subject: 标题

接下来我们看go代码如何实现上面的过程

package main

import (
      "net/smtp"
    "fmt"
      "log"
    "crypto/tls"
)

func main() {
    // Connect to the remote SMTP server.
    c, err := smtp.Dial("smtp.qq.com:25")
    
    // 导入tls需要的相关证书
    //certificate,err := tls.LoadX509KeyPair("/Users/xujie/go/src/awesomeProject/server.crt","/Users/xujie/go/src/awesomeProject/server.key")
    //tlsConfig := tls.Config{Certificates:[]tls.Certificate{certificate},ServerName:"smtp.qq.com",InsecureSkipVerify:false}
    
    // 必须设置tls传输协议配置 
    tlsConfig := tls.Config{ServerName:"smtp.qq.com",InsecureSkipVerify:true}
    c.StartTLS(&tlsConfig)
    auth := smtp.PlainAuth("", "[email protected]", "jskcanmjrpakeaeb", "smtp.qq.com")

    err = c.Auth(auth)
    if err != nil {
      fmt.Println(err)
    }
    fmt.Println(c)
    if err != nil {
        log.Fatal(err)
    }
    
    // 设置发送方和接收方的邮箱
    if err := c.Mail("[email protected]"); err != nil {
        log.Fatal(err)
    }
    if err := c.Rcpt("[email protected]"); err != nil {
        log.Fatal(err)
    }
    // 发送数据
    wc, err := c.Data()
    if err != nil {
        log.Fatal(err)
    }
    
    // 设置发送内容
    _, err = fmt.Fprintf(wc, "This is the email body")
    if err != nil {
        log.Fatal(err)
    }
    
    // 结束发送
    err = wc.Close()
    if err != nil {
       log.Fatal(err)
    }
    //发送关闭命令
    err = c.Quit()

    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("发送完成")
}

以上的发送过程略微有些复杂,但是那个能够演示整个邮件发送的过程,那么go有没有提供简单的发送邮件的接口呢?

使用封装后的邮件发送代码

package main

import (
      "net/smtp"
    "fmt"
)

func main() {
    // 1 获取授权信息
    auth := smtp.PlainAuth("", "[email protected]", "jskcanmjrpakea", "smtp.qq.com")
    // Connect to the server, authenticate, set the sender and recipient,
    // and send the email all in one step.

    // 2.接收人的邮箱地址
    to := []string{"[email protected]"}
    msg := []byte("This is the email body.")

    // 3.发送邮件
    err := smtp.SendMail("smtp.qq.com:25", auth, "[email protected]", to, msg)
    if err != nil {
    fmt.Println(err)
    }
}

发送邮件需要先设置授权信息,需要验证自己邮件服务商信息的正确性

func PlainAuth(identity, username, password, host string) Auth

我用的是自己的qq邮箱,这里需要注意一下,如果是qq邮箱password填写的qq邮箱提供的授权码,授权码怎么来的,请查阅 https://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=1001256

image.png
image.png

func SendMail(addr string, a Auth, from string, to []string, msg []byte) error 发送邮件需要连接到的服务商地址,注意地址必须包含端口号 ,这msg必须注意一下邮件消息的内容,我们待会说

通过配置好了之后就可以发送邮件了,下面是我们接受到邮件截图,不过这个邮件没有主题 没有发件人 没有内容.

image.png

下面我们把上面缺省的信息加上

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

func main() {
    auth := smtp.PlainAuth("", "[email protected]", "jskcanmjrpakeaeb", "smtp.qq.com")
    // Connect to the server, authenticate, set the sender and recipient,
    // and send the email all in one step.
    to := []string{"[email protected]"}

    content_type := "Content-Type: text/plain; charset=UTF-8"
    nickname := "From:" + "孙悟空" + "<[email protected]>"
    subject :=  "Subject:"+"大战天庭"
    toUsers := "To: " + strings.Join(to, ",")
    body := "\n"+"This is the email body." // 内容前最少需要加两个\n
    msg := strings.Join([]string{nickname,subject,toUsers,content_type,body},"\r\n")
    fmt.Println(string(msg))
    err := smtp.SendMail("smtp.qq.com:25", auth, "[email protected]", to, []byte(msg))
    if err != nil {
    fmt.Println(err)
    }
}
image.png

这样我们就实现了邮件的发送

下面在介绍一个发邮件的第三方包

go get go get gopkg.in/gomail.v2

使用

package main

import (
    "gopkg.in/gomail.v2"
     "fmt"
    "crypto/tls"
            )

func main() {
   msg := gomail.NewMessage()
   msg.SetHeader("From","[email protected]")
   msg.SetHeader("To","[email protected]")
   msg.SetHeader("Subject","名博主")
   msg.Attach("index.go")
   var html = "
邮件内容
" msg.SetBody("text/html",html) msg.Embed("/Users/xujie/go/src/awesomeProject/img.jpg") d := gomail.NewDialer("smtp.qq.com",587,"[email protected]","jskcanmjrpakeaeb") d.TLSConfig = &tls.Config{InsecureSkipVerify: true} error := d.DialAndSend(msg) if error != nil { fmt.Println(error) } }

注意header 设置的格式 首字母必须大写,其余小写

image.png

下面我们讲讲发送附件的格式

From:孙悟空<[email protected]>
Subject:大战天庭
To: [email protected]
Content-type: multipart/mixed; boundary="#BOUNDARY#" // 修改报文发送的类型,为混合模式boundary 指定分割表示符


--#BOUNDARY#   // 每种类型前必须执行文件类型
Content-Type: text/plain; charset=utf-8 // 必须和上面的标识符紧挨
Content-Transfer-Encoding: quoted-printable// 必须和上面的内容紧挨着

This is the email body // 这个是文本内容 主要需要空一格

--#BOUNDARY# // 分割 
Content-Type: application/octet-stream; name=att.txt 
Content-Disposition: attachment; filename=att.txt // 设置文件处理方式为附件
Content-Transfer-Encoding: base64

CnBhY2thZ2UgYXdlc29tZVByb2plY3QKCmZ1bmMgbWFpbigpewoKfQ== // 这个是附件的base64编码文件 主要需要空一格

看看代码如何实现

package main

import (
"net/smtp"
"fmt"
"strings"
    "os"
    "io/ioutil"
    "encoding/base64"
)

func main() {
auth := smtp.PlainAuth("", "[email protected]", "jskcanmjrpakeaeb", "smtp.qq.com")

 // 发送人的
 sender := "From:" + "孙悟空" + "<[email protected]>"

 // 接受人
 receivers := []string{"[email protected]","[email protected]"}
 to := "To: " + strings.Join(receivers, ",")

 // 邮件标题
 subject :=  "Subject:"+"大战天庭"

 // 设置传输类型为多文件混合
 contentType := "Content-type: multipart/mixed; boundary=\"#BOUNDARY#\"\r\n\r\n"

 header := strings.Join([]string{sender,to,subject,contentType},"\r\n")

 // 构造文本结构
  boundary := "--#BOUNDARY#"
  bodyContentType := "Content-Type: text/plain; charset=utf-8\r\n"+
      "Content-Transfer-Encoding: quoted-printable\r\n"
  body := "邮件的正文" // 内容前最少需要加两个\n

  // 构造附件结构
  attent :=  "\r\n--#BOUNDARY#\r\n" +
    "Content-Type: application/octet-stream; name=att.txt\r\n" +
     "Content-Disposition: attachment; filename=att.txt\r\n" +
    "Content-Transfer-Encoding: base64\r\n" +
    "\r\n"

  // 从本地读取一个文件 进行base64 编码
  file,_:= os.Open("/Users/xujie/go/src/awesomeProject/index.go")
  data,_ := ioutil.ReadAll(file)

   // 拼接一个完成的数据字符串
   msg := strings.Join([]string{header,boundary,bodyContentType,body, attent,base64.StdEncoding.EncodeToString(data)},"\r\n")

   fmt.Println(string(msg))
   err := smtp.SendMail("smtp.qq.com:25", auth, "[email protected]", receivers, []byte(msg))
   if err != nil {
     fmt.Println(err)
   }
 }

我们发邮件的时候,还有两个操作,就是抄送和密送

Date: Wed, 6 Jan 2010 12:11:48 +0800

From: "carven_li" < carven_li @smtp.com>

To: "carven" 

Cc: "sam" ,

  "yoyo" 

BCC: "clara" 

Cc 抄送,BCC 密送 格式为:昵称 + <邮箱地址>,昵称 + <邮箱地址>,........只要按照上面的格式进行拼接格式即可

SMTP 认证

前提:

要进行身份认证, 先要知道当前SMTP服务器支持哪些认证方式

  1. LOGIN认证方式

LOGIN认证方式是基于明文传输的, 因此没什么安全性可言, 如信息被截获, 那么用户名和密码也就泄露了. 认证过程如下:
AUTH LOGIN
334 VXNlcm5hbWU6 //服务器返回信息, Base64编码的Username:
bXlOYW1l //输入用户名, 也需Base64编码
334 UGFzc3dvcmQ6 //服务器返回信息, Base64编码的Password::
bXlQYXNzd29yZA== //输入密码, 也需Base64编码
235 2.0.0 OK Authenticated // 535 5.7.0 authentication failed

2). NTLM认证方式

NTLM认证方式过程与LOGIN认证方式是一模一样的, 只需将AUTH LOGN改成AUTH NTLM.就行了.

3). PLAIN认证方式

PLAIN认证方式消息过过程与LOGIN和NTLM有所不同, 其格式为: “NULL+UserName+NULL+Password”, 其中NULL为C语言中的’\0’

4). CRAM-MD5认证方式

前面所介绍的三种方式, 都是将用户名和密码经过BASE64编码后直接发送到服务器端的, BASE64编码并不是一种安全的加密算法, 其所有信息都可能通过反编码, 没有什么安全性可言. 而CRAM-MD5方式与前三种不同, 它是基于Challenge/Response的方式, 其中Challenge是由服务器产生的, 每次连接产生的Challenge都不同, 而Response是由用户名,密码,Challenge组合而成的, 具体格式如下:
response=base64_encode(username : H_MAC(challenge, password))

5). DIGEST-MD5认证方式

DIGEST-MD5认证也是Challenge/Response的方式, 与CRAM-MD5相比, 它的Challenge信息更多, 其Response计算方式也非常复杂

go 实现了两种认证

//返回一个实现了CRAM-MD5身份认证机制(参见[RFC 2195](http://tools.ietf.org/html/rfc2195))的Auth接口。返回的接口使用给出的用户名和密码,采用响应——回答机制与服务端进行身份认证

func CRAMMD5Auth(username, secret string) Auth
返回一个实现了PLAIN身份认证机制(参见[RFC 4616](http://tools.ietf.org/html/rfc4616))的Auth接口。返回的接口使用给出的用户名和密码,通过TLS连接到主机认证,采用identity为身份管理和行动(通常应设identity为"",以便使用username为身份)。
func PlainAuth(identity, username, password, host string) Auth

你可能感兴趣的:(net/smtp)