Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dmarc: fix handling if multiple records #73

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 39 additions & 21 deletions dmarc/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,38 @@ func LookupWithOptions(domain string, options *LookupOptions) (*Record, error) {
}
return nil, errors.New("dmarc: failed to lookup TXT record: " + err.Error())
}

// net.LookupTXT will concatenate strings contained in a single TXT record.
// In other words, net.LookupTXT returns one entry per TXT record, even if
// a record contains multiple strings.
if len(txts) == 0 {
return nil, ErrNoPolicy
}

// Long keys are split in multiple parts
txt := strings.Join(txts, "")
return Parse(txt)
// RFC 6376:
// Records that do not start with a "v=" tag that identifies the
// current version of DMARC are discarded.
for _, record := range txts {
rec, err := Parse(record)
if err != nil {
if err.Error() != "dmarc: not a DMARC1 record" {
return nil, err
}
} else {
return rec, nil
}
}

return nil, ErrNoPolicy
}

func Parse(txt string) (*Record, error) {
params, err := parseParams(txt)
if err != nil {
return nil, err
}
var err error = nil
params := parseParams(txt)

if params["v"] != "DMARC1" {
return nil, errors.New("dmarc: unsupported DMARC version")
v, ok := params["v"]
if !ok || v != "DMARC1" {
return nil, errors.New("dmarc: not a DMARC1 record")
}

rec := new(Record)
Expand Down Expand Up @@ -157,21 +172,24 @@ func Parse(txt string) (*Record, error) {
return rec, nil
}

func parseParams(s string) (map[string]string, error) {
pairs := strings.Split(s, ";")
func parseParams(s string) map[string]string {
tagSpecs := strings.Split(s, ";")
params := make(map[string]string)
for _, s := range pairs {
kv := strings.SplitN(s, "=", 2)
if len(kv) != 2 {
if strings.TrimSpace(s) == "" {
continue
}
return params, errors.New("dmarc: malformed params")
}

params[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
for _, tagSpec := range tagSpecs {
tagName, tagValue, ok := strings.Cut(tagSpec, "=")
// RFC 6376:
// Syntax errors in the remainder of the record SHOULD be discarded in
// favor of default values (if any) or ignored outright.
if ok {
// RFC 6376:
// Note that WSP is allowed anywhere around tags. In particular, any
// WSP after the "=" and any WSP before the terminating ";" is not
// part of the value; however, WSP inside the value is significant.
params[strings.TrimSpace(tagName)] = strings.TrimSpace(tagValue)
}
}
return params, nil
return params
}

func parsePolicy(s, param string) (Policy, error) {
Expand Down