github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/services/mailservice/mail.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package mailservice 5 6 import ( 7 "context" 8 "crypto/tls" 9 "errors" 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/mattermost/mattermost-server/v5/mlog" 23 "github.com/mattermost/mattermost-server/v5/model" 24 "github.com/mattermost/mattermost-server/v5/services/filesstore" 25 "github.com/mattermost/mattermost-server/v5/utils" 26 ) 27 28 type mailData struct { 29 mimeTo string 30 smtpTo string 31 from mail.Address 32 cc string 33 replyTo mail.Address 34 subject string 35 htmlBody string 36 attachments []*model.FileInfo 37 embeddedFiles map[string]io.Reader 38 mimeHeaders map[string]string 39 } 40 41 // smtpClient is implemented by an smtp.Client. See https://golang.org/pkg/net/smtp/#Client. 42 // 43 type smtpClient interface { 44 Mail(string) error 45 Rcpt(string) error 46 Data() (io.WriteCloser, error) 47 } 48 49 func encodeRFC2047Word(s string) string { 50 return mime.BEncoding.Encode("utf-8", s) 51 } 52 53 type SmtpConnectionInfo struct { 54 SmtpUsername string 55 SmtpPassword string 56 SmtpServerName string 57 SmtpServerHost string 58 SmtpPort string 59 SmtpServerTimeout int 60 SkipCertVerification bool 61 ConnectionSecurity string 62 Auth bool 63 } 64 65 type authChooser struct { 66 smtp.Auth 67 connectionInfo *SmtpConnectionInfo 68 } 69 70 func (a *authChooser) Start(server *smtp.ServerInfo) (string, []byte, error) { 71 smtpAddress := a.connectionInfo.SmtpServerName + ":" + a.connectionInfo.SmtpPort 72 a.Auth = LoginAuth(a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, smtpAddress) 73 for _, method := range server.Auth { 74 if method == "PLAIN" { 75 a.Auth = smtp.PlainAuth("", a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, a.connectionInfo.SmtpServerName+":"+a.connectionInfo.SmtpPort) 76 break 77 } 78 } 79 return a.Auth.Start(server) 80 } 81 82 type loginAuth struct { 83 username, password, host string 84 } 85 86 func LoginAuth(username, password, host string) smtp.Auth { 87 return &loginAuth{username, password, host} 88 } 89 90 func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { 91 if !server.TLS { 92 return "", nil, errors.New("unencrypted connection") 93 } 94 95 if server.Name != a.host { 96 return "", nil, errors.New("wrong host name") 97 } 98 99 return "LOGIN", []byte{}, nil 100 } 101 102 func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { 103 if more { 104 switch string(fromServer) { 105 case "Username:": 106 return []byte(a.username), nil 107 case "Password:": 108 return []byte(a.password), nil 109 default: 110 return nil, errors.New("Unknown fromServer") 111 } 112 } 113 return nil, nil 114 } 115 116 func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, *model.AppError) { 117 var conn net.Conn 118 var err error 119 120 smtpAddress := connectionInfo.SmtpServerHost + ":" + connectionInfo.SmtpPort 121 dialer := &net.Dialer{ 122 Timeout: time.Duration(connectionInfo.SmtpServerTimeout) * time.Second, 123 } 124 125 if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_TLS { 126 tlsconfig := &tls.Config{ 127 InsecureSkipVerify: connectionInfo.SkipCertVerification, 128 ServerName: connectionInfo.SmtpServerName, 129 } 130 131 conn, err = tls.DialWithDialer(dialer, "tcp", smtpAddress, tlsconfig) 132 if err != nil { 133 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) 134 } 135 } else { 136 conn, err = dialer.Dial("tcp", smtpAddress) 137 if err != nil { 138 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open.app_error", nil, err.Error(), http.StatusInternalServerError) 139 } 140 } 141 142 return conn, nil 143 } 144 145 func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) { 146 return ConnectToSMTPServerAdvanced( 147 &SmtpConnectionInfo{ 148 ConnectionSecurity: *config.EmailSettings.ConnectionSecurity, 149 SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, 150 SmtpServerName: *config.EmailSettings.SMTPServer, 151 SmtpServerHost: *config.EmailSettings.SMTPServer, 152 SmtpPort: *config.EmailSettings.SMTPPort, 153 SmtpServerTimeout: *config.EmailSettings.SMTPServerTimeout, 154 }, 155 ) 156 } 157 158 func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) { 159 ctx, cancel := context.WithCancel(ctx) 160 defer cancel() 161 162 var c *smtp.Client 163 ec := make(chan error) 164 go func() { 165 var err error 166 c, err = smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort) 167 if err != nil { 168 ec <- err 169 return 170 } 171 cancel() 172 }() 173 174 select { 175 case <-ctx.Done(): 176 err := ctx.Err() 177 if err != nil && err.Error() != "context canceled" { 178 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) 179 } 180 case err := <-ec: 181 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) 182 } 183 184 if hostname != "" { 185 err := c.Hello(hostname) 186 if err != nil { 187 mlog.Error("Failed to to set the HELO to SMTP server", mlog.Err(err)) 188 return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.helo.app_error", nil, err.Error(), http.StatusInternalServerError) 189 } 190 } 191 192 if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_STARTTLS { 193 tlsconfig := &tls.Config{ 194 InsecureSkipVerify: connectionInfo.SkipCertVerification, 195 ServerName: connectionInfo.SmtpServerName, 196 } 197 c.StartTLS(tlsconfig) 198 } 199 200 if connectionInfo.Auth { 201 if err := c.Auth(&authChooser{connectionInfo: connectionInfo}); err != nil { 202 return nil, model.NewAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error(), http.StatusInternalServerError) 203 } 204 } 205 return c, nil 206 } 207 208 func NewSMTPClient(ctx context.Context, conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) { 209 return NewSMTPClientAdvanced( 210 ctx, 211 conn, 212 utils.GetHostnameFromSiteURL(*config.ServiceSettings.SiteURL), 213 &SmtpConnectionInfo{ 214 ConnectionSecurity: *config.EmailSettings.ConnectionSecurity, 215 SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, 216 SmtpServerName: *config.EmailSettings.SMTPServer, 217 SmtpServerHost: *config.EmailSettings.SMTPServer, 218 SmtpPort: *config.EmailSettings.SMTPPort, 219 SmtpServerTimeout: *config.EmailSettings.SMTPServerTimeout, 220 Auth: *config.EmailSettings.EnableSMTPAuth, 221 SmtpUsername: *config.EmailSettings.SMTPUsername, 222 SmtpPassword: *config.EmailSettings.SMTPPassword, 223 }, 224 ) 225 } 226 227 func TestConnection(config *model.Config) *model.AppError { 228 if !*config.EmailSettings.SendEmailNotifications { 229 return &model.AppError{Message: "SendEmailNotifications is not true"} 230 } 231 232 conn, err := ConnectToSMTPServer(config) 233 if err != nil { 234 return &model.AppError{Message: "Could not connect to SMTP server, check SMTP server settings.", DetailedError: err.DetailedError} 235 } 236 defer conn.Close() 237 238 sec := *config.EmailSettings.SMTPServerTimeout 239 240 ctx := context.Background() 241 ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second) 242 defer cancel() 243 244 c, err := NewSMTPClient(ctx, conn, config) 245 if err != nil { 246 return &model.AppError{Message: "Could not connect to SMTP server, check SMTP server settings."} 247 } 248 c.Close() 249 c.Quit() 250 251 return nil 252 } 253 254 func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, config *model.Config, enableComplianceFeatures bool, ccMail string) *model.AppError { 255 fromMail := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.FeedbackEmail} 256 replyTo := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.ReplyToAddress} 257 258 mail := mailData{ 259 mimeTo: to, 260 smtpTo: to, 261 from: fromMail, 262 cc: ccMail, 263 replyTo: replyTo, 264 subject: subject, 265 htmlBody: htmlBody, 266 embeddedFiles: embeddedFiles, 267 } 268 269 return sendMailUsingConfigAdvanced(mail, config, enableComplianceFeatures) 270 } 271 272 func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool, ccMail string) *model.AppError { 273 return SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, nil, config, enableComplianceFeatures, ccMail) 274 } 275 276 // allows for sending an email with attachments and differing MIME/SMTP recipients 277 func sendMailUsingConfigAdvanced(mail mailData, config *model.Config, enableComplianceFeatures bool) *model.AppError { 278 if len(*config.EmailSettings.SMTPServer) == 0 { 279 return nil 280 } 281 282 conn, err := ConnectToSMTPServer(config) 283 if err != nil { 284 return err 285 } 286 defer conn.Close() 287 288 sec := *config.EmailSettings.SMTPServerTimeout 289 290 ctx := context.Background() 291 ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second) 292 defer cancel() 293 294 c, err := NewSMTPClient(ctx, conn, config) 295 if err != nil { 296 return err 297 } 298 defer c.Quit() 299 defer c.Close() 300 301 fileBackend, err := filesstore.NewFileBackend(&config.FileSettings, enableComplianceFeatures) 302 if err != nil { 303 return err 304 } 305 306 return SendMail(c, mail, fileBackend, time.Now()) 307 } 308 309 func SendMail(c smtpClient, mail mailData, fileBackend filesstore.FileBackend, date time.Time) *model.AppError { 310 mlog.Debug("sending mail", mlog.String("to", mail.smtpTo), mlog.String("subject", mail.subject)) 311 312 htmlMessage := "\r\n<html><body>" + mail.htmlBody + "</body></html>" 313 314 txtBody, err := html2text.FromString(mail.htmlBody) 315 if err != nil { 316 mlog.Warn("Unable to convert html body to text", mlog.Err(err)) 317 txtBody = "" 318 } 319 320 headers := map[string][]string{ 321 "From": {mail.from.String()}, 322 "To": {mail.mimeTo}, 323 "Subject": {encodeRFC2047Word(mail.subject)}, 324 "Content-Transfer-Encoding": {"8bit"}, 325 "Auto-Submitted": {"auto-generated"}, 326 "Precedence": {"bulk"}, 327 } 328 329 if len(mail.replyTo.Address) > 0 { 330 headers["Reply-To"] = []string{mail.replyTo.String()} 331 } 332 333 if len(mail.cc) > 0 { 334 headers["CC"] = []string{mail.cc} 335 } 336 337 for k, v := range mail.mimeHeaders { 338 headers[k] = []string{encodeRFC2047Word(v)} 339 } 340 341 m := gomail.NewMessage(gomail.SetCharset("UTF-8")) 342 m.SetHeaders(headers) 343 m.SetDateHeader("Date", date) 344 m.SetBody("text/plain", txtBody) 345 m.AddAlternative("text/html", htmlMessage) 346 347 for name, reader := range mail.embeddedFiles { 348 m.EmbedReader(name, reader) 349 } 350 351 for _, fileInfo := range mail.attachments { 352 bytes, err := fileBackend.ReadFile(fileInfo.Path) 353 if err != nil { 354 return err 355 } 356 357 m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error { 358 if _, err := writer.Write(bytes); err != nil { 359 return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError) 360 } 361 return nil 362 })) 363 } 364 365 if err = c.Mail(mail.from.Address); err != nil { 366 return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError) 367 } 368 369 if err = c.Rcpt(mail.smtpTo); err != nil { 370 return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError) 371 } 372 373 w, err := c.Data() 374 if err != nil { 375 return model.NewAppError("SendMail", "utils.mail.send_mail.msg_data.app_error", nil, err.Error(), http.StatusInternalServerError) 376 } 377 378 _, err = m.WriteTo(w) 379 if err != nil { 380 return model.NewAppError("SendMail", "utils.mail.send_mail.msg.app_error", nil, err.Error(), http.StatusInternalServerError) 381 } 382 err = w.Close() 383 if err != nil { 384 return model.NewAppError("SendMail", "utils.mail.send_mail.close.app_error", nil, err.Error(), http.StatusInternalServerError) 385 } 386 387 return nil 388 }