github.com/rr250/mattermost-server@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  }