github.com/philhug/dnscontrol@v0.2.4-0.20180625181521-921fa9849001/pkg/spflib/parse.go (about) 1 package spflib 2 3 import ( 4 "fmt" 5 "strings" 6 7 "bytes" 8 9 "io" 10 11 "github.com/pkg/errors" 12 ) 13 14 // SPFRecord stores the parts of an SPF record. 15 type SPFRecord struct { 16 Parts []*SPFPart 17 } 18 19 // Lookups returns the number of DNS lookups required by s. 20 func (s *SPFRecord) Lookups() int { 21 count := 0 22 for _, p := range s.Parts { 23 if p.IsLookup { 24 count++ 25 } 26 if p.IncludeRecord != nil { 27 count += p.IncludeRecord.Lookups() 28 } 29 } 30 return count 31 } 32 33 // SPFPart stores a part of an SPF record, with attributes. 34 type SPFPart struct { 35 Text string 36 IsLookup bool 37 IncludeRecord *SPFRecord 38 IncludeDomain string 39 } 40 41 var qualifiers = map[byte]bool{ 42 '?': true, 43 '~': true, 44 '-': true, 45 '+': true, 46 } 47 48 // Parse parses a raw SPF record. 49 func Parse(text string, dnsres Resolver) (*SPFRecord, error) { 50 if !strings.HasPrefix(text, "v=spf1 ") { 51 return nil, errors.Errorf("Not an spf record") 52 } 53 parts := strings.Split(text, " ") 54 rec := &SPFRecord{} 55 for _, part := range parts[1:] { 56 if part == "" { 57 continue 58 } 59 p := &SPFPart{Text: part} 60 if qualifiers[part[0]] { 61 part = part[1:] 62 } 63 rec.Parts = append(rec.Parts, p) 64 if part == "all" { 65 // all. nothing else matters. 66 break 67 } else if strings.HasPrefix(part, "a") || strings.HasPrefix(part, "mx") { 68 p.IsLookup = true 69 } else if strings.HasPrefix(part, "ip4:") || strings.HasPrefix(part, "ip6:") { 70 // ip address, 0 lookups 71 continue 72 } else if strings.HasPrefix(part, "include:") { 73 p.IsLookup = true 74 p.IncludeDomain = strings.TrimPrefix(part, "include:") 75 if dnsres != nil { 76 subRecord, err := dnsres.GetSPF(p.IncludeDomain) 77 if err != nil { 78 return nil, err 79 } 80 p.IncludeRecord, err = Parse(subRecord, dnsres) 81 if err != nil { 82 return nil, errors.Errorf("In included spf: %s", err) 83 } 84 } 85 } else if strings.HasPrefix(part, "exists:") { 86 p.IsLookup = true 87 } else { 88 return nil, errors.Errorf("Unsupported spf part %s", part) 89 } 90 91 } 92 return rec, nil 93 } 94 95 func dump(rec *SPFRecord, indent string, w io.Writer) { 96 97 fmt.Fprintf(w, "%sTotal Lookups: %d\n", indent, rec.Lookups()) 98 fmt.Fprint(w, indent+"v=spf1") 99 for _, p := range rec.Parts { 100 fmt.Fprint(w, " "+p.Text) 101 } 102 fmt.Fprintln(w) 103 indent += "\t" 104 for _, p := range rec.Parts { 105 if p.IsLookup { 106 fmt.Fprintln(w, indent+p.Text) 107 } 108 if p.IncludeRecord != nil { 109 dump(p.IncludeRecord, indent+"\t", w) 110 } 111 } 112 } 113 114 // Print prints an SPFRecord. 115 func (s *SPFRecord) Print() string { 116 w := &bytes.Buffer{} 117 dump(s, "", w) 118 return w.String() 119 }