net/smtp

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

8BITMIME RFC 1652
AUTH RFC 2554
STARTTLS RFC 3207

  • SMPT
1594482-ad588f637f823908.png
来源于百度

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

  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 : 2461556682@qq.com // 第四步 设置发送方的邮箱
R:250 Ok
S:rcpt to :454719014@qq.com // 第五步 设置接受方的邮箱
R:250 Ok
S:Data // 第六步发送数据
R:354 End data with <CR><LF>.<CR><LF>
S:hello world
S:. // 第七步 发送完成后告诉服务器一声哈 输入一个.就可以
R:250 Ok: queued as
S:quit // 第八步关闭连接
R:221 Bye
R:Connection closed by foreign host.

smtp使用的端口号是25

  • 邮件格式报文和 MIME

FROM: 2461556682@qq.com
TO: 454719014@qq.com
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("", "2461556682@qq.com", "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("2461556682@qq.com"); err != nil {
        log.Fatal(err)
    }
    if err := c.Rcpt("454719014@qq.com"); 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("", "2461556682@qq.com", "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{"454719014@qq.com"}
    msg := []byte("This is the email body.")

    // 3.发送邮件
    err := smtp.SendMail("smtp.qq.com:25", auth, "2461556682@qq.com", 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

1594482-68abec38889af986.png
image.png
1594482-d05980a03a4cf10f.png
image.png

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

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

1594482-f8d15776fa1ff8d5.png
image.png

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

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

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

    content_type := "Content-Type: text/plain; charset=UTF-8"
    nickname := "From:" + "孙悟空" + "<2461556682@qq.com>"
    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, "2461556682@qq.com", to, []byte(msg))
    if err != nil {
    fmt.Println(err)
    }
}
1594482-5ac3416058fdf963.png
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","2461556682@qq.com")
   msg.SetHeader("To","454719014@qq.com")
   msg.SetHeader("Subject","名博主")
   msg.Attach("index.go")
   var html = "<div style=\"font-size:100px\">邮件内容</div><img src=\"http://img0.imgtn.bdimg.com/it/u=1054357935,1017735693&fm=26&gp=0.jpg\"></img>"
   msg.SetBody("text/html",html)
   msg.Embed("/Users/xujie/go/src/awesomeProject/img.jpg")
   d := gomail.NewDialer("smtp.qq.com",587,"2461556682@qq.com","jskcanmjrpakeaeb")
    d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
   error := d.DialAndSend(msg)
   if error != nil {
       fmt.Println(error)
   }
}

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

1594482-bcc49b2341687fd3.png
image.png

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

From:孙悟空<2461556682@qq.com>
Subject:大战天庭
To: 454719014@qq.com
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("", "2461556682@qq.com", "jskcanmjrpakeaeb", "smtp.qq.com")

 // 发送人的
 sender := "From:" + "孙悟空" + "<2461556682@qq.com>"

 // 接受人
 receivers := []string{"454719014@qq.com","2461556682@qq.com"}
 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, "2461556682@qq.com", 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" <carven@smtp.com>

Cc: "sam" <sam@smtp.com>,

  "yoyo" <yoyo@smtp.com>

BCC: "clara" <clara@tsmtp.com>

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值