forked from kei51e/dmarc-reporter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dmarc-reporter.js
156 lines (139 loc) · 4.25 KB
/
dmarc-reporter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* This script reads DMARC report XML files and outputs a table to the console.
*
* Usage: node dmarc-reporter.js file1.xml file2.xml ...
* node dmarc-reporter.js *.xml
*
* How to setup: (requires Node.js)
*
* 1. Create a directory and save this script to a file named dmarc-reporter.js.
* 2. Save your DMARC report XML files to the same directory.
* 3. Open a terminal and navigate to the directory.
* 4. Run "npm install table xml2js" to install the required packages.
* 5. Run the script with the command: node dmarc-reporter.js file1.xml file2.xml ...
*
*/
import { promises } from "fs";
import { table, getBorderCharacters } from "table";
import { Parser } from "xml2js";
import getopts from "getopts";
const opts = {
alias: {
border: ["b", "B"],
color: ["c", "C"],
failed: ["F"],
help: ["h", "H", "?"]
},
default: {
border: true,
color: true,
failed: false,
help: false
}
};
const parser = new Parser({ explicitArray: false });
// Set color for pass/fail text.
const setTextColor = function (text) {
if (text === "pass") {
return "\x1b[32m" + text + "\x1b[0m";
} else if (text === "fail") {
return "\x1b[31m" + text + "\x1b[0m";
}
};
const usage = function () {
console.log("Usage: node dmarc-reporter.js [--[no-]border] [--[no-]color] [--failed] file1.xml file2.xml");
console.log(" node dmarc-reporter.js [--[no-]border] [--[no-]color] [--failed] *.xml");
return;
}
// Parse a single file.
const parseFile = async function (fileName, color = true, failed = false) {
const data = await promises.readFile(fileName);
let json = await parser.parseStringPromise(data);
json = json.feedback;
const reporter = json.report_metadata.org_name;
const startDate = new Date(parseInt(json.report_metadata.date_range.begin) * 1000);
const endDate = new Date(parseInt(json.report_metadata.date_range.end) * 1000);
if (!Array.isArray(json.record)) {
json.record = [json.record];
}
const record = json.record.map((r) => {
if (!failed || (r.row.policy_evaluated.spf === "fail" && r.row.policy_evaluated.dkim === "fail")) {
return [
r.row.source_ip,
r.row.count,
color
? setTextColor(r.row.policy_evaluated.spf)
: r.row.policy_evaluated.spf,
r.auth_results.spf.domain || "",
color
? setTextColor(r.row.policy_evaluated.dkim)
: r.row.policy_evaluated.dkim,
Array.isArray(r.auth_results.dkim)
? r.auth_results.dkim.map((x) => x.selector || "").join(", ")
: r.auth_results.dkim?.selector || "",
r.identifiers.header_from || "",
r.identifiers.envelope_from || "",
r.identifiers.envelope_to || "",
reporter,
startDate.toISOString().split("T")[0],
endDate.toISOString().split("T")[0],
];
}
});
return record.filter((x) => x !== undefined);
};
const parseFiles = async function (fileNames, color = true, border = true, failed = false) {
let records = [];
for (let fileName of fileNames) {
let record = await parseFile(fileName, color, failed);
records = records.concat(record);
}
// Sort by start date.
records.sort((a, b) => {
return a[a.length - 2].localeCompare(b[b.length - 2]);
});
// Add header.
if (border) {
records.unshift([
"Source IP",
"Count",
"SPF",
"SPF Domain",
"DKIM",
"DKIM Selector",
"Header From",
"Envelope From",
"Envelope To",
"Reporter",
"Start Date",
"End Date",
]);
}
// Output the table to the console.
if (records.length > 0) {
console.log(
border ?
table(records, { border: getBorderCharacters('norc') }) :
table(records, {
border: getBorderCharacters('void'),
columnDefault: {
paddingLeft: 0,
paddingRight: 1,
alignment: 'left'
},
drawHorizontalLine: () => false
})
);
}
return records;
};
const main = async function (args) {
const options = getopts(args, opts);
if (options.help || options._ === undefined || options._.length == 0) {
usage();
return;
}
return await parseFiles(options._, options.color, options.border, options.failed);
};
// Run it!
main(process.argv.slice(2));