fleet/server/mail/mail.go
angrylogic c873833a5f Include RFC822 From header. (#1743)
The DMARC and DKIM email authentication systems both require the RFC822
From header to function.  Kolide currently only includes the configured
sender address as the SMTP Envelop From address (e.g., the MAIL FROM
command).  This patch also includes the configured sender address in the
RFC822 email From header which should allow these emails to pass both
DKIM and DMARC authentication.
2018-04-25 09:02:09 -07:00

161 lines
4.0 KiB
Go

// Package mail provides implementations of the Kolide MailService
package mail
import (
"crypto/tls"
"fmt"
"net"
"net/smtp"
"time"
"github.com/kolide/fleet/server/kolide"
"github.com/pkg/errors"
)
func NewService() kolide.MailService {
return &mailService{}
}
type mailService struct{}
type sender interface {
sendMail(e kolide.Email, msg []byte) error
}
func Test(mailer kolide.MailService, e kolide.Email) error {
mailBody, err := getMessageBody(e)
if err != nil {
return errors.Wrap(err, "failed to get message body")
}
svc, ok := mailer.(sender)
if !ok {
return nil
}
err = svc.sendMail(e, mailBody)
if err != nil {
return errors.Wrap(err, "sending mail")
}
return nil
}
const (
PortSSL = 465
PortTLS = 587
)
func getMessageBody(e kolide.Email) ([]byte, error) {
body, err := e.Mailer.Message()
if err != nil {
return nil, errors.Wrap(err, "get mailer message")
}
mime := `MIME-version: 1.0;` + "\r\n"
content := `Content-Type: text/html; charset="UTF-8";` + "\r\n"
subject := "Subject: " + e.Subject + "\r\n"
from := "From: " + e.Config.SMTPSenderAddress + "\r\n"
msg := []byte(subject + from + mime + content + "\r\n" + string(body) + "\r\n")
return msg, nil
}
func (m mailService) SendEmail(e kolide.Email) error {
if !e.Config.SMTPConfigured {
return fmt.Errorf("email not configured")
}
msg, err := getMessageBody(e)
if err != nil {
return err
}
return m.sendMail(e, msg)
}
func smtpAuth(e kolide.Email) (smtp.Auth, error) {
if e.Config.SMTPAuthenticationType != kolide.AuthTypeUserNamePassword {
return nil, nil
}
var auth smtp.Auth
switch e.Config.SMTPAuthenticationMethod {
case kolide.AuthMethodCramMD5:
auth = smtp.CRAMMD5Auth(e.Config.SMTPUserName, e.Config.SMTPPassword)
case kolide.AuthMethodPlain:
auth = smtp.PlainAuth("", e.Config.SMTPUserName, e.Config.SMTPPassword, e.Config.SMTPServer)
default:
return nil, fmt.Errorf("unknown SMTP auth type '%d'", e.Config.SMTPAuthenticationMethod)
}
return auth, nil
}
func (m mailService) sendMail(e kolide.Email, msg []byte) error {
smtpHost := fmt.Sprintf("%s:%d", e.Config.SMTPServer, e.Config.SMTPPort)
auth, err := smtpAuth(e)
if err != nil {
return errors.Wrap(err, "failed to get smtp auth")
}
if e.Config.SMTPAuthenticationMethod == kolide.AuthMethodCramMD5 {
err = smtp.SendMail(smtpHost, auth, e.Config.SMTPSenderAddress, e.To, msg)
if err != nil {
return errors.Wrap(err, "failed to send mail. cramd5 auth method")
}
return nil
}
client, err := dialTimeout(smtpHost)
if err != nil {
return errors.Wrap(err, "could not dial smtp host")
}
defer client.Close()
if e.Config.SMTPEnableStartTLS {
if ok, _ := client.Extension("STARTTLS"); ok {
config := &tls.Config{
ServerName: e.Config.SMTPServer,
InsecureSkipVerify: !e.Config.SMTPVerifySSLCerts,
}
if err = client.StartTLS(config); err != nil {
return errors.Wrap(err, "startTLS error")
}
}
}
if auth != nil {
if err = client.Auth(auth); err != nil {
return errors.Wrap(err, "client auth error")
}
}
if err = client.Mail(e.Config.SMTPSenderAddress); err != nil {
return errors.Wrap(err, "could not issue mail to provided address")
}
for _, recip := range e.To {
if err = client.Rcpt(recip); err != nil {
return errors.Wrap(err, "failed to get recipient")
}
}
writer, err := client.Data()
if err != nil {
return errors.Wrap(err, "getting client data")
}
_, err = writer.Write(msg)
if err = writer.Close(); err != nil {
return errors.Wrap(err, "failed to close writer")
}
if err := client.Quit(); err != nil {
return errors.Wrap(err, "error on client quit")
}
return nil
}
// dialTimeout sets a timeout on net.Dial to prevent email from attempting to
// send indefinitely.
func dialTimeout(addr string) (*smtp.Client, error) {
conn, err := net.DialTimeout("tcp", addr, 15*time.Second)
if err != nil {
return nil, errors.Wrap(err, "dialing with timeout")
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, errors.Wrap(err, "split host port")
}
return smtp.NewClient(conn, host)
}