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}