From 52b6e1bddf47bbd42bbdcf78193e0afe313ded4b Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Thu, 11 Jan 2024 13:08:21 +1100 Subject: [PATCH] Adde SPF basic security check #13 --- classes/check/dnsspf.php | 99 +++++++++++++++++++++++++++++++++++++ classes/spf.php | 68 +++++++++++++++++++++++++ lang/en/tool_emailutils.php | 1 + lib.php | 11 +++++ 4 files changed, 179 insertions(+) create mode 100644 classes/check/dnsspf.php create mode 100644 classes/spf.php diff --git a/classes/check/dnsspf.php b/classes/check/dnsspf.php new file mode 100644 index 0000000..5848fa9 --- /dev/null +++ b/classes/check/dnsspf.php @@ -0,0 +1,99 @@ +. +/** + * DNS Email SPF check. + * + * @package tool_heartbeat + * @author Brendan Heywood + * @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\spf; + +/** + * DNS Email SPF check. + * + * @package tool_heartbeat + * @author Brendan Heywood + * @copyright Catalyst IT 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class dnsspf extends check { + + /** + * 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 = ''; + + $spf = new spf(); + + $noreply = $spf->get_noreply(); + $details .= "

No reply email: $noreply

"; + + $noreplydomain = $spf->get_noreply_domain(); + $details .= "

No reply domain: $noreplydomain

"; + + $spf = $spf->get_spf_record(); + + // Does it has an SPF record at all? + if (!empty($spf)) { + $details .= "

SPF record:
$spf

"; + $status = result::OK; + $summary = 'SPF record exists'; + + } else { + $status = result::CRITICAL; + $summary = 'Missing SPF record'; + $details .= "

$domain does not have an SPF record

"; + } + + return new result($status, $summary, $details); + } + + /** + * Find the top level domain + * + * @return domain + */ + public function get_base_domain($domain) { + $parts = explode('.', $domain); + $size = count($parts); + for ($c = $size - 1; $c >= 0; $c--) { + $end = join('.', array_slice($parts, $c, $size)); + $result = @dns_get_record($end . '.', DNS_A); + if (!empty($result)) { + return $end; + } + } + return $domain; + } + +} diff --git a/classes/spf.php b/classes/spf.php new file mode 100644 index 0000000..f14c3a6 --- /dev/null +++ b/classes/spf.php @@ -0,0 +1,68 @@ +. + +/** + * SPF utils + * + * @package tool_heartbeat + * @copyright Catalyst IT 2024 + * @author Brendan Heywood + * @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 + * @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 { + + public function get_noreply() { + global $CFG; + + return $CFG->noreplyaddress; + } + + public function get_noreply_domain() { + global $CFG; + + $noreplydomain = substr($CFG->noreplyaddress, strpos($CFG->noreplyaddress, '@') + 1); + return $noreplydomain; + } + + + public function get_spf_record() { + + $domain = self::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 ''; + } + +} + diff --git a/lang/en/tool_emailutils.php b/lang/en/tool_emailutils.php index 8360123..8c04de0 100644 --- a/lang/en/tool_emailutils.php +++ b/lang/en/tool_emailutils.php @@ -31,6 +31,7 @@ $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['checkdnsspf'] = 'DNS Email SPF check'; $string['dkimmanagerhelp'] = '

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:

  1. First decide and set the $CFG->noreply email as the domain of the reply email is tied to the signing. diff --git a/lib.php b/lib.php index 12be8ed..4c86821 100644 --- a/lib.php +++ b/lib.php @@ -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(), + ]; +} +