diff --git a/emailsender/emailsender.go b/emailsender/emailsender.go new file mode 100644 index 0000000..2610239 --- /dev/null +++ b/emailsender/emailsender.go @@ -0,0 +1,191 @@ +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 +} diff --git a/example/aaa.txt b/example/attachment.file similarity index 100% rename from example/aaa.txt rename to example/attachment.file diff --git a/email_test.go b/example/email_test.go similarity index 78% rename from email_test.go rename to example/email_test.go index 7d7eedb..8c9b7c4 100644 --- a/email_test.go +++ b/example/email_test.go @@ -3,10 +3,12 @@ package email import ( "strings" "testing" + + "qoobing.com/gomod/email" ) func TestAttachment(t *testing.T) { - m := NewMessage("Hi", "this is the body") + m := email.NewMessage("Hi", "this is the body") if err := m.AttachBuffer("test.ics", []byte("test"), false); err != nil { t.Fatal(err) diff --git a/example/main.go b/example/main.go index 48d900a..09b5af7 100644 --- a/example/main.go +++ b/example/main.go @@ -22,7 +22,7 @@ func main1() { m.To = []string{toAddr} // add attachments - if err := m.Attach("aaa.txt"); err != nil { + if err := m.Attach("attachment.file"); err != nil { log.Fatal(err) } @@ -48,7 +48,7 @@ func main2() { m.To = []string{toAddr} // add attachments - if err := m.Attach("aaa.txt"); err != nil { + if err := m.Attach("attachment.file"); err != nil { log.Fatal(err) } @@ -75,7 +75,7 @@ func main3() { m.To = []string{toAddr} // add attachments - if err := m.Attach("aaa.txt"); err != nil { + if err := m.Attach("attachment.file"); err != nil { log.Fatal(err) }