#18: implement SMTP Submission (587/465), SASL PLAIN/LOGIN auth and preflight diagnostics
This commit is contained in:
1 parent
7249419efc
commit
d56f29d084
70 files changed
+5163
-659
No files matched your search
@@ -0,0 +1,137 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user