From 1a64deeb894dc95e2645a75771732c6cc53a79ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Wed, 4 Oct 2023 01:35:06 +0200 Subject: Squash changes containing private information There were a lot of changes since the previous commit, but a lot of them contained personnal information about users. All thos changes got stashed into a single commit (history is kept in a different place) and private information was moved in a separate private repository --- .../eldiron/websites/tools/dmarc_reports/api.php | 122 +++++++++++++++++++ .../eldiron/websites/tools/dmarc_reports/app.js | 103 ++++++++++++++++ .../websites/tools/dmarc_reports/default.css | 130 +++++++++++++++++++++ .../websites/tools/dmarc_reports/index.html | 128 ++++++++++++++++++++ 4 files changed, 483 insertions(+) create mode 100644 systems/eldiron/websites/tools/dmarc_reports/api.php create mode 100644 systems/eldiron/websites/tools/dmarc_reports/app.js create mode 100644 systems/eldiron/websites/tools/dmarc_reports/default.css create mode 100644 systems/eldiron/websites/tools/dmarc_reports/index.html (limited to 'systems/eldiron/websites/tools/dmarc_reports') diff --git a/systems/eldiron/websites/tools/dmarc_reports/api.php b/systems/eldiron/websites/tools/dmarc_reports/api.php new file mode 100644 index 0000000..850f9ce --- /dev/null +++ b/systems/eldiron/websites/tools/dmarc_reports/api.php @@ -0,0 +1,122 @@ + "ok", +); +$mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname, $dbport); + +function error_die($text, $number) { + http_response_code("500"); + $message = array( + "status" => "error", + "message" => $text, + "code" => $number + ); + + die(json_encode($message)); +} + +$anonymous = isset($_GET['anonymous']) && $_GET['anonymous']; +function maybe_anonymize($string, $long = false) { + global $anonymous_key; + global $anonymous; + if ($anonymous) { + if ($long) { + return md5($anonymous_key . ":" . $string); + } else { + return substr(md5($anonymous_key . ":" . $string), 0, 6); + } + } else { + return $string; + } +} + +if (!$anonymous && (!isset($_SERVER['HTTP_AUTHORIZATION']) || $_SERVER['HTTP_AUTHORIZATION'] === "")) { + header('WWW-Authenticate: Basic realm="Immae"'); + header('HTTP/1.0 401 Unauthorized'); + echo "You need to be authenticated to access private information"; + exit; +} + +if ($mysqli->connect_errno) { + error_die($mysqli->connect_error, $mysqli->connect_errno); +} + +if (!isset($_GET['serial'])) { + $response["domains"] = array(); + $query = $mysqli->query("SELECT DISTINCT domain FROM `report` ORDER BY domain"); + if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } + while($row = $query->fetch_assoc()) { + $response["domains"][] = maybe_anonymize($row['domain']); + } + + $response["orgs"] = array(); + $query = $mysqli->query("SELECT DISTINCT org FROM `report` ORDER BY org"); + if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } + while($row = $query->fetch_assoc()) { + $response["orgs"][] = maybe_anonymize($row['org']); + } + + $response["dates"] = array(); + $query = $mysqli->query("SELECT DISTINCT DISTINCT year(mindate) as year, month(mindate) as month FROM `report` ORDER BY year DESC,month DESC"); + if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } + while($row = $query->fetch_assoc()) { + $response["dates"][] = sprintf( "%'.04d-%'.02d", $row['year'], $row['month'] ); + } + + $response["summaries"] = array(); + if (isset($_GET['errors_only'])) { + $where = " WHERE (spfresult != 'pass' or dkimresult != 'pass')"; + } else { + $where = ""; + } + + $sql = "SELECT report.* , sum(rptrecord.rcount) AS rcount, MIN(rptrecord.dkimresult) AS dkimresult, MIN(rptrecord.spfresult) AS spfresult FROM report LEFT JOIN (SELECT rcount, COALESCE(dkimresult, 'neutral') AS dkimresult, COALESCE(spfresult, 'neutral') AS spfresult, serial FROM rptrecord) AS rptrecord ON report.serial = rptrecord.serial$where GROUP BY serial ORDER BY mindate ASC, maxdate ASC, org"; + $query = $mysqli->query($sql); + if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } + while($row = $query->fetch_assoc()) { + $wanted_keys = array( + 'domain', 'org', 'reportid', 'mindate', 'maxdate', 'rcount', 'serial', 'policy_adkim', 'policy_aspf', 'policy_none', 'policy_sp', 'policy_pct', 'spfresult', 'dkimresult' + ); + $row = array_intersect_key($row, array_fill_keys($wanted_keys, '1')); + $row["domain"] = maybe_anonymize($row["domain"]); + $row["org"] = maybe_anonymize($row["org"]); + $row["reportid"] = maybe_anonymize($row["reportid"], true); + $response["summaries"][] = $row; + } +} else { + $response["rptrecord"] = []; + $sql = $mysqli->prepare("SELECT * FROM rptrecord where serial = ?"); + $sql->bind_param("s", $_GET["serial"]); + $sql->execute(); + $query = $sql->get_result(); + if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } + while($row = $query->fetch_assoc()) { + if ($row['ip']) { + $ip = long2ip($row['ip']); + $host = gethostbyaddr($ip); + } elseif ( $row['ip6'] ) { + $ip = inet_ntop($row['ip6']); + $host = gethostbyaddr($ip); + } else { + $ip = "-"; + $host = "-"; + } + $wanted_keys = array( + 'ip', 'host', 'rcount', 'disposition', 'reason', 'dkimdomain', 'dkimresult', 'spfdomain', 'spfresult' + ); + $row = array_intersect_key($row, array_fill_keys($wanted_keys, '1')); + $row['ip'] = maybe_anonymize($ip); + $row['host'] = maybe_anonymize($host); + $row['dkimdomain'] = maybe_anonymize($row['dkimdomain']); + $row['spfdomain'] = maybe_anonymize($row['spfdomain']); + $response["rptrecord"][] = $row; + } +} + +header("Content-Type: application/json"); + +echo json_encode($response, JSON_PRETTY_PRINT); +?> diff --git a/systems/eldiron/websites/tools/dmarc_reports/app.js b/systems/eldiron/websites/tools/dmarc_reports/app.js new file mode 100644 index 0000000..8e8a6c4 --- /dev/null +++ b/systems/eldiron/websites/tools/dmarc_reports/app.js @@ -0,0 +1,103 @@ +const app = new Vue({ + el: '#app', + data: { + info: null, + summaries: null, + selectedSummary: null, + filterGreen: true, + filterDomain: null, + filterOrg: null, + //filterDate: (new Date()).toISOString().substring(0, 7), + filterDate: null, + reverse: true, + anonymous: true, + }, + created: async function () { + let that = this; + + if ('anonymous' in localStorage) { + this.anonymous = JSON.parse(localStorage.anonymous); + } + this.fetchAll(); + }, + methods: { + fetchAll: async function() { + try { + this.info = await this.getInfo(); + this.summaries = this.info.summaries; + } catch (error) { + this.info = null; + this.summaries = null; + } + }, + toggleAnonymous: function() { + this.anonymous = !this.anonymous; + localStorage.anonymous = this.anonymous; + this.fetchAll(); + }, + filtered: function () { + let that = this; + let filtered = this.summaries.filter(function (summary) { + return (!that.filterGreen || that.getColor(summary) !== "lime") + && (!that.filterDomain || summary.domain === that.filterDomain) + && (!that.filterOrg || summary.org === that.filterOrg) + && (!that.filterDate || that.inDates(summary)); + }); + if (this.reverse) { + return filtered.reverse(); + } else { + return filtered; + } + }, + toggle: async function(summary) { + if (this.selectedSummary && this.selectedSummary.serial === summary.serial) { + this.selectedSummary = null; + } else { + if (!summary.details) { + summary.details = await this.getDetails(summary.serial); + } + this.selectedSummary = summary; + } + }, + inDates: function(summary) { + if (!this.filterDate) { return true; } + + let mindate = (new Date(summary.mindate)).toISOString().substring(0, 7); + let maxdate = (new Date(summary.maxdate)).toISOString().substring(0, 7); + + return mindate === this.filterDate || maxdate === this.filterDate; + }, + printDate: function (date) { + return (new Date(date)).toISOString().replace("T", " ").replace(/\..*Z$/, " UTC"); + }, + getColor: function (element) { + if (element.dkimresult === "fail" && element.spfresult === "fail") { + return "red"; + } else if (element.dkimresult === "fail" || element.spfresult === "fail") { + return "orange"; + } else if (element.dkimresult === "pass" && element.spfresult === "pass") { + return "lime"; + } else { + return "yellow"; + } + }, + getInfo: function (event) { + let anonymous = this.anonymous ? "anonymous=1" : ""; + return fetch(`api.php?${anonymous}`).then(function (response) { + if (response.status != 200) { return; } + return response.text().then(function (body) { + return JSON.parse(body); + }); + }); + }, + getDetails: function (serial) { + let anonymous = this.anonymous ? "&anonymous=1" : ""; + return fetch(`api.php?serial=${serial}${anonymous}`).then(function (response) { + if (response.status != 200) { return; } + return response.text().then(function (body) { + return JSON.parse(body); + }); + }); + } + } +}); diff --git a/systems/eldiron/websites/tools/dmarc_reports/default.css b/systems/eldiron/websites/tools/dmarc_reports/default.css new file mode 100644 index 0000000..9e0c63f --- /dev/null +++ b/systems/eldiron/websites/tools/dmarc_reports/default.css @@ -0,0 +1,130 @@ +h1 { + text-align: center; +} + +p.warninginfo { + text-align: center; +} + +table.reportlist { + margin: 2em auto 2em auto; + border-collapse: collapse; + clear: both; +} + +table.reportlist td, table.reportlist th { + padding:3px; +} + +table.reportlist thead { + border-top: 1px solid grey; + border-bottom: 1px solid grey; + +} +table.reportlist tbody tr:first-child td { + padding-top: 10px; +} +table.reportlist tr.sum { + border-top: 1px solid grey; +} +table.reportlist tr.selected { + background-color: lightgrey; +} +.reportdesc { + font-weight: bold; + width: 90%; + margin-left: auto; + margin-right: auto; +} + +tr.summaryrow { + cursor: pointer; +} + +tr.summaryrow:hover, tr.summaryrow.selected { + background-color: lightgray; + border-left: 1px solid lightgray; +} + +td.reportcell { + border-bottom: 1px solid lightgray; + border-left: 1px solid lightgray; + border-right: 1px solid lightgray; +} + +table.reportdata { + margin: 0px auto 0px auto; + border-collapse: separate; + border-spacing: 2px; +} + +table.reportdata tr th, table.reportdata tr td { + text-align: center; + padding: 3px; +} + +table.reportdata tr.red { + background-color: #FF0000; +} + +table.reportdata tr.orange { + background-color: #FFA500; +} + +table.reportdata tr.lime { + background-color: #00FF00; +} + +table.reportdata tr.yellow { + background-color: #FFFF00; +} + +.optionblock { + background: lightgrey; + padding: 0.4em; + float: right; + margin: auto 2em 1em auto; + white-space: nowrap; +} + +.optionlabel { + font-weight: bold; + float: left; clear: left; + margin-right: 1em; +} + +.options { + font-size: 70%; + text-align: right; + border: none; + width: 97%; + padding: 0.4em; +} + +.center { + text-align:center; +} + +.circle_lime:before { + content: ' \25CF'; + font-size: 25px; + color: #00FF00; +} + +.circle_red:before { + content: ' \25CF'; + font-size: 25px; + color: #FF0000; +} + +.circle_yellow:before { + content: ' \25CF'; + font-size: 25px; + color: #FFFF00; +} + +.circle_orange:before { + content: ' \25CF'; + font-size: 25px; + color: #FFA500; +} diff --git a/systems/eldiron/websites/tools/dmarc_reports/index.html b/systems/eldiron/websites/tools/dmarc_reports/index.html new file mode 100644 index 0000000..0afc82f --- /dev/null +++ b/systems/eldiron/websites/tools/dmarc_reports/index.html @@ -0,0 +1,128 @@ + + + + + + + + Dmarc reports + + + + +
+
+
+ Anonymize + + +
+ +
+ +

DMARC Reports

+

+ No information could be fetched. If in non-anonymous mode you need to be logged-in +

+ + + + + + + + + + + + + + + +
Start DateEnd DateDomainReporting OrganizationReport IDMessages
+
+ + + + + + -- cgit v1.2.3