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  }