@@ -51,13 +51,54 @@ func LookupWithOptions(domain string, options *LookupOptions) (*Record, error) {
51
51
}
52
52
return nil , errors .New ("dmarc: failed to lookup TXT record: " + err .Error ())
53
53
}
54
+
55
+ // net.LookupTXT will concatenate strings contained in a single TXT record.
56
+ // In other words, net.LookupTXT returns one entry per TXT record, even if
57
+ // a record contains multiple strings.
54
58
if len (txts ) == 0 {
55
59
return nil , ErrNoPolicy
56
60
}
57
61
58
- // Long keys are split in multiple parts
59
- txt := strings .Join (txts , "" )
60
- return Parse (txt )
62
+ // RFC 6376:
63
+ // Records that do not start with a "v=" tag that identifies the
64
+ // current version of DMARC are discarded.
65
+ dmarcRecords := make ([]string , 0 , len (txts ))
66
+ for _ , record := range txts {
67
+ if IsDmarcRecord (record ) {
68
+ dmarcRecords = append (dmarcRecords , record )
69
+ }
70
+ }
71
+
72
+ if len (dmarcRecords ) != 1 {
73
+ return nil , ErrNoPolicy
74
+ }
75
+
76
+ return Parse (dmarcRecords [0 ])
77
+ }
78
+
79
+ func IsDmarcRecord (txt string ) bool {
80
+ // RFC 6376:
81
+ // DMARC records follow the extensible "tag-value" syntax for DNS-based
82
+ // key records defined in DKIM.
83
+ firstTagSpec , _ , _ := strings .Cut (txt , ";" )
84
+
85
+ tagName , tagValue , found := strings .Cut (firstTagSpec , "=" )
86
+ if ! found {
87
+ return false
88
+ }
89
+
90
+ // RFC 6376:
91
+ // Note that WSP is allowed anywhere around tags. In particular, any
92
+ // WSP after the "=" and any WSP before the terminating ";" is not part
93
+ // of the value; however, WSP inside the value is significant.
94
+ if strings .TrimSpace (tagName ) != "v" {
95
+ return false
96
+ }
97
+ if strings .TrimSpace (tagValue ) != "DMARC1" {
98
+ return false
99
+ }
100
+
101
+ return true
61
102
}
62
103
63
104
func Parse (txt string ) (* Record , error ) {
0 commit comments