Implement SMTP StartTLS and Adaptive Auth
- If the STARTTLS extension is advertised we automatically STARTTLS before authenticating or sending - Uses the secure config key to determine if we should verify the cert. By default it does not verify the cert (should not break any configs) - Attempt auth when the config has a SMTP password and the server supports the AUTH extension and either the PLAIN or LOGIN mechanism - Check the mechanisms supported by the server and use PLAIN or LOGIN depending on which is supported - Changed secure key to use boolean values instead of strings - Arranged SMTP notifier properties/vars to be in the same order - Log the steps for STARTTLS (debug only) - Log the steps for AUTH (debug only)pull/525/head
parent
716e017521
commit
c4b56a6002
|
@ -17,10 +17,10 @@ type EmailNotifierConfiguration struct {
|
|||
type SMTPNotifierConfiguration struct {
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Secure string `yaml:"secure"`
|
||||
Sender string `yaml:"sender"`
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Sender string `yaml:"sender"`
|
||||
Secure bool `yaml:"secure"`
|
||||
}
|
||||
|
||||
// NotifierConfiguration representes the configuration of the notifier to use when sending notifications to users.
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
type loginAuth struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte{}, nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if !more {
|
||||
return nil, nil
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(fromServer, []byte("Username:")):
|
||||
return []byte(a.username), nil
|
||||
case bytes.Equal(fromServer, []byte("Password:")):
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected challenge/data from server: %s.", fromServer)
|
||||
}
|
||||
}
|
|
@ -1,52 +1,112 @@
|
|||
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 {
|
||||
address string
|
||||
sender string
|
||||
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{
|
||||
host: configuration.Host,
|
||||
port: configuration.Port,
|
||||
address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
|
||||
sender: configuration.Sender,
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *SMTPNotifier) authenticatedSend(recipient string, msg string) error {
|
||||
auth := smtp.PlainAuth("", n.username, n.password, n.host)
|
||||
err := smtp.SendMail(fmt.Sprintf("%s:%d", n.host, n.port), auth, n.sender,
|
||||
[]string{recipient}, []byte(msg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 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
|
||||
|
||||
func (n *SMTPNotifier) unauthenticatedSend(recipient string, msg string) error {
|
||||
// Connect to the remote SMTP server.
|
||||
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 != "" {
|
||||
|
||||
// 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. 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
|
||||
|
@ -77,18 +137,3 @@ func (n *SMTPNotifier) unauthenticatedSend(recipient string, msg string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
if n.password != "" {
|
||||
return n.authenticatedSend(recipient, msg)
|
||||
}
|
||||
return n.unauthenticatedSend(recipient, msg)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue