diff --git a/README.md b/README.md index 1da5240..e5b5f2e 100644 --- a/README.md +++ b/README.md @@ -32,31 +32,24 @@ Mailpit was originally **inspired** by MailHog which is [no longer maintained](h ## Features -- Runs entirely from a single [static binary](https://mailpit.axllent.org/docs/install/) -- Modern web UI to view emails (formatted HTML, highlighted HTML source, text, headers, raw source, and MIME attachments -including image thumbnails), including optional [HTTPS](https://mailpit.axllent.org/docs/configuration/https/) -- Optional [basic authentication](https://mailpit.axllent.org/docs/configuration/frontend-authentication/) for web UI & API +- Runs entirely from a single [static binary](https://mailpit.axllent.org/docs/install/) or multi-architecture [Docker images](https://mailpit.axllent.org/docs/install/docker/) +- Modern web UI with advanced [mail search](https://mailpit.axllent.org/docs/usage/search-filters/) to view emails (formatted HTML, highlighted HTML source, text, headers, raw source, and MIME attachments +including image thumbnails), including optional [HTTPS](https://mailpit.axllent.org/docs/configuration/http/) & [authentication](https://mailpit.axllent.org/docs/configuration/http/) +- [SMTP server](https://mailpit.axllent.org/docs/configuration/smtp/) with optional STARTTLS or SSL/TLS, authentication (including an "accept any" mode) +- A [REST API](https://mailpit.axllent.org/docs/api-v1/) for integration testing +- Real-time web UI updates using web sockets for new mail & optional [browser notifications](https://mailpit.axllent.org/docs/usage/notifications/) when new mail is received +- Optional [POP3 server](https://mailpit.axllent.org/docs/configuration/pop3/) to download captured message directly into your email client - [HTML check](https://mailpit.axllent.org/docs/usage/html-check/) to test & score mail client compatibility with HTML emails - [Link check](https://mailpit.axllent.org/docs/usage/link-check/) to test message links (HTML & text) & linked images - [Spam check](https://mailpit.axllent.org/docs/usage/spamassassin/) to test message "spamminess" using a running SpamAssassin server - [Create screenshots](https://mailpit.axllent.org/docs/usage/html-screenshots/) of HTML messages via web UI -- `List-Unsubscribe` syntax validation - Mobile and tablet HTML preview toggle in desktop mode -- Advanced [mail search](https://mailpit.axllent.org/docs/usage/search-filters/) -- [Message tagging](https://mailpit.axllent.org/docs/usage/tagging/) -- Real-time web UI updates using web sockets for new mail & optional browser notifications for new mail (when accessed -via either HTTPS or `localhost` only) -- SMTP server with optional [STARTTLS & SMTP authentication](https://mailpit.axllent.org/docs/configuration/smtp-authentication/) (including an -"accept any" mode) -- [SMTP relaying](https://mailpit.axllent.org/docs/configuration/smtp-relay/) (message release) - relay messages via a different SMTP server -including an optional allowlist of accepted recipients -- Fast SMTP processing & storing - ingesting 100-200 emails per second depending on CPU, network speed & email size, -easily handling tens of thousands of emails -- Optional [POP3 server](https://mailpit.axllent.org/docs/configuration/pop3/) to download captured message directly into your email client -- Configurable automatic email pruning (default keeps the most recent 500 emails) -- A simple [REST API](https://mailpit.axllent.org/docs/api-v1/) for integration testing +- [Message tagging](https://mailpit.axllent.org/docs/usage/tagging/) including manual tagging or automated tagging using filtering and "plus addressing" +- [SMTP relaying](https://mailpit.axllent.org/docs/configuration/smtp-relay/) (message release) - relay messages via a different SMTP server including an optional allowlist of accepted recipients +- Fast message [storing & processing](https://mailpit.axllent.org/docs/configuration/email-storage/) - ingesting 100-200 emails per second over SMTP depending on CPU, network speed & email size, +easily handling tens of thousands of emails, with automatic email pruning (by default keeping the most recent 500 emails) +- `List-Unsubscribe` syntax validation - Optional [webhook](https://mailpit.axllent.org/docs/integration/webhook/) for received messages -- Multi-architecture [Docker images](https://mailpit.axllent.org/docs/install/docker/) ## Installation diff --git a/cmd/root.go b/cmd/root.go index 0866e04..98658af 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -108,7 +108,8 @@ func init() { rootCmd.Flags().BoolVar(&config.SMTPAuthAcceptAny, "smtp-auth-accept-any", config.SMTPAuthAcceptAny, "Accept any SMTP username and password, including none") rootCmd.Flags().StringVar(&config.SMTPTLSCert, "smtp-tls-cert", config.SMTPTLSCert, "TLS certificate for SMTP (STARTTLS) - requires smtp-tls-key") rootCmd.Flags().StringVar(&config.SMTPTLSKey, "smtp-tls-key", config.SMTPTLSKey, "TLS key for SMTP (STARTTLS) - requires smtp-tls-cert") - rootCmd.Flags().BoolVar(&config.SMTPTLSRequired, "smtp-tls-required", config.SMTPTLSRequired, "Require TLS SMTP encryption") + rootCmd.Flags().BoolVar(&config.SMTPRequireSTARTTLS, "smtp-require-starttls", config.SMTPRequireSTARTTLS, "Require SMTP client use STARTTLS") + rootCmd.Flags().BoolVar(&config.SMTPRequireTLS, "smtp-require-tls", config.SMTPRequireTLS, "Require client use SSL/TLS") rootCmd.Flags().BoolVar(&config.SMTPAuthAllowInsecure, "smtp-auth-allow-insecure", config.SMTPAuthAllowInsecure, "Allow insecure PLAIN & LOGIN SMTP authentication") rootCmd.Flags().BoolVar(&config.SMTPStrictRFCHeaders, "smtp-strict-rfc-headers", config.SMTPStrictRFCHeaders, "Return SMTP error if message headers contain ") rootCmd.Flags().IntVar(&config.SMTPMaxRecipients, "smtp-max-recipients", config.SMTPMaxRecipients, "Maximum SMTP recipients allowed") @@ -146,6 +147,11 @@ func init() { rootCmd.Flags().Lookup("smtp-ssl-cert").Deprecated = "use --smtp-tls-cert" rootCmd.Flags().Lookup("smtp-ssl-key").Hidden = true rootCmd.Flags().Lookup("smtp-ssl-key").Deprecated = "use --smtp-tls-key" + + // DEPRECATED FLAGS 2024/03/16 + rootCmd.Flags().BoolVar(&config.SMTPRequireSTARTTLS, "smtp-tls-required", config.SMTPRequireSTARTTLS, "smtp-require-starttls") + rootCmd.Flags().Lookup("smtp-tls-required").Hidden = true + rootCmd.Flags().Lookup("smtp-tls-required").Deprecated = "use --smtp-require-starttls" } // Load settings from environment @@ -213,9 +219,13 @@ func initConfigFromEnv() { } config.SMTPTLSCert = os.Getenv("MP_SMTP_TLS_CERT") config.SMTPTLSKey = os.Getenv("MP_SMTP_TLS_KEY") - if getEnabledFromEnv("MP_SMTP_TLS_REQUIRED") { - config.SMTPTLSRequired = true + if getEnabledFromEnv("MP_SMTP_REQUIRE_STARTTLS") { + config.SMTPRequireSTARTTLS = true } + if getEnabledFromEnv("MP_SMTP_REQUIRE_TLS") { + config.SMTPRequireTLS = true + } + if getEnabledFromEnv("MP_SMTP_AUTH_ALLOW_INSECURE") { config.SMTPAuthAllowInsecure = true } @@ -306,6 +316,11 @@ func initDeprecatedConfigFromEnv() { logger.Log().Warn("ENV MP_STRICT_RFC_HEADERS has been deprecated, use MP_SMTP_STRICT_RFC_HEADERS") config.SMTPStrictRFCHeaders = true } + // deprecated 2024/03.16 + if getEnabledFromEnv("MP_SMTP_TLS_REQUIRED") { + logger.Log().Warn("ENV MP_SMTP_TLS_REQUIRED has been deprecated, use MP_SMTP_REQUIRE_STARTTLS") + config.SMTPRequireSTARTTLS = true + } } // Wrapper to get a boolean from an environment variable diff --git a/config/config.go b/config/config.go index 94d6a70..5cdf58e 100644 --- a/config/config.go +++ b/config/config.go @@ -53,10 +53,14 @@ var ( // SMTPTLSKey file SMTPTLSKey string - // SMTPTLSRequired to enforce TLS + // SMTPRequireSTARTTLS to enforce the use of STARTTLS // The only allowed commands are NOOP, EHLO, STARTTLS and QUIT (as specified in RFC 3207) until // the connection is upgraded to TLS i.e. until STARTTLS is issued. - SMTPTLSRequired bool + SMTPRequireSTARTTLS bool + + // SMTPRequireTLS to allow only SSL/TLS connections for all connections + // + SMTPRequireTLS bool // SMTPAuthFile for SMTP authentication SMTPAuthFile string @@ -242,12 +246,16 @@ func VerifyConfig() error { if !isFile(SMTPTLSKey) { return fmt.Errorf("[smtp] TLS key not found: %s", SMTPTLSKey) } - } else if SMTPTLSRequired { + } else if SMTPRequireTLS { return errors.New("[smtp] TLS cannot be required without an SMTP TLS certificate and key") + } else if SMTPRequireSTARTTLS { + return errors.New("[smtp] STARTTLS cannot be required without an SMTP TLS certificate and key") } - - if SMTPTLSRequired && SMTPAuthAllowInsecure { - return errors.New("[smtp] TLS cannot be required while also allowing insecure authentication") + if SMTPRequireSTARTTLS && SMTPAuthAllowInsecure || SMTPRequireTLS && SMTPAuthAllowInsecure { + return errors.New("[smtp] TLS cannot be required with --smtp-auth-allow-insecure") + } + if SMTPRequireSTARTTLS && SMTPRequireTLS { + return errors.New("[smtp] TLS & STARTTLS cannot be required together") } if SMTPAuthFile != "" { @@ -265,6 +273,18 @@ func VerifyConfig() error { if err := auth.SetSMTPAuth(string(b)); err != nil { return err } + + if !SMTPAuthAllowInsecure { + // https://www.rfc-editor.org/rfc/rfc4954 + // A server implementation MUST implement a configuration in which + // it does NOT permit any plaintext password mechanisms, unless either + // the STARTTLS [SMTP-TLS] command has been negotiated or some other + // mechanism that protects the session from password snooping has been + // provided. Server sites SHOULD NOT use any configuration which + // permits a plaintext password mechanism without such a protection + // mechanism against password snooping. + SMTPRequireSTARTTLS = true + } } if auth.SMTPCredentials != nil && SMTPAuthAcceptAny { @@ -272,7 +292,7 @@ func VerifyConfig() error { } if SMTPTLSCert == "" && (auth.SMTPCredentials != nil || SMTPAuthAcceptAny) && !SMTPAuthAllowInsecure { - return errors.New("[smtp] authentication requires TLS encryption, run with `--smtp-auth-allow-insecure` to allow insecure authentication") + return errors.New("[smtp] authentication requires STARTTLS or TLS encryption, run with `--smtp-auth-allow-insecure` to allow insecure authentication") } // POP3 server diff --git a/server/smtpd/smtpd.go b/server/smtpd/smtpd.go index e9fb0f4..63786e5 100644 --- a/server/smtpd/smtpd.go +++ b/server/smtpd/smtpd.go @@ -177,19 +177,35 @@ func handlerRcpt(remoteAddr net.Addr, from string, to string) bool { func Listen() error { if config.SMTPAuthAllowInsecure { if auth.SMTPCredentials != nil { - logger.Log().Info("[smtpd] enabling login auth (insecure)") + logger.Log().Info("[smtpd] enabling login authentication (insecure)") } else if config.SMTPAuthAcceptAny { - logger.Log().Info("[smtpd] enabling all auth (insecure)") + logger.Log().Info("[smtpd] enabling any authentication (insecure)") } } else { if auth.SMTPCredentials != nil { - logger.Log().Info("[smtpd] enabling login auth (TLS)") + logger.Log().Info("[smtpd] enabling login authentication") } else if config.SMTPAuthAcceptAny { - logger.Log().Info("[smtpd] enabling any auth (TLS)") + logger.Log().Info("[smtpd] enabling any authentication") } } - logger.Log().Infof("[smtpd] starting on %s", config.SMTPListen) + smtpType := "no encryption" + + if config.SMTPTLSCert != "" { + if config.SMTPRequireSTARTTLS { + smtpType = "STARTTLS required" + } else if config.SMTPRequireTLS { + smtpType = "SSL/TLS required" + } else { + smtpType = "STARTTLS optional" + if !config.SMTPAuthAllowInsecure && auth.SMTPCredentials != nil { + smtpType = "STARTTLS required" + } + } + + } + + logger.Log().Infof("[smtpd] starting on %s (%s)", config.SMTPListen, smtpType) return listenAndServe(config.SMTPListen, mailHandler, authHandler) } @@ -221,7 +237,8 @@ func listenAndServe(addr string, handler smtpd.Handler, authHandler smtpd.AuthHa } if config.SMTPTLSCert != "" { - srv.TLSRequired = config.SMTPTLSRequired + srv.TLSRequired = config.SMTPRequireSTARTTLS + srv.TLSListener = config.SMTPRequireTLS // if true overrules srv.TLSRequired if err := srv.ConfigureTLS(config.SMTPTLSCert, config.SMTPTLSKey); err != nil { return err }