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