Skip to content

Commit

Permalink
Relax postmaster checks #51
Browse files Browse the repository at this point in the history
  • Loading branch information
bwalkerl committed Oct 22, 2024
1 parent 8b566eb commit 4e20b9d
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 19 deletions.
4 changes: 1 addition & 3 deletions classes/check/dnsmx.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,7 @@ public function get_result(): result {
$status = result::WARNING;
$summary = "MX DNS record missing";
} else {
$allmxdomains = join('<br>', array_map(function ($x) {
return $x['target'] . ' (' . $x['pri'] . ')';
}, $mxdomains));
$allmxdomains = $dns->format_mx_records($mxdomains);
$details .= "<p>MX record found on domain <code>$noreplydomain</code> pointing to<br><code>$allmxdomains</code></p>";
$status = result::OK;
$summary = "MX record points to " . $mxdomains[0]['target'];
Expand Down
60 changes: 46 additions & 14 deletions classes/check/dnspostmastertools.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,50 +54,82 @@ 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 = '<table class="admintable generaltable table-sm w-auto">';
$details .= '<tr><th>Vendor</th><th>Token</th><th>Confirmed</th><th>Url</th></tr>';
$status = result::INFO;
$summary = '';

$dns = new dns_util();

$noreply = $dns->get_noreply();
$details .= "<p>No reply email: <code>$noreply</code></p>";

$noreplydomain = $dns->get_noreply_domain();

// Later intend to support other email providers.
$vendors = ['google'];
$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 .= "<p>$token was not found in TXT records</p>";
$status = result::ERROR;
$status = result::WARNING;
$summary = "Post master tools not verified with $vendor";
} else {
$confirmed = 'Yes';
$status = result::OK;
}
$details .= "<tr><td>$vendor</td><td>$token</td><td>$confirmed</td></tr>";

$vendorinfo = (object) [
'vendor' => $vendor,
'token' => $token,
'confirmed' => $confirmed,
'url' => '',
];
}
$details .= '</table>';

$details = $OUTPUT->render_from_template('tool_emailutils/postmaster', [
'noreply' => $noreply,
'userdomains' => $userdomaininfo,
'vendorinfo' => $vendorinfo,
]);

return new result($status, $summary, $details);
}
Expand Down
30 changes: 30 additions & 0 deletions classes/dns_util.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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('<br>', array_map(function ($x) {
return $x['target'] . ' (' . $x['pri'] . ')';
}, $mxrecords));
}

/**
* Get matching record contents
* @param string $domain domain to check
Expand Down
5 changes: 5 additions & 0 deletions lang/en/tool_emailutils.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
$string['dnsspfinclude_help'] = '<p>This is an SPF include domain which is expected to be present in the record. For example if this was set to <code>spf.acme.org</code> then the SPF security check would pass if the SPF record was <code>v=spf1 include:spf.acme.org -all</code>.</p>
<p>The * char can be used as a wildcard eg <code>*acme.org</code> would also match.</p>
';
$string['domain'] = 'Domain';
$string['domaindefaultnoreply'] = 'Default noreply';
$string['downloadsuppressionlist'] = 'Download Suppression List';
$string['enabled'] = 'Enabled';
Expand All @@ -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: <code>{$a}</code>';
$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';
Expand Down Expand Up @@ -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';
83 changes: 83 additions & 0 deletions templates/postmaster.mustache
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_emailutils/postmaster
Example context (json):
{
"noreply": "[email protected]",
"userdomains": [
{
"domain": "@gmail.com",
"count": 100,
"mxrecords": "string",
"vendor": "google",
}
],
"vendorinfo": [
{
"vendors": "google",
"token": "token",
"confirmed": "N/A",
"url": "https://example.com",
}
],
}
}}
<p>{{#str}} noreplyemail, tool_emailutils, {{ noreply }} {{/str}}</p>
<table class="admintable generaltable table-sm w-auto">
<thead>
<tr>
<th>{{#str}} vendor, tool_emailutils {{/str}}</th>
<th>{{#str}} token, core_webservice {{/str}}</th>
<th>{{#str}} confirmed, core_admin {{/str}}</th>
<th>{{#str}} url {{/str}}</th>
</tr>
</thead>
<tbody>
{{#vendorinfo}}
<tr>
<td>{{ vendor }}</td>
<td>{{ token }}</td>
<td>{{ confirmed }}</td>
<td>{{ url }}</td>
</tr>
{{/vendorinfo}}
</tbody>
</table>

<h5>{{#str}} userdomains, tool_emailutils {{/str}}</h5>
<table class="admintable generaltable table-sm w-auto">
<thead>
<tr>
<th>{{#str}} domain, tool_emailutils {{/str}}</th>
<th>{{#str}} users {{/str}}</th>
<th>{{#str}} mxrecords, tool_emailutils {{/str}}</th>
<th>{{#str}} vendor, tool_emailutils {{/str}}</th>
</tr>
</thead>
<tbody>
{{#userdomains}}
<tr>
<td>{{ domain }}</td>
<td>{{ count }}</td>
<td><code>{{{ mxrecords }}}</code>
<td>{{ vendors }}</td>
</tr>
{{/userdomains}}
</tbody>
</table>
4 changes: 2 additions & 2 deletions version.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down

0 comments on commit 4e20b9d

Please sign in to comment.