8// todo: DMARCPolicy should be named just Policy, but this is causing conflicting types in sherpadoc output. should somehow get the dmarc-prefix only in the sherpadoc.
 
10// Policy as used in DMARC DNS record for "p=" or "sp=".
 
11type DMARCPolicy string
 
16	PolicyEmpty      DMARCPolicy = "" // Only for the optional Record.SubdomainPolicy.
 
17	PolicyNone       DMARCPolicy = "none"
 
18	PolicyQuarantine DMARCPolicy = "quarantine"
 
19	PolicyReject     DMARCPolicy = "reject"
 
22// URI is a destination address for reporting.
 
24	Address string // Should start with "mailto:".
 
25	MaxSize uint64 // Optional maximum message size, subject to Unit.
 
26	Unit    string // "" (b), "k", "m", "g", "t" (case insensitive), unit size, where k is 2^10 etc.
 
29// String returns a string representation of the URI for inclusion in a DMARC
 
31func (u URI) String() string {
 
33	s = strings.ReplaceAll(s, ",", "%2C")
 
34	s = strings.ReplaceAll(s, "!", "%21")
 
36		s += fmt.Sprintf("!%d", u.MaxSize)
 
44// Align specifies the required alignment of a domain name.
 
48	AlignStrict  Align = "s" // Strict requires an exact domain name match.
 
49	AlignRelaxed Align = "r" // Relaxed requires either an exact or subdomain name match.
 
52// Record is a DNS policy or reporting record.
 
56//	v=DMARC1; p=reject; rua=mailto:postmaster@mox.example
 
58	Version                    string      // "v=DMARC1", fixed.
 
59	Policy                     DMARCPolicy // Required, for "p=".
 
60	SubdomainPolicy            DMARCPolicy // Like policy but for subdomains. Optional, for "sp=".
 
61	AggregateReportAddresses   []URI       // Optional, for "rua=". Destination addresses for aggregate reports.
 
62	FailureReportAddresses     []URI       // Optional, for "ruf=". Destination addresses for failure reports.
 
63	ADKIM                      Align       // Alignment: "r" (default) for relaxed or "s" for simple. For "adkim=".
 
64	ASPF                       Align       // Alignment: "r" (default) for relaxed or "s" for simple. For "aspf=".
 
65	AggregateReportingInterval int         // In seconds, default 86400. For "ri="
 
66	FailureReportingOptions    []string    // "0" (default), "1", "d", "s". For "fo=".
 
67	ReportingFormat            []string    // "afrf" (default). For "rf=".
 
68	Percentage                 int         // Between 0 and 100, default 100. For "pct=". Policy applies randomly to this percentage of messages.
 
71// DefaultRecord holds the defaults for a DMARC record.
 
72var DefaultRecord = Record{
 
76	AggregateReportingInterval: 86400,
 
77	FailureReportingOptions:    []string{"0"},
 
78	ReportingFormat:            []string{"afrf"},
 
82// String returns the DMARC record for use as DNS TXT record.
 
83func (r Record) String() string {
 
84	b := &strings.Builder{}
 
85	b.WriteString("v=" + r.Version)
 
88	write := func(do bool, tag, value string) {
 
90			fmt.Fprintf(b, ";%s=%s", tag, value)
 
94	write(r.Policy != "", "p", string(r.Policy))
 
95	write(r.SubdomainPolicy != "", "sp", string(r.SubdomainPolicy))
 
96	if len(r.AggregateReportAddresses) > 0 {
 
97		l := make([]string, len(r.AggregateReportAddresses))
 
98		for i, a := range r.AggregateReportAddresses {
 
101		s := strings.Join(l, ",")
 
102		write(true, "rua", s)
 
104	if len(r.FailureReportAddresses) > 0 {
 
105		l := make([]string, len(r.FailureReportAddresses))
 
106		for i, a := range r.FailureReportAddresses {
 
109		s := strings.Join(l, ",")
 
110		write(true, "ruf", s)
 
112	write(r.ADKIM != "" && r.ADKIM != "r", "adkim", string(r.ADKIM))
 
113	write(r.ASPF != "" && r.ASPF != "r", "aspf", string(r.ASPF))
 
114	write(r.AggregateReportingInterval != DefaultRecord.AggregateReportingInterval, "ri", fmt.Sprintf("%d", r.AggregateReportingInterval))
 
115	if len(r.FailureReportingOptions) > 1 || len(r.FailureReportingOptions) == 1 && r.FailureReportingOptions[0] != "0" {
 
116		write(true, "fo", strings.Join(r.FailureReportingOptions, ":"))
 
118	if len(r.ReportingFormat) > 1 || len(r.ReportingFormat) == 1 && !strings.EqualFold(r.ReportingFormat[0], "afrf") {
 
119		write(true, "rf", strings.Join(r.FailureReportingOptions, ":"))
 
121	write(r.Percentage != 100, "pct", fmt.Sprintf("%d", r.Percentage))