diff options
Diffstat (limited to 'internal/config/config.go')
| -rw-r--r-- | internal/config/config.go | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/internal/config/config.go b/internal/config/config.go index 5ee60ea..faf2a93 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,6 +3,7 @@ package config import ( "fmt" "net" + "net/mail" "os" "strconv" "strings" @@ -23,11 +24,16 @@ type SMTPConfig struct { Port int Recipient string EnvelopeFrom string + Sender string + Username string + Password string + PasswordFile string HELO string TLSServerName string RequireTLS bool ImplicitTLS bool Timeout time.Duration + ProbeInterval time.Duration DryRun bool } @@ -55,6 +61,8 @@ type SecurityConfig struct { MessageIDDomain string IdenticonsCLI string RequireFace bool + CSRFKeyFile string + CSRFKey []byte } func Load() (Config, error) { @@ -66,11 +74,15 @@ func Load() (Config, error) { Port: envInt("N2U_SMTP_PORT", "M2U_SMTP_PORT", 25), Recipient: env("N2U_SMTP_RECIPIENT", "M2U_SMTP_RECIPIENT", "mail2news@mail2news.tcpreset.net"), EnvelopeFrom: env("N2U_SMTP_ENVELOPE_FROM", "M2U_SMTP_ENVELOPE_FROM", "n2usenet@virebent.art"), + Sender: envAny("n2usenet@virebent.art", "N2U_SMTP_SENDER", "M2U_SMTP_SENDER", "N2U_SMTP_HEADER_FROM", "M2U_SMTP_HEADER_FROM"), + Username: env("N2U_SMTP_USERNAME", "M2U_SMTP_USERNAME", ""), + PasswordFile: env("N2U_SMTP_PASSWORD_FILE", "M2U_SMTP_PASSWORD_FILE", ""), HELO: env("N2U_SMTP_HELO", "M2U_SMTP_HELO", "n2usenet.virebent.art"), TLSServerName: env("N2U_SMTP_TLS_SERVER_NAME", "M2U_SMTP_TLS_SERVER_NAME", "mail.tcpreset.net"), RequireTLS: envBool("N2U_SMTP_REQUIRE_TLS", "M2U_SMTP_REQUIRE_TLS", true), ImplicitTLS: envBool("N2U_SMTP_IMPLICIT_TLS", "M2U_SMTP_IMPLICIT_TLS", false), Timeout: envDuration("N2U_SMTP_TIMEOUT", "M2U_SMTP_TIMEOUT", 90*time.Second), + ProbeInterval: envDuration("N2U_TRANSPORT_PROBE_INTERVAL", "M2U_TRANSPORT_PROBE_INTERVAL", 15*time.Minute), DryRun: envBool("N2U_DRY_RUN", "M2U_DRY_RUN", false), }, Nym: NymConfig{ @@ -96,8 +108,23 @@ func Load() (Config, error) { MessageIDDomain: env("N2U_MESSAGE_ID_DOMAIN", "M2U_MESSAGE_ID_DOMAIN", "n2usenet.virebent.art"), IdenticonsCLI: env("N2U_IDENTICONS_CLI", "M2U_IDENTICONS_CLI", "/usr/local/bin/identicons-cli"), RequireFace: envBool("N2U_REQUIRE_FACE", "M2U_REQUIRE_FACE", true), + CSRFKeyFile: env("N2U_CSRF_KEY_FILE", "M2U_CSRF_KEY_FILE", ""), }, } + if cfg.SMTP.PasswordFile != "" { + password, err := readSecretFile(cfg.SMTP.PasswordFile) + if err != nil { + return Config{}, fmt.Errorf("read SMTP password file: %w", err) + } + cfg.SMTP.Password = password + } + if cfg.Security.CSRFKeyFile != "" { + key, err := readSecretFile(cfg.Security.CSRFKeyFile) + if err != nil { + return Config{}, fmt.Errorf("read CSRF key file: %w", err) + } + cfg.Security.CSRFKey = []byte(key) + } if err := cfg.Validate(); err != nil { return Config{}, err } @@ -120,9 +147,22 @@ func (c Config) Validate() error { if c.SMTP.EnvelopeFrom == "" { return fmt.Errorf("N2U_SMTP_ENVELOPE_FROM is required") } + sender, err := mail.ParseAddress(c.SMTP.Sender) + if err != nil || sender.Address == "" { + return fmt.Errorf("N2U_SMTP_SENDER must be a valid email address") + } + if (c.SMTP.Username == "") != (c.SMTP.Password == "") { + return fmt.Errorf("N2U_SMTP_USERNAME and N2U_SMTP_PASSWORD_FILE must be configured together") + } + if c.SMTP.Username != "" && !c.SMTP.RequireTLS && !c.SMTP.ImplicitTLS { + return fmt.Errorf("SMTP authentication requires TLS") + } if c.SMTP.RequireTLS && c.SMTP.TLSServerName == "" { return fmt.Errorf("N2U_SMTP_TLS_SERVER_NAME is required when TLS is required") } + if c.SMTP.ProbeInterval < time.Minute { + return fmt.Errorf("N2U_TRANSPORT_PROBE_INTERVAL must be at least 1m") + } if c.Nym.Enabled { if _, _, err := net.SplitHostPort(c.Nym.SocksAddr); err != nil { return fmt.Errorf("invalid N2U_NYM_SOCKS: %w", err) @@ -146,6 +186,9 @@ func (c Config) Validate() error { if c.Security.RequireFace && c.Security.IdenticonsCLI == "" { return fmt.Errorf("N2U_IDENTICONS_CLI is required when N2U_REQUIRE_FACE=true") } + if c.Security.CSRFKeyFile != "" && len(c.Security.CSRFKey) < 32 { + return fmt.Errorf("N2U_CSRF_KEY_FILE must contain at least 32 bytes") + } return nil } @@ -157,6 +200,15 @@ func env(primary, legacy, fallback string) string { return v } +func envAny(fallback string, keys ...string) string { + for _, key := range keys { + if value := strings.TrimSpace(os.Getenv(key)); value != "" { + return value + } + } + return fallback +} + func envBool(primary, legacy string, fallback bool) bool { v, ok := envValue(primary, legacy) if !ok { @@ -207,3 +259,15 @@ func envValue(primary, legacy string) (string, bool) { } return "", false } + +func readSecretFile(path string) (string, error) { + raw, err := os.ReadFile(path) + if err != nil { + return "", err + } + value := strings.TrimSpace(string(raw)) + if value == "" { + return "", fmt.Errorf("file is empty") + } + return value, nil +} |
