Skip to content

Commit

Permalink
Split Lookup code in a separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
emersion committed Mar 22, 2019
1 parent f1225c8 commit de7a7fe
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 186 deletions.
186 changes: 0 additions & 186 deletions dmarc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@
package dmarc

import (
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -53,184 +48,3 @@ type Record struct {
ReportURIFailure []string // "ruf"
SubdomainPolicy Policy // "sp"
}

type tempFailError string

func (err tempFailError) Error() string {
return "dmarc: " + string(err)
}

// IsTempFail returns true if the error returned by Lookup is a temporary
// failure.
func IsTempFail(err error) bool {
_, ok := err.(tempFailError)
return ok
}

// Lookup queries a DMARC record for a specified domain.
func Lookup(domain string) (*Record, error) {
txts, err := net.LookupTXT("_dmarc." + domain)
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
return nil, tempFailError("TXT record unavailable: " + err.Error())
} else if err != nil {
return nil, errors.New("dmarc: failed to lookup TXT record: " + err.Error())
}

// Long keys are split in multiple parts
txt := strings.Join(txts, "")
params, err := parseParams(txt)
if err != nil {
return nil, err
}

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

rec := new(Record)

p, ok := params["p"]
if !ok {
return nil, errors.New("dmarc: record is missing a 'p' parameter")
}
rec.Policy, err = parsePolicy(p, "p")
if err != nil {
return nil, err
}

if adkim, ok := params["adkim"]; ok {
rec.DKIMAlignment, err = parseAlignmentMode(adkim, "adkim")
if err != nil {
return nil, err
}
}

if aspf, ok := params["aspf"]; ok {
rec.SPFAlignment, err = parseAlignmentMode(aspf, "aspf")
if err != nil {
return nil, err
}
}

if fo, ok := params["fo"]; ok {
rec.FailureOptions, err = parseFailureOptions(fo)
if err != nil {
return nil, err
}
}

if pct, ok := params["pct"]; ok {
i, err := strconv.Atoi(pct)
if err != nil {
return nil, fmt.Errorf("dmarc: invalid parameter 'pct': %v", err)
}
if i < 0 || i > 100 {
return nil, fmt.Errorf("dmarc: invalid parameter 'pct': value %v out of bounds", i)
}
rec.Percent = &i
}

if rf, ok := params["rf"]; ok {
l := strings.Split(rf, ":")
rec.ReportFormat = make([]ReportFormat, len(l))
for i, f := range l {
switch f {
case "afrf":
rec.ReportFormat[i] = ReportFormat(f)
default:
return nil, errors.New("dmarc: invalid parameter 'rf'")
}
}
}

if ri, ok := params["ri"]; ok {
i, err := strconv.Atoi(ri)
if err != nil {
return nil, fmt.Errorf("dmarc: invalid parameter 'ri': %v", err)
}
if i <= 0 {
return nil, fmt.Errorf("dmarc: invalid parameter 'ri': negative or zero duration")
}
rec.ReportInterval = time.Duration(i) * time.Second
}

if rua, ok := params["rua"]; ok {
rec.ReportURIAggregate = parseURIList(rua)
}

if ruf, ok := params["ruf"]; ok {
rec.ReportURIFailure = parseURIList(ruf)
}

if sp, ok := params["sp"]; ok {
rec.SubdomainPolicy, err = parsePolicy(sp, "sp")
if err != nil {
return nil, err
}
}

return rec, nil
}

func parseParams(s string) (map[string]string, error) {
pairs := 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])
}
return params, nil
}

func parsePolicy(s, param string) (Policy, error) {
switch s {
case "none", "quarantine", "reject":
return Policy(s), nil
default:
return "", fmt.Errorf("dmarc: invalid policy for parameter '%v'", param)
}
}

func parseAlignmentMode(s, param string) (AlignmentMode, error) {
switch s {
case "r", "s":
return AlignmentMode(s), nil
default:
return "", fmt.Errorf("dmarc: invalid alignment mode for parameter '%v'", param)
}
}

func parseFailureOptions(s string) (FailureOptions, error) {
l := strings.Split(s, ":")
var opts FailureOptions
for _, o := range l {
switch strings.TrimSpace(o) {
case "0":
opts |= FailureAll
case "1":
opts |= FailureAny
case "d":
opts |= FailureDKIM
case "s":
opts |= FailureSPF
default:
return 0, errors.New("dmarc: invalid failure option in parameter 'fo'")
}
}
return opts, nil
}

func parseURIList(s string) []string {
l := strings.Split(s, ",")
for i, u := range l {
l[i] = strings.TrimSpace(u)
}
return l
}
191 changes: 191 additions & 0 deletions lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package dmarc

import (
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
)

type tempFailError string

func (err tempFailError) Error() string {
return "dmarc: " + string(err)
}

// IsTempFail returns true if the error returned by Lookup is a temporary
// failure.
func IsTempFail(err error) bool {
_, ok := err.(tempFailError)
return ok
}

// Lookup queries a DMARC record for a specified domain.
func Lookup(domain string) (*Record, error) {
txts, err := net.LookupTXT("_dmarc." + domain)
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
return nil, tempFailError("TXT record unavailable: " + err.Error())
} else if err != nil {
return nil, errors.New("dmarc: failed to lookup TXT record: " + err.Error())
}

// Long keys are split in multiple parts
txt := strings.Join(txts, "")
params, err := parseParams(txt)
if err != nil {
return nil, err
}

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

rec := new(Record)

p, ok := params["p"]
if !ok {
return nil, errors.New("dmarc: record is missing a 'p' parameter")
}
rec.Policy, err = parsePolicy(p, "p")
if err != nil {
return nil, err
}

if adkim, ok := params["adkim"]; ok {
rec.DKIMAlignment, err = parseAlignmentMode(adkim, "adkim")
if err != nil {
return nil, err
}
}

if aspf, ok := params["aspf"]; ok {
rec.SPFAlignment, err = parseAlignmentMode(aspf, "aspf")
if err != nil {
return nil, err
}
}

if fo, ok := params["fo"]; ok {
rec.FailureOptions, err = parseFailureOptions(fo)
if err != nil {
return nil, err
}
}

if pct, ok := params["pct"]; ok {
i, err := strconv.Atoi(pct)
if err != nil {
return nil, fmt.Errorf("dmarc: invalid parameter 'pct': %v", err)
}
if i < 0 || i > 100 {
return nil, fmt.Errorf("dmarc: invalid parameter 'pct': value %v out of bounds", i)
}
rec.Percent = &i
}

if rf, ok := params["rf"]; ok {
l := strings.Split(rf, ":")
rec.ReportFormat = make([]ReportFormat, len(l))
for i, f := range l {
switch f {
case "afrf":
rec.ReportFormat[i] = ReportFormat(f)
default:
return nil, errors.New("dmarc: invalid parameter 'rf'")
}
}
}

if ri, ok := params["ri"]; ok {
i, err := strconv.Atoi(ri)
if err != nil {
return nil, fmt.Errorf("dmarc: invalid parameter 'ri': %v", err)
}
if i <= 0 {
return nil, fmt.Errorf("dmarc: invalid parameter 'ri': negative or zero duration")
}
rec.ReportInterval = time.Duration(i) * time.Second
}

if rua, ok := params["rua"]; ok {
rec.ReportURIAggregate = parseURIList(rua)
}

if ruf, ok := params["ruf"]; ok {
rec.ReportURIFailure = parseURIList(ruf)
}

if sp, ok := params["sp"]; ok {
rec.SubdomainPolicy, err = parsePolicy(sp, "sp")
if err != nil {
return nil, err
}
}

return rec, nil
}

func parseParams(s string) (map[string]string, error) {
pairs := 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])
}
return params, nil
}

func parsePolicy(s, param string) (Policy, error) {
switch s {
case "none", "quarantine", "reject":
return Policy(s), nil
default:
return "", fmt.Errorf("dmarc: invalid policy for parameter '%v'", param)
}
}

func parseAlignmentMode(s, param string) (AlignmentMode, error) {
switch s {
case "r", "s":
return AlignmentMode(s), nil
default:
return "", fmt.Errorf("dmarc: invalid alignment mode for parameter '%v'", param)
}
}

func parseFailureOptions(s string) (FailureOptions, error) {
l := strings.Split(s, ":")
var opts FailureOptions
for _, o := range l {
switch strings.TrimSpace(o) {
case "0":
opts |= FailureAll
case "1":
opts |= FailureAny
case "d":
opts |= FailureDKIM
case "s":
opts |= FailureSPF
default:
return 0, errors.New("dmarc: invalid failure option in parameter 'fo'")
}
}
return opts, nil
}

func parseURIList(s string) []string {
l := strings.Split(s, ",")
for i, u := range l {
l[i] = strings.TrimSpace(u)
}
return l
}

0 comments on commit de7a7fe

Please sign in to comment.