Skip to content

Commit

Permalink
Fix incorrect parsing of auth_results
Browse files Browse the repository at this point in the history
The DMARC spec allows for zero or more DKIM results and one or more SPF
results.  Prior to this commit, we only allowed for a single result for
each type.  Update the dmarc.AuthResults struct to use a slice for each
type.

For reference, see the definition of AuthResultType in Appendix C of RFC
7489.

This change in the data model required updates to the Go templates.  The
two HTML templates are almost exactly the same, so pull the common body
out into a separate const variable to ease maintenance.

Fixes tierpod#45
  • Loading branch information
moorereason committed Apr 13, 2024
1 parent e47ca16 commit 7f3a0a5
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 208 deletions.
239 changes: 71 additions & 168 deletions cmd/dmarc-report-converter/consts.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,6 @@
package main

const htmlTmpl = `
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap core CSS -->
<link href="{{.AssetsPath}}/css/bootstrap.min.css" rel="stylesheet">
<style>
td.left-border, th.left-border
{
border-left: 1px solid #dee2e6;
}
.borderless td, .borderless th
{
border: none;
}
table.table.bottomless
{
margin-bottom: 0rem;
}
</style>
</head>
<body>
<script src="{{.AssetsPath}}/js/jquery-3.3.1.min.js"></script>
<script src="{{.AssetsPath}}/js/bootstrap.min.js"></script>
<script src="{{.AssetsPath}}/js/Chart.min.js"></script>
<script src="{{.AssetsPath}}/js/dmarc-report-converter.js"></script>
const htmlMain = `
<p></p>
<div class="container">
<div class="row">
Expand Down Expand Up @@ -121,25 +88,40 @@ table.table.bottomless
<span class="badge bg-success">{{.Row.PolicyEvaluated.SPF}}</span>
{{- end }}
</td>
<td class="left-border">{{.AuthResults.DKIM.Domain}}</td>
<td title="selector: {{.AuthResults.DKIM.Selector}}">
{{- if eq .AuthResults.DKIM.Result "pass"}}
<span class="badge bg-success">{{.AuthResults.DKIM.Result}}</span>
{{- else if eq .AuthResults.DKIM.Result "fail"}}
<span class="badge bg-danger">{{.AuthResults.DKIM.Result}}</span>
{{- else}}
<span class="badge bg-warning">{{.AuthResults.DKIM.Result}}</span>
{{- end}}
<td class="left-border">
{{- $len := len .AuthResults.DKIM }}
{{- range $j, $foo := .AuthResults.DKIM }}
{{- .Domain }}
{{- if lt $j $len }}<br/>{{ end }}
{{- end -}}
</td>
<td>{{.AuthResults.SPF.Domain}}</td>
<td title="scope: {{.AuthResults.SPF.Scope}}">
{{- if eq .AuthResults.SPF.Result "pass"}}
<span class="badge bg-success">{{.AuthResults.SPF.Result}}</span>
{{- else if eq .AuthResults.SPF.Result "fail"}}
<span class="badge bg-danger">{{.AuthResults.SPF.Result}}</span>
{{- else}}
<span class="badge bg-warning">{{.AuthResults.SPF.Result}}</span>
{{- end}}
<td>
{{- range $j, $d := .AuthResults.DKIM }}
<span title="selector: {{.Selector}}" class="badge bg-
{{- if eq .Result "pass" }}success
{{- else if eq .Result "fail" }}danger
{{- else }}warning
{{- end -}}
">{{.Result}}</span>
{{- if lt $j $len }}<br/>{{ end }}
{{- end -}}
</td>
<td>
{{- $len = len .AuthResults.SPF }}
{{- range $j, $foo := .AuthResults.SPF }}
{{- .Domain }}
{{- if lt $j $len }}<br/>{{ end }}
{{- end -}}
<td>
{{- range $j, $d := .AuthResults.SPF }}
<span title="scope: {{.Scope}}" class="badge bg-
{{- if eq .Result "pass" }}success
{{- else if eq .Result "fail" }}danger
{{- else }}warning
{{- end -}}
">{{.Result}}</span>
{{- if lt $j $len }}<br/>{{ end }}
{{- end -}}
</td>
</tr>
{{- end }}
Expand All @@ -148,6 +130,42 @@ table.table.bottomless
</div>
</div>
</div>
`

const htmlTmpl = `
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap core CSS -->
<link href="{{.AssetsPath}}/css/bootstrap.min.css" rel="stylesheet">
<style>
td.left-border, th.left-border
{
border-left: 1px solid #dee2e6;
}
.borderless td, .borderless th
{
border: none;
}
table.table.bottomless
{
margin-bottom: 0rem;
}
</style>
</head>
<body>
<script src="{{.AssetsPath}}/js/jquery-3.3.1.min.js"></script>
<script src="{{.AssetsPath}}/js/bootstrap.min.js"></script>
<script src="{{.AssetsPath}}/js/Chart.min.js"></script>
<script src="{{.AssetsPath}}/js/dmarc-report-converter.js"></script>
` + htmlMain + `
</body>
</html>`

Expand Down Expand Up @@ -179,122 +197,7 @@ table.table.bottomless
</style>
</head>
<body>
<p></p>
<div class="container">
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
DMARC Report, id {{.Report.ReportMetadata.ReportID}}
</div>
<div class="card-body">
<table class="table table-sm borderless bottomless">
<tbody>
<tr>
<td>Organization</td>
<td><a href="{{.Report.ReportMetadata.ExtraContactInfo}}">{{.Report.ReportMetadata.OrgName}}</a> ({{.Report.ReportMetadata.Email}})</td>
</tr>
<tr>
<td>Date range</td>
<td>since {{.Report.ReportMetadata.DateRange.Begin.UTC}} until {{.Report.ReportMetadata.DateRange.End.UTC}}</td>
</tr>
<tr>
<td>Policy published</td>
<td>{{.Report.PolicyPublished.Domain}}: p={{.Report.PolicyPublished.Policy}} sp={{.Report.PolicyPublished.SPolicy}} pct={{.Report.PolicyPublished.Pct}} adkim={{.Report.PolicyPublished.ADKIM}} aspf={{.Report.PolicyPublished.ASPF}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<p></p>
<div class="row">
<div class="col">
<div class="progress">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ .Report.MessagesStats.PassedPercent }}%" aria-valuenow="{{ .Report.MessagesStats.Passed }}" aria-valuemin="0" aria-valuemax="{{ .Report.MessagesStats.All }}">passed {{ .Report.MessagesStats.PassedPercent }}%</div>
</div>
</div>
<div class="col-md-auto">
<span class="badge bg-success">passed {{ .Report.MessagesStats.Passed }}</span> <span class="badge bg-danger">failed {{ .Report.MessagesStats.Failed }}</span> <span class="badge text-bg-light">total {{ .Report.MessagesStats.All }}</span>
</div>
</div>
<p></p>
<div class="row">
<div class="col">
<table class="table table-bordered table-sm" id="items-table">
<thead>
<tr>
<th scope="col" colspan="3"></th>
<th scope="col" colspan="3" class="left-border">policy evaluated</th>
<th scope="col" colspan="4" class="left-border">auth results</th>
</tr>
<tr>
<th scope="col">ip</th>
<th scope="col" data-toggle="tooltip" title="ptr records">hostname</th>
<th scope="col" data-toggle="tooltip" title="messages count">msgs</th>
<th scope="col" class="left-border">disposition</th>
<th scope="col">DKIM</th>
<th scope="col">SPF</th>
<th scope="col" class="left-border">DKIM domain</th>
<th scope="col">result</th>
<th scope="col">SPF domain</th>
<th scope="col">result</th>
</tr>
</thead>
<tbody id="items-table">
{{- range .Report.Records }}
{{- if .IsPassed }}
<tr class="policy-evaluated-result-pass table-success">
{{- else }}
<tr class="policy-evaluated-result-fail">
{{- end }}
<td id="ip">{{.Row.SourceIP}}</td>
<td id="hostname">{{.Row.SourceHostname}}</td>
<td id="msgc">{{.Row.Count}}</td>
<td class="left-border" title="identifiers&#13;header_from: {{.Identifiers.HeaderFrom}}&#13;envelope_from: {{.Identifiers.EnvelopeFrom}}">{{.Row.PolicyEvaluated.Disposition}}</td>
<td>
{{- if eq .Row.PolicyEvaluated.DKIM "fail" }}
<span class="badge bg-danger">{{.Row.PolicyEvaluated.DKIM}}</span>
{{- else }}
<span class="badge bg-success">{{.Row.PolicyEvaluated.DKIM}}</span>
{{- end}}
</td>
<td>
{{- if eq .Row.PolicyEvaluated.SPF "fail" }}
<span class="badge bg-danger">{{.Row.PolicyEvaluated.SPF}}</span>
{{- else }}
<span class="badge bg-success">{{.Row.PolicyEvaluated.SPF}}</span>
{{- end }}
</td>
<td class="left-border">{{.AuthResults.DKIM.Domain}}</td>
<td title="selector: {{.AuthResults.DKIM.Selector}}">
{{- if eq .AuthResults.DKIM.Result "pass"}}
<span class="badge bg-success">{{.AuthResults.DKIM.Result}}</span>
{{- else if eq .AuthResults.DKIM.Result "fail"}}
<span class="badge bg-danger">{{.AuthResults.DKIM.Result}}</span>
{{- else}}
<span class="badge bg-warning">{{.AuthResults.DKIM.Result}}</span>
{{- end}}
</td>
<td>{{.AuthResults.SPF.Domain}}</td>
<td title="scope: {{.AuthResults.SPF.Scope}}">
{{- if eq .AuthResults.SPF.Result "pass"}}
<span class="badge bg-success">{{.AuthResults.SPF.Result}}</span>
{{- else if eq .AuthResults.SPF.Result "fail"}}
<span class="badge bg-danger">{{.AuthResults.SPF.Result}}</span>
{{- else}}
<span class="badge bg-warning">{{.AuthResults.SPF.Result}}</span>
{{- end}}
</td>
</tr>
{{- end }}
</tbody>
</table>
</div>
</div>
</div>
<body>` + htmlMain + `
</body>
</html>`

Expand Down
4 changes: 2 additions & 2 deletions pkg/dmarc/dmarc.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ type Identifiers struct {

// AuthResults represents feedback>record>auth_results section
type AuthResults struct {
DKIM DKIMAuthResult `xml:"dkim" json:"dkim"`
SPF SPFAuthResult `xml:"spf" json:"spf"`
DKIM []DKIMAuthResult `xml:"dkim" json:"dkim"`
SPF []SPFAuthResult `xml:"spf" json:"spf"`
}

// DKIMAuthResult represnets feedback>record>auth_results>dkim sections
Expand Down
63 changes: 37 additions & 26 deletions pkg/dmarc/dmarc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,24 @@ var xmlRecord1 = Record{
EnvelopeFrom: "",
},
AuthResults: AuthResults{
DKIM: DKIMAuthResult{
Domain: "test.net",
Result: "pass",
Selector: "selector",
DKIM: []DKIMAuthResult{
{
Domain: "test1.net",
Result: "fail",
Selector: "selector1",
},
{
Domain: "test2.net",
Result: "pass",
Selector: "selector2",
},
},
SPF: SPFAuthResult{
Domain: "test.net",
Result: "pass",
Scope: "mfrom",
SPF: []SPFAuthResult{
{
Domain: "test.net",
Result: "pass",
Scope: "mfrom",
},
},
},
}
Expand All @@ -71,15 +80,19 @@ var xmlRecord2 = Record{
EnvelopeFrom: "",
},
AuthResults: AuthResults{
DKIM: DKIMAuthResult{
Domain: "test2.net",
Result: "fail",
Selector: "selector",
DKIM: []DKIMAuthResult{
{
Domain: "test2.net",
Result: "fail",
Selector: "selector",
},
},
SPF: SPFAuthResult{
Domain: "test2.net",
Result: "softfail",
Scope: "mfrom",
SPF: []SPFAuthResult{
{
Domain: "test2.net",
Result: "softfail",
Scope: "mfrom",
},
},
},
}
Expand Down Expand Up @@ -225,15 +238,13 @@ var xmlEmptyReport = Report{
EnvelopeFrom: "",
},
AuthResults: AuthResults{
DKIM: DKIMAuthResult{
Domain: "",
Result: "",
Selector: "",
},
SPF: SPFAuthResult{
Domain: "",
Result: "",
Scope: "",
DKIM: nil,
SPF: []SPFAuthResult{
{
Domain: "",
Result: "",
Scope: "",
},
},
},
},
Expand All @@ -259,6 +270,6 @@ func TestReadParse_Empty(t *testing.T) {
}

if !reflect.DeepEqual(out, xmlEmptyReport) {
t.Errorf("ReadParse(%v): parsed structs are invalid: %+v", testFile, out)
t.Errorf("ReadParse(%v): parsed structs are invalid:\nGOT:\n%#v\nWANT:\n%#v", testFile, out, xmlEmptyReport)
}
}
20 changes: 12 additions & 8 deletions pkg/dmarc/merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ func TestReport_MergeRecord(t *testing.T) {
EnvelopeFrom: "",
},
AuthResults: AuthResults{
DKIM: DKIMAuthResult{
Domain: "test3.net",
Result: "fail",
Selector: "selector",
DKIM: []DKIMAuthResult{
{
Domain: "test3.net",
Result: "fail",
Selector: "selector",
},
},
SPF: SPFAuthResult{
Domain: "test3.net",
Result: "softfail",
Scope: "mfrom",
SPF: []SPFAuthResult{
{
Domain: "test3.net",
Result: "softfail",
Scope: "mfrom",
},
},
},
}
Expand Down
Loading

0 comments on commit 7f3a0a5

Please sign in to comment.