package emailsender import ( "net/mail" "regexp" "strings" "time" "qoobing.com/gomod/email" "qoobing.com/gomod/email/smtp" "qoobing.com/gomod/log" ) // /////////////////////////////////////// // 发送器接口 // /////////////////////////////////////// type Sender interface { Send(to string, name, body string) error SenderType() string SenderSupportAddressType() string } type Config struct { Id string `toml:"id"` //id Type string `toml:"type"` //必选,发送器类型 Smtp string `toml:"smtp" ` //必选,SMTP服务器地址(含端口) From string `toml:"from"` //必选,发件人 DryRun bool `toml:"dryrun"` //可选,只执行发送流程,但不真实发送 SmtpUser string `toml:"smtpuser"` //可选,SMTP授权人地址,默认时从from中取 SmtpPass string `toml:"smtppass"` //可选,SMTP授权人密码,默认时从from中取 SmtpProxy string `toml:"smtpproxy"` //可选,SMTP代理地址,默认时为空 WhiteList []string `toml:"whitelist"` //可选,发送白名单,默认为空 BlackList []string `toml:"blacklist"` //可选,发送黑名单,默认为空 } func (cfg Config) GetId() string { return cfg.Id } func (cfg Config) NewSender() (Sender, error) { return New(cfg), nil } type EmailPlainAuthSender struct { cfg Config send func(to string, name, body string) error } func New(cfg Config) *EmailPlainAuthSender { var ( arrFrom = strings.Split(cfg.From, "/") fromName = arrFrom[0] fromAddr = arrFrom[1] fromPass = arrFrom[2] arrSmtp = strings.Split(cfg.Smtp, ":") smtpAddr = arrSmtp[0] smtpPort = arrSmtp[1] smtpUser = cfg.SmtpUser smtpPass = cfg.SmtpPass ) if smtpUser == "" { smtpUser = fromAddr } if smtpPass == "" { smtpPass = fromPass } type emailItem struct { to string name string body string } var whiteListRegs = []*regexp.Regexp{} for _, r := range cfg.WhiteList { whiteListRegs = append(whiteListRegs, regexp.MustCompile(r)) } var blackListRegs = []*regexp.Regexp{} for _, r := range cfg.BlackList { blackListRegs = append(blackListRegs, regexp.MustCompile(r)) } var addressInWhiteList = func(address string) bool { for _, reg := range blackListRegs { if reg.Match([]byte(address)) { return false } } if len(whiteListRegs) == 0 { return true } for _, reg := range whiteListRegs { if reg.Match([]byte(address)) { return true } } return false } var emailQueue = make(chan emailItem, 1024) var emailSendFunc = func(to string, name, body string) (err error) { eml := emailItem{to, name, body} emailQueue <- eml return nil } var emailSendFuncInner = func() { defer func() { if re := recover(); re != nil { log.Errorf("email inner sender exit with recover: %v", re) } }() for { eml, ok := <-emailQueue if !ok { log.Infof("emailQueue closed") return } log.Infof("start send email to [%s]", eml.to) if !addressInWhiteList(eml.to) { // white-blck-list log.Infof("send email to [%s] ignored for not in white list.", eml.to) continue } else if cfg.DryRun { // dryrun mode log.Infof("end send email to [%s]: ignored for dryrun mode.", eml.to) continue } // compose the message m := email.NewHTMLMessage(eml.name, eml.body) m.To = []string{eml.to} m.From = mail.Address{Name: fromName, Address: fromAddr} // send it to smtp server auth := smtp.PlainAuth("", smtpUser, smtpPass, smtpAddr) addr := smtpAddr + ":" + smtpPort if cfg.SmtpProxy != "" { addr += "|" + cfg.SmtpProxy } if err := email.Send(addr, auth, m); err != nil { log.Errorf("failed send email to [%s]", err.Error()) log.Infof("to[%v],from[%s]", m.To, m.From) log.Infof("smtpUser[%s],smtpPass[%s],smtpAddr[%s:%s],smtpProxy[%s]", smtpUser, secLogPass(smtpPass), smtpAddr, smtpPort, secLogProxy(cfg.SmtpProxy)) continue } log.Infof("success send email to [%s]", eml.to) } } go func() { for { emailSendFuncInner() log.Infof("email inner sender exit abnormal, restart it 5s later...") time.Sleep(5 * time.Second) } }() var sender = &EmailPlainAuthSender{ cfg: cfg, send: emailSendFunc, } return sender } func (sender *EmailPlainAuthSender) Send(to string, name, body string) error { return sender.send(to, name, body) } func (sender *EmailPlainAuthSender) SenderType() string { return "EMAIL" // SENDER_TYPE_EMAIL } func (sender *EmailPlainAuthSender) SenderSupportAddressType() string { return "EMAIL_ADDRESS" // SENDER_SUPPORT_ADDRESS_TYPE_EMAIL_ADDRESS } func secLogPass(password string) string { n := len(password) if n < 3 { return "*****" } else if n < 8 { return "***" + password[n-2:] } return "" + password[:1] + "**" + password[n-2:] } func secLogProxy(proxy string) string { if strings.HasPrefix(proxy, "socks5://") && strings.Contains(proxy, "@") { arr := strings.Split(proxy, "@") if len(arr) == 2 { return arr[1] } } return proxy }