Files
dmarcaggparser/dmarc/report.go
2016-08-14 19:37:43 +02:00

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
}