github.com/cjdelisle/matterfoss@v5.11.1+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 gomail "gopkg.in/mail.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 replyTo := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.ReplyToAddress} 203 204 return SendMailUsingConfigAdvanced(to, to, fromMail, replyTo, subject, htmlBody, nil, nil, config, enableComplianceFeatures) 205 } 206 207 // allows for sending an email with attachments and differing MIME/SMTP recipients 208 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 { 209 if !*config.EmailSettings.SendEmailNotifications || len(*config.EmailSettings.SMTPServer) == 0 { 210 return nil 211 } 212 213 conn, err := ConnectToSMTPServer(config) 214 if err != nil { 215 return err 216 } 217 defer conn.Close() 218 219 c, err := NewSMTPClient(conn, config) 220 if err != nil { 221 return err 222 } 223 defer c.Quit() 224 defer c.Close() 225 226 fileBackend, err := filesstore.NewFileBackend(&config.FileSettings, enableComplianceFeatures) 227 if err != nil { 228 return err 229 } 230 231 return SendMail(c, mimeTo, smtpTo, from, replyTo, subject, htmlBody, attachments, mimeHeaders, fileBackend, time.Now()) 232 } 233 234 func SendMail(c *smtp.Client, 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 { 235 mlog.Debug(fmt.Sprintf("sending mail to %v with subject of '%v'", smtpTo, subject)) 236 237 htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>" 238 239 txtBody, err := html2text.FromString(htmlBody) 240 if err != nil { 241 mlog.Warn(fmt.Sprint(err)) 242 txtBody = "" 243 } 244 245 headers := map[string][]string{ 246 "From": {from.String()}, 247 "Reply-To": {replyTo.String()}, 248 "To": {mimeTo}, 249 "Subject": {encodeRFC2047Word(subject)}, 250 "Content-Transfer-Encoding": {"8bit"}, 251 "Auto-Submitted": {"auto-generated"}, 252 "Precedence": {"bulk"}, 253 } 254 for k, v := range mimeHeaders { 255 headers[k] = []string{encodeRFC2047Word(v)} 256 } 257 258 m := gomail.NewMessage(gomail.SetCharset("UTF-8")) 259 m.SetHeaders(headers) 260 m.SetDateHeader("Date", date) 261 m.SetBody("text/plain", txtBody) 262 m.AddAlternative("text/html", htmlMessage) 263 264 for _, fileInfo := range attachments { 265 bytes, err := fileBackend.ReadFile(fileInfo.Path) 266 if err != nil { 267 return err 268 } 269 270 m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error { 271 if _, err := writer.Write(bytes); err != nil { 272 return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError) 273 } 274 return nil 275 })) 276 } 277 278 if err = c.Mail(from.Address); err != nil { 279 return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError) 280 } 281 282 if err = c.Rcpt(smtpTo); err != nil { 283 return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError) 284 } 285 286 w, err := c.Data() 287 if err != nil { 288 return model.NewAppError("SendMail", "utils.mail.send_mail.msg_data.app_error", nil, err.Error(), http.StatusInternalServerError) 289 } 290 291 _, err = m.WriteTo(w) 292 if err != nil { 293 return model.NewAppError("SendMail", "utils.mail.send_mail.msg.app_error", nil, err.Error(), http.StatusInternalServerError) 294 } 295 err = w.Close() 296 if err != nil { 297 return model.NewAppError("SendMail", "utils.mail.send_mail.close.app_error", nil, err.Error(), http.StatusInternalServerError) 298 } 299 300 return nil 301 }