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