mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
c873833a5f
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.
161 lines
4.0 KiB
Go
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)
|
|
}
|