authelia/internal/notification/smtp_notifier.go
James Elliott 6e946dc859 Added sec warn, more debug logging detail
- Added a warning for users who attempt authentication on servers that don't allow STARTTLS (they are transmitted in plain text)
- Included a note when AUTH fails due to no supported mechanisms including the mechanisms supported (PLAIN and LOGIN)
2019-12-28 09:35:01 +01:00

143 lines
4.1 KiB
Go

package notification
import (
"crypto/tls"
"errors"
"fmt"
"net/smtp"
"strings"
"github.com/authelia/authelia/internal/configuration/schema"
"github.com/authelia/authelia/internal/utils"
log "github.com/sirupsen/logrus"
)
// SMTPNotifier a notifier to send emails to SMTP servers.
type SMTPNotifier struct {
username string
password string
sender string
host string
port int
secure bool
address string
}
// NewSMTPNotifier create an SMTPNotifier targeting a given address.
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier {
return &SMTPNotifier{
username: configuration.Username,
password: configuration.Password,
sender: configuration.Sender,
host: configuration.Host,
port: configuration.Port,
secure: configuration.Secure,
address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
}
}
// Send send a identity verification link to a user.
func (n *SMTPNotifier) Send(recipient string, subject string, body string) error {
msg := "From: " + n.sender + "\n" +
"To: " + recipient + "\n" +
"Subject: " + subject + "\n" +
"Content-Type: text/html\n" +
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
body
c, err := smtp.Dial(n.address)
if err != nil {
return err
}
// Do StartTLS if available (some servers only provide the auth extnesion after, and encrpytion is preferred)
starttls, _ := c.Extension("STARTTLS")
if starttls {
tlsconfig := &tls.Config{
InsecureSkipVerify: !n.secure,
ServerName: n.host,
}
log.Debugf("SMTP server supports STARTTLS (InsecureSkipVerify: %t, ServerName: %s), attempting", tlsconfig.InsecureSkipVerify, tlsconfig.ServerName)
err := c.StartTLS(tlsconfig)
if err != nil {
return err
} else {
log.Debug("SMTP STARTTLS completed without error")
}
} else {
log.Debug("SMTP server does not support STARTTLS, skipping")
}
// Attempt AUTH if password is specified only
if n.password != "" {
if !starttls {
log.Warn("Authentication is being attempted over an insecure connection. Using a SMTP server that supports STARTTLS is recommended, especially if the server is not on your local network (username and pasword are being transmitted in plain-text).")
}
// Check the server supports AUTH, and get the mechanisms
authExtension, m := c.Extension("AUTH")
if authExtension {
log.Debugf("Config has SMTP password and server supports AUTH with the following mechanisms: %s.", m)
mechanisms := strings.Split(m, " ")
var auth smtp.Auth
// Adaptively select the AUTH mechanism to use based on what the server advertised
if utils.IsStringInSlice("PLAIN", mechanisms) {
auth = smtp.PlainAuth("", n.username, n.password, n.host)
log.Debug("SMTP server supports AUTH PLAIN, attempting...")
} else if utils.IsStringInSlice("LOGIN", mechanisms) {
auth = LoginAuth(n.username, n.password)
log.Debug("SMTP server supports AUTH LOGIN, attempting...")
}
// Throw error since AUTH extension is not supported
if auth == nil {
return fmt.Errorf("SMTP server does not advertise a AUTH mechanism that Authelia supports (PLAIN or LOGIN). Advertised mechanisms: %s.", m)
}
// Authenticate
err := c.Auth(auth)
if err != nil {
return err
} else {
log.Debug("SMTP AUTH completed successfully.")
}
} else {
return errors.New("SMTP server does not advertise the AUTH extension but a password was specified. Either disable auth (don't specify a password/comment the password), or specify an SMTP host and port that supports AUTH PLAIN or AUTH LOGIN.")
}
} else {
log.Debug("SMTP config has no password specified for use with AUTH, skipping.")
}
// Set the sender and recipient first
if err := c.Mail(n.sender); err != nil {
return err
}
if err := c.Rcpt(recipient); err != nil {
return err
}
// Send the email body.
wc, err := c.Data()
if err != nil {
return err
}
_, err = fmt.Fprintf(wc, msg)
if err != nil {
return err
}
err = wc.Close()
if err != nil {
return err
}
// Send the QUIT command and close the connection.
err = c.Quit()
if err != nil {
return err
}
return nil
}