smtp包实现了简单邮件传输协议(SMTP),参见RFC 5321。同时本包还实现了如下扩展
8BITMIME RFC 1652
AUTH RFC 2554
STARTTLS RFC 3207
- SMPT
我们简单描述了一下发送邮件的过程
- 用户A 调用自己的邮件代理程序并提供 用户B 的邮件地址,撰写报文,然后指示用户代理发送该报文
- 用户A 的代理程序把报文发送给它的邮件服务器S1,服务器将报文放在报文队列中
- S1服务器上的SMTP 客户端发现了这个报文队列中的报文,它就创建了一个与用户B邮件服务器S2上的SMTP服务器的tcp连接,在经过初步握手之后,S1上的SMTP客户端通过tcp连接发送用户A的报文
- 在用户B 的邮件服务器S2上,SMTP的服务器接受到用户A发送给用户B的报文之后,就放在了用户B所在的报文邮箱中
- 在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
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
发送邮件需要连接到的服务商地址,注意地址必须包含端口号 ,这msg必须注意一下邮件消息的内容,我们待会说
通过配置好了之后就可以发送邮件了,下面是我们接受到邮件截图,不过这个邮件没有主题 没有发件人 没有内容.
下面我们把上面缺省的信息加上
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)
}
}
这样我们就实现了邮件的发送
下面在介绍一个发邮件的第三方包
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 设置的格式 首字母必须大写,其余小写
下面我们讲讲发送附件的格式
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服务器支持哪些认证方式
- 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