github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/services/mailservice/mail.go (about) 1 // Copyright (c) 2015-present Xenia, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package mailservice 5 6 import ( 7 "crypto/tls" 8 "errors" 9 "fmt" 10 "io" 11 "mime" 12 "net" 13 "net/mail" 14 "net/smtp" 15 "time" 16 17 gomail "gopkg.in/mail.v2" 18 19 "net/http" 20 21 "github.com/jaytaylor/html2text" 22 "github.com/xzl8028/xenia-server/mlog" 23 "github.com/xzl8028/xenia-server/model" 24 "github.com/xzl8028/xenia-server/services/filesstore" 25 "github.com/xzl8028/xenia-server/utils" 26 ) 27 28 // smtpClient is implemented by an smtp.Client. See https://golang.org/pkg/net/smtp/#Client. 29 // 30 type smtpClient interface { 31 Mail(string) error 32 Rcpt(string) error 33 Data() (io.WriteCloser, error) 34 } 35 36 func encodeRFC2047Word(s string) string { 37 return mime.BEncoding.Encode("utf-8", s) 38 } 39 40 type SmtpConnectionInfo struct { 41 SmtpUsername string 42 SmtpPassword string 43 SmtpServerName string 44 SmtpServerHost string 45 SmtpPort string 46 SkipCertVerification bool 47 ConnectionSecurity string 48 Auth bool 49 } 50 51 type authChooser struct { 52 smtp.Auth 53 connectionInfo *SmtpConnectionInfo 54 } 55 56 func (a *authChooser) Start(server *smtp.ServerInfo) (string, []byte, error) { 57 smtpAddress := a.connectionInfo.SmtpServerName + ":" + a.connectionInfo.SmtpPort 58 a.Auth = LoginAuth(a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, smtpAddress) 59 for _, method := range server.Auth { 60 if method == "PLAIN" { 61 a.Auth = smtp.PlainAuth("", a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, a.connectionInfo.SmtpServerName+":"+a.connectionInfo.SmtpPort) 62 break 63 } 64 } 65 return a.Auth.Start(server) 66 } 67 68 type loginAuth struct { 69 username, password, host string 70 } 71 72 func LoginAuth(username, password, host string) smtp.Auth { 73 return &loginAuth{username, password, host} 74 } 75 76 func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { 77 if !server.TLS { 78 return "", nil, errors.New("unencrypted connection") 79 } 80 81 if server.Name != a.host { 82 return "", nil, errors.New("wrong host name") 83 } 84 85 return "LOGIN", []byte{}, nil 86 } 87 88 func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { 89 if more { 90 switch string(fromServer) { 91 case "Username:": 92 return []byte(a.username), nil 93 case "Password:": 94 return []byte(a.password), nil 95 default: 96 return nil, errors.New("Unknown fromServer") 97 } 98 } 99 return nil, nil 100 } 101 102 func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, *model.AppError) { 103 var conn net.Conn 104 var err error 105 106 smtpAddress := connectionInfo.SmtpServerHost + ":" + connectionInfo.SmtpPort 107 if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_TLS { 108 tlsconfig := &tls.Config{ 109 InsecureSkipVerify: connectionInfo.SkipCertVerification, 110 ServerName: connectionInfo.SmtpServerName, 111 } 112 113 conn, err = tls.Dial("tcp", smtpAddress, tlsconfig) 114 if err != nil { 115 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) 116 } 117 } else { 118 conn, err = net.Dial("tcp", smtpAddress) 119 if err != nil { 120 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open.app_error", nil, err.Error(), http.StatusInternalServerError) 121 } 122 } 123 124 return conn, nil 125 } 126 127 func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) { 128 return ConnectToSMTPServerAdvanced( 129 &SmtpConnectionInfo{ 130 ConnectionSecurity: *config.EmailSettings.ConnectionSecurity, 131 SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, 132 SmtpServerName: *config.EmailSettings.SMTPServer, 133 SmtpServerHost: *config.EmailSettings.SMTPServer, 134 SmtpPort: *config.EmailSettings.SMTPPort, 135 }, 136 ) 137 } 138 139 func NewSMTPClientAdvanced(conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) { 140 c, err := smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort) 141 if err != nil { 142 mlog.Error(fmt.Sprintf("Failed to open a connection to SMTP server %v", err)) 143 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) 144 } 145 146 if hostname != "" { 147 err = c.Hello(hostname) 148 if err != nil { 149 mlog.Error(fmt.Sprintf("Failed to to set the HELO to SMTP server %v", err)) 150 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.helo.app_error", nil, err.Error(), http.StatusInternalServerError) 151 } 152 } 153 154 if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_STARTTLS { 155 tlsconfig := &tls.Config{ 156 InsecureSkipVerify: connectionInfo.SkipCertVerification, 157 ServerName: connectionInfo.SmtpServerName, 158 } 159 c.StartTLS(tlsconfig) 160 } 161 162 if connectionInfo.Auth { 163 if err = c.Auth(&authChooser{connectionInfo: connectionInfo}); err != nil { 164 return nil, model.NewAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error(), http.StatusInternalServerError) 165 } 166 } 167 return c, nil 168 } 169 170 func NewSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) { 171 return NewSMTPClientAdvanced( 172 conn, 173 utils.GetHostnameFromSiteURL(*config.ServiceSettings.SiteURL), 174 &SmtpConnectionInfo{ 175 ConnectionSecurity: *config.EmailSettings.ConnectionSecurity, 176 SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, 177 SmtpServerName: *config.EmailSettings.SMTPServer, 178 SmtpServerHost: *config.EmailSettings.SMTPServer, 179 SmtpPort: *config.EmailSettings.SMTPPort, 180 Auth: *config.EmailSettings.EnableSMTPAuth, 181 SmtpUsername: *config.EmailSettings.SMTPUsername, 182 SmtpPassword: *config.EmailSettings.SMTPPassword, 183 }, 184 ) 185 } 186 187 func TestConnection(config *model.Config) { 188 if !*config.EmailSettings.SendEmailNotifications { 189 return 190 } 191 192 conn, err1 := ConnectToSMTPServer(config) 193 if err1 != nil { 194 mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err1.Message), err1.DetailedError)) 195 return 196 } 197 defer conn.Close() 198 199 c, err2 := NewSMTPClient(conn, config) 200 if err2 != nil { 201 mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err2.Message), err2.DetailedError)) 202 return 203 } 204 defer c.Quit() 205 defer c.Close() 206 } 207 208 func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool) *model.AppError { 209 fromMail := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.FeedbackEmail} 210 replyTo := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.ReplyToAddress} 211 212 return SendMailUsingConfigAdvanced(to, to, fromMail, replyTo, subject, htmlBody, nil, nil, config, enableComplianceFeatures) 213 } 214 215 // allows for sending an email with attachments and differing MIME/SMTP recipients 216 func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError { 217 if !*config.EmailSettings.SendEmailNotifications || len(*config.EmailSettings.SMTPServer) == 0 { 218 return nil 219 } 220 221 conn, err := ConnectToSMTPServer(config) 222 if err != nil { 223 return err 224 } 225 defer conn.Close() 226 227 c, err := NewSMTPClient(conn, config) 228 if err != nil { 229 return err 230 } 231 defer c.Quit() 232 defer c.Close() 233 234 fileBackend, err := filesstore.NewFileBackend(&config.FileSettings, enableComplianceFeatures) 235 if err != nil { 236 return err 237 } 238 239 return SendMail(c, mimeTo, smtpTo, from, replyTo, subject, htmlBody, attachments, mimeHeaders, fileBackend, time.Now()) 240 } 241 242 func SendMail(c smtpClient, mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend filesstore.FileBackend, date time.Time) *model.AppError { 243 mlog.Debug(fmt.Sprintf("sending mail to %v with subject of '%v'", smtpTo, subject)) 244 245 htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>" 246 247 txtBody, err := html2text.FromString(htmlBody) 248 if err != nil { 249 mlog.Warn(fmt.Sprint(err)) 250 txtBody = "" 251 } 252 253 headers := map[string][]string{ 254 "From": {from.String()}, 255 "To": {mimeTo}, 256 "Subject": {encodeRFC2047Word(subject)}, 257 "Content-Transfer-Encoding": {"8bit"}, 258 "Auto-Submitted": {"auto-generated"}, 259 "Precedence": {"bulk"}, 260 } 261 262 if len(replyTo.Address) > 0 { 263 headers["Reply-To"] = []string{replyTo.String()} 264 } 265 266 for k, v := range mimeHeaders { 267 headers[k] = []string{encodeRFC2047Word(v)} 268 } 269 270 m := gomail.NewMessage(gomail.SetCharset("UTF-8")) 271 m.SetHeaders(headers) 272 m.SetDateHeader("Date", date) 273 m.SetBody("text/plain", txtBody) 274 m.AddAlternative("text/html", htmlMessage) 275 276 for _, fileInfo := range attachments { 277 bytes, err := fileBackend.ReadFile(fileInfo.Path) 278 if err != nil { 279 return err 280 } 281 282 m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error { 283 if _, err := writer.Write(bytes); err != nil { 284 return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError) 285 } 286 return nil 287 })) 288 } 289 290 if err = c.Mail(from.Address); err != nil { 291 return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError) 292 } 293 294 if err = c.Rcpt(smtpTo); err != nil { 295 return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError) 296 } 297 298 w, err := c.Data() 299 if err != nil { 300 return model.NewAppError("SendMail", "utils.mail.send_mail.msg_data.app_error", nil, err.Error(), http.StatusInternalServerError) 301 } 302 303 _, err = m.WriteTo(w) 304 if err != nil { 305 return model.NewAppError("SendMail", "utils.mail.send_mail.msg.app_error", nil, err.Error(), http.StatusInternalServerError) 306 } 307 err = w.Close() 308 if err != nil { 309 return model.NewAppError("SendMail", "utils.mail.send_mail.close.app_error", nil, err.Error(), http.StatusInternalServerError) 310 } 311 312 return nil 313 }