package mailrelay import ( "bytes" "fmt" "net/mail" "strings" ) // Policy enforces the From-header domain allowlist. // // Per the spike (§5/§9) the check is on the From *header* domain, not the SMTP // envelope MAIL FROM — Resend itself validates the header domain (the unverified- // domain 550 lands at DATA, after MAIL/RCPT are accepted), so the relay must check // the same thing. The recommendation is validate-and-REJECT (never silently rewrite), // so a misconfigured app surfaces loudly instead of having its From re-stamped. type Policy struct { allowed map[string]bool } // NewPolicy builds a From-domain allowlist (case-insensitive). An empty list // defaults to {"felhom.eu"} (the one verified Resend domain). func NewPolicy(domains []string) *Policy { p := &Policy{allowed: make(map[string]bool)} if len(domains) == 0 { domains = []string{"felhom.eu"} } for _, d := range domains { d = strings.ToLower(strings.TrimSpace(d)) if d != "" { p.allowed[d] = true } } return p } // FromDomain parses the From *header* of a raw MIME message and returns the // lowercased domain of the first address. It reads only the headers — the body is // left untouched (the relay forwards raw bytes; this never re-serialises the message). func FromDomain(raw []byte) (string, error) { msg, err := mail.ReadMessage(bytes.NewReader(raw)) if err != nil { return "", fmt.Errorf("parsing message headers: %w", err) } from := strings.TrimSpace(msg.Header.Get("From")) if from == "" { return "", fmt.Errorf("missing From header") } addr, err := mail.ParseAddress(from) if err != nil { // Tolerate a From that is actually an address *list* (rare, but valid). list, lerr := mail.ParseAddressList(from) if lerr != nil || len(list) == 0 { return "", fmt.Errorf("parsing From address %q: %w", from, err) } addr = list[0] } at := strings.LastIndex(addr.Address, "@") if at < 0 || at == len(addr.Address)-1 { return "", fmt.Errorf("From address %q has no domain", addr.Address) } return strings.ToLower(addr.Address[at+1:]), nil } // Check returns nil iff the message's From-header domain is in the allowlist. // A parse failure is treated as a rejection (fail-closed): an app that can't be // parsed shouldn't reach Resend. func (p *Policy) Check(raw []byte) error { dom, err := FromDomain(raw) if err != nil { return err } if !p.allowed[dom] { return fmt.Errorf("From domain %q not in allowlist", dom) } return nil } // Allowed reports whether a bare domain is in the allowlist (inspection/tests). func (p *Policy) Allowed(domain string) bool { return p.allowed[strings.ToLower(strings.TrimSpace(domain))] }