all repos — navani @ 3d8e721aa152691f68eeeb75ae9bd23dab55b1ba

forlater's primary mail processing service

mail/check.go (view raw)

 1package mail
 2
 3import (
 4	"fmt"
 5	"log"
 6	"net"
 7	"net/mail"
 8	"strings"
 9
10	"blitiri.com.ar/go/spf"
11	"golang.org/x/net/idna"
12)
13
14func verifySPF(ips []net.IP, domain string, from string) (passes []spf.Result, err error) {
15	passes = []spf.Result{}
16	for _, ip := range ips {
17		result, _ := spf.CheckHostWithSender(ip, domain, from)
18		// if err != nil {
19		// 	return nil, fmt.Errorf("check spf: %w\n", err)
20		// }
21		if result == spf.Pass {
22			passes = append(passes, result)
23		}
24	}
25	return passes, nil
26}
27
28func VerifySPF(email string) (bool, error) {
29	e, err := mail.ParseAddress(email)
30	if err != nil {
31		return false, fmt.Errorf("parse address: %w", err)
32	}
33	domain := strings.Split(e.Address, "@")[1]
34
35	domain, err = idna.ToASCII(domain)
36	if err != nil {
37		return false, fmt.Errorf("to ascii: %w\n", err)
38	}
39
40	mxs, err := net.LookupMX(domain)
41	if err != nil {
42		return false, fmt.Errorf("mx lookup: %w\n", err)
43	}
44
45	mxPasses := []spf.Result{}
46	// Check MX IPs against SPF record.
47	for _, mx := range mxs {
48		ips, err := net.LookupIP(mx.Host)
49		if err != nil {
50			return false, fmt.Errorf("ip lookup: %w\n", err)
51		}
52		passes, err := verifySPF(ips, domain, e.Address)
53		if err != nil {
54			return false, fmt.Errorf("mx spf: %w\n", err)
55		}
56		mxPasses = append(mxPasses, passes...)
57	}
58
59	if len(mxPasses) == 0 {
60		// Check domain IP against SPF record.
61		ips, err := net.LookupIP(domain)
62		if err != nil {
63			return false, fmt.Errorf("ip lookup: %w\n", err)
64		}
65		passes, err := verifySPF(ips, domain, e.Address)
66		if err != nil {
67			return false, fmt.Errorf("domain spf: %w\n", err)
68		}
69		// If both MX IP and domain IP fail SPF, we return false.
70		if len(passes) == 0 {
71			return false, nil
72		}
73	}
74
75	log.Printf("spf passed: %s\n", email)
76	return true, nil
77}