Skip to content

Commit

Permalink
Adde SPF basic security check #12
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanheywood committed Jan 11, 2024
1 parent f0c2325 commit 62fec78
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 4 deletions.
105 changes: 105 additions & 0 deletions classes/check/dnsspf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
// 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/>.
/**
* DNS Email SPF check.
*
* @package tool_heartbeat
* @author Brendan Heywood <[email protected]>
* @copyright Catalyst IT 2024
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
*/

namespace tool_emailutils\check;
use core\check\check;
use core\check\result;
use tool_emailutils\dns_util;

/**
* DNS Email SPF check.
*
* @package tool_heartbeat
* @author Brendan Heywood <[email protected]>
* @copyright Catalyst IT 2024
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dnsspf extends check {

/**
* A link to a place to action this
*
* @return \action_link|null
*/
public function get_action_link(): ?\action_link {
return new \action_link(
new \moodle_url('/admin/tool/emailutils/dkim.php'),
get_string('dkimmanager', 'tool_emailutils'));
}

/**
* Get Result.
*
* @return result
*/
public function get_result() : result {
global $DB, $CFG;

$url = new \moodle_url($CFG->wwwroot);
$domain = $url->get_host();

$details = '';
$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();
$details .= "<p>No reply domain: <code>$noreplydomain</code></p>";

$spf = $dns->get_spf_record();

// Does it have an SPF record at all?
if (empty($spf)) {
$summary = 'Missing SPF record';
$details .= "<p>$domain does not have an SPF record</p>";
return new result(result::ERROR, $summary, $details);
}

$details .= "<p>SPF record:<br><code>$spf</code></p>";
$status = result::OK;
$summary = 'SPF record exists';

$include = get_config('tool_emailutils', 'dnsspfinclude');
if (!empty($include)) {
$present = $dns->include_present($include);
if ($present) {
$summary = "SPF record exists and has '$present' include";
$details .= "<p>Expecting include: <code>$include</code> and matched on <code>$present</code></p>";
} else {
$status = result::ERROR;
$summary = "SPF record exists but is missing '$include' include";
$details .= "<p>Expecting include is missing: <code>$include</code></p>";
}
}


return new result($status, $summary, $details);
}

}
109 changes: 109 additions & 0 deletions classes/dns_util.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php
// 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/>.

/**
* SPF utils
*
* @package tool_heartbeat
* @copyright Catalyst IT 2024
* @author Brendan Heywood <[email protected]>
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace tool_emailutils;

/**
* SPF utils
*
* @package tool_heartbeat
* @copyright Catalyst IT 2024
* @author Brendan Heywood <[email protected]>
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dns_util {

/**
* Get no reply
* @return string email
*/
public function get_noreply() {
global $CFG;

return $CFG->noreplyaddress;
}

/**
* Get no reply domain
* @return string domain
*/
public function get_noreply_domain() {
global $CFG;

$noreplydomain = substr($CFG->noreplyaddress, strpos($CFG->noreplyaddress, '@') + 1);
return $noreplydomain;
}

/**
* Get spf txt record contents
* @return string txt record
*/
public function get_spf_record() {

$domain = $this->get_noreply_domain();
$records = dns_get_record($domain, DNS_TXT);
foreach ($records as $record) {
$txt = $record['txt'];
if (substr($txt, 0, 6) == 'v=spf1') {
return $txt;
}
}
return '';
}

/**
* Get spf txt record contents
* @return string url
*/
public function get_mxtoolbox_spf_url() {
}


/**
* Returns the include if matched
*
* The include can have a wildcard and this will return the actual matched value.
* @param string include domain
* @return string matched include
*/
public function include_present(string $include) {
$txt = $this->get_spf_record();

$escaped = preg_quote($include);

// Allow a * wildcard match.
$escaped = str_replace('\*', '\S*', $escaped);
$regex = "/include:($escaped)/U";
if (preg_match($regex, $txt, $matches)) {
return $matches[1];
}

return '';
}

}

12 changes: 9 additions & 3 deletions lang/en/tool_emailutils.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
$string['bouncesreset'] = 'Bounces have been reset for the selected users';
$string['configmissing'] = 'Missing config.php setting ($CFG->handlebounces) please review config-dist.php for more information.';
$string['complaints'] = 'For a list of complaints, search for ".c.invalid"';
$string['dkimmanager'] = 'DKIM manager';
$string['dkimmanager'] = 'SPF & DKIM manager';
$string['checkdnsspf'] = 'DNS Email SPF check';
$string['dkimmanagerhelp'] = '<p>This shows all DKIM key pairs / selectors available for email signing, including those made by this admin tool or put in place by external tools such as open-dkim. For most systems this is the end to end setup:</p>
<ol>
<li>First decide and set the <code>$CFG->noreply</code> email as the domain of the reply email is tied to the signing.
Expand All @@ -42,6 +43,11 @@
<li>Also confirm the DKIM headers validate using a 3rd party tool, such as those provided by Gmail and most email clients
</ol>
';
$string['dnssettings'] = 'SPF / DKIM / DMARC DNS settings';
$string['dnsspfinclude'] = 'SPF include';
$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.ache.org -all</code>.</p>
<p>The * char can be used as a wildcard eg <code>*acme.org</code> would also match.</p>
';
$string['domaindefaultnoreply'] = 'Default noreply';
$string['enabled'] = 'Enabled';
$string['enabled_help'] = 'Allow the plugin to process incoming messages';
Expand All @@ -57,7 +63,7 @@
$string['privacy:metadata:tool_emailutils_list'] = 'Information.';
$string['privacy:metadata:tool_emailutils_list:userid'] = 'The ID of the user.';
$string['privacy:metadata:tool_emailutils_list:updatedid'] = 'The ID of updated user.';
$string['pluginname'] = 'Amazon SES Complaints';
$string['pluginname'] = 'Email utilities';
$string['resetbounces'] = 'Reset the number of bounces';
$string['sendcount'] = 'Send count';
$string['selectoractive'] = 'Active selector';
Expand All @@ -75,6 +81,6 @@
$string['selectordelete'] = 'Delete key pair';
$string['selectordeleted'] = 'Key pair has been deleted';
$string['selectordeleteconfirm'] = 'This will permanently delete this selector\'s private and public keys and is irreversable.';
$string['settings'] = 'Settings';
$string['settings'] = 'AWS SES settings';
$string['username'] = 'Username';
$string['username_help'] = 'HTTP Basic Auth Username';
11 changes: 11 additions & 0 deletions lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ function tool_emailutils_bulk_user_actions() {
];
}

/**
* Security checks.
*
* @return array
*/
function tool_emailutils_security_checks() {
return [
new \tool_emailutils\check\dnsspf(),
];
}

14 changes: 14 additions & 0 deletions settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@
new moodle_url('/admin/tool/emailutils/index.php')
));

// DNS check settings.
$settings = new admin_settingpage(
'tool_emailutils_dns',
new lang_string('dnssettings', 'tool_emailutils')
);

$settings->add(new admin_setting_configtext(
'tool_emailutils/dnsspfinclude',
new lang_string('dnsspfinclude', 'tool_emailutils'),
new lang_string('dnsspfinclude_help', 'tool_emailutils'),
'')
);
$ADMIN->add('tool_emailutils', $settings);

// Plugin Settings Page.
$settings = new admin_settingpage(
'tool_emailutils_options',
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@
'local_aws' => 2020061500
];
$plugin->maturity = MATURITY_STABLE;
$plugin->supported = [39, 400];
$plugin->supported = [39, 403];

0 comments on commit 62fec78

Please sign in to comment.