github.com/infraboard/keyauth@v0.8.1/apps/system/notify/mail/mail.go (about) 1 package mail 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "net/mail" 8 "net/smtp" 9 "strings" 10 11 "github.com/infraboard/mcube/logger" 12 "github.com/infraboard/mcube/logger/zap" 13 14 "github.com/infraboard/keyauth/apps/system/notify" 15 ) 16 17 // NewSender todo 18 func NewSender(conf *Config) (notify.MailSender, error) { 19 if err := conf.Validate(); err != nil { 20 return nil, err 21 } 22 23 return &sender{ 24 Config: conf, 25 log: zap.L().Named("Mail Sender"), 26 }, nil 27 } 28 29 type sender struct { 30 *Config 31 32 log logger.Logger 33 } 34 35 // Send 发送邮件 36 func (s *sender) Send(req *notify.SendMailRequest) error { 37 if err := req.Validate(); err != nil { 38 return fmt.Errorf("validate send mail request error, %s", err) 39 } 40 41 c, err := s.client() 42 if err != nil { 43 s.log.Errorf("new smtp client error, %s", err) 44 return err 45 } 46 47 // 设置发信人 48 from, err := mail.ParseAddress(s.From) 49 if err != nil { 50 return fmt.Errorf("parsing from addresses: %s", err) 51 } 52 if err := c.Mail(from.Address); err != nil { 53 return fmt.Errorf("sending mail from: %s", err) 54 } 55 56 // 设置收信人 57 toAddrs, err := mail.ParseAddressList(req.To) 58 if err != nil { 59 return fmt.Errorf("parsing to addresses: %s", err) 60 } 61 for _, addr := range toAddrs { 62 if err := c.Rcpt(addr.Address); err != nil { 63 return fmt.Errorf("sending rcpt to: %s", err) 64 } 65 } 66 67 msg, err := req.PrepareBody(s.From) 68 if err != nil { 69 return fmt.Errorf("parpare body error") 70 } 71 72 // 设置内容 73 s.log.Debugf("start issues a DATA command to the server ...") 74 w, err := c.Data() 75 if err != nil { 76 return err 77 } 78 79 s.log.Debugf("start send message to the server ...") 80 _, err = w.Write(msg) 81 if err != nil { 82 return err 83 } 84 85 err = w.Close() 86 if err != nil { 87 return err 88 } 89 90 return c.Quit() 91 } 92 93 func (s *sender) auth(mechs string) (smtp.Auth, error) { 94 var ( 95 errStr []string 96 ) 97 98 for _, mech := range strings.Split(mechs, " ") { 99 switch mech { 100 case "CRAM-MD5": 101 if s.AuthSecret == "" { 102 errStr = append(errStr, "missing secret for CRAM-MD5 auth mechanism") 103 continue 104 } 105 return smtp.CRAMMD5Auth(s.AuthUserName, s.AuthSecret), nil 106 case "PLAIN": 107 if s.AuthPassword == "" { 108 errStr = append(errStr, "missing password for PLAIN auth mechanism") 109 continue 110 } 111 identity := s.AuthIdentity 112 113 // We need to know the hostname for both auth and TLS. 114 host, _, err := net.SplitHostPort(s.Host) 115 if err != nil { 116 return nil, fmt.Errorf("invalid address: %s", err) 117 } 118 return smtp.PlainAuth(identity, s.AuthUserName, s.AuthPassword, host), nil 119 case "LOGIN": 120 if s.AuthPassword == "" { 121 errStr = append(errStr, "missing password for LOGIN auth mechanism") 122 continue 123 } 124 return newLoginAuth(s.AuthUserName, s.AuthPassword), nil 125 case "NTLM": 126 errStr = append(errStr, "NTLM auth not impliment") 127 } 128 129 } 130 131 if len(errStr) == 0 { 132 errStr = append(errStr, "unknown auth mechanism: "+mechs) 133 } 134 135 return nil, errors.New(strings.Join(errStr, ",")) 136 } 137 138 func (s *sender) client() (*smtp.Client, error) { 139 s.log.Debug("start new smtp client ...") 140 141 // We need to know the hostname for both auth and TLS. 142 var c *smtp.Client 143 144 host, port, err := net.SplitHostPort(s.Host) 145 if err != nil { 146 return nil, fmt.Errorf("invalid address: %s", err) 147 } 148 149 if port == "465" { 150 tlsConfig, err := s.TLSConfig.NewTLSConfig() 151 if err != nil { 152 return nil, err 153 } 154 if tlsConfig.ServerName == "" { 155 tlsConfig.ServerName = host 156 } 157 158 conn, err := s.TLSConfig.Connect(s.Host) 159 if err != nil { 160 return nil, err 161 } 162 c, err = smtp.NewClient(conn, host) 163 if err != nil { 164 return nil, err 165 } 166 s.log.Debug("new tls smtp client ok") 167 } else { 168 // Connect to the SMTP smarthost. 169 c, err = smtp.Dial(s.Host) 170 if err != nil { 171 return nil, err 172 } 173 s.log.Debug("new smtp client ok") 174 } 175 176 // 发送自定义Hello数据 177 if s.Hello != "" { 178 err := c.Hello(s.Hello) 179 if err != nil { 180 return nil, err 181 } 182 } 183 184 // Global Config guarantees RequireTLS is not nil. 185 if s.RequireTLS { 186 s.log.Debug("start STARTTLS ...") 187 if ok, _ := c.Extension("STARTTLS"); !ok { 188 return nil, fmt.Errorf("require_tls: true (default), but %q does not advertise the STARTTLS extension", s.Host) 189 } 190 191 tlsConf, err := s.TLSConfig.NewTLSConfig() 192 if err != nil { 193 return nil, err 194 } 195 196 if tlsConf.ServerName == "" { 197 tlsConf.ServerName = host 198 } 199 200 if err := c.StartTLS(tlsConf); err != nil { 201 return nil, fmt.Errorf("starttls failed: %s", err) 202 } 203 s.log.Debug("start STARTTLS ok") 204 } 205 206 // 根据服务器推荐的方式选择认证方式 207 if !s.SkipAuth { 208 s.log.Debug("start auth ...") 209 if ok, mech := c.Extension("AUTH"); ok { 210 auth, err := s.auth(mech) 211 if err != nil { 212 return nil, err 213 } 214 215 if auth == nil { 216 return nil, fmt.Errorf("auth mechanism is nil") 217 } 218 219 if err := c.Auth(auth); err != nil { 220 return nil, fmt.Errorf("%T failed: %s", auth, err) 221 } 222 s.log.Debug("auth ok") 223 } 224 } 225 226 s.log.Debug("new smtp client ok") 227 return c, nil 228 }