Files
mattermost/utils/mail.go
Daniel Schalla 531897b1f0 add megacheck as makefile target (#9288)
Fix code issues in channel_test.go

Fix Channel Test Issues detected by Megacheck

Fix API Emoji Test Issues detected by Megacheck

Fixed API Issues Reported by Megacheck

Fixed App issues reported by megacheck

Remaining fixes

removed test added by mistake from old HEAD

gofmt

Store Fixes

simplified returns

Fix test for multi member channel delete

revert to delete unused function
2018-09-03 14:08:40 +02:00

298 lines
9.4 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package utils
import (
"crypto/tls"
"errors"
"fmt"
"io"
"mime"
"net"
"net/mail"
"net/smtp"
"time"
"gopkg.in/gomail.v2"
"net/http"
"github.com/jaytaylor/html2text"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
)
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
SkipCertVerification bool
ConnectionSecurity string
Auth bool
}
type authChooser struct {
smtp.Auth
connectionInfo *SmtpConnectionInfo
}
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)
for _, method := range server.Auth {
if method == "PLAIN" {
a.Auth = smtp.PlainAuth("", a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, a.connectionInfo.SmtpServerName+":"+a.connectionInfo.SmtpPort)
break
}
}
return a.Auth.Start(server)
}
type loginAuth struct {
username, password, host string
}
func LoginAuth(username, password, host string) smtp.Auth {
return &loginAuth{username, password, host}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if !server.TLS {
return "", nil, errors.New("unencrypted connection")
}
if server.Name != a.host {
return "", nil, errors.New("wrong host name")
}
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown fromServer")
}
}
return nil, nil
}
func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, *model.AppError) {
var conn net.Conn
var err error
smtpAddress := connectionInfo.SmtpServerHost + ":" + connectionInfo.SmtpPort
if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_TLS {
tlsconfig := &tls.Config{
InsecureSkipVerify: connectionInfo.SkipCertVerification,
ServerName: connectionInfo.SmtpServerName,
}
conn, err = tls.Dial("tcp", smtpAddress, tlsconfig)
if err != nil {
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError)
}
} else {
conn, err = net.Dial("tcp", smtpAddress)
if err != nil {
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open.app_error", nil, err.Error(), http.StatusInternalServerError)
}
}
return conn, nil
}
func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
return ConnectToSMTPServerAdvanced(
&SmtpConnectionInfo{
ConnectionSecurity: config.EmailSettings.ConnectionSecurity,
SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification,
SmtpServerName: config.EmailSettings.SMTPServer,
SmtpServerHost: config.EmailSettings.SMTPServer,
SmtpPort: config.EmailSettings.SMTPPort,
},
)
}
func NewSMTPClientAdvanced(conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) {
c, err := smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort)
if err != nil {
mlog.Error(fmt.Sprintf("Failed to open a connection to SMTP server %v", err))
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if hostname != "" {
err := c.Hello(hostname)
if err != nil {
mlog.Error(fmt.Sprintf("Failed to to set the HELO to SMTP server %v", err))
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.helo.app_error", nil, err.Error(), http.StatusInternalServerError)
}
}
if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_STARTTLS {
tlsconfig := &tls.Config{
InsecureSkipVerify: connectionInfo.SkipCertVerification,
ServerName: connectionInfo.SmtpServerName,
}
c.StartTLS(tlsconfig)
}
if connectionInfo.Auth {
if err = c.Auth(&authChooser{connectionInfo: connectionInfo}); err != nil {
return nil, model.NewAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error(), http.StatusInternalServerError)
}
}
return c, nil
}
func NewSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) {
return NewSMTPClientAdvanced(
conn,
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,
Auth: *config.EmailSettings.EnableSMTPAuth,
SmtpUsername: config.EmailSettings.SMTPUsername,
SmtpPassword: config.EmailSettings.SMTPPassword,
},
)
}
func TestConnection(config *model.Config) {
if !config.EmailSettings.SendEmailNotifications {
return
}
conn, err1 := ConnectToSMTPServer(config)
if err1 != nil {
mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", T(err1.Message), err1.DetailedError))
return
}
defer conn.Close()
c, err2 := NewSMTPClient(conn, config)
if err2 != nil {
mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", T(err2.Message), err2.DetailedError))
return
}
defer c.Quit()
defer c.Close()
}
func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail}
return SendMailUsingConfigAdvanced(to, to, fromMail, subject, htmlBody, nil, nil, config, enableComplianceFeatures)
}
// allows for sending an email with attachments and differing MIME/SMTP recipients
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 {
if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 {
return nil
}
conn, err := ConnectToSMTPServer(config)
if err != nil {
return err
}
defer conn.Close()
c, err := NewSMTPClient(conn, config)
if err != nil {
return err
}
defer c.Quit()
defer c.Close()
fileBackend, err := NewFileBackend(&config.FileSettings, enableComplianceFeatures)
if err != nil {
return err
}
return SendMail(c, mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, fileBackend, time.Now())
}
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 {
mlog.Debug(fmt.Sprintf("sending mail to %v with subject of '%v'", smtpTo, subject))
htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>"
txtBody, err := html2text.FromString(htmlBody)
if err != nil {
mlog.Warn(fmt.Sprint(err))
txtBody = ""
}
headers := map[string][]string{
"From": {from.String()},
"To": {mimeTo},
"Subject": {encodeRFC2047Word(subject)},
"Content-Transfer-Encoding": {"8bit"},
"Auto-Submitted": {"auto-generated"},
"Precedence": {"bulk"},
}
for k, v := range mimeHeaders {
headers[k] = []string{encodeRFC2047Word(v)}
}
m := gomail.NewMessage(gomail.SetCharset("UTF-8"))
m.SetHeaders(headers)
m.SetDateHeader("Date", date)
m.SetBody("text/plain", txtBody)
m.AddAlternative("text/html", htmlMessage)
for _, fileInfo := range attachments {
bytes, err := fileBackend.ReadFile(fileInfo.Path)
if err != nil {
return err
}
m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error {
if _, err := writer.Write(bytes); err != nil {
return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError)
}
return nil
}))
}
if err := c.Mail(from.Address); err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if err := c.Rcpt(smtpTo); err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError)
}
w, err := c.Data()
if err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.msg_data.app_error", nil, err.Error(), http.StatusInternalServerError)
}
_, err = m.WriteTo(w)
if err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.msg.app_error", nil, err.Error(), http.StatusInternalServerError)
}
err = w.Close()
if err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.close.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return nil
}