From dda30481b6e4285c2bea20a54ab910fe327c5310 Mon Sep 17 00:00:00 2001
From: Brendan Heywood <brendan@catalyst-au.net>
Date: Thu, 11 Jan 2024 13:08:21 +1100
Subject: [PATCH] Adde SPF basic security check #12

---
 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 @@
+<?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 <brendan@catalyst-au.net>
+ * @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 <brendan@catalyst-au.net>
+ * @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 .= "<p>No reply email: <code>$noreply</code></p>";
+
+        $noreplydomain = $spf->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';
+
+        } else {
+            $status = result::CRITICAL;
+            $summary = 'Missing SPF record';
+            $details .= "<p>$domain does not have an SPF record</p>";
+        }
+
+        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 @@
+<?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 <brendan@catalyst-au.net>
+ * @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 <brendan@catalyst-au.net>
+ * @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'] = '<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.
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(),
+    ];
+}
+