smtp.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. package mail
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "crypto/tls"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "errors"
  9. "fmt"
  10. "io/ioutil"
  11. "log"
  12. "net/mail"
  13. "net/smtp"
  14. "path"
  15. "path/filepath"
  16. "regexp"
  17. "strconv"
  18. "strings"
  19. "github.com/beego/beego/v2/adapter/logs"
  20. )
  21. var (
  22. imageRegex = regexp.MustCompile(`(src|background)=["'](.*?)["']`)
  23. schemeRegxp = regexp.MustCompile(`^[a-zA-Z]+://`)
  24. )
  25. // Mail will represent a formatted email
  26. type Mail struct {
  27. To []string
  28. ToName []string
  29. Subject string
  30. HTML string
  31. Text string
  32. From string
  33. Bcc []string
  34. FromName string
  35. ReplyTo string
  36. Date string
  37. Files map[string]string
  38. Headers string
  39. BaseDir string //内容中图片路径
  40. Charset string //编码
  41. RetReceipt string //回执地址,空白则禁用回执
  42. }
  43. // NewMail returns a new Mail
  44. func NewMail() Mail {
  45. return Mail{}
  46. }
  47. // SMTPClient struct
  48. type SMTPClient struct {
  49. smtpAuth smtp.Auth
  50. host string
  51. port string
  52. user string
  53. secure string
  54. }
  55. // SMTPConfig 配置结构体
  56. type SMTPConfig struct {
  57. Username string
  58. Password string
  59. Host string
  60. Port int
  61. Secure string
  62. Identity string
  63. }
  64. func (s *SMTPConfig) Address() string {
  65. if s.Port == 0 {
  66. s.Port = 25
  67. }
  68. return s.Host + `:` + strconv.Itoa(s.Port)
  69. }
  70. func (s *SMTPConfig) Auth() smtp.Auth {
  71. var auth smtp.Auth
  72. s.Secure = strings.ToUpper(s.Secure)
  73. switch s.Secure {
  74. case "NONE":
  75. auth = unencryptedAuth{smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)}
  76. case "LOGIN":
  77. auth = LoginAuth(s.Username, s.Password)
  78. case "SSL":
  79. fallthrough
  80. default:
  81. //auth = smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)
  82. auth = unencryptedAuth{smtp.PlainAuth(s.Identity, s.Username, s.Password, s.Host)}
  83. }
  84. return auth
  85. }
  86. func NewSMTPClient(conf *SMTPConfig) SMTPClient {
  87. return SMTPClient{
  88. smtpAuth: conf.Auth(),
  89. host: conf.Host,
  90. port: strconv.Itoa(conf.Port),
  91. user: conf.Username,
  92. secure: conf.Secure,
  93. }
  94. }
  95. // NewMail returns a new Mail
  96. func (c *SMTPClient) NewMail() Mail {
  97. return NewMail()
  98. }
  99. // Send - It can be used for generic SMTP stuff
  100. func (c *SMTPClient) Send(m Mail) error {
  101. length := 0
  102. if len(m.Charset) == 0 {
  103. m.Charset = "utf-8"
  104. }
  105. boundary := "COSCMSBOUNDARYFORSMTPGOLIB"
  106. var message bytes.Buffer
  107. message.WriteString(fmt.Sprintf("X-SMTPAPI: %s\r\n", m.Headers))
  108. //回执
  109. if len(m.RetReceipt) > 0 {
  110. message.WriteString(fmt.Sprintf("Return-Receipt-To: %s\r\n", m.RetReceipt))
  111. message.WriteString(fmt.Sprintf("Disposition-Notification-To: %s\r\n", m.RetReceipt))
  112. }
  113. message.WriteString(fmt.Sprintf("From: %s <%s>\r\n", m.FromName, m.From))
  114. if len(m.ReplyTo) > 0 {
  115. message.WriteString(fmt.Sprintf("Return-Path: %s\r\n", m.ReplyTo))
  116. }
  117. length = len(m.To)
  118. if length > 0 {
  119. nameLength := len(m.ToName)
  120. if nameLength > 0 {
  121. message.WriteString(fmt.Sprintf("To: %s <%s>", m.ToName[0], m.To[0]))
  122. } else {
  123. message.WriteString(fmt.Sprintf("To: <%s>", m.To[0]))
  124. }
  125. for i := 1; i < length; i++ {
  126. if nameLength > i {
  127. message.WriteString(fmt.Sprintf(", %s <%s>", m.ToName[i], m.To[i]))
  128. } else {
  129. message.WriteString(fmt.Sprintf(", <%s>", m.To[i]))
  130. }
  131. }
  132. }
  133. length = len(m.Bcc)
  134. if length > 0 {
  135. message.WriteString(fmt.Sprintf("Bcc: <%s>", m.Bcc[0]))
  136. for i := 1; i < length; i++ {
  137. message.WriteString(fmt.Sprintf(", <%s>", m.Bcc[i]))
  138. }
  139. }
  140. message.WriteString("\r\n")
  141. message.WriteString(fmt.Sprintf("Subject: %s\r\n", m.Subject))
  142. message.WriteString("MIME-Version: 1.0\r\n")
  143. if m.Files != nil {
  144. message.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\r\n\n--%s\r\n", boundary, boundary))
  145. }
  146. if len(m.HTML) > 0 {
  147. //解析内容中的图片
  148. rs := imageRegex.FindAllStringSubmatch(m.HTML, -1)
  149. var embedImages string
  150. for _, v := range rs {
  151. surl := v[2]
  152. if v2 := schemeRegxp.FindStringIndex(surl); v2 == nil {
  153. filename := path.Base(surl)
  154. directory := path.Dir(surl)
  155. if directory == "." {
  156. directory = ""
  157. }
  158. h := md5.New()
  159. h.Write([]byte(surl + "@coscms.0"))
  160. cid := hex.EncodeToString(h.Sum(nil))
  161. if len(m.BaseDir) > 0 && !strings.HasSuffix(m.BaseDir, "/") {
  162. m.BaseDir += "/"
  163. }
  164. if len(directory) > 0 && !strings.HasSuffix(directory, "/") {
  165. directory += "/"
  166. }
  167. if str, err := m.ReadAttachment(m.BaseDir + directory + filename); err == nil {
  168. re3 := regexp.MustCompile(v[1] + `=["']` + regexp.QuoteMeta(surl) + `["']`)
  169. m.HTML = re3.ReplaceAllString(m.HTML, v[1]+`="cid:`+cid+`"`)
  170. embedImages += fmt.Sprintf("--%s\r\n", boundary)
  171. embedImages += fmt.Sprintf("Content-Type: application/octet-stream; name=\"%s\"; charset=\"%s\"\r\n", filename, m.Charset)
  172. embedImages += fmt.Sprintf("Content-Description: %s\r\n", filename)
  173. embedImages += fmt.Sprintf("Content-Disposition: inline; filename=\"%s\"; charset=\"%s\"\r\n", filename, m.Charset)
  174. embedImages += fmt.Sprintf("Content-Transfer-Encoding: base64\r\nContent-ID: <%s>\r\n\r\n%s\r\n\n", cid, str)
  175. }
  176. }
  177. }
  178. part := fmt.Sprintf("Content-Type: text/html\r\n\n%s\r\n\n", m.HTML)
  179. message.WriteString(part)
  180. message.WriteString(embedImages)
  181. } else {
  182. part := fmt.Sprintf("Content-Type: text/plain\r\n\n%s\r\n\n", m.Text)
  183. message.WriteString(part)
  184. }
  185. if m.Files != nil {
  186. for key, value := range m.Files {
  187. message.WriteString(fmt.Sprintf("--%s\r\n", boundary))
  188. message.WriteString("Content-Type: application/octect-stream\r\n")
  189. message.WriteString("Content-Transfer-Encoding:base64\r\n")
  190. message.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"; charset=\"%s\"\r\n\r\n%s\r\n\n", key, m.Charset, value))
  191. }
  192. message.WriteString(fmt.Sprintf("--%s--", boundary))
  193. }
  194. if c.secure == "SSL" || c.secure == "TLS" {
  195. return c.SendTLS(m, message)
  196. }
  197. return smtp.SendMail(c.host+":"+c.port, c.smtpAuth, m.From, m.To, message.Bytes())
  198. }
  199. //SendTLS 通过TLS发送
  200. func (c *SMTPClient) SendTLS(m Mail, message bytes.Buffer) error {
  201. var ct *smtp.Client
  202. var err error
  203. // TLS config
  204. tlsconfig := &tls.Config{
  205. InsecureSkipVerify: true,
  206. ServerName: c.host,
  207. }
  208. // Here is the key, you need to call tls.Dial instead of smtp.Dial
  209. // for smtp servers running on 465 that require an ssl connection
  210. // from the very beginning (no starttls)
  211. conn, err := tls.Dial("tcp", c.host+":"+c.port, tlsconfig)
  212. if err != nil {
  213. log.Println(err, c.host)
  214. return err
  215. }
  216. ct, err = smtp.NewClient(conn, c.host)
  217. if err != nil {
  218. log.Println(err)
  219. return err
  220. }
  221. //if err := ct.StartTLS(tlsconfig);err != nil {
  222. // log.Println("StartTLS Error:",err,c.host,c.port)
  223. // return err
  224. //}
  225. //if err := ct.StartTLS(tlsconfig);err != nil {
  226. // fmt.Println(err)
  227. // return err
  228. //}
  229. fmt.Println(c.smtpAuth)
  230. if ok, s := ct.Extension("AUTH"); ok {
  231. logs.Info(s)
  232. // Auth
  233. if err = ct.Auth(c.smtpAuth); err != nil {
  234. log.Println("Auth Error:",
  235. err,
  236. c.user,
  237. )
  238. return err
  239. }
  240. }
  241. // To && From
  242. if err = ct.Mail(m.From); err != nil {
  243. log.Println("Mail Error:", err, m.From)
  244. return err
  245. }
  246. for _, v := range m.To {
  247. if err := ct.Rcpt(v); err != nil {
  248. log.Println("Rcpt Error:", err, v)
  249. return err
  250. }
  251. }
  252. // Data
  253. w, err := ct.Data()
  254. if err != nil {
  255. log.Println("Data Object Error:", err)
  256. return err
  257. }
  258. _, err = w.Write(message.Bytes())
  259. if err != nil {
  260. log.Println("Write Data Object Error:", err)
  261. return err
  262. }
  263. err = w.Close()
  264. if err != nil {
  265. log.Println("Data Object Close Error:", err)
  266. return err
  267. }
  268. ct.Quit()
  269. return nil
  270. }
  271. // AddTo will take a valid email address and store it in the mail.
  272. // It will return an error if the email is invalid.
  273. func (m *Mail) AddTo(email string) error {
  274. //Parses a single RFC 5322 address, e.g. "Barry Gibbs <[email protected]>"
  275. parsedAddess, e := mail.ParseAddress(email)
  276. if e != nil {
  277. return e
  278. }
  279. m.AddRecipient(parsedAddess)
  280. return nil
  281. }
  282. // SetTos 设置收信人Email地址
  283. func (m *Mail) SetTos(emails []string) {
  284. m.To = emails
  285. }
  286. // AddToName will add a new receipient name to mail
  287. func (m *Mail) AddToName(name string) {
  288. m.ToName = append(m.ToName, name)
  289. }
  290. // AddRecipient will take an already parsed mail.Address
  291. func (m *Mail) AddRecipient(receipient *mail.Address) {
  292. m.To = append(m.To, receipient.Address)
  293. if len(receipient.Name) > 0 {
  294. m.ToName = append(m.ToName, receipient.Name)
  295. }
  296. }
  297. // AddSubject will set the subject of the mail
  298. func (m *Mail) AddSubject(s string) {
  299. m.Subject = s
  300. }
  301. // AddHTML will set the body of the mail
  302. func (m *Mail) AddHTML(html string) {
  303. m.HTML = html
  304. }
  305. // AddText will set the body of the email
  306. func (m *Mail) AddText(text string) {
  307. m.Text = text
  308. }
  309. // AddFrom will set the senders email
  310. func (m *Mail) AddFrom(from string) error {
  311. //Parses a single RFC 5322 address, e.g. "Barry Gibbs <[email protected]>"
  312. parsedAddess, e := mail.ParseAddress(from)
  313. if e != nil {
  314. return e
  315. }
  316. m.From = parsedAddess.Address
  317. m.FromName = parsedAddess.Name
  318. return nil
  319. }
  320. // AddBCC works like AddTo but for BCC
  321. func (m *Mail) AddBCC(email string) error {
  322. parsedAddess, e := mail.ParseAddress(email)
  323. if e != nil {
  324. return e
  325. }
  326. m.Bcc = append(m.Bcc, parsedAddess.Address)
  327. return nil
  328. }
  329. // AddRecipientBCC works like AddRecipient but for BCC
  330. func (m *Mail) AddRecipientBCC(email *mail.Address) {
  331. m.Bcc = append(m.Bcc, email.Address)
  332. }
  333. // AddFromName will set the senders name
  334. func (m *Mail) AddFromName(name string) {
  335. m.FromName = name
  336. }
  337. // AddReplyTo will set the return address
  338. func (m *Mail) AddReplyTo(reply string) {
  339. m.ReplyTo = reply
  340. }
  341. // AddDate specifies the date
  342. func (m *Mail) AddDate(date string) {
  343. m.Date = date
  344. }
  345. // AddAttachment will include file/s in mail
  346. func (m *Mail) AddAttachment(filePath string) error {
  347. if m.Files == nil {
  348. m.Files = make(map[string]string)
  349. }
  350. str, err := m.ReadAttachment(filePath)
  351. if err != nil {
  352. return err
  353. }
  354. _, filename := filepath.Split(filePath)
  355. m.Files[filename] = str
  356. return nil
  357. }
  358. // ReadAttachment reading attachment
  359. func (m *Mail) ReadAttachment(filePath string) (string, error) {
  360. file, e := ioutil.ReadFile(filePath)
  361. if e != nil {
  362. return "", e
  363. }
  364. encoded := base64.StdEncoding.EncodeToString(file)
  365. totalChars := len(encoded)
  366. maxLength := 500 //每行最大长度
  367. totalLines := totalChars / maxLength
  368. var buf bytes.Buffer
  369. for i := 0; i < totalLines; i++ {
  370. buf.WriteString(encoded[i*maxLength:(i+1)*maxLength] + "\n")
  371. }
  372. buf.WriteString(encoded[totalLines*maxLength:])
  373. return buf.String(), nil
  374. }
  375. // AddHeaders addding header string
  376. func (m *Mail) AddHeaders(headers string) {
  377. m.Headers = headers
  378. }
  379. // =======================================================
  380. // unencryptedAuth
  381. // =======================================================
  382. type unencryptedAuth struct {
  383. smtp.Auth
  384. }
  385. func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  386. s := *server
  387. s.TLS = true
  388. return a.Auth.Start(&s)
  389. }
  390. // ======================================================
  391. // loginAuth
  392. // ======================================================
  393. type loginAuth struct {
  394. username, password string
  395. }
  396. // LoginAuth loginAuth方式认证
  397. func LoginAuth(username, password string) smtp.Auth {
  398. return &loginAuth{username, password}
  399. }
  400. func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  401. if !server.TLS {
  402. return "", nil, errors.New("unencrypted connection")
  403. }
  404. return "LOGIN", []byte(a.username), nil
  405. }
  406. func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  407. if more {
  408. switch string(fromServer) {
  409. case "Username:":
  410. return []byte(a.username), nil
  411. case "Password:":
  412. return []byte(a.password), nil
  413. default:
  414. return nil, errors.New("Unkown fromServer")
  415. }
  416. }
  417. return nil, nil
  418. }