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  }