169 lines
3.8 KiB
Go
169 lines
3.8 KiB
Go
package dmarc
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"io"
|
|
"fmt"
|
|
"time"
|
|
"strconv"
|
|
"strings"
|
|
"net"
|
|
)
|
|
|
|
const AuthResultType_DKIM = "dkim"
|
|
const AuthResultType_SPF = "spf"
|
|
|
|
|
|
type DateRange struct {
|
|
Begin time.Time
|
|
End time.Time
|
|
}
|
|
|
|
// Unmarshal the DateRange element into time.Time objects.
|
|
// Yahoo tends to put some extra whitespace behind its
|
|
// timestamps, so we trim that first.
|
|
func (dr *DateRange) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
rangeStr := struct {
|
|
Begin string `xml:"begin"`
|
|
End string `xml:"end"`
|
|
}{}
|
|
d.DecodeElement(&rangeStr, &start)
|
|
|
|
begin, err := strconv.Atoi(strings.TrimSpace(rangeStr.Begin))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
end, err := strconv.Atoi(strings.TrimSpace(rangeStr.End))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dr.Begin = time.Unix(int64(begin), 0).UTC()
|
|
dr.End = time.Unix(int64(end), 0).UTC()
|
|
return nil
|
|
}
|
|
|
|
type ReportMetadata struct {
|
|
OrgName string `xml:"org_name"`
|
|
Email string `xml:"email"`
|
|
ExtraContact string `xml:"extra_contact_info"`
|
|
ReportId string `xml:"report_id"`
|
|
DateRange DateRange `xml:"date_range"`
|
|
}
|
|
|
|
type PolicyPublished struct {
|
|
Domain string `xml:"domain"`
|
|
Adkim string `xml:"adkim"`
|
|
Aspf string `xml:"aspf"`
|
|
Policy string `xml:"p"`
|
|
SubdomainPolicy string `xml:"sp"`
|
|
Percentage int `xml:"pct"`
|
|
}
|
|
|
|
type PolicyEvaluated struct {
|
|
Disposition string `xml:"disposition"`
|
|
Dkim string `xml:"dkim"`
|
|
Spf string `xml:"spf"`
|
|
}
|
|
|
|
type Row struct {
|
|
SourceIp net.IP
|
|
Count int `xml:"count"`
|
|
PolicyEvaluated PolicyEvaluated `xml:"policy_evaluated"`
|
|
}
|
|
|
|
func (r *Row) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
rowAlias := struct {
|
|
SourceIp string `xml:"source_ip"`
|
|
Count int `xml:"count"`
|
|
PolicyEvaluated PolicyEvaluated `xml:"policy_evaluated"`
|
|
}{}
|
|
d.DecodeElement(&rowAlias, &start)
|
|
|
|
r.Count = rowAlias.Count
|
|
r.PolicyEvaluated = rowAlias.PolicyEvaluated
|
|
r.SourceIp = net.ParseIP(rowAlias.SourceIp)
|
|
if r.SourceIp == nil {
|
|
return fmt.Errorf("Could not parse source_ip")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type Identifiers struct {
|
|
HeaderFrom string `xml:"header_from"`
|
|
}
|
|
|
|
type AuthResult struct {
|
|
Type string // Either SPF or DKIM
|
|
Domain string `xml:"domain"`
|
|
Result string `xml:"result"`
|
|
}
|
|
|
|
func (ar *AuthResult) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
res := struct {
|
|
XMLName xml.Name
|
|
Domain string `xml:"domain"`
|
|
Result string `xml:"result"`
|
|
}{}
|
|
d.DecodeElement(&res, &start)
|
|
|
|
ar.Domain = res.Domain
|
|
ar.Result = res.Result
|
|
|
|
if res.XMLName.Local != AuthResultType_DKIM && res.XMLName.Local != AuthResultType_SPF {
|
|
return fmt.Errorf("Unrecognized AuthResult type: %s", res.XMLName.Local)
|
|
}
|
|
|
|
ar.Type = res.XMLName.Local
|
|
return nil
|
|
}
|
|
|
|
type AuthResults struct {
|
|
AuthResult []AuthResult `xml:",any"`
|
|
}
|
|
|
|
type Record struct {
|
|
Row Row `xml:"row"`
|
|
Identifiers Identifiers `xml:"identifiers"`
|
|
AuthResults AuthResults `xml:"auth_results"`
|
|
}
|
|
|
|
type FeedbackReport struct {
|
|
Metadata ReportMetadata `xml:"report_metadata"`
|
|
PolicyPublished PolicyPublished `xml:"policy_published"`
|
|
Record []Record `xml:"record"`
|
|
}
|
|
|
|
func ParseReader(xmlFileReader io.Reader) (*FeedbackReport, error) {
|
|
var f *FeedbackReport
|
|
|
|
decoder := xml.NewDecoder(xmlFileReader)
|
|
var inElement string
|
|
|
|
for {
|
|
t, err := decoder.Token()
|
|
if t == nil && err == io.EOF {
|
|
break;
|
|
}
|
|
if t == nil {
|
|
return nil, err
|
|
}
|
|
switch se := t.(type) {
|
|
case xml.StartElement:
|
|
inElement = se.Name.Local
|
|
if inElement == "feedback" {
|
|
err := decoder.DecodeElement(&f, &se)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("Unknown root element: %s", inElement)
|
|
}
|
|
default:
|
|
}
|
|
}
|
|
|
|
return f, nil
|
|
}
|