aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/private/environment.nix1
-rw-r--r--modules/private/websites/tools/tools/dmarc_reports.nix5
-rw-r--r--modules/private/websites/tools/tools/dmarc_reports/api.php45
-rw-r--r--modules/private/websites/tools/tools/dmarc_reports/app.js33
-rw-r--r--modules/private/websites/tools/tools/dmarc_reports/default.css256
-rw-r--r--modules/private/websites/tools/tools/dmarc_reports/index.html12
6 files changed, 211 insertions, 141 deletions
diff --git a/modules/private/environment.nix b/modules/private/environment.nix
index 5fbd023..0002622 100644
--- a/modules/private/environment.nix
+++ b/modules/private/environment.nix
@@ -825,6 +825,7 @@ in
825 type = submodule { 825 type = submodule {
826 options = { 826 options = {
827 mysql = mkMysqlOptions "DMARC" {}; 827 mysql = mkMysqlOptions "DMARC" {};
828 anonymous_key = mkOption { type = str; description = "Anonymous hashing key"; };
828 }; 829 };
829 }; 830 };
830 }; 831 };
diff --git a/modules/private/websites/tools/tools/dmarc_reports.nix b/modules/private/websites/tools/tools/dmarc_reports.nix
index 2e44526..e264e80 100644
--- a/modules/private/websites/tools/tools/dmarc_reports.nix
+++ b/modules/private/websites/tools/tools/dmarc_reports.nix
@@ -12,6 +12,7 @@ rec {
12 $dbuser = "${env.mysql.user}"; 12 $dbuser = "${env.mysql.user}";
13 $dbpass = "${env.mysql.password}"; 13 $dbpass = "${env.mysql.password}";
14 $dbport = "${env.mysql.port}"; 14 $dbport = "${env.mysql.port}";
15 $anonymous_key = "${env.anonymous_key}";
15 ?> 16 ?>
16 ''; 17 '';
17 }]; 18 }];
@@ -32,7 +33,11 @@ rec {
32 33
33 AllowOverride None 34 AllowOverride None
34 Options +FollowSymlinks 35 Options +FollowSymlinks
36
37 SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
38 Use LDAPConnect
35 Require all granted 39 Require all granted
40 Require ldap-attribute uid=immae
36 </Directory> 41 </Directory>
37 ''; 42 '';
38 }; 43 };
diff --git a/modules/private/websites/tools/tools/dmarc_reports/api.php b/modules/private/websites/tools/tools/dmarc_reports/api.php
index 9b7f0c0..5d4657e 100644
--- a/modules/private/websites/tools/tools/dmarc_reports/api.php
+++ b/modules/private/websites/tools/tools/dmarc_reports/api.php
@@ -18,6 +18,28 @@ function error_die($text, $number) {
18 die(json_encode($message)); 18 die(json_encode($message));
19} 19}
20 20
21$anonymous = isset($_GET['anonymous']) && $_GET['anonymous'];
22function maybe_anonymize($string, $long = false) {
23 global $anonymous_key;
24 global $anonymous;
25 if ($anonymous) {
26 if ($long) {
27 return md5($anonymous_key . ":" . $string);
28 } else {
29 return substr(md5($anonymous_key . ":" . $string), 0, 6);
30 }
31 } else {
32 return $string;
33 }
34}
35
36if (!$anonymous && (!isset($_SERVER['HTTP_AUTHORIZATION']) || $_SERVER['HTTP_AUTHORIZATION'] === "")) {
37 header('WWW-Authenticate: Basic realm="Immae"');
38 header('HTTP/1.0 401 Unauthorized');
39 echo "You need to be authenticated to access private information";
40 exit;
41}
42
21if ($mysqli->connect_errno) { 43if ($mysqli->connect_errno) {
22 error_die($mysqli->connect_error, $mysqli->connect_errno); 44 error_die($mysqli->connect_error, $mysqli->connect_errno);
23} 45}
@@ -27,14 +49,14 @@ if (!isset($_GET['serial'])) {
27 $query = $mysqli->query("SELECT DISTINCT domain FROM `report` ORDER BY domain"); 49 $query = $mysqli->query("SELECT DISTINCT domain FROM `report` ORDER BY domain");
28 if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } 50 if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); }
29 while($row = $query->fetch_assoc()) { 51 while($row = $query->fetch_assoc()) {
30 $response["domains"][] = $row['domain']; 52 $response["domains"][] = maybe_anonymize($row['domain']);
31 } 53 }
32 54
33 $response["orgs"] = array(); 55 $response["orgs"] = array();
34 $query = $mysqli->query("SELECT DISTINCT org FROM `report` ORDER BY org"); 56 $query = $mysqli->query("SELECT DISTINCT org FROM `report` ORDER BY org");
35 if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } 57 if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); }
36 while($row = $query->fetch_assoc()) { 58 while($row = $query->fetch_assoc()) {
37 $response["orgs"][] = $row['org']; 59 $response["orgs"][] = maybe_anonymize($row['org']);
38 } 60 }
39 61
40 $response["dates"] = array(); 62 $response["dates"] = array();
@@ -55,7 +77,13 @@ if (!isset($_GET['serial'])) {
55 $query = $mysqli->query($sql); 77 $query = $mysqli->query($sql);
56 if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); } 78 if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); }
57 while($row = $query->fetch_assoc()) { 79 while($row = $query->fetch_assoc()) {
58 unset($row["raw_xml"]); 80 $wanted_keys = array(
81 'domain', 'org', 'reportid', 'mindate', 'maxdate', 'rcount', 'serial', 'policy_adkim', 'policy_aspf', 'policy_none', 'policy_sp', 'policy_pct', 'spfresult', 'dkimresult'
82 );
83 $row = array_intersect_key($row, array_fill_keys($wanted_keys, '1'));
84 $row["domain"] = maybe_anonymize($row["domain"]);
85 $row["org"] = maybe_anonymize($row["org"]);
86 $row["reportid"] = maybe_anonymize($row["reportid"], true);
59 $response["summaries"][] = $row; 87 $response["summaries"][] = $row;
60 } 88 }
61} else { 89} else {
@@ -76,9 +104,14 @@ if (!isset($_GET['serial'])) {
76 $ip = "-"; 104 $ip = "-";
77 $host = "-"; 105 $host = "-";
78 } 106 }
79 $row['ip'] = $ip; 107 $wanted_keys = array(
80 $row['host'] = $host; 108 'ip', 'host', 'rcount', 'disposition', 'reason', 'dkimdomain', 'dkimresult', 'spfdomain', 'spfresult'
81 unset($row['ip6']); 109 );
110 $row = array_intersect_key($row, array_fill_keys($wanted_keys, '1'));
111 $row['ip'] = maybe_anonymize($ip);
112 $row['host'] = maybe_anonymize($host);
113 $row['dkimdomain'] = maybe_anonymize($row['dkimdomain']);
114 $row['spfdomain'] = maybe_anonymize($row['spfdomain']);
82 $response["rptrecord"][] = $row; 115 $response["rptrecord"][] = $row;
83 } 116 }
84} 117}
diff --git a/modules/private/websites/tools/tools/dmarc_reports/app.js b/modules/private/websites/tools/tools/dmarc_reports/app.js
index 7fe67d0..8e8a6c4 100644
--- a/modules/private/websites/tools/tools/dmarc_reports/app.js
+++ b/modules/private/websites/tools/tools/dmarc_reports/app.js
@@ -2,7 +2,7 @@ const app = new Vue({
2 el: '#app', 2 el: '#app',
3 data: { 3 data: {
4 info: null, 4 info: null,
5 summaries: [], 5 summaries: null,
6 selectedSummary: null, 6 selectedSummary: null,
7 filterGreen: true, 7 filterGreen: true,
8 filterDomain: null, 8 filterDomain: null,
@@ -10,16 +10,31 @@ const app = new Vue({
10 //filterDate: (new Date()).toISOString().substring(0, 7), 10 //filterDate: (new Date()).toISOString().substring(0, 7),
11 filterDate: null, 11 filterDate: null,
12 reverse: true, 12 reverse: true,
13 anonymous: true,
13 }, 14 },
14 created: async function () { 15 created: async function () {
15 let that = this; 16 let that = this;
16 17
17 try { 18 if ('anonymous' in localStorage) {
18 this.info = await this.getInfo(); 19 this.anonymous = JSON.parse(localStorage.anonymous);
19 this.summaries = this.info.summaries; 20 }
20 } catch (error) {} 21 this.fetchAll();
21 }, 22 },
22 methods: { 23 methods: {
24 fetchAll: async function() {
25 try {
26 this.info = await this.getInfo();
27 this.summaries = this.info.summaries;
28 } catch (error) {
29 this.info = null;
30 this.summaries = null;
31 }
32 },
33 toggleAnonymous: function() {
34 this.anonymous = !this.anonymous;
35 localStorage.anonymous = this.anonymous;
36 this.fetchAll();
37 },
23 filtered: function () { 38 filtered: function () {
24 let that = this; 39 let that = this;
25 let filtered = this.summaries.filter(function (summary) { 40 let filtered = this.summaries.filter(function (summary) {
@@ -53,7 +68,7 @@ const app = new Vue({
53 return mindate === this.filterDate || maxdate === this.filterDate; 68 return mindate === this.filterDate || maxdate === this.filterDate;
54 }, 69 },
55 printDate: function (date) { 70 printDate: function (date) {
56 return (new Date(date)).toISOString(); 71 return (new Date(date)).toISOString().replace("T", " ").replace(/\..*Z$/, " UTC");
57 }, 72 },
58 getColor: function (element) { 73 getColor: function (element) {
59 if (element.dkimresult === "fail" && element.spfresult === "fail") { 74 if (element.dkimresult === "fail" && element.spfresult === "fail") {
@@ -67,7 +82,8 @@ const app = new Vue({
67 } 82 }
68 }, 83 },
69 getInfo: function (event) { 84 getInfo: function (event) {
70 return fetch('api.php').then(function (response) { 85 let anonymous = this.anonymous ? "anonymous=1" : "";
86 return fetch(`api.php?${anonymous}`).then(function (response) {
71 if (response.status != 200) { return; } 87 if (response.status != 200) { return; }
72 return response.text().then(function (body) { 88 return response.text().then(function (body) {
73 return JSON.parse(body); 89 return JSON.parse(body);
@@ -75,7 +91,8 @@ const app = new Vue({
75 }); 91 });
76 }, 92 },
77 getDetails: function (serial) { 93 getDetails: function (serial) {
78 return fetch(`api.php?serial=${serial}`).then(function (response) { 94 let anonymous = this.anonymous ? "&anonymous=1" : "";
95 return fetch(`api.php?serial=${serial}${anonymous}`).then(function (response) {
79 if (response.status != 200) { return; } 96 if (response.status != 200) { return; }
80 return response.text().then(function (body) { 97 return response.text().then(function (body) {
81 return JSON.parse(body); 98 return JSON.parse(body);
diff --git a/modules/private/websites/tools/tools/dmarc_reports/default.css b/modules/private/websites/tools/tools/dmarc_reports/default.css
index 11608cc..9e0c63f 100644
--- a/modules/private/websites/tools/tools/dmarc_reports/default.css
+++ b/modules/private/websites/tools/tools/dmarc_reports/default.css
@@ -1,126 +1,130 @@
1h1 { 1h1 {
2 text-align: center; 2 text-align: center;
3} 3}
4 4
5table.reportlist { 5p.warninginfo {
6 margin: 2em auto 2em auto; 6 text-align: center;
7 border-collapse: collapse; 7}
8 clear: both; 8
9} 9table.reportlist {
10 10 margin: 2em auto 2em auto;
11table.reportlist td, table.reportlist th { 11 border-collapse: collapse;
12 padding:3px; 12 clear: both;
13} 13}
14 14
15table.reportlist thead { 15table.reportlist td, table.reportlist th {
16 border-top: 1px solid grey; 16 padding:3px;
17 border-bottom: 1px solid grey; 17}
18 18
19} 19table.reportlist thead {
20table.reportlist tbody tr:first-child td { 20 border-top: 1px solid grey;
21 padding-top: 10px; 21 border-bottom: 1px solid grey;
22} 22
23table.reportlist tr.sum { 23}
24 border-top: 1px solid grey; 24table.reportlist tbody tr:first-child td {
25} 25 padding-top: 10px;
26table.reportlist tr.selected { 26}
27 background-color: lightgrey; 27table.reportlist tr.sum {
28} 28 border-top: 1px solid grey;
29.reportdesc { 29}
30 font-weight: bold; 30table.reportlist tr.selected {
31 width: 90%; 31 background-color: lightgrey;
32 margin-left: auto; 32}
33 margin-right: auto; 33.reportdesc {
34} 34 font-weight: bold;
35 35 width: 90%;
36tr.summaryrow { 36 margin-left: auto;
37 cursor: pointer; 37 margin-right: auto;
38} 38}
39 39
40tr.summaryrow:hover, tr.summaryrow.selected { 40tr.summaryrow {
41 background-color: lightgray; 41 cursor: pointer;
42 border-left: 1px solid lightgray; 42}
43} 43
44 44tr.summaryrow:hover, tr.summaryrow.selected {
45td.reportcell { 45 background-color: lightgray;
46 border-bottom: 1px solid lightgray; 46 border-left: 1px solid lightgray;
47 border-left: 1px solid lightgray; 47}
48 border-right: 1px solid lightgray; 48
49} 49td.reportcell {
50 50 border-bottom: 1px solid lightgray;
51table.reportdata { 51 border-left: 1px solid lightgray;
52 margin: 0px auto 0px auto; 52 border-right: 1px solid lightgray;
53 border-collapse: separate; 53}
54 border-spacing: 2px; 54
55} 55table.reportdata {
56 56 margin: 0px auto 0px auto;
57table.reportdata tr th, table.reportdata tr td { 57 border-collapse: separate;
58 text-align: center; 58 border-spacing: 2px;
59 padding: 3px; 59}
60} 60
61 61table.reportdata tr th, table.reportdata tr td {
62table.reportdata tr.red { 62 text-align: center;
63 background-color: #FF0000; 63 padding: 3px;
64} 64}
65 65
66table.reportdata tr.orange { 66table.reportdata tr.red {
67 background-color: #FFA500; 67 background-color: #FF0000;
68} 68}
69 69
70table.reportdata tr.lime { 70table.reportdata tr.orange {
71 background-color: #00FF00; 71 background-color: #FFA500;
72} 72}
73 73
74table.reportdata tr.yellow { 74table.reportdata tr.lime {
75 background-color: #FFFF00; 75 background-color: #00FF00;
76} 76}
77 77
78.optionblock { 78table.reportdata tr.yellow {
79 background: lightgrey; 79 background-color: #FFFF00;
80 padding: 0.4em; 80}
81 float: right; 81
82 margin: auto 2em 1em auto; 82.optionblock {
83 white-space: nowrap; 83 background: lightgrey;
84} 84 padding: 0.4em;
85 85 float: right;
86.optionlabel { 86 margin: auto 2em 1em auto;
87 font-weight: bold; 87 white-space: nowrap;
88 float: left; clear: left; 88}
89 margin-right: 1em; 89
90} 90.optionlabel {
91 91 font-weight: bold;
92.options { 92 float: left; clear: left;
93 font-size: 70%; 93 margin-right: 1em;
94 text-align: right; 94}
95 border: none; 95
96 width: 97%; 96.options {
97 padding: 0.4em; 97 font-size: 70%;
98} 98 text-align: right;
99 99 border: none;
100.center { 100 width: 97%;
101 text-align:center; 101 padding: 0.4em;
102} 102}
103 103
104.circle_lime:before { 104.center {
105 content: ' \25CF'; 105 text-align:center;
106 font-size: 25px; 106}
107 color: #00FF00; 107
108} 108.circle_lime:before {
109 109 content: ' \25CF';
110.circle_red:before { 110 font-size: 25px;
111 content: ' \25CF'; 111 color: #00FF00;
112 font-size: 25px; 112}
113 color: #FF0000; 113
114} 114.circle_red:before {
115 115 content: ' \25CF';
116.circle_yellow:before { 116 font-size: 25px;
117 content: ' \25CF'; 117 color: #FF0000;
118 font-size: 25px; 118}
119 color: #FFFF00; 119
120} 120.circle_yellow:before {
121 121 content: ' \25CF';
122.circle_orange:before { 122 font-size: 25px;
123 content: ' \25CF'; 123 color: #FFFF00;
124 font-size: 25px; 124}
125 color: #FFA500; 125
126} 126.circle_orange:before {
127 content: ' \25CF';
128 font-size: 25px;
129 color: #FFA500;
130}
diff --git a/modules/private/websites/tools/tools/dmarc_reports/index.html b/modules/private/websites/tools/tools/dmarc_reports/index.html
index ded05e1..af29cdf 100644
--- a/modules/private/websites/tools/tools/dmarc_reports/index.html
+++ b/modules/private/websites/tools/tools/dmarc_reports/index.html
@@ -11,7 +11,13 @@
11 11
12<body> 12<body>
13 <div id="app" style="width: 100%"> 13 <div id="app" style="width: 100%">
14 <div class="optionblock" v-if="info"> 14 <div class="optionblock">
15 <div class='options'>
16 <span class='optionlabel'>Anonymize</span>
17 <label><input type="radio" :value="false" v-model="anonymous" v-on:click="toggleAnonymous()"> no</label>
18 <label><input type="radio" :value="true" v-model="anonymous" v-on:click="toggleAnonymous()"> yes</label>
19 </div>
20 <template v-if="info">
15 <div class='options'> 21 <div class='options'>
16 <span class='optionlabel'>Hide all-green lines:</span> 22 <span class='optionlabel'>Hide all-green lines:</span>
17 <label><input type="radio" :value="false" v-model="filterGreen"> no</label> 23 <label><input type="radio" :value="false" v-model="filterGreen"> no</label>
@@ -43,9 +49,13 @@
43 <option v-for="date in info.dates" :value="date">{{ date }}</option> 49 <option v-for="date in info.dates" :value="date">{{ date }}</option>
44 </select> 50 </select>
45 </div> 51 </div>
52 </template>
46 </div> 53 </div>
47 54
48 <h1 class='main'>DMARC Reports</h1> 55 <h1 class='main'>DMARC Reports</h1>
56 <p v-if="!info" class="warninginfo">
57 No information could be fetched. If in non-anonymous mode you need to be logged-in
58 </p>
49 <table class='reportlist' v-if="summaries"> 59 <table class='reportlist' v-if="summaries">
50 <thead> 60 <thead>
51 <tr> 61 <tr>