Skip to content

Commit

Permalink
Improve SPF check
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanheywood committed Jan 11, 2024
1 parent dda3048 commit 8f7d0c4
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 20 deletions.
50 changes: 37 additions & 13 deletions classes/check/dnsspf.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
namespace tool_emailutils\check;
use core\check\check;
use core\check\result;
use tool_emailutils\spf;
use tool_emailutils\dns_util;

/**
* DNS Email SPF check.
Expand All @@ -38,6 +38,17 @@
*/
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.
*
Expand All @@ -53,28 +64,41 @@ public function get_result() : result {
$status = result::INFO;
$summary = '';

$spf = new spf();
$dns = new dns_util();

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

$noreplydomain = $spf->get_noreply_domain();
$noreplydomain = $dns->get_noreply_domain();
$details .= "<p>No reply domain: <code>$noreplydomain</code></p>";

$spf = $spf->get_spf_record();

// Does it has an SPF record at all?
if (!empty($spf)) {
$details .= "<p>SPF record:<br><code>$spf</code></p>";
$status = result::OK;
$summary = 'SPF record exists';
$spf = $dns->get_spf_record();

} else {
$status = result::CRITICAL;
// 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);
}

Expand Down
47 changes: 44 additions & 3 deletions classes/spf.php → classes/dns_util.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,36 @@
* @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 spf {
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 = self::get_noreply_domain();
$domain = $this->get_noreply_domain();
$records = dns_get_record($domain, DNS_TXT);
foreach ($records as $record) {
$txt = $record['txt'];
Expand All @@ -64,5 +75,35 @@ public function get_spf_record() {
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 '';
}

}

11 changes: 8 additions & 3 deletions lang/en/tool_emailutils.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
$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>
Expand All @@ -43,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 record 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 @@ -58,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 @@ -76,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';
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 8f7d0c4

Please sign in to comment.