mirror of
https://github.com/axllent/mailpit.git
synced 2025-08-15 20:13:16 +02:00
Feature: Options to support auth without STARTTLS, and accept any login
@see #56
This commit is contained in:
117
cmd/root.go
117
cmd/root.go
@@ -74,47 +74,7 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().BoolP("help", "h", false, "This help")
|
rootCmd.PersistentFlags().BoolP("help", "h", false, "This help")
|
||||||
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
||||||
|
|
||||||
// defaults from envars if provided
|
initConfigFromEnv()
|
||||||
if len(os.Getenv("MP_DATA_FILE")) > 0 {
|
|
||||||
config.DataFile = os.Getenv("MP_DATA_FILE")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_SMTP_BIND_ADDR")) > 0 {
|
|
||||||
config.SMTPListen = os.Getenv("MP_SMTP_BIND_ADDR")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_UI_BIND_ADDR")) > 0 {
|
|
||||||
config.HTTPListen = os.Getenv("MP_UI_BIND_ADDR")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_MAX_MESSAGES")) > 0 {
|
|
||||||
config.MaxMessages, _ = strconv.Atoi(os.Getenv("MP_MAX_MESSAGES"))
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_TAG")) > 0 {
|
|
||||||
config.SMTPCLITags = os.Getenv("MP_TAG")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_UI_AUTH_FILE")) > 0 {
|
|
||||||
config.UIAuthFile = os.Getenv("MP_UI_AUTH_FILE")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_UI_SSL_CERT")) > 0 {
|
|
||||||
config.UISSLCert = os.Getenv("MP_UI_SSL_CERT")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_UI_SSL_KEY")) > 0 {
|
|
||||||
config.UISSLKey = os.Getenv("MP_UI_SSL_KEY")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_SMTP_AUTH_FILE")) > 0 {
|
|
||||||
config.SMTPAuthFile = os.Getenv("MP_SMTP_AUTH_FILE")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_SMTP_SSL_CERT")) > 0 {
|
|
||||||
config.SMTPSSLCert = os.Getenv("MP_SMTP_SSL_CERT")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_SMTP_SSL_KEY")) > 0 {
|
|
||||||
config.SMTPSSLKey = os.Getenv("MP_SMTP_SSL_KEY")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_WEBROOT")) > 0 {
|
|
||||||
config.Webroot = os.Getenv("MP_WEBROOT")
|
|
||||||
}
|
|
||||||
if len(os.Getenv("MP_USE_MESSAGE_DATES")) > 0 {
|
|
||||||
v := strings.ToLower(os.Getenv("MP_USE_MESSAGE_DATES"))
|
|
||||||
config.UseMessageDates = v != "0" && v != "false" && v != "yes"
|
|
||||||
}
|
|
||||||
|
|
||||||
// deprecated 2022/08/06
|
// deprecated 2022/08/06
|
||||||
if len(os.Getenv("MP_AUTH_FILE")) > 0 {
|
if len(os.Getenv("MP_AUTH_FILE")) > 0 {
|
||||||
@@ -149,8 +109,10 @@ func init() {
|
|||||||
rootCmd.Flags().StringVar(&config.UISSLKey, "ui-ssl-key", config.UISSLKey, "SSL key for web UI - requires ui-ssl-cert")
|
rootCmd.Flags().StringVar(&config.UISSLKey, "ui-ssl-key", config.UISSLKey, "SSL key for web UI - requires ui-ssl-cert")
|
||||||
|
|
||||||
rootCmd.Flags().StringVar(&config.SMTPAuthFile, "smtp-auth-file", config.SMTPAuthFile, "A password file for SMTP authentication")
|
rootCmd.Flags().StringVar(&config.SMTPAuthFile, "smtp-auth-file", config.SMTPAuthFile, "A password file for SMTP authentication")
|
||||||
|
rootCmd.Flags().BoolVar(&config.SMTPAuthAcceptAny, "smtp-auth-accept-any", false, "Accept any SMTP username and password, including none")
|
||||||
rootCmd.Flags().StringVar(&config.SMTPSSLCert, "smtp-ssl-cert", config.SMTPSSLCert, "SSL certificate for SMTP - requires smtp-ssl-key")
|
rootCmd.Flags().StringVar(&config.SMTPSSLCert, "smtp-ssl-cert", config.SMTPSSLCert, "SSL certificate for SMTP - requires smtp-ssl-key")
|
||||||
rootCmd.Flags().StringVar(&config.SMTPSSLKey, "smtp-ssl-key", config.SMTPSSLKey, "SSL key for SMTP - requires smtp-ssl-cert")
|
rootCmd.Flags().StringVar(&config.SMTPSSLKey, "smtp-ssl-key", config.SMTPSSLKey, "SSL key for SMTP - requires smtp-ssl-cert")
|
||||||
|
rootCmd.Flags().BoolVar(&config.SMTPAuthAllowInsecure, "smtp-auth-allow-insecure", false, "Enable insecure PLAIN & LOGIN authentication")
|
||||||
rootCmd.Flags().StringVarP(&config.SMTPCLITags, "tag", "t", config.SMTPCLITags, "Tag new messages matching filters")
|
rootCmd.Flags().StringVarP(&config.SMTPCLITags, "tag", "t", config.SMTPCLITags, "Tag new messages matching filters")
|
||||||
|
|
||||||
rootCmd.Flags().BoolVarP(&config.QuietLogging, "quiet", "q", false, "Quiet logging (errors only)")
|
rootCmd.Flags().BoolVarP(&config.QuietLogging, "quiet", "q", false, "Quiet logging (errors only)")
|
||||||
@@ -172,3 +134,76 @@ func init() {
|
|||||||
rootCmd.Flags().Lookup("data").Hidden = true
|
rootCmd.Flags().Lookup("data").Hidden = true
|
||||||
rootCmd.Flags().Lookup("data").Deprecated = "use --db-file"
|
rootCmd.Flags().Lookup("data").Deprecated = "use --db-file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load settings from environment
|
||||||
|
func initConfigFromEnv() {
|
||||||
|
// defaults from envars if provided
|
||||||
|
if len(os.Getenv("MP_DATA_FILE")) > 0 {
|
||||||
|
config.DataFile = os.Getenv("MP_DATA_FILE")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_SMTP_BIND_ADDR")) > 0 {
|
||||||
|
config.SMTPListen = os.Getenv("MP_SMTP_BIND_ADDR")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_UI_BIND_ADDR")) > 0 {
|
||||||
|
config.HTTPListen = os.Getenv("MP_UI_BIND_ADDR")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_MAX_MESSAGES")) > 0 {
|
||||||
|
config.MaxMessages, _ = strconv.Atoi(os.Getenv("MP_MAX_MESSAGES"))
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_TAG")) > 0 {
|
||||||
|
config.SMTPCLITags = os.Getenv("MP_TAG")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI
|
||||||
|
if len(os.Getenv("MP_UI_AUTH_FILE")) > 0 {
|
||||||
|
config.UIAuthFile = os.Getenv("MP_UI_AUTH_FILE")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_UI_SSL_CERT")) > 0 {
|
||||||
|
config.UISSLCert = os.Getenv("MP_UI_SSL_CERT")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_UI_SSL_KEY")) > 0 {
|
||||||
|
config.UISSLKey = os.Getenv("MP_UI_SSL_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMTP
|
||||||
|
if len(os.Getenv("MP_SMTP_AUTH_FILE")) > 0 {
|
||||||
|
config.SMTPAuthFile = os.Getenv("MP_SMTP_AUTH_FILE")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_SMTP_SSL_CERT")) > 0 {
|
||||||
|
config.SMTPSSLCert = os.Getenv("MP_SMTP_SSL_CERT")
|
||||||
|
}
|
||||||
|
if len(os.Getenv("MP_SMTP_SSL_KEY")) > 0 {
|
||||||
|
config.SMTPSSLKey = os.Getenv("MP_SMTP_SSL_KEY")
|
||||||
|
}
|
||||||
|
if getEnabledFromEnv("MP_SMTP_AUTH_ACCEPT_ANY") {
|
||||||
|
config.SMTPAuthAcceptAny = true
|
||||||
|
}
|
||||||
|
if getEnabledFromEnv("MP_SMTP_AUTH_ALLOW_INSECURE") {
|
||||||
|
config.SMTPAuthAllowInsecure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(os.Getenv("MP_WEBROOT")) > 0 {
|
||||||
|
config.Webroot = os.Getenv("MP_WEBROOT")
|
||||||
|
}
|
||||||
|
if getEnabledFromEnv("MP_USE_MESSAGE_DATES") {
|
||||||
|
config.UseMessageDates = true
|
||||||
|
}
|
||||||
|
if getEnabledFromEnv("MP_USE_MESSAGE_DATES") {
|
||||||
|
config.UseMessageDates = true
|
||||||
|
}
|
||||||
|
if getEnabledFromEnv("MP_QUIET") {
|
||||||
|
config.QuietLogging = true
|
||||||
|
}
|
||||||
|
if getEnabledFromEnv("MP_VERBOSE") {
|
||||||
|
config.VerboseLogging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnabledFromEnv(k string) bool {
|
||||||
|
if len(os.Getenv(k)) > 0 {
|
||||||
|
v := strings.ToLower(os.Getenv(k))
|
||||||
|
return v == "1" || v == "true" || v == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@@ -66,6 +66,12 @@ var (
|
|||||||
// SMTPAuth used for euthentication
|
// SMTPAuth used for euthentication
|
||||||
SMTPAuth *htpasswd.File
|
SMTPAuth *htpasswd.File
|
||||||
|
|
||||||
|
// SMTPAuthAllowInsecure allows PLAIN & LOGIN unencrypted authentication
|
||||||
|
SMTPAuthAllowInsecure bool
|
||||||
|
|
||||||
|
// SMTPAuthAcceptAny accepts any username/password including none
|
||||||
|
SMTPAuthAcceptAny bool
|
||||||
|
|
||||||
// SMTPCLITags is used to map the CLI args
|
// SMTPCLITags is used to map the CLI args
|
||||||
SMTPCLITags string
|
SMTPCLITags string
|
||||||
|
|
||||||
@@ -153,8 +159,8 @@ func VerifyConfig() error {
|
|||||||
return fmt.Errorf("SMTP password file not found: %s", SMTPAuthFile)
|
return fmt.Errorf("SMTP password file not found: %s", SMTPAuthFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if SMTPSSLCert == "" {
|
if SMTPAuthAcceptAny {
|
||||||
return errors.New("SMTP authentication requires SMTP encryption")
|
return errors.New("SMTP authentication can either use --smtp-auth-file or --smtp-auth-accept-any")
|
||||||
}
|
}
|
||||||
|
|
||||||
a, err := htpasswd.New(SMTPAuthFile, htpasswd.DefaultSystems, nil)
|
a, err := htpasswd.New(SMTPAuthFile, htpasswd.DefaultSystems, nil)
|
||||||
@@ -164,6 +170,10 @@ func VerifyConfig() error {
|
|||||||
SMTPAuth = a
|
SMTPAuth = a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if SMTPSSLCert == "" && (SMTPAuthFile != "" || SMTPAuthAcceptAny) && !SMTPAuthAllowInsecure {
|
||||||
|
return errors.New("SMTP authentication requires SSL encryption, run with `--smtp-auth-allow-insecure` to allow insecure authentication")
|
||||||
|
}
|
||||||
|
|
||||||
validWebrootRe := regexp.MustCompile(`[^0-9a-zA-Z\/\-\_\.]`)
|
validWebrootRe := regexp.MustCompile(`[^0-9a-zA-Z\/\-\_\.]`)
|
||||||
if validWebrootRe.MatchString(Webroot) {
|
if validWebrootRe.MatchString(Webroot) {
|
||||||
return fmt.Errorf("Invalid characters in Webroot (%s). Valid chars include: [a-z A-Z 0-9 _ . - /]", Webroot)
|
return fmt.Errorf("Invalid characters in Webroot (%s). Valid chars include: [a-z A-Z 0-9 _ . - /]", Webroot)
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/axllent/mailpit/config"
|
"github.com/axllent/mailpit/config"
|
||||||
"github.com/axllent/mailpit/storage"
|
"github.com/axllent/mailpit/storage"
|
||||||
@@ -33,21 +34,40 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subject := msg.Header.Get("Subject")
|
subject := msg.Header.Get("Subject")
|
||||||
logger.Log().Debugf("[smtp] received mail from %s for %s with subject %s", from, to[0], subject)
|
logger.Log().Debugf("[smtp] received (%s) from:%s to:%s subject:%q", cleanIP(origin), from, to[0], subject)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func authHandler(remoteAddr net.Addr, mechanism string, username []byte, password []byte, shared []byte) (bool, error) {
|
func authHandler(remoteAddr net.Addr, mechanism string, username []byte, password []byte, shared []byte) (bool, error) {
|
||||||
return config.SMTPAuth.Match(string(username), string(password)), nil
|
allow := config.SMTPAuth.Match(string(username), string(password))
|
||||||
|
if allow {
|
||||||
|
logger.Log().Debugf("[smtp] allow %s login:%q from:%s", mechanism, string(username), cleanIP(remoteAddr))
|
||||||
|
} else {
|
||||||
|
logger.Log().Warnf("[smtp] deny %s login:%q from:%s", mechanism, string(username), cleanIP(remoteAddr))
|
||||||
|
}
|
||||||
|
return allow, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow any username and password
|
||||||
|
func authHandlerAny(remoteAddr net.Addr, mechanism string, username []byte, password []byte, shared []byte) (bool, error) {
|
||||||
|
logger.Log().Debugf("[smtp] allow %s login %q from %s", mechanism, string(username), cleanIP(remoteAddr))
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen starts the SMTPD server
|
// Listen starts the SMTPD server
|
||||||
func Listen() error {
|
func Listen() error {
|
||||||
if config.SMTPSSLCert != "" {
|
if config.SMTPAuthAllowInsecure {
|
||||||
logger.Log().Info("[smtp] enabling TLS")
|
if config.SMTPAuthFile != "" {
|
||||||
}
|
logger.Log().Infof("[smtp] enabling login auth via %s (insecure)", config.SMTPAuthFile)
|
||||||
if config.SMTPAuthFile != "" {
|
} else if config.SMTPAuthAcceptAny {
|
||||||
logger.Log().Info("[smtp] enabling authentication")
|
logger.Log().Info("[smtp] enabling all auth (insecure)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if config.SMTPAuthFile != "" {
|
||||||
|
logger.Log().Infof("[smtp] enabling login auth via %s (TLS)", config.SMTPAuthFile)
|
||||||
|
} else if config.SMTPAuthAcceptAny {
|
||||||
|
logger.Log().Info("[smtp] enabling any auth (TLS)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Log().Infof("[smtp] starting on %s", config.SMTPListen)
|
logger.Log().Infof("[smtp] starting on %s", config.SMTPListen)
|
||||||
@@ -65,17 +85,30 @@ func listenAndServe(addr string, handler smtpd.Handler, authHandler smtpd.AuthHa
|
|||||||
AuthRequired: false,
|
AuthRequired: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.SMTPAuthAllowInsecure {
|
||||||
|
srv.AuthMechs = map[string]bool{"CRAM-MD5": false, "PLAIN": true, "LOGIN": true}
|
||||||
|
}
|
||||||
|
|
||||||
if config.SMTPAuthFile != "" {
|
if config.SMTPAuthFile != "" {
|
||||||
|
srv.AuthMechs = map[string]bool{"CRAM-MD5": false, "PLAIN": true, "LOGIN": true}
|
||||||
srv.AuthHandler = authHandler
|
srv.AuthHandler = authHandler
|
||||||
srv.AuthRequired = true
|
srv.AuthRequired = true
|
||||||
|
} else if config.SMTPAuthAcceptAny {
|
||||||
|
srv.AuthMechs = map[string]bool{"CRAM-MD5": false, "PLAIN": true, "LOGIN": true}
|
||||||
|
srv.AuthHandler = authHandlerAny
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.SMTPSSLCert != "" {
|
if config.SMTPSSLCert != "" {
|
||||||
err := srv.ConfigureTLS(config.SMTPSSLCert, config.SMTPSSLKey)
|
if err := srv.ConfigureTLS(config.SMTPSSLCert, config.SMTPSSLKey); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return srv.ListenAndServe()
|
return srv.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanIP(i net.Addr) string {
|
||||||
|
parts := strings.Split(i.String(), ":")
|
||||||
|
|
||||||
|
return parts[0]
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user