1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-01-10 00:43:53 +02:00

Merge branch 'feature/smtp-auth' into develop

This commit is contained in:
Ralph Slooten 2023-03-12 15:06:44 +13:00
commit 755ff37cdc
6 changed files with 250 additions and 102 deletions

View File

@ -6,7 +6,7 @@
![CodeQL](https://github.com/axllent/mailpit/actions/workflows/codeql-analysis.yml/badge.svg) ![CodeQL](https://github.com/axllent/mailpit/actions/workflows/codeql-analysis.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/axllent/mailpit)](https://goreportcard.com/report/github.com/axllent/mailpit) [![Go Report Card](https://goreportcard.com/badge/github.com/axllent/mailpit)](https://goreportcard.com/report/github.com/axllent/mailpit)
Mailpit is a multi-platform email testing tool for developers. Mailpit is a multi-platform email testing tool 7 API for developers.
It acts as both an SMTP server, and provides a web interface to view all captured emails. It acts as both an SMTP server, and provides a web interface to view all captured emails.
@ -27,8 +27,8 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster.
- Configurable automatic email pruning (default keeps the most recent 500 emails) - Configurable automatic email pruning (default keeps the most recent 500 emails)
- Email storage either in a temporary or persistent database ([see wiki](https://github.com/axllent/mailpit/wiki/Email-storage)) - Email storage either in a temporary or persistent database ([see wiki](https://github.com/axllent/mailpit/wiki/Email-storage))
- Fast SMTP processing & storing - approximately 70-100 emails per second depending on CPU, network speed & email size - Fast SMTP processing & storing - approximately 70-100 emails per second depending on CPU, network speed & email size
- Can handle hundreds of thousands of emails - Can handle tens of thousands of emails
- Optional SMTP with STARTTLS & SMTP authentication ([see wiki](https://github.com/axllent/mailpit/wiki/SMTP-with-STARTTLS-and-authentication)) - Optional SMTP with STARTTLS & SMTP authentication, including an "accept anything" mode ([see wiki](https://github.com/axllent/mailpit/wiki/SMTP-with-STARTTLS-and-authentication))
- Optional HTTPS for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/HTTPS)) - Optional HTTPS for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/HTTPS))
- Optional basic authentication for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/Basic-authentication)) - Optional basic authentication for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/Basic-authentication))
- A simple REST API ([see docs](docs/apiv1/README.md)) - A simple REST API ([see docs](docs/apiv1/README.md))

View File

@ -74,6 +74,66 @@ 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
// load and warn deprecated ENV vars
initDeprecatedConfigFromEnv()
// load ENV vars
initConfigFromEnv()
rootCmd.Flags().StringVarP(&config.DataFile, "db-file", "d", config.DataFile, "Database file to store persistent data")
rootCmd.Flags().StringVarP(&config.SMTPListen, "smtp", "s", config.SMTPListen, "SMTP bind interface and port")
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI")
rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages to store")
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
rootCmd.Flags().BoolVar(&config.UseMessageDates, "use-message-dates", false, "Use message dates as the received dates")
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI authentication")
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-tls-cert", config.UITLSCert, "TLS certificate for web UI (HTTPS) - requires ui-tls-key")
rootCmd.Flags().StringVar(&config.UITLSKey, "ui-tls-key", config.UITLSKey, "TLS key for web UI (HTTPS) - requires ui-tls-cert")
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.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.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().BoolVarP(&config.QuietLogging, "quiet", "q", false, "Quiet logging (errors only)")
rootCmd.Flags().BoolVarP(&config.VerboseLogging, "verbose", "v", false, "Verbose logging")
// deprecated flags 2022/08/06
rootCmd.Flags().StringVarP(&config.UIAuthFile, "auth-file", "a", config.UIAuthFile, "A password file for web UI authentication")
rootCmd.Flags().StringVar(&config.UITLSCert, "ssl-cert", config.UITLSCert, "SSL certificate - requires ssl-key")
rootCmd.Flags().StringVar(&config.UITLSKey, "ssl-key", config.UITLSKey, "SSL key - requires ssl-cert")
rootCmd.Flags().Lookup("auth-file").Hidden = true
rootCmd.Flags().Lookup("auth-file").Deprecated = "use --ui-auth-file"
rootCmd.Flags().Lookup("ssl-cert").Hidden = true
rootCmd.Flags().Lookup("ssl-cert").Deprecated = "use --ui-tls-cert"
rootCmd.Flags().Lookup("ssl-key").Hidden = true
rootCmd.Flags().Lookup("ssl-key").Deprecated = "use --ui-tls-key"
// deprecated flags 2022/08/30
rootCmd.Flags().StringVar(&config.DataFile, "data", config.DataFile, "Database file to store persistent data")
rootCmd.Flags().Lookup("data").Hidden = true
rootCmd.Flags().Lookup("data").Deprecated = "use --db-file"
// deprecated flags 2023/03/12
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-ssl-cert", config.UITLSCert, "SSL certificate for web UI - requires ui-ssl-key")
rootCmd.Flags().StringVar(&config.UITLSKey, "ui-ssl-key", config.UITLSKey, "SSL key for web UI - requires ui-ssl-cert")
rootCmd.Flags().StringVar(&config.SMTPTLSCert, "smtp-ssl-cert", config.SMTPTLSCert, "SSL certificate for SMTP - requires smtp-ssl-key")
rootCmd.Flags().StringVar(&config.SMTPTLSKey, "smtp-ssl-key", config.SMTPTLSKey, "SSL key for SMTP - requires smtp-ssl-cert")
rootCmd.Flags().Lookup("ui-ssl-cert").Hidden = true
rootCmd.Flags().Lookup("ui-ssl-cert").Deprecated = "use --ui-tls-cert"
rootCmd.Flags().Lookup("ui-ssl-key").Hidden = true
rootCmd.Flags().Lookup("ui-ssl-key").Deprecated = "use --ui-tls-key"
rootCmd.Flags().Lookup("smtp-ssl-cert").Hidden = true
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"
}
// Load settings from environment
func initConfigFromEnv() {
// defaults from envars if provided // defaults from envars if provided
if len(os.Getenv("MP_DATA_FILE")) > 0 { if len(os.Getenv("MP_DATA_FILE")) > 0 {
config.DataFile = os.Getenv("MP_DATA_FILE") config.DataFile = os.Getenv("MP_DATA_FILE")
@ -90,85 +150,99 @@ func init() {
if len(os.Getenv("MP_TAG")) > 0 { if len(os.Getenv("MP_TAG")) > 0 {
config.SMTPCLITags = os.Getenv("MP_TAG") config.SMTPCLITags = os.Getenv("MP_TAG")
} }
// UI
if len(os.Getenv("MP_UI_AUTH_FILE")) > 0 { if len(os.Getenv("MP_UI_AUTH_FILE")) > 0 {
config.UIAuthFile = os.Getenv("MP_UI_AUTH_FILE") config.UIAuthFile = os.Getenv("MP_UI_AUTH_FILE")
} }
if len(os.Getenv("MP_UI_SSL_CERT")) > 0 { if len(os.Getenv("MP_UI_TLS_CERT")) > 0 {
config.UISSLCert = os.Getenv("MP_UI_SSL_CERT") config.UITLSCert = os.Getenv("MP_UI_TLS_CERT")
} }
if len(os.Getenv("MP_UI_SSL_KEY")) > 0 { if len(os.Getenv("MP_UI_TLS_KEY")) > 0 {
config.UISSLKey = os.Getenv("MP_UI_SSL_KEY") config.UITLSKey = os.Getenv("MP_UI_TLS_KEY")
} }
// SMTP
if len(os.Getenv("MP_SMTP_AUTH_FILE")) > 0 { if len(os.Getenv("MP_SMTP_AUTH_FILE")) > 0 {
config.SMTPAuthFile = os.Getenv("MP_SMTP_AUTH_FILE") config.SMTPAuthFile = os.Getenv("MP_SMTP_AUTH_FILE")
} }
if len(os.Getenv("MP_SMTP_SSL_CERT")) > 0 { if len(os.Getenv("MP_SMTP_TLS_CERT")) > 0 {
config.SMTPSSLCert = os.Getenv("MP_SMTP_SSL_CERT") config.SMTPTLSCert = os.Getenv("MP_SMTP_TLS_CERT")
} }
if len(os.Getenv("MP_SMTP_SSL_KEY")) > 0 { if len(os.Getenv("MP_SMTP_TLS_KEY")) > 0 {
config.SMTPSSLKey = os.Getenv("MP_SMTP_SSL_KEY") config.SMTPTLSKey = os.Getenv("MP_SMTP_TLS_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 { if len(os.Getenv("MP_WEBROOT")) > 0 {
config.Webroot = os.Getenv("MP_WEBROOT") config.Webroot = os.Getenv("MP_WEBROOT")
} }
if len(os.Getenv("MP_USE_MESSAGE_DATES")) > 0 { if getEnabledFromEnv("MP_USE_MESSAGE_DATES") {
v := strings.ToLower(os.Getenv("MP_USE_MESSAGE_DATES")) config.UseMessageDates = true
config.UseMessageDates = v != "0" && v != "false" && v != "yes"
} }
if getEnabledFromEnv("MP_USE_MESSAGE_DATES") {
config.UseMessageDates = true
}
if getEnabledFromEnv("MP_QUIET") {
config.QuietLogging = true
}
if getEnabledFromEnv("MP_VERBOSE") {
config.VerboseLogging = true
}
}
// load deprecated settings from environment and warn
func initDeprecatedConfigFromEnv() {
// deprecated 2022/08/06 // deprecated 2022/08/06
if len(os.Getenv("MP_AUTH_FILE")) > 0 { if len(os.Getenv("MP_AUTH_FILE")) > 0 {
fmt.Println("MP_AUTH_FILE has been deprecated, use MP_UI_AUTH_FILE") fmt.Println("ENV MP_AUTH_FILE has been deprecated, use MP_UI_AUTH_FILE")
config.UIAuthFile = os.Getenv("MP_AUTH_FILE") config.UIAuthFile = os.Getenv("MP_AUTH_FILE")
} }
// deprecated 2022/08/06 // deprecated 2022/08/06
if len(os.Getenv("MP_SSL_CERT")) > 0 { if len(os.Getenv("MP_SSL_CERT")) > 0 {
fmt.Println("MP_SSL_CERT has been deprecated, use MP_UI_SSL_CERT") fmt.Println("ENV MP_SSL_CERT has been deprecated, use MP_UI_TLS_CERT")
config.UISSLCert = os.Getenv("MP_SSL_CERT") config.UITLSCert = os.Getenv("MP_SSL_CERT")
} }
// deprecated 2022/08/06 // deprecated 2022/08/06
if len(os.Getenv("MP_SSL_KEY")) > 0 { if len(os.Getenv("MP_SSL_KEY")) > 0 {
fmt.Println("MP_SSL_KEY has been deprecated, use MP_UI_SSL_KEY") fmt.Println("ENV MP_SSL_KEY has been deprecated, use MP_UI_TLS_KEY")
config.UISSLKey = os.Getenv("MP_SSL_KEY") config.UITLSKey = os.Getenv("MP_TLS_KEY")
} }
// deprecated 2022/08/28 // deprecated 2022/08/28
if len(os.Getenv("MP_DATA_DIR")) > 0 { if len(os.Getenv("MP_DATA_DIR")) > 0 {
fmt.Println("MP_DATA_DIR has been deprecated, use MP_DATA_FILE") fmt.Println("ENV MP_DATA_DIR has been deprecated, use MP_DATA_FILE")
config.DataFile = os.Getenv("MP_DATA_DIR") config.DataFile = os.Getenv("MP_DATA_DIR")
} }
// deprecated 2023/03/12
rootCmd.Flags().StringVarP(&config.DataFile, "db-file", "d", config.DataFile, "Database file to store persistent data") if len(os.Getenv("MP_UI_SSL_CERT")) > 0 {
rootCmd.Flags().StringVarP(&config.SMTPListen, "smtp", "s", config.SMTPListen, "SMTP bind interface and port") fmt.Println("ENV MP_UI_SSL_CERT has been deprecated, use MP_UI_TLS_CERT")
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI") config.UITLSCert = os.Getenv("MP_UI_SSL_CERT")
rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages to store") }
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API") if len(os.Getenv("MP_UI_SSL_KEY")) > 0 {
rootCmd.Flags().BoolVar(&config.UseMessageDates, "use-message-dates", false, "Use message dates as the received dates") fmt.Println("ENV MP_UI_SSL_KEY has been deprecated, use MP_UI_TLS_KEY")
config.UITLSKey = os.Getenv("MP_UI_SSL_KEY")
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI authentication") }
rootCmd.Flags().StringVar(&config.UISSLCert, "ui-ssl-cert", config.UISSLCert, "SSL certificate for web UI - requires ui-ssl-key") if len(os.Getenv("MP_SMTP_SSL_CERT")) > 0 {
rootCmd.Flags().StringVar(&config.UISSLKey, "ui-ssl-key", config.UISSLKey, "SSL key for web UI - requires ui-ssl-cert") fmt.Println("ENV MP_SMTP_CERT has been deprecated, use MP_SMTP_TLS_CERT")
config.SMTPTLSCert = os.Getenv("MP_SMTP_SSL_CERT")
rootCmd.Flags().StringVar(&config.SMTPAuthFile, "smtp-auth-file", config.SMTPAuthFile, "A password file for SMTP authentication") }
rootCmd.Flags().StringVar(&config.SMTPSSLCert, "smtp-ssl-cert", config.SMTPSSLCert, "SSL certificate for SMTP - requires smtp-ssl-key") if len(os.Getenv("MP_SMTP_SSL_KEY")) > 0 {
rootCmd.Flags().StringVar(&config.SMTPSSLKey, "smtp-ssl-key", config.SMTPSSLKey, "SSL key for SMTP - requires smtp-ssl-cert") fmt.Println("ENV MP_SMTP_KEY has been deprecated, use MP_SMTP_TLS_KEY")
rootCmd.Flags().StringVarP(&config.SMTPCLITags, "tag", "t", config.SMTPCLITags, "Tag new messages matching filters") config.SMTPTLSKey = os.Getenv("MP_SMTP_SMTP_KEY")
}
rootCmd.Flags().BoolVarP(&config.QuietLogging, "quiet", "q", false, "Quiet logging (errors only)") }
rootCmd.Flags().BoolVarP(&config.VerboseLogging, "verbose", "v", false, "Verbose logging")
// Wrapper to get a boolean from an environment variable
// deprecated 2022/08/06 func getEnabledFromEnv(k string) bool {
rootCmd.Flags().StringVarP(&config.UIAuthFile, "auth-file", "a", config.UIAuthFile, "A password file for web UI authentication") if len(os.Getenv(k)) > 0 {
rootCmd.Flags().StringVar(&config.UISSLCert, "ssl-cert", config.UISSLCert, "SSL certificate - requires ssl-key") v := strings.ToLower(os.Getenv(k))
rootCmd.Flags().StringVar(&config.UISSLKey, "ssl-key", config.UISSLKey, "SSL key - requires ssl-cert") return v == "1" || v == "true" || v == "yes"
rootCmd.Flags().Lookup("auth-file").Hidden = true }
rootCmd.Flags().Lookup("auth-file").Deprecated = "use --ui-auth-file"
rootCmd.Flags().Lookup("ssl-cert").Hidden = true return false
rootCmd.Flags().Lookup("ssl-cert").Deprecated = "use --ui-ssl-cert"
rootCmd.Flags().Lookup("ssl-key").Hidden = true
rootCmd.Flags().Lookup("ssl-key").Deprecated = "use --ui-ssl-key"
// deprecated 2022/08/30
rootCmd.Flags().StringVar(&config.DataFile, "data", config.DataFile, "Database file to store persistent data")
rootCmd.Flags().Lookup("data").Hidden = true
rootCmd.Flags().Lookup("data").Deprecated = "use --db-file"
} }

View File

@ -39,11 +39,11 @@ var (
// NoLogging for tests // NoLogging for tests
NoLogging = false NoLogging = false
// UISSLCert file // UITLSCert file
UISSLCert string UITLSCert string
// UISSLKey file // UITLSKey file
UISSLKey string UITLSKey string
// UIAuthFile for basic authentication // UIAuthFile for basic authentication
UIAuthFile string UIAuthFile string
@ -54,11 +54,11 @@ var (
// Webroot to define the base path for the UI and API // Webroot to define the base path for the UI and API
Webroot = "/" Webroot = "/"
// SMTPSSLCert file // SMTPTLSCert file
SMTPSSLCert string SMTPTLSCert string
// SMTPSSLKey file // SMTPTLSKey file
SMTPSSLKey string SMTPTLSKey string
// SMTPAuthFile for SMTP authentication // SMTPAuthFile for SMTP authentication
SMTPAuthFile string SMTPAuthFile string
@ -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
@ -120,31 +126,31 @@ func VerifyConfig() error {
UIAuth = a UIAuth = a
} }
if UISSLCert != "" && UISSLKey == "" || UISSLCert == "" && UISSLKey != "" { if UITLSCert != "" && UITLSKey == "" || UITLSCert == "" && UITLSKey != "" {
return errors.New("You must provide both a UI SSL certificate and a key") return errors.New("You must provide both a UI TLS certificate and a key")
} }
if UISSLCert != "" { if UITLSCert != "" {
if !isFile(UISSLCert) { if !isFile(UITLSCert) {
return fmt.Errorf("SSL certificate not found: %s", UISSLCert) return fmt.Errorf("TLS certificate not found: %s", UITLSCert)
} }
if !isFile(UISSLKey) { if !isFile(UITLSKey) {
return fmt.Errorf("SSL key not found: %s", UISSLKey) return fmt.Errorf("TLS key not found: %s", UITLSKey)
} }
} }
if SMTPSSLCert != "" && SMTPSSLKey == "" || SMTPSSLCert == "" && SMTPSSLKey != "" { if SMTPTLSCert != "" && SMTPTLSKey == "" || SMTPTLSCert == "" && SMTPTLSKey != "" {
return errors.New("You must provide both an SMTP SSL certificate and a key") return errors.New("You must provide both an SMTP TLS certificate and a key")
} }
if SMTPSSLCert != "" { if SMTPTLSCert != "" {
if !isFile(SMTPSSLCert) { if !isFile(SMTPTLSCert) {
return fmt.Errorf("SMTP SSL certificate not found: %s", SMTPSSLCert) return fmt.Errorf("SMTP TLS certificate not found: %s", SMTPTLSCert)
} }
if !isFile(SMTPSSLKey) { if !isFile(SMTPTLSKey) {
return fmt.Errorf("SMTP SSL key not found: %s", SMTPSSLKey) return fmt.Errorf("SMTP TLS key not found: %s", SMTPTLSKey)
} }
} }
@ -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 SMTPTLSCert == "" && (SMTPAuthFile != "" || SMTPAuthAcceptAny) && !SMTPAuthAllowInsecure {
return errors.New("SMTP authentication requires TLS 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)

View File

@ -3,18 +3,19 @@ package server
import ( import (
"compress/gzip" "compress/gzip"
"embed" "embed"
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/server/apiv1"
"github.com/axllent/mailpit/server/handlers"
"github.com/axllent/mailpit/server/websockets"
"github.com/axllent/mailpit/utils/logger"
"github.com/gorilla/mux"
"io" "io"
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"strings" "strings"
"sync/atomic" "sync/atomic"
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/server/apiv1"
"github.com/axllent/mailpit/server/handlers"
"github.com/axllent/mailpit/server/websockets"
"github.com/axllent/mailpit/utils/logger"
"github.com/gorilla/mux"
) )
//go:embed ui //go:embed ui
@ -62,9 +63,9 @@ func Listen() {
// Mark the application here as ready // Mark the application here as ready
isReady.Store(true) isReady.Store(true)
if config.UISSLCert != "" && config.UISSLKey != "" { if config.UITLSCert != "" && config.UITLSKey != "" {
logger.Log().Infof("[http] starting secure server on https://%s%s", config.HTTPListen, config.Webroot) logger.Log().Infof("[http] starting secure server on https://%s%s", config.HTTPListen, config.Webroot)
logger.Log().Fatal(http.ListenAndServeTLS(config.HTTPListen, config.UISSLCert, config.UISSLKey, nil)) logger.Log().Fatal(http.ListenAndServeTLS(config.HTTPListen, config.UITLSCert, config.UITLSKey, nil))
} else { } else {
logger.Log().Infof("[http] starting server on http://%s%s", config.HTTPListen, config.Webroot) logger.Log().Infof("[http] starting server on http://%s%s", config.HTTPListen, config.Webroot)
logger.Log().Fatal(http.ListenAndServe(config.HTTPListen, nil)) logger.Log().Fatal(http.ListenAndServe(config.HTTPListen, nil))

View File

@ -1,3 +1,4 @@
// Package smtpd is the SMTP daemon
package smtpd package smtpd
import ( import (
@ -5,6 +6,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 +35,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 +86,30 @@ func listenAndServe(addr string, handler smtpd.Handler, authHandler smtpd.AuthHa
AuthRequired: false, AuthRequired: false,
} }
if config.SMTPAuthFile != "" { if config.SMTPAuthAllowInsecure {
srv.AuthHandler = authHandler srv.AuthMechs = map[string]bool{"CRAM-MD5": false, "PLAIN": true, "LOGIN": true}
srv.AuthRequired = true
} }
if config.SMTPSSLCert != "" { if config.SMTPAuthFile != "" {
err := srv.ConfigureTLS(config.SMTPSSLCert, config.SMTPSSLKey) srv.AuthMechs = map[string]bool{"CRAM-MD5": false, "PLAIN": true, "LOGIN": true}
if err != nil { srv.AuthHandler = authHandler
srv.AuthRequired = true
} else if config.SMTPAuthAcceptAny {
srv.AuthMechs = map[string]bool{"CRAM-MD5": false, "PLAIN": true, "LOGIN": true}
srv.AuthHandler = authHandlerAny
}
if config.SMTPTLSCert != "" {
if err := srv.ConfigureTLS(config.SMTPTLSCert, config.SMTPTLSKey); 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]
}

View File

@ -381,7 +381,8 @@ func Search(search string, start, limit int) ([]MessageSummary, error) {
return results, err return results, err
} }
// GetMessage returns a data.Message generated from the mailbox_data collection. // GetMessage returns a Message generated from the mailbox_data collection.
// If the message lacks a date header, then the received datetime is used.
func GetMessage(id string) (*Message, error) { func GetMessage(id string) (*Message, error) {
raw, err := GetMessageRaw(id) raw, err := GetMessageRaw(id)
if err != nil { if err != nil {
@ -403,7 +404,35 @@ func GetMessage(id string) (*Message, error) {
from = &mail.Address{Name: env.GetHeader("From")} from = &mail.Address{Name: env.GetHeader("From")}
} }
date, _ := env.Date() date, err := env.Date()
if err != nil {
// return received datetime when message does not contain a date header
q := sqlf.From("mailbox").
Select(`Data`).
OrderBy("Sort DESC").
Where(`ID = ?`, id)
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
var summary string
em := MessageSummary{}
if err := row.Scan(&summary); err != nil {
logger.Log().Error(err)
return
}
if err := json.Unmarshal([]byte(summary), &em); err != nil {
logger.Log().Error(err)
return
}
logger.Log().Debugf("[db] %s does not contain a date header, using received datetime", id)
date = em.Created
}); err != nil {
logger.Log().Error(err)
}
}
obj := Message{ obj := Message{
ID: id, ID: id,