diff --git a/app/admin.go b/app/admin.go index 3d9c491f35..a58af79543 100644 --- a/app/admin.go +++ b/app/admin.go @@ -226,7 +226,8 @@ func (a *App) TestEmail(userID string, cfg *model.Config) *model.AppError { T := utils.GetUserTranslations(user.Locale) license := a.Srv().License() - if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance, ""); err != nil { + mailConfig := a.Srv().MailServiceConfig() + if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), mailConfig, license != nil && *license.Features.Compliance, ""); err != nil { return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError) } diff --git a/app/app.go b/app/app.go index d29d1aa91e..ca6c640a2d 100644 --- a/app/app.go +++ b/app/app.go @@ -497,7 +497,8 @@ func (a *App) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User, subject := T("api.templates.warn_metric_ack.subject") bodyPage.Props["Title"] = warnMetricDisplayTexts.EmailBody - if err := mailservice.SendMailUsingConfig(model.MM_SUPPORT_ADVISOR_ADDRESS, subject, bodyPage.Render(), a.Config(), false, sender.Email); err != nil { + mailConfig := a.Srv().MailServiceConfig() + if err := mailservice.SendMailUsingConfig(model.MM_SUPPORT_ADVISOR_ADDRESS, subject, bodyPage.Render(), mailConfig, false, sender.Email); err != nil { return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError) } } diff --git a/app/config.go b/app/config.go index 8c536932b4..7032c7d1d4 100644 --- a/app/config.go +++ b/app/config.go @@ -22,6 +22,7 @@ import ( "github.com/mattermost/mattermost-server/v5/config" "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/mattermost-server/v5/model" + "github.com/mattermost/mattermost-server/v5/services/mailservice" "github.com/mattermost/mattermost-server/v5/utils" ) @@ -450,3 +451,25 @@ func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config) } } } + +func (s *Server) MailServiceConfig() *mailservice.SMTPConfig { + emailSettings := s.Config().EmailSettings + hostname := utils.GetHostnameFromSiteURL(*s.Config().ServiceSettings.SiteURL) + cfg := mailservice.SMTPConfig{ + Hostname: hostname, + ConnectionSecurity: *emailSettings.ConnectionSecurity, + SkipServerCertificateVerification: *emailSettings.SkipServerCertificateVerification, + ServerName: *emailSettings.SMTPServer, + Server: *emailSettings.SMTPServer, + Port: *emailSettings.SMTPPort, + ServerTimeout: *emailSettings.SMTPServerTimeout, + Username: *emailSettings.SMTPUsername, + Password: *emailSettings.SMTPPassword, + EnableSMTPAuth: *emailSettings.EnableSMTPAuth, + SendEmailNotifications: *emailSettings.SendEmailNotifications, + FeedbackName: *emailSettings.FeedbackName, + FeedbackEmail: *emailSettings.FeedbackEmail, + ReplyToAddress: *emailSettings.ReplyToAddress, + } + return &cfg +} diff --git a/app/email.go b/app/email.go index 912dac40a8..b6984c2fd8 100644 --- a/app/email.go +++ b/app/email.go @@ -558,14 +558,16 @@ func (es *EmailService) sendMail(to, subject, htmlBody string) error { func (es *EmailService) sendMailWithCC(to, subject, htmlBody string, ccMail string) error { license := es.srv.License() - return mailservice.SendMailUsingConfig(to, subject, htmlBody, es.srv.Config(), license != nil && *license.Features.Compliance, ccMail) + mailConfig := es.srv.MailServiceConfig() + + return mailservice.SendMailUsingConfig(to, subject, htmlBody, mailConfig, license != nil && *license.Features.Compliance, ccMail) } func (es *EmailService) sendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader) error { license := es.srv.License() - config := es.srv.Config() + mailConfig := es.srv.MailServiceConfig() - return mailservice.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, config, license != nil && *license.Features.Compliance, "") + return mailservice.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, mailConfig, license != nil && *license.Features.Compliance, "") } func (es *EmailService) CreateVerifyEmailToken(userID string, newEmail string) (*model.Token, *model.AppError) { diff --git a/app/security_update_check.go b/app/security_update_check.go index a56f96365a..8834b79187 100644 --- a/app/security_update_check.go +++ b/app/security_update_check.go @@ -115,7 +115,9 @@ func (s *Server) DoSecurityUpdateCheck() { for _, user := range users { mlog.Info("Sending security bulletin", mlog.String("bulletin_id", bulletin.Id), mlog.String("user_email", user.Email)) license := s.License() - mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), s.Config(), license != nil && *license.Features.Compliance, "") + mailConfig := s.MailServiceConfig() + + mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), mailConfig, license != nil && *license.Features.Compliance, "") } bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id} diff --git a/app/server.go b/app/server.go index 37408eee6d..d99cc8fa7f 100644 --- a/app/server.go +++ b/app/server.go @@ -487,7 +487,9 @@ func NewServer(options ...Option) (*Server, error) { } s.WebSocketRouter.app = fakeApp - if nErr := mailservice.TestConnection(s.Config()); nErr != nil { + mailConfig := s.MailServiceConfig() + + if nErr := mailservice.TestConnection(mailConfig); nErr != nil { mlog.Error("Mail server connection test is failed", mlog.Err(nErr)) } diff --git a/services/mailservice/mail.go b/services/mailservice/mail.go index 4de64c9d42..5ae92c9b2c 100644 --- a/services/mailservice/mail.go +++ b/services/mailservice/mail.go @@ -18,11 +18,30 @@ import ( gomail "gopkg.in/mail.v2" "github.com/mattermost/mattermost-server/v5/mlog" - "github.com/mattermost/mattermost-server/v5/model" - "github.com/mattermost/mattermost-server/v5/services/filesstore" - "github.com/mattermost/mattermost-server/v5/utils" ) +const ( + TLS = "TLS" + StartTLS = "STARTTLS" +) + +type SMTPConfig struct { + ConnectionSecurity string + SkipServerCertificateVerification bool + Hostname string + ServerName string + Server string + Port string + ServerTimeout int + Username string + Password string + EnableSMTPAuth bool + SendEmailNotifications bool + FeedbackName string + FeedbackEmail string + ReplyToAddress string +} + type mailData struct { mimeTo string smtpTo string @@ -31,7 +50,6 @@ type mailData struct { replyTo mail.Address subject string htmlBody string - attachments []*model.FileInfo embeddedFiles map[string]io.Reader mimeHeaders map[string]string } @@ -48,29 +66,17 @@ func encodeRFC2047Word(s string) string { return mime.BEncoding.Encode("utf-8", s) } -type SmtpConnectionInfo struct { - SmtpUsername string - SmtpPassword string - SmtpServerName string - SmtpServerHost string - SmtpPort string - SmtpServerTimeout int - SkipCertVerification bool - ConnectionSecurity string - Auth bool -} - type authChooser struct { smtp.Auth - connectionInfo *SmtpConnectionInfo + config *SMTPConfig } func (a *authChooser) Start(server *smtp.ServerInfo) (string, []byte, error) { - smtpAddress := a.connectionInfo.SmtpServerName + ":" + a.connectionInfo.SmtpPort - a.Auth = LoginAuth(a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, smtpAddress) + smtpAddress := a.config.ServerName + ":" + a.config.Port + a.Auth = LoginAuth(a.config.Username, a.config.Password, smtpAddress) for _, method := range server.Auth { if method == "PLAIN" { - a.Auth = smtp.PlainAuth("", a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, a.connectionInfo.SmtpServerName+":"+a.connectionInfo.SmtpPort) + a.Auth = smtp.PlainAuth("", a.config.Username, a.config.Password, a.config.ServerName+":"+a.config.Port) break } } @@ -111,19 +117,19 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { return nil, nil } -func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, error) { +func ConnectToSMTPServerAdvanced(config *SMTPConfig) (net.Conn, error) { var conn net.Conn var err error - smtpAddress := connectionInfo.SmtpServerHost + ":" + connectionInfo.SmtpPort + smtpAddress := config.Server + ":" + config.Port dialer := &net.Dialer{ - Timeout: time.Duration(connectionInfo.SmtpServerTimeout) * time.Second, + Timeout: time.Duration(config.ServerTimeout) * time.Second, } - if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_TLS { + if config.ConnectionSecurity == TLS { tlsconfig := &tls.Config{ - InsecureSkipVerify: connectionInfo.SkipCertVerification, - ServerName: connectionInfo.SmtpServerName, + InsecureSkipVerify: config.SkipServerCertificateVerification, + ServerName: config.ServerName, } conn, err = tls.DialWithDialer(dialer, "tcp", smtpAddress, tlsconfig) @@ -140,20 +146,11 @@ func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, return conn, nil } -func ConnectToSMTPServer(config *model.Config) (net.Conn, error) { - return ConnectToSMTPServerAdvanced( - &SmtpConnectionInfo{ - ConnectionSecurity: *config.EmailSettings.ConnectionSecurity, - SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, - SmtpServerName: *config.EmailSettings.SMTPServer, - SmtpServerHost: *config.EmailSettings.SMTPServer, - SmtpPort: *config.EmailSettings.SMTPPort, - SmtpServerTimeout: *config.EmailSettings.SMTPServerTimeout, - }, - ) +func ConnectToSMTPServer(config *SMTPConfig) (net.Conn, error) { + return ConnectToSMTPServerAdvanced(config) } -func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, error) { +func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, config *SMTPConfig) (*smtp.Client, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -161,7 +158,7 @@ func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, hostname string, ec := make(chan error) go func() { var err error - c, err = smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort) + c, err = smtp.NewClient(conn, config.ServerName+":"+config.Port) if err != nil { ec <- err return @@ -179,50 +176,39 @@ func NewSMTPClientAdvanced(ctx context.Context, conn net.Conn, hostname string, return nil, errors.Wrap(err, "unable to connect to the SMTP server") } - if hostname != "" { - err := c.Hello(hostname) + if config.Hostname != "" { + err := c.Hello(config.Hostname) if err != nil { return nil, errors.Wrap(err, "unable to send hello message") } } - if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_STARTTLS { + if config.ConnectionSecurity == StartTLS { tlsconfig := &tls.Config{ - InsecureSkipVerify: connectionInfo.SkipCertVerification, - ServerName: connectionInfo.SmtpServerName, + InsecureSkipVerify: config.SkipServerCertificateVerification, + ServerName: config.ServerName, } c.StartTLS(tlsconfig) } - if connectionInfo.Auth { - if err := c.Auth(&authChooser{connectionInfo: connectionInfo}); err != nil { + if config.EnableSMTPAuth { + if err := c.Auth(&authChooser{config: config}); err != nil { return nil, errors.Wrap(err, "authentication failed") } } return c, nil } -func NewSMTPClient(ctx context.Context, conn net.Conn, config *model.Config) (*smtp.Client, error) { +func NewSMTPClient(ctx context.Context, conn net.Conn, config *SMTPConfig) (*smtp.Client, error) { return NewSMTPClientAdvanced( ctx, conn, - utils.GetHostnameFromSiteURL(*config.ServiceSettings.SiteURL), - &SmtpConnectionInfo{ - ConnectionSecurity: *config.EmailSettings.ConnectionSecurity, - SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification, - SmtpServerName: *config.EmailSettings.SMTPServer, - SmtpServerHost: *config.EmailSettings.SMTPServer, - SmtpPort: *config.EmailSettings.SMTPPort, - SmtpServerTimeout: *config.EmailSettings.SMTPServerTimeout, - Auth: *config.EmailSettings.EnableSMTPAuth, - SmtpUsername: *config.EmailSettings.SMTPUsername, - SmtpPassword: *config.EmailSettings.SMTPPassword, - }, + config, ) } -func TestConnection(config *model.Config) error { - if !*config.EmailSettings.SendEmailNotifications { +func TestConnection(config *SMTPConfig) error { + if !config.SendEmailNotifications { return errors.New("SendEmailNotifications is not true") } @@ -232,7 +218,7 @@ func TestConnection(config *model.Config) error { } defer conn.Close() - sec := *config.EmailSettings.SMTPServerTimeout + sec := config.ServerTimeout ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second) @@ -248,9 +234,9 @@ func TestConnection(config *model.Config) error { return nil } -func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, config *model.Config, enableComplianceFeatures bool, ccMail string) error { - fromMail := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.FeedbackEmail} - replyTo := mail.Address{Name: *config.EmailSettings.FeedbackName, Address: *config.EmailSettings.ReplyToAddress} +func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, config *SMTPConfig, enableComplianceFeatures bool, ccMail string) error { + fromMail := mail.Address{Name: config.FeedbackName, Address: config.FeedbackEmail} + replyTo := mail.Address{Name: config.FeedbackName, Address: config.ReplyToAddress} mail := mailData{ mimeTo: to, @@ -266,13 +252,13 @@ func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embedded return sendMailUsingConfigAdvanced(mail, config, enableComplianceFeatures) } -func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool, ccMail string) error { +func SendMailUsingConfig(to, subject, htmlBody string, config *SMTPConfig, enableComplianceFeatures bool, ccMail string) error { return SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, nil, config, enableComplianceFeatures, ccMail) } -// allows for sending an email with attachments and differing MIME/SMTP recipients -func sendMailUsingConfigAdvanced(mail mailData, config *model.Config, enableComplianceFeatures bool) error { - if *config.EmailSettings.SMTPServer == "" { +// allows for sending an email with differing MIME/SMTP recipients +func sendMailUsingConfigAdvanced(mail mailData, config *SMTPConfig, enableComplianceFeatures bool) error { + if config.Server == "" { return nil } @@ -282,7 +268,7 @@ func sendMailUsingConfigAdvanced(mail mailData, config *model.Config, enableComp } defer conn.Close() - sec := *config.EmailSettings.SMTPServerTimeout + sec := config.ServerTimeout ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, time.Duration(sec)*time.Second) @@ -295,15 +281,10 @@ func sendMailUsingConfigAdvanced(mail mailData, config *model.Config, enableComp defer c.Quit() defer c.Close() - fileBackend, nErr := filesstore.NewFileBackend(config.FileSettings.ToFileBackendSettings(enableComplianceFeatures)) - if nErr != nil { - return errors.Wrap(nErr, "unable to initialize file backend") - } - - return SendMail(c, mail, fileBackend, time.Now()) + return SendMail(c, mail, time.Now()) } -func SendMail(c smtpClient, mail mailData, fileBackend filesstore.FileBackend, date time.Time) error { +func SendMail(c smtpClient, mail mailData, date time.Time) error { mlog.Debug("sending mail", mlog.String("to", mail.smtpTo), mlog.String("subject", mail.subject)) htmlMessage := "\r\n
" + mail.htmlBody + "" @@ -345,20 +326,6 @@ func SendMail(c smtpClient, mail mailData, fileBackend filesstore.FileBackend, d m.EmbedReader(name, reader) } - for _, fileInfo := range mail.attachments { - bytes, nErr := fileBackend.ReadFile(fileInfo.Path) - if nErr != nil { - return errors.Wrap(err, "failed to read attachment") - } - - m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error { - if _, nErr = writer.Write(bytes); nErr != nil { - return errors.Wrap(err, "failed to write attachment to email") - } - return nil - })) - } - if err = c.Mail(mail.from.Address); err != nil { return errors.Wrap(err, "failed to set the from address") } diff --git a/services/mailservice/mail_test.go b/services/mailservice/mail_test.go index 82a2232ef9..c844964037 100644 --- a/services/mailservice/mail_test.go +++ b/services/mailservice/mail_test.go @@ -6,7 +6,6 @@ package mailservice import ( "bytes" "context" - "fmt" "io" "io/ioutil" "net" @@ -19,16 +18,38 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-server/v5/config" - "github.com/mattermost/mattermost-server/v5/model" - "github.com/mattermost/mattermost-server/v5/services/filesstore" - "github.com/mattermost/mattermost-server/v5/utils" ) +func getConfig() *SMTPConfig { + server := os.Getenv("MM_EMAILSETTINGS_SMTPSERVER") + if server == "" { + server = "localhost" + } + port := os.Getenv("MM_EMAILSETTINGS_SMTPPORT") + if port == "" { + port = "10025" + } + + return &SMTPConfig{ + ConnectionSecurity: "", + SkipServerCertificateVerification: false, + Hostname: "localhost", + ServerName: server, + Server: server, + Port: port, + ServerTimeout: 10, + Username: "", + Password: "", + EnableSMTPAuth: false, + SendEmailNotifications: true, + FeedbackName: "", + FeedbackEmail: "test@example.com", + ReplyToAddress: "test@example.com", + } +} + func TestMailConnectionFromConfig(t *testing.T) { - store := config.NewTestMemoryStore() - cfg := store.Get() + cfg := getConfig() conn, err := ConnectToSMTPServer(cfg) require.NoError(t, err, "Should connect to the SMTP Server %v", err) @@ -37,8 +58,8 @@ func TestMailConnectionFromConfig(t *testing.T) { require.NoError(t, err, "Should get new SMTP client") - *cfg.EmailSettings.SMTPServer = "wrongServer" - *cfg.EmailSettings.SMTPPort = "553" + cfg.Server = "wrongServer" + cfg.Port = "553" _, err = ConnectToSMTPServer(cfg) @@ -46,56 +67,23 @@ func TestMailConnectionFromConfig(t *testing.T) { } func TestMailConnectionAdvanced(t *testing.T) { - store := config.NewTestMemoryStore() - cfg := store.Get() + cfg := getConfig() - conn, err := ConnectToSMTPServerAdvanced( - &SmtpConnectionInfo{ - ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity, - SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, - SmtpServerName: *cfg.EmailSettings.SMTPServer, - SmtpServerHost: *cfg.EmailSettings.SMTPServer, - SmtpPort: *cfg.EmailSettings.SMTPPort, - }, - ) + conn, err := ConnectToSMTPServerAdvanced(cfg) require.NoError(t, err, "Should connect to the SMTP Server") defer conn.Close() - _, err2 := NewSMTPClientAdvanced( - context.Background(), - conn, - utils.GetHostnameFromSiteURL(*cfg.ServiceSettings.SiteURL), - &SmtpConnectionInfo{ - ConnectionSecurity: *cfg.EmailSettings.ConnectionSecurity, - SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification, - SmtpServerName: *cfg.EmailSettings.SMTPServer, - SmtpServerHost: *cfg.EmailSettings.SMTPServer, - SmtpPort: *cfg.EmailSettings.SMTPPort, - Auth: *cfg.EmailSettings.EnableSMTPAuth, - SmtpUsername: *cfg.EmailSettings.SMTPUsername, - SmtpPassword: *cfg.EmailSettings.SMTPPassword, - SmtpServerTimeout: 1, - }, - ) + _, err2 := NewSMTPClientAdvanced(context.Background(), conn, cfg) require.NoError(t, err2, "Should get new SMTP client") l, err3 := net.Listen("tcp", "localhost:") // emulate nc -l