From 4e20b9d62f11b01acd3bd636bc56e6cc25a10692 Mon Sep 17 00:00:00 2001 From: Benjamin Walker Date: Thu, 17 Oct 2024 13:17:17 +1000 Subject: [PATCH] Relax postmaster checks #51 --- classes/check/dnsmx.php | 4 +- classes/check/dnspostmastertools.php | 60 +++++++++++++++----- classes/dns_util.php | 30 ++++++++++ lang/en/tool_emailutils.php | 5 ++ templates/postmaster.mustache | 83 ++++++++++++++++++++++++++++ version.php | 4 +- 6 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 templates/postmaster.mustache diff --git a/classes/check/dnsmx.php b/classes/check/dnsmx.php index 9f2c0b8..7d13dca 100644 --- a/classes/check/dnsmx.php +++ b/classes/check/dnsmx.php @@ -78,9 +78,7 @@ public function get_result(): result { $status = result::WARNING; $summary = "MX DNS record missing"; } else { - $allmxdomains = join('
', array_map(function ($x) { - return $x['target'] . ' (' . $x['pri'] . ')'; - }, $mxdomains)); + $allmxdomains = $dns->format_mx_records($mxdomains); $details .= "

MX record found on domain $noreplydomain pointing to
$allmxdomains

"; $status = result::OK; $summary = "MX record points to " . $mxdomains[0]['target']; diff --git a/classes/check/dnspostmastertools.php b/classes/check/dnspostmastertools.php index 784d166..399e3db 100644 --- a/classes/check/dnspostmastertools.php +++ b/classes/check/dnspostmastertools.php @@ -54,21 +54,14 @@ public function get_action_link(): ?\action_link { * @return result */ public function get_result(): result { - global $DB, $CFG; + global $OUTPUT; - $url = new \moodle_url($CFG->wwwroot); - $domain = $url->get_host(); - - $details = ''; - $details .= ''; $status = result::INFO; $summary = ''; $dns = new dns_util(); $noreply = $dns->get_noreply(); - $details .= "

No reply email: $noreply

"; - $noreplydomain = $dns->get_noreply_domain(); // Later intend to support other email providers. @@ -76,28 +69,67 @@ public function get_result(): result { $vendornames = join(', ', $vendors); $summary = "Post master tools setup for $vendornames "; - $status = result::INFO; + // Check the most common user domains. + $userdomains = $dns->get_user_domains(10); + $userdomaininfo = []; + foreach ($userdomains as $domain) { + $mxrecords = $dns->get_mx_record($domain->domain); + $allmxdomains = $dns->format_mx_records($mxrecords); + $domainvendors = array_filter($vendors, function($vendor) use ($mxrecords) { + foreach ($mxrecords as $mxrecord) { + if (strpos($mxrecord['target'], $vendor) !== false) { + return true; + } + } + }); + $userdomaininfo[] = (object) [ + 'domain' => '@' . $domain->domain, + 'count' => $domain->count, + 'mxrecords' => $allmxdomains, + 'vendors' => implode(',', $domainvendors), + ]; + } + $status = result::INFO; + $vendorinfo = []; foreach ($vendors as $vendor) { $token = get_config('tool_emailutils', 'postmaster' . $vendor . 'token'); $record = $dns->get_matching_dns_record($noreplydomain, $token); + $usevendor = !empty(array_filter($userdomaininfo, function($row) use ($vendor) { + return strpos($row->vendors, $vendor) !== false; + })); - if (empty($token)) { + if (empty($token) && !$usevendor) { + $summary = "Post master tools not required for $vendor"; + $status = result::NA; + $confirmed = 'N/A'; + } else if (empty($token)) { $summary = "Post master tools not setup with $vendor"; - $status = result::WARNING; + $status = result::INFO; $confirmed = 'N/A'; } else if (empty($record)) { $confirmed = 'No'; $details .= "

$token was not found in TXT records

"; - $status = result::ERROR; + $status = result::WARNING; $summary = "Post master tools not verified with $vendor"; } else { $confirmed = 'Yes'; $status = result::OK; } - $details .= ""; + + $vendorinfo = (object) [ + 'vendor' => $vendor, + 'token' => $token, + 'confirmed' => $confirmed, + 'url' => '', + ]; } - $details .= '
VendorTokenConfirmedUrl
$vendor$token$confirmed
'; + + $details = $OUTPUT->render_from_template('tool_emailutils/postmaster', [ + 'noreply' => $noreply, + 'userdomains' => $userdomaininfo, + 'vendorinfo' => $vendorinfo, + ]); return new result($status, $summary, $details); } diff --git a/classes/dns_util.php b/classes/dns_util.php index 09596c0..7e13ab7 100644 --- a/classes/dns_util.php +++ b/classes/dns_util.php @@ -96,6 +96,25 @@ public function get_subdomains($domain) { return rtrim(strstr($domain, $primarydomain, true), '.'); } + /** + * Gets the most common user email domains from the database. + * @param int $number limits the number of domains + * @return array + */ + public function get_user_domains(int $number = 0): array { + global $DB; + + $domainsql = $DB->sql_substr('LOWER(email)', $DB->sql_position("'@'", 'email') . ' + 1'); + $validemail = $DB->sql_like('email', ':pattern'); + $params = ['pattern' => '%@%.%']; + $sql = "SELECT $domainsql AS domain, count(*) AS count + FROM {user} + WHERE $validemail AND suspended = 0 AND deleted = 0 + GROUP BY $domainsql + ORDER BY count DESC"; + return $DB->get_records_sql($sql, $params, 0, $number); + } + /** * Get spf txt record contents * @param string $domain specify a different domain @@ -235,6 +254,17 @@ public function get_mx_record($domain) { return $records; } + /** + * Formats MX records to display in checks + * @param array $mxrecords + * @return string + */ + public function format_mx_records(array $mxrecords): string { + return join('
', array_map(function ($x) { + return $x['target'] . ' (' . $x['pri'] . ')'; + }, $mxrecords)); + } + /** * Get matching record contents * @param string $domain domain to check diff --git a/lang/en/tool_emailutils.php b/lang/en/tool_emailutils.php index fbc8fbd..ed77c90 100644 --- a/lang/en/tool_emailutils.php +++ b/lang/en/tool_emailutils.php @@ -59,6 +59,7 @@ $string['dnsspfinclude_help'] = '

This is an SPF include domain which is expected to be present in the record. For example if this was set to spf.acme.org then the SPF security check would pass if the SPF record was v=spf1 include:spf.acme.org -all.

The * char can be used as a wildcard eg *acme.org would also match.

'; +$string['domain'] = 'Domain'; $string['domaindefaultnoreply'] = 'Default noreply'; $string['downloadsuppressionlist'] = 'Download Suppression List'; $string['enabled'] = 'Enabled'; @@ -68,6 +69,8 @@ $string['header_help'] = 'HTTP Basic Auth Header'; $string['incorrect_access'] = 'Incorrect access detected. For use only by AWS SNS.'; $string['list'] = 'Complaints List'; +$string['noreplyemail'] = 'No reply email: {$a}'; +$string['mxrecords'] = 'MX records'; $string['mxtoolbox'] = 'MXtoolbox links'; $string['not_implemented'] = 'Not implemented yet. Search the user report for emails ending with ".b.invalid" and ".c.invalid".'; $string['password'] = 'Password'; @@ -103,3 +106,5 @@ $string['task_update_suppression_list'] = 'Update email suppression list'; $string['username'] = 'Username'; $string['username_help'] = 'HTTP Basic Auth Username'; +$string['userdomains'] = 'Most common user email domains'; +$string['vendor'] = 'Vendor'; diff --git a/templates/postmaster.mustache b/templates/postmaster.mustache new file mode 100644 index 0000000..f7cb45e --- /dev/null +++ b/templates/postmaster.mustache @@ -0,0 +1,83 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tool_emailutils/postmaster + + Example context (json): + { + "noreply": "noreply@example.com", + "userdomains": [ + { + "domain": "@gmail.com", + "count": 100, + "mxrecords": "string", + "vendor": "google", + } + ], + "vendorinfo": [ + { + "vendors": "google", + "token": "token", + "confirmed": "N/A", + "url": "https://example.com", + } + ], + } +}} +

{{#str}} noreplyemail, tool_emailutils, {{ noreply }} {{/str}}

+ + + + + + + + + + + {{#vendorinfo}} + + + + + + + {{/vendorinfo}} + +
{{#str}} vendor, tool_emailutils {{/str}}{{#str}} token, core_webservice {{/str}}{{#str}} confirmed, core_admin {{/str}}{{#str}} url {{/str}}
{{ vendor }}{{ token }}{{ confirmed }}{{ url }}
+ +
{{#str}} userdomains, tool_emailutils {{/str}}
+ + + + + + + + + + + {{#userdomains}} + + + + + + {{/userdomains}} + +
{{#str}} domain, tool_emailutils {{/str}}{{#str}} users {{/str}}{{#str}} mxrecords, tool_emailutils {{/str}}{{#str}} vendor, tool_emailutils {{/str}}
{{ domain }}{{ count }}{{{ mxrecords }}} + {{ vendors }}
diff --git a/version.php b/version.php index e279fcb..be6894c 100644 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024100101; -$plugin->release = 2024011502; +$plugin->version = 2024101700; +$plugin->release = 2024101700; $plugin->requires = 2020061500; $plugin->component = 'tool_emailutils'; $plugin->dependencies = ['local_aws' => 2020061500];