Golang 发送匿名邮件、普通邮件

2020-10-12 13:00:21

通过官方 net/smtp 包实现一个发送邮件的工具类(支持发送匿名邮件、附件邮件) 可用于发送简单的预警日志等场景 send_mail.go: ``` package mail import ( "bytes" "crypto/tls" "encoding/base64" "errors" "fmt" "math" "net" "net/smtp" "strings" "time" ) type MailSmtp struct { Anonymous bool // 是否匿名发送 SSL bool // 是否 TLS 连接 Addr string // SMTP 地址:端口 Username string Password string } type MailContent struct { From string To string Subject string // 主题 Content string // 正文 AttachName string // 附件名 AttachData []byte // 附件数据 } // 发送邮件 func SendMail(mailSmtp MailSmtp, mailCont MailContent) { // 匿名邮件 if mailSmtp.Anonymous { err := smtp.SendMail(getMxHost(mailCont.To)+":25", nil, mailCont.From, []string{mailCont.To}, makeMimeData(mailCont)) if err != nil { panic("邮件发送失败 => " + err.Error()) } return } // 需要认证 host, _, err := net.SplitHostPort(mailSmtp.Addr) if err != nil { panic(err) } auth := smtp.PlainAuth("", mailSmtp.Username, mailSmtp.Password, host) if mailSmtp.SSL { // TLS 连接 err := sendMailTLS(mailSmtp.Addr, auth, mailCont.From, []string{mailCont.To}, makeMimeData(mailCont)) if err != nil { panic("邮件发送失败 => " + err.Error()) } } else { // 普通连接 err := smtp.SendMail(mailSmtp.Addr, auth, mailCont.From, []string{mailCont.To}, makeMimeData(mailCont)) if err != nil { panic("邮件发送失败 => " + err.Error()) } } } // 构造邮件内容 func makeMimeData(mailCont MailContent) []byte { mimeData := bytes.NewBuffer(nil) boundary := "----=_Part_" + fmt.Sprintf("%014d", time.Now().Unix()) // 头部 mimeData.WriteString(fmt.Sprintf("From: \"%s\"<%s>\r\n", mailCont.From, mailCont.From)) mimeData.WriteString(fmt.Sprintf("To: \"%s\"<%s>\r\n", mailCont.To, mailCont.To)) mimeData.WriteString(fmt.Sprintf("Subject: =?UTF-8?B?%s?=\r\n", base64ToStr([]byte(mailCont.Subject)))) mimeData.WriteString("Mime-Version: 1.0\r\n") mimeData.WriteString(fmt.Sprintf("Content-Type: multipart/mixed;charset=\"utf-8\";boundary=\"%s\"\r\n", boundary)) mimeData.WriteString("Content-Transfer-Encoding: 8Bit\r\n") mimeData.WriteString(fmt.Sprintf("Date: %s\r\n", time.Now())) mimeData.WriteString("X-Mailer: Go-Mail\r\n\r\n") mimeData.WriteString("This is a multi-part message in MIME format.\r\n\r\n") // 正文 mimeData.WriteString(fmt.Sprintf("--%s\r\n", boundary)) mimeData.WriteString("Content-Type: text/html;charset=\"utf-8\"\r\n") mimeData.WriteString("Content-Transfer-Encoding: base64\r\n\r\n") mimeData.WriteString(base64TolenCtrl([]byte(mailCont.Content))) mimeData.WriteString("\r\n\r\n") // 附件 if mailCont.AttachData != nil { mimeData.WriteString(fmt.Sprintf("--%s\r\n", boundary)) mimeData.WriteString("Content-Type: application/octet-stream;charset=\"utf-8\"\r\n") mimeData.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"=?UTF-8?B?%s?=\"\r\n", base64ToStr([]byte(mailCont.AttachName)))) mimeData.WriteString("Content-Transfer-Encoding: base64\r\n\r\n") mimeData.WriteString(base64TolenCtrl(mailCont.AttachData)) mimeData.WriteString("\r\n\r\n") } // 结尾 mimeData.WriteString(fmt.Sprintf("--%s--", boundary)) return mimeData.Bytes() } // 控制字符串单行长度 func base64TolenCtrl(data []byte) string { const maxLen = 70 var buff strings.Builder encodeStr := base64ToStr(data) for len(encodeStr) > maxLen { buff.WriteString(encodeStr[:maxLen] + "\r\n") encodeStr = encodeStr[maxLen:] } buff.WriteString(encodeStr) return buff.String() } // 将数据进行 base64 编码 func base64ToStr(data []byte) string { return base64.StdEncoding.EncodeToString(data) } // 根据邮箱取 MX 记录 func getMxHost(email string) string { split := strings.Split(email, "@") if len(split) != 2 { panic("邮件格式错误 => " + email) } mxs, err := net.LookupMX(split[1]) if err != nil { panic("解析邮箱MX失败 => " + err.Error()) } if len(mxs) == 0 { panic("对应邮箱无 MX 记录") } // 取优先级最高的记录 host := "" pref := uint16(math.MaxUint16) for _, mx := range mxs { if mx.Pref < pref { host = mx.Host pref = mx.Pref } } return host[:len(host)-1] } // 发送基于 TLS 的邮件 // 基于 Golang 函数 smtp.SendMail 做了补充,使其支持 TLS 连接 func sendMailTLS(addr string, a smtp.Auth, from string, to []string, msg []byte) error { if err := validateLine(from); err != nil { return err } for _, recp := range to { if err := validateLine(recp); err != nil { return err } } c, err := dialTLS(addr) if err != nil { return err } defer c.Close() if err = c.Hello("localhost"); err != nil { return err } if ok, _ := c.Extension("STARTTLS"); ok { host, _, _ := net.SplitHostPort(addr) config := &tls.Config{ServerName: host} if err = c.StartTLS(config); err != nil { return err } } if a != nil { if ok, _ := c.Extension("AUTH"); ok { if err = c.Auth(a); err != nil { return err } } } if err = c.Mail(from); err != nil { return err } for _, addr := range to { if err = c.Rcpt(addr); err != nil { return err } } w, err := c.Data() if err != nil { return err } _, err = w.Write(msg) if err != nil { return err } err = w.Close() if err != nil { return err } return c.Quit() } func dialTLS(addr string) (*smtp.Client, error) { conn, err := tls.Dial("tcp", addr, nil) if err != nil { return nil, err } host, _, _ := net.SplitHostPort(addr) return smtp.NewClient(conn, host) } func validateLine(line string) error { if strings.ContainsAny(line, "\n\r") { return errors.New("smtp: A line must not contain CR or LF") } return nil } ``` send_mail_test.go: ``` package mail import ( "testing" ) func TestSendMail(t *testing.T) { mailStmp := MailSmtp{ Anonymous: true, SSL: false, Addr: "", Username: "", Password: "", } mailCont := MailContent{ From: "anonymous@anonymous.com", To: "xxx@qq.com", Subject: "匿名邮件测试", Content: "匿名邮件测试
换行", AttachName: "附件.txt", AttachData: []byte("附件内容1234567890"), } SendMail(mailStmp, mailCont) } ```

分类:编程     关键词:邮件,匿名

评论(0) 浏览(1212)