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