net/smtp

smtp包實(shí)現(xiàn)了簡單郵件傳輸協(xié)議(SMTP),參見RFC 5321。同時(shí)本包還實(shí)現(xiàn)了如下擴(kuò)展

8BITMIME RFC 1652
AUTH RFC 2554
STARTTLS RFC 3207

  • SMPT
來源于百度

我們簡單描述了一下發(fā)送郵件的過程

  1. 用戶A 調(diào)用自己的郵件代理程序并提供 用戶B 的郵件地址,撰寫報(bào)文,然后指示用戶代理發(fā)送該報(bào)文
  2. 用戶A 的代理程序把報(bào)文發(fā)送給它的郵件服務(wù)器S1,服務(wù)器將報(bào)文放在報(bào)文隊(duì)列中
  3. S1服務(wù)器上的SMTP 客戶端發(fā)現(xiàn)了這個(gè)報(bào)文隊(duì)列中的報(bào)文,它就創(chuàng)建了一個(gè)與用戶B郵件服務(wù)器S2上的SMTP服務(wù)器的tcp連接,在經(jīng)過初步握手之后,S1上的SMTP客戶端通過tcp連接發(fā)送用戶A的報(bào)文
  4. 在用戶B 的郵件服務(wù)器S2上,SMTP的服務(wù)器接受到用戶A發(fā)送給用戶B的報(bào)文之后,就放在了用戶B所在的報(bào)文郵箱中
  5. 在b方便的時(shí)候,就調(diào)用用戶代理程序閱讀該報(bào)文

下面我們演示一下linux發(fā)送郵件的過程

S : telnet smtp.qq.com 25 // 第一步 和服務(wù)器進(jìn)行握手連接
R: Trying 157.255.174.111... //
Connected to smtp.qq.com.
Escape character is '^]'.
220 smtp.qq.com Esmtp QQ Mail Server // 服務(wù)器發(fā)送來電
S: HELO XUJIE // 第二步 和服務(wù)器打個(gè)招呼
R :250 smtp.qq.com
S: AUTH LOGIN // 第三步 輸入授權(quán)信息
R: 334 VXNlcm5hbWU6
S :MjQ2MTU1NjY4MkBxcS5jb20=
R:334 UGFzc3dvcmQ6
S:anNrY2FubWpycGFrZWFlYg==
R:235 Authentication successful
S:MAIL FROM : 2461556682@qq.com // 第四步 設(shè)置發(fā)送方的郵箱
R:250 Ok
S:rcpt to :454719014@qq.com // 第五步 設(shè)置接受方的郵箱
R:250 Ok
S:Data // 第六步發(fā)送數(shù)據(jù)
R:354 End data with <CR><LF>.<CR><LF>
S:hello world
S:. // 第七步 發(fā)送完成后告訴服務(wù)器一聲哈 輸入一個(gè).就可以
R:250 Ok: queued as
S:quit // 第八步關(guān)閉連接
R:221 Bye
R:Connection closed by foreign host.

smtp使用的端口號是25

  • 郵件格式報(bào)文和 MIME

FROM: 2461556682@qq.com
TO: 454719014@qq.com
Subject: 標(biāo)題

接下來我們看go代碼如何實(shí)現(xiàn)上面的過程

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")
    
    // 導(dǎo)入tls需要的相關(guān)證書
    //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}
    
    // 必須設(shè)置tls傳輸協(xié)議配置 
    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)
    }
    
    // 設(shè)置發(fā)送方和接收方的郵箱
    if err := c.Mail("2461556682@qq.com"); err != nil {
        log.Fatal(err)
    }
    if err := c.Rcpt("454719014@qq.com"); err != nil {
        log.Fatal(err)
    }
    // 發(fā)送數(shù)據(jù)
    wc, err := c.Data()
    if err != nil {
        log.Fatal(err)
    }
    
    // 設(shè)置發(fā)送內(nèi)容
    _, err = fmt.Fprintf(wc, "This is the email body")
    if err != nil {
        log.Fatal(err)
    }
    
    // 結(jié)束發(fā)送
    err = wc.Close()
    if err != nil {
       log.Fatal(err)
    }
    //發(fā)送關(guān)閉命令
    err = c.Quit()

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

以上的發(fā)送過程略微有些復(fù)雜,但是那個(gè)能夠演示整個(gè)郵件發(fā)送的過程,那么go有沒有提供簡單的發(fā)送郵件的接口呢?

使用封裝后的郵件發(fā)送代碼

package main

import (
      "net/smtp"
    "fmt"
)

func main() {
    // 1 獲取授權(quán)信息
    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.發(fā)送郵件
    err := smtp.SendMail("smtp.qq.com:25", auth, "2461556682@qq.com", to, msg)
    if err != nil {
    fmt.Println(err)
    }
}

發(fā)送郵件需要先設(shè)置授權(quán)信息,需要驗(yàn)證自己郵件服務(wù)商信息的正確性

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

我用的是自己的qq郵箱,這里需要注意一下,如果是qq郵箱password填寫的qq郵箱提供的授權(quán)碼,授權(quán)碼怎么來的,請查閱 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 發(fā)送郵件需要連接到的服務(wù)商地址,注意地址必須包含端口號 ,這msg必須注意一下郵件消息的內(nèi)容,我們待會說

通過配置好了之后就可以發(fā)送郵件了,下面是我們接受到郵件截圖,不過這個(gè)郵件沒有主題 沒有發(fā)件人 沒有內(nèi)容.

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:"+"大戰(zhàn)天庭"
    toUsers := "To: " + strings.Join(to, ",")
    body := "\n"+"This is the email body." // 內(nèi)容前最少需要加兩個(gè)\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)
    }
}
image.png

這樣我們就實(shí)現(xiàn)了郵件的發(fā)送

下面在介紹一個(gè)發(fā)郵件的第三方包

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\">郵件內(nèi)容</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 設(shè)置的格式 首字母必須大寫,其余小寫

image.png

下面我們講講發(fā)送附件的格式

From:孫悟空<2461556682@qq.com>
Subject:大戰(zhàn)天庭
To: 454719014@qq.com
Content-type: multipart/mixed; boundary="#BOUNDARY#" // 修改報(bào)文發(fā)送的類型,為混合模式boundary 指定分割表示符


--#BOUNDARY#   // 每種類型前必須執(zhí)行文件類型
Content-Type: text/plain; charset=utf-8 // 必須和上面的標(biāo)識符緊挨
Content-Transfer-Encoding: quoted-printable// 必須和上面的內(nèi)容緊挨著

This is the email body // 這個(gè)是文本內(nèi)容 主要需要空一格

--#BOUNDARY# // 分割 
Content-Type: application/octet-stream; name=att.txt 
Content-Disposition: attachment; filename=att.txt // 設(shè)置文件處理方式為附件
Content-Transfer-Encoding: base64

CnBhY2thZ2UgYXdlc29tZVByb2plY3QKCmZ1bmMgbWFpbigpewoKfQ== // 這個(gè)是附件的base64編碼文件 主要需要空一格

看看代碼如何實(shí)現(xiàn)

package main

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

func main() {
auth := smtp.PlainAuth("", "2461556682@qq.com", "jskcanmjrpakeaeb", "smtp.qq.com")

 // 發(fā)送人的
 sender := "From:" + "孫悟空" + "<2461556682@qq.com>"

 // 接受人
 receivers := []string{"454719014@qq.com","2461556682@qq.com"}
 to := "To: " + strings.Join(receivers, ",")

 // 郵件標(biāo)題
 subject :=  "Subject:"+"大戰(zhàn)天庭"

 // 設(shè)置傳輸類型為多文件混合
 contentType := "Content-type: multipart/mixed; boundary=\"#BOUNDARY#\"\r\n\r\n"

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

 // 構(gòu)造文本結(jié)構(gòu)
  boundary := "--#BOUNDARY#"
  bodyContentType := "Content-Type: text/plain; charset=utf-8\r\n"+
      "Content-Transfer-Encoding: quoted-printable\r\n"
  body := "郵件的正文" // 內(nèi)容前最少需要加兩個(gè)\n

  // 構(gòu)造附件結(jié)構(gòu)
  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"

  // 從本地讀取一個(gè)文件 進(jìn)行base64 編碼
  file,_:= os.Open("/Users/xujie/go/src/awesomeProject/index.go")
  data,_ := ioutil.ReadAll(file)

   // 拼接一個(gè)完成的數(shù)據(jù)字符串
   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)
   }
 }

我們發(fā)郵件的時(shí)候,還有兩個(gè)操作,就是抄送和密送

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 密送 格式為:昵稱 + <郵箱地址>,昵稱 + <郵箱地址>,........只要按照上面的格式進(jìn)行拼接格式即可

SMTP 認(rèn)證

前提:

要進(jìn)行身份認(rèn)證, 先要知道當(dāng)前SMTP服務(wù)器支持哪些認(rèn)證方式

  1. LOGIN認(rèn)證方式

LOGIN認(rèn)證方式是基于明文傳輸?shù)? 因此沒什么安全性可言, 如信息被截獲, 那么用戶名和密碼也就泄露了. 認(rèn)證過程如下:
AUTH LOGIN
334 VXNlcm5hbWU6 //服務(wù)器返回信息, Base64編碼的Username:
bXlOYW1l //輸入用戶名, 也需Base64編碼
334 UGFzc3dvcmQ6 //服務(wù)器返回信息, Base64編碼的Password::
bXlQYXNzd29yZA== //輸入密碼, 也需Base64編碼
235 2.0.0 OK Authenticated // 535 5.7.0 authentication failed

2). NTLM認(rèn)證方式

NTLM認(rèn)證方式過程與LOGIN認(rèn)證方式是一模一樣的, 只需將AUTH LOGN改成AUTH NTLM.就行了.

3). PLAIN認(rèn)證方式

PLAIN認(rèn)證方式消息過過程與LOGIN和NTLM有所不同, 其格式為: “NULL+UserName+NULL+Password”, 其中NULL為C語言中的’\0’

4). CRAM-MD5認(rèn)證方式

前面所介紹的三種方式, 都是將用戶名和密碼經(jīng)過BASE64編碼后直接發(fā)送到服務(wù)器端的, BASE64編碼并不是一種安全的加密算法, 其所有信息都可能通過反編碼, 沒有什么安全性可言. 而CRAM-MD5方式與前三種不同, 它是基于Challenge/Response的方式, 其中Challenge是由服務(wù)器產(chǎn)生的, 每次連接產(chǎn)生的Challenge都不同, 而Response是由用戶名,密碼,Challenge組合而成的, 具體格式如下:
response=base64_encode(username : H_MAC(challenge, password))

5). DIGEST-MD5認(rèn)證方式

DIGEST-MD5認(rèn)證也是Challenge/Response的方式, 與CRAM-MD5相比, 它的Challenge信息更多, 其Response計(jì)算方式也非常復(fù)雜

go 實(shí)現(xiàn)了兩種認(rèn)證

//返回一個(gè)實(shí)現(xiàn)了CRAM-MD5身份認(rèn)證機(jī)制(參見[RFC 2195](http://tools.ietf.org/html/rfc2195))的Auth接口。返回的接口使用給出的用戶名和密碼,采用響應(yīng)——回答機(jī)制與服務(wù)端進(jìn)行身份認(rèn)證

func CRAMMD5Auth(username, secret string) Auth
返回一個(gè)實(shí)現(xiàn)了PLAIN身份認(rèn)證機(jī)制(參見[RFC 4616](http://tools.ietf.org/html/rfc4616))的Auth接口。返回的接口使用給出的用戶名和密碼,通過TLS連接到主機(jī)認(rèn)證,采用identity為身份管理和行動(dòng)(通常應(yīng)設(shè)identity為"",以便使用username為身份)。
func PlainAuth(identity, username, password, host string) Auth
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容