138 lines
3.8 KiB
Go
138 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// config is the resolved environment of PLAN.md section 13.
|
|
type config struct {
|
|
DatabaseURL string
|
|
PrimaryMailHost string
|
|
MailDomain string
|
|
SpoolDir string
|
|
|
|
MasterKey string
|
|
|
|
TLSMode string
|
|
TraefikAcmeDir string
|
|
TraefikAcmeFile string
|
|
CertPollInterval time.Duration
|
|
StaticCertPath string
|
|
StaticCertKeyPath string
|
|
|
|
RspamdURL string
|
|
ClamavAddr string
|
|
|
|
MtaStsMode string
|
|
OutboundDane bool
|
|
OutboundMtaSts bool
|
|
DnssecResolver string
|
|
MaxMessageBytes int64
|
|
|
|
RelayHost string
|
|
RelayPort int
|
|
RelayUser string
|
|
RelayPass string
|
|
|
|
// ArcSealEnabled adds an ARC set to accepted messages. It is off by
|
|
// default because it only earns its keep on a host that forwards mail
|
|
// onward; sealing mail we are the final destination for changes the
|
|
// stored message for no benefit (PLAN.md section 5).
|
|
ArcSealEnabled bool
|
|
|
|
// AcmeEmail and AcmeDirectoryURL configure mode B (PLAN.md section 8.3).
|
|
AcmeEmail string
|
|
AcmeDirectoryURL string
|
|
|
|
CloudflareToken string
|
|
PublicIPv4 string
|
|
PublicIPv6 string
|
|
DnsSyncInterval time.Duration
|
|
}
|
|
|
|
func loadConfig() config {
|
|
return config{
|
|
DatabaseURL: os.Getenv("DATABASE_URL"),
|
|
PrimaryMailHost: envOrDefault("PRIMARY_MAIL_HOST", "mail.localhost"),
|
|
MailDomain: os.Getenv("MAIL_DOMAIN"),
|
|
SpoolDir: envOrDefault("PROTOCOLS_SPOOL_DIR", "/var/lambdamail/spool"),
|
|
|
|
MasterKey: os.Getenv("LAMBDAMAIL_MASTER_KEY"),
|
|
|
|
TLSMode: strings.ToLower(envOrDefault("TLS_MODE", "traefik")),
|
|
TraefikAcmeDir: os.Getenv("TRAEFIK_ACME_DIR"),
|
|
TraefikAcmeFile: envOrDefault("TRAEFIK_ACME_FILENAME", "acme.json"),
|
|
CertPollInterval: time.Duration(envInt("CERT_POLL_INTERVAL_SECONDS", 60)) * time.Second,
|
|
StaticCertPath: os.Getenv("PROTOCOLS_CERT_PATH"),
|
|
StaticCertKeyPath: os.Getenv("PROTOCOLS_KEY_PATH"),
|
|
|
|
RspamdURL: os.Getenv("RSPAMD_URL"),
|
|
ClamavAddr: os.Getenv("CLAMAV_ADDR"),
|
|
|
|
MtaStsMode: envOrDefault("MTA_STS_MODE", "testing"),
|
|
// DANE stays off unless explicitly enabled: PLAN.md section 5.1
|
|
// explains why publishing TLSA under Traefik-managed certificates
|
|
// breaks mail sixty days later.
|
|
OutboundDane: envBool("OUTBOUND_DANE_ENABLED", false),
|
|
OutboundMtaSts: envBool("OUTBOUND_MTA_STS_ENABLED", true),
|
|
DnssecResolver: envOrDefault("DNSSEC_RESOLVER", "1.1.1.1:53"),
|
|
MaxMessageBytes: int64(envInt("MAX_MESSAGE_BYTES", 52428800)),
|
|
|
|
RelayHost: os.Getenv("RELAY_HOST"),
|
|
RelayPort: envInt("RELAY_PORT", 587),
|
|
RelayUser: os.Getenv("RELAY_USER"),
|
|
RelayPass: os.Getenv("RELAY_PASS"),
|
|
|
|
ArcSealEnabled: envBool("ARC_SEAL_ENABLED", false),
|
|
|
|
AcmeEmail: os.Getenv("ACME_EMAIL"),
|
|
AcmeDirectoryURL: os.Getenv("ACME_DIRECTORY_URL"),
|
|
|
|
CloudflareToken: os.Getenv("CLOUDFLARE_API_TOKEN"),
|
|
PublicIPv4: os.Getenv("PUBLIC_IPV4"),
|
|
PublicIPv6: os.Getenv("PUBLIC_IPV6"),
|
|
// PLAN.md section 7.5 reconciles every six hours to detect drift.
|
|
DnsSyncInterval: time.Duration(envInt("DNS_SYNC_INTERVAL_SECONDS", 6*60*60)) * time.Second,
|
|
}
|
|
}
|
|
|
|
// domainOfMailHost derives the mail domain from the mail host when it was not
|
|
// configured explicitly: "mail.example.com" yields "example.com".
|
|
func (c config) domain() string {
|
|
if c.MailDomain != "" {
|
|
return c.MailDomain
|
|
}
|
|
if _, rest, found := strings.Cut(c.PrimaryMailHost, "."); found {
|
|
return rest
|
|
}
|
|
return c.PrimaryMailHost
|
|
}
|
|
|
|
func envOrDefault(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envInt(key string, fallback int) int {
|
|
if v := os.Getenv(key); v != "" {
|
|
if parsed, err := strconv.Atoi(v); err == nil {
|
|
return parsed
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envBool(key string, fallback bool) bool {
|
|
if v := os.Getenv(key); v != "" {
|
|
if parsed, err := strconv.ParseBool(v); err == nil {
|
|
return parsed
|
|
}
|
|
}
|
|
return fallback
|
|
}
|