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