Skip to content

Commit

Permalink
Added DKIM manager page #7
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanheywood committed Aug 16, 2023
1 parent d30f95b commit 05b4230
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 0 deletions.
225 changes: 225 additions & 0 deletions classes/dkim_manager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<?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/>.

/**
* DKIM manager
*
* This loads, verifies and can auto create DKIM pairs of certificates
* Code largely adapted from PHPMailer
*
* @package tool_emailutils
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Brendan Heywood <[email protected]>
*/

namespace tool_emailutils;

/**
* DKIM manager
*/
class dkim_manager {

/** @var Domain */
protected $domain;

/** @var Selector */
protected $selector;

/** @var Private key */
protected $privatekey;

/** @var Public key */
protected $publickey;

/** @var DNS record */
protected $dnsrecord;

/** Digest algorythm */
const DIGEST_ALG = 'sha256';

/**
* Create or load the certificates for a domain and selector
* @param string $domain domain
* @param string $selector
* @param bool $autocreate Should this autocreate cert pairs if they don't exist?
*/
public function __construct($domain, $selector, $autocreate = false) {
$this->domain = $domain;
$this->selector = $selector;

$privatekeyfile = $this->get_private_key_path();
$publickeyfile = $this->get_public_key_path();

if (!file_exists($privatekeyfile) && $autocreate) {

$this->get_base_path(true);
// Create a 2048-bit RSA key with an SHA256 digest.
$pk = openssl_pkey_new(
[
'digest_alg' => self::DIGEST_ALG,
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]
);

// Save both keys.
openssl_pkey_export_to_file($pk, $privatekeyfile);
$details = openssl_pkey_get_details($pk);
file_put_contents($publickeyfile, $details['key']);
}

$this->privatekey = file_get_contents($privatekeyfile);
$this->publickey = file_get_contents($publickeyfile);
}

/**
* Get the domain file path
*/
public function get_domain_path() {
global $CFG;
return $CFG->dataroot . '/dkim/' . $this->domain . '/';
}

/**
* Get the domain file path
* @param bool $create auto create the directories
*/
public function get_base_path($create = false) {
$certdir = $this->get_domain_path();
if ($create) {
mkdir($certdir, 0777, true);
}
return $certdir . '/' . $this->selector;
}

/**
* Get the private key file path
*/
public function get_private_key_path() {
return $this->get_base_path() . '.private';
}

/**
* Get the public key file path
*/
public function get_public_key_path() {
return $this->get_base_path() . '.public';
}

/**
* Get the DNS record file path
*/
public function get_dns_record_path() {
return $this->get_base_path() . '.txt';
}

/**
* Get the domain the DKIM record should be stored at
*/
public function get_dns_domain() {
return "{$this->selector}._domainkey.{$this->domain}";
}

/**
* Get the key of the DKIM txt record
*/
public function get_dns_key() {
return $this->get_dns_domain() . ' IN TXT';
}

/**
* Get the value of the DKIM record
*
* This loads the public key and then stores the DNS record in a file.
*/
public function get_dns_value() {
if (!empty($this->dnsrecord)) {
return $this->dnsrecord;
}

// TODO add support for records added by open dkim
// These do not include the public key in the normal format, only in the DNS value format.

if (empty($this->publickey)) {
return "ERROR: Can't find public key";
}

$dnsvalue = 'v=DKIM1;';
$dnsvalue .= ' h=' . self::DIGEST_ALG . ';'; // Hash algorythm.
$dnsvalue .= ' t=s;'; // No sub domains allowed.
$dnsvalue .= ' k=rsa;'; // Key type.
$dnsvalue .= ' p='; // Public key.

$publickey = $this->publickey;
$publickey = preg_replace('/^-+.*?-+$/m', '', $publickey); // Remove PEM wrapper.
$publickey = str_replace(["\r", "\n"], '', $publickey); // Strip line breaks.
$dnsvalue .= $publickey;

$this->dnsrecord = trim($dnsvalue);

return $this->dnsrecord;
}

/**
* Get a chunked version of the DKIM record
*
* Strip and split the key into smaller parts and format for DNS as many systems
* don't like long TXT entries but are OK if it's split into 255-char chunks.
*/
public function get_dns_value_chunked() {

$rawvalue = $this->get_dns_value();

// Split into chunks.
$keyparts = str_split($rawvalue, 253); // Becomes 255 when quotes are included.
// Quote each chunk.
foreach ($keyparts as $keypart) {
$dnsvalue .= '"' . trim($keypart) . '" ';
}

return $dnsvalue;

}
/**
* Get the alternate escaped version of the DKIM record
*
* Some DNS servers don't like ;(semi colon) chars unless backslash-escaped
*/
public function get_dns_value_escaped() {

$value = $this->get_dns_value_chunked();
$value = str_replace(';', '\;', $value);
return $value;

}

/**
* Delete all info about a selector
*/
public function delete_selector() {
$privatekeyfile = $this->get_private_key_path();
$publickeyfile = $this->get_public_key_path();
$dnsrecordfile = $this->get_dns_record_path();
$domaindir = $this->get_domain_path();

@unlink($privatekeyfile);
@unlink($publickeyfile);
@unlink($dnsrecordfile);
@rmdir($domaindir);
}

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

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

namespace tool_emailutils\form;

defined('MOODLE_INTERNAL') || die;

require_once("$CFG->libdir/formslib.php");

/**
* Selector form
*/
class create_dkim extends \moodleform {

/**
* Selector
* @see moodleform::definition()
*/
public function definition() {

global $CFG;

$mform = $this->_form;
$noreplydomain = substr($CFG->noreplyaddress, strpos($CFG->noreplyaddress, '@') + 1);

$group = [];

$group[] =& $mform->createElement('text', 'domain', array("size" => 20));
$mform->setDefault("domain", $noreplydomain);
$mform->setType('domain', PARAM_HOST);

$group[] =& $mform->createElement('text', 'selector', array("size" => 20));

$selector = \userdate(time(), get_string('selectordefault', 'tool_emailutils'));
$mform->setDefault("selector", $selector);
$mform->setType('selector', PARAM_HOST);

$mform->addGroup($group, 'selector', get_string('selectorcreate', 'tool_emailutils'), '', false);

$this->add_action_buttons(true, get_string('selectorcreatesubmit', 'tool_emailutils'));
}

/**
* Validate
*
* @param mixed $data date
* @param mixed $files files
* @return mixed errors
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
return $errors;
}
}
Loading

0 comments on commit 05b4230

Please sign in to comment.