mirror of
https://github.com/axllent/mailpit.git
synced 2025-05-15 22:16:44 +02:00
291 lines
7.7 KiB
Go
291 lines
7.7 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/mail"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/axllent/mailpit/internal/logger"
|
|
"github.com/axllent/mailpit/internal/smtpd/chaos"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Parse the --max-age value (if set)
|
|
func parseMaxAge() error {
|
|
if MaxAge == "" {
|
|
return nil
|
|
}
|
|
|
|
re := regexp.MustCompile(`^\d+(h|d)$`)
|
|
if !re.MatchString(MaxAge) {
|
|
return fmt.Errorf("max-age must be either <int>h for hours or <int>d for days: %s", MaxAge)
|
|
}
|
|
|
|
if strings.HasSuffix(MaxAge, "h") {
|
|
hours, err := strconv.Atoi(strings.TrimSuffix(MaxAge, "h"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
MaxAgeInHours = hours
|
|
|
|
return nil
|
|
}
|
|
|
|
days, err := strconv.Atoi(strings.TrimSuffix(MaxAge, "d"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
logger.Log().Debugf("[db] auto-deleting messages older than %s", MaxAge)
|
|
|
|
MaxAgeInHours = days * 24
|
|
return nil
|
|
}
|
|
|
|
// Parse the SMTPRelayConfigFile (if set)
|
|
func parseRelayConfig(c string) error {
|
|
if c == "" {
|
|
return nil
|
|
}
|
|
|
|
c = filepath.Clean(c)
|
|
|
|
if !isFile(c) {
|
|
return fmt.Errorf("[relay] configuration not found or readable: %s", c)
|
|
}
|
|
|
|
data, err := os.ReadFile(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := yaml.Unmarshal(data, &SMTPRelayConfig); err != nil {
|
|
return err
|
|
}
|
|
|
|
if SMTPRelayConfig.Host == "" {
|
|
return errors.New("[relay] host not set")
|
|
}
|
|
|
|
// DEPRECATED 2024/03/12
|
|
if SMTPRelayConfig.RecipientAllowlist != "" {
|
|
logger.Log().Warn("[relay] 'recipient-allowlist' is deprecated, use 'allowed-recipients' instead")
|
|
if SMTPRelayConfig.AllowedRecipients == "" {
|
|
SMTPRelayConfig.AllowedRecipients = SMTPRelayConfig.RecipientAllowlist
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate the SMTPRelayConfig (if Host is set)
|
|
func validateRelayConfig() error {
|
|
if SMTPRelayConfig.Host == "" {
|
|
return nil
|
|
}
|
|
|
|
if SMTPRelayConfig.Port == 0 {
|
|
SMTPRelayConfig.Port = 25 // default
|
|
}
|
|
|
|
SMTPRelayConfig.Auth = strings.ToLower(SMTPRelayConfig.Auth)
|
|
|
|
if SMTPRelayConfig.Auth == "" || SMTPRelayConfig.Auth == "none" || SMTPRelayConfig.Auth == "false" {
|
|
SMTPRelayConfig.Auth = "none"
|
|
} else if SMTPRelayConfig.Auth == "plain" {
|
|
if SMTPRelayConfig.Username == "" || SMTPRelayConfig.Password == "" {
|
|
return fmt.Errorf("[relay] host username or password not set for PLAIN authentication")
|
|
}
|
|
} else if SMTPRelayConfig.Auth == "login" {
|
|
SMTPRelayConfig.Auth = "login"
|
|
if SMTPRelayConfig.Username == "" || SMTPRelayConfig.Password == "" {
|
|
return fmt.Errorf("[relay] host username or password not set for LOGIN authentication")
|
|
}
|
|
} else if strings.HasPrefix(SMTPRelayConfig.Auth, "cram") {
|
|
SMTPRelayConfig.Auth = "cram-md5"
|
|
if SMTPRelayConfig.Username == "" || SMTPRelayConfig.Secret == "" {
|
|
return fmt.Errorf("[relay] host username or secret not set for CRAM-MD5 authentication")
|
|
}
|
|
} else {
|
|
return fmt.Errorf("[relay] authentication method not supported: %s", SMTPRelayConfig.Auth)
|
|
}
|
|
|
|
if SMTPRelayConfig.AllowedRecipients != "" {
|
|
re, err := regexp.Compile(SMTPRelayConfig.AllowedRecipients)
|
|
if err != nil {
|
|
return fmt.Errorf("[relay] failed to compile recipient allowlist regexp: %s", err.Error())
|
|
}
|
|
|
|
SMTPRelayConfig.AllowedRecipientsRegexp = re
|
|
logger.Log().Infof("[relay] recipient allowlist is active with the following regexp: %s", SMTPRelayConfig.AllowedRecipients)
|
|
}
|
|
|
|
if SMTPRelayConfig.BlockedRecipients != "" {
|
|
re, err := regexp.Compile(SMTPRelayConfig.BlockedRecipients)
|
|
if err != nil {
|
|
return fmt.Errorf("[relay] failed to compile recipient blocklist regexp: %s", err.Error())
|
|
}
|
|
|
|
SMTPRelayConfig.BlockedRecipientsRegexp = re
|
|
logger.Log().Infof("[relay] recipient blocklist is active with the following regexp: %s", SMTPRelayConfig.BlockedRecipients)
|
|
}
|
|
|
|
if SMTPRelayConfig.OverrideFrom != "" {
|
|
m, err := mail.ParseAddress(SMTPRelayConfig.OverrideFrom)
|
|
if err != nil {
|
|
return fmt.Errorf("[relay] override-from is not a valid email address: %s", SMTPRelayConfig.OverrideFrom)
|
|
}
|
|
|
|
SMTPRelayConfig.OverrideFrom = m.Address
|
|
}
|
|
|
|
if SMTPRelayConfig.STARTTLS && SMTPRelayConfig.TLS {
|
|
return fmt.Errorf("[relay] TLS & STARTTLS cannot be required together")
|
|
}
|
|
|
|
ReleaseEnabled = true
|
|
|
|
logger.Log().Infof("[relay] enabling message relaying via %s:%d", SMTPRelayConfig.Host, SMTPRelayConfig.Port)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Parse the SMTPForwardConfigFile (if set)
|
|
func parseForwardConfig(c string) error {
|
|
if c == "" {
|
|
return nil
|
|
}
|
|
|
|
c = filepath.Clean(c)
|
|
|
|
if !isFile(c) {
|
|
return fmt.Errorf("[forward] configuration not found or readable: %s", c)
|
|
}
|
|
|
|
data, err := os.ReadFile(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := yaml.Unmarshal(data, &SMTPForwardConfig); err != nil {
|
|
return err
|
|
}
|
|
|
|
if SMTPForwardConfig.Host == "" {
|
|
return errors.New("[forward] host not set")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate the SMTPForwardConfig (if Host is set)
|
|
func validateForwardConfig() error {
|
|
if SMTPForwardConfig.Host == "" {
|
|
return nil
|
|
}
|
|
|
|
if SMTPForwardConfig.Port == 0 {
|
|
SMTPForwardConfig.Port = 25 // default
|
|
}
|
|
|
|
SMTPForwardConfig.Auth = strings.ToLower(SMTPForwardConfig.Auth)
|
|
|
|
if SMTPForwardConfig.Auth == "" || SMTPForwardConfig.Auth == "none" || SMTPForwardConfig.Auth == "false" {
|
|
SMTPForwardConfig.Auth = "none"
|
|
} else if SMTPForwardConfig.Auth == "plain" {
|
|
if SMTPForwardConfig.Username == "" || SMTPForwardConfig.Password == "" {
|
|
return fmt.Errorf("[forward] host username or password not set for PLAIN authentication")
|
|
}
|
|
} else if SMTPForwardConfig.Auth == "login" {
|
|
SMTPForwardConfig.Auth = "login"
|
|
if SMTPForwardConfig.Username == "" || SMTPForwardConfig.Password == "" {
|
|
return fmt.Errorf("[forward] host username or password not set for LOGIN authentication")
|
|
}
|
|
} else if strings.HasPrefix(SMTPForwardConfig.Auth, "cram") {
|
|
SMTPForwardConfig.Auth = "cram-md5"
|
|
if SMTPForwardConfig.Username == "" || SMTPForwardConfig.Secret == "" {
|
|
return fmt.Errorf("[forward] host username or secret not set for CRAM-MD5 authentication")
|
|
}
|
|
} else {
|
|
return fmt.Errorf("[forward] authentication method not supported: %s", SMTPForwardConfig.Auth)
|
|
}
|
|
|
|
if SMTPForwardConfig.To == "" {
|
|
return errors.New("[forward] To addresses missing")
|
|
}
|
|
|
|
to := []string{}
|
|
addresses := strings.Split(SMTPForwardConfig.To, ",")
|
|
for _, a := range addresses {
|
|
a = strings.TrimSpace(a)
|
|
m, err := mail.ParseAddress(a)
|
|
if err != nil {
|
|
return fmt.Errorf("[forward] To address is not a valid email address: %s", a)
|
|
}
|
|
to = append(to, m.Address)
|
|
}
|
|
|
|
if len(to) == 0 {
|
|
return errors.New("[forward] no valid To addresses found")
|
|
}
|
|
|
|
// overwrite the To field with the cleaned up list
|
|
SMTPForwardConfig.To = strings.Join(to, ",")
|
|
|
|
if SMTPForwardConfig.OverrideFrom != "" {
|
|
m, err := mail.ParseAddress(SMTPForwardConfig.OverrideFrom)
|
|
if err != nil {
|
|
return fmt.Errorf("[forward] override-from is not a valid email address: %s", SMTPForwardConfig.OverrideFrom)
|
|
}
|
|
|
|
SMTPForwardConfig.OverrideFrom = m.Address
|
|
}
|
|
|
|
if SMTPForwardConfig.STARTTLS && SMTPForwardConfig.TLS {
|
|
return fmt.Errorf("[forward] TLS & STARTTLS cannot be required together")
|
|
}
|
|
|
|
logger.Log().Infof("[forward] enabling message forwarding to %s via %s:%d", SMTPForwardConfig.To, SMTPForwardConfig.Host, SMTPForwardConfig.Port)
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseChaosTriggers() error {
|
|
if ChaosTriggers == "" {
|
|
return nil
|
|
}
|
|
|
|
re := regexp.MustCompile(`^([a-zA-Z0-0]+):(\d\d\d):(\d+(\.\d)?)$`)
|
|
|
|
parts := strings.Split(ChaosTriggers, ",")
|
|
for _, p := range parts {
|
|
p = strings.TrimSpace(p)
|
|
if !re.MatchString(p) {
|
|
return fmt.Errorf("invalid argument: %s", p)
|
|
}
|
|
|
|
matches := re.FindAllStringSubmatch(p, 1)
|
|
key := matches[0][1]
|
|
errorCode, err := strconv.Atoi(matches[0][2])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
probability, err := strconv.Atoi(matches[0][3])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := chaos.Set(key, errorCode, probability); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|