Skip to content

Commit 05b4230

Browse files
Added DKIM manager page #7
1 parent d30f95b commit 05b4230

File tree

7 files changed

+620
-0
lines changed

7 files changed

+620
-0
lines changed

classes/dkim_manager.php

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* DKIM manager
19+
*
20+
* This loads, verifies and can auto create DKIM pairs of certificates
21+
* Code largely adapted from PHPMailer
22+
*
23+
* @package tool_emailutils
24+
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
25+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26+
* @author Brendan Heywood <[email protected]>
27+
*/
28+
29+
namespace tool_emailutils;
30+
31+
/**
32+
* DKIM manager
33+
*/
34+
class dkim_manager {
35+
36+
/** @var Domain */
37+
protected $domain;
38+
39+
/** @var Selector */
40+
protected $selector;
41+
42+
/** @var Private key */
43+
protected $privatekey;
44+
45+
/** @var Public key */
46+
protected $publickey;
47+
48+
/** @var DNS record */
49+
protected $dnsrecord;
50+
51+
/** Digest algorythm */
52+
const DIGEST_ALG = 'sha256';
53+
54+
/**
55+
* Create or load the certificates for a domain and selector
56+
* @param string $domain domain
57+
* @param string $selector
58+
* @param bool $autocreate Should this autocreate cert pairs if they don't exist?
59+
*/
60+
public function __construct($domain, $selector, $autocreate = false) {
61+
$this->domain = $domain;
62+
$this->selector = $selector;
63+
64+
$privatekeyfile = $this->get_private_key_path();
65+
$publickeyfile = $this->get_public_key_path();
66+
67+
if (!file_exists($privatekeyfile) && $autocreate) {
68+
69+
$this->get_base_path(true);
70+
// Create a 2048-bit RSA key with an SHA256 digest.
71+
$pk = openssl_pkey_new(
72+
[
73+
'digest_alg' => self::DIGEST_ALG,
74+
'private_key_bits' => 2048,
75+
'private_key_type' => OPENSSL_KEYTYPE_RSA,
76+
]
77+
);
78+
79+
// Save both keys.
80+
openssl_pkey_export_to_file($pk, $privatekeyfile);
81+
$details = openssl_pkey_get_details($pk);
82+
file_put_contents($publickeyfile, $details['key']);
83+
}
84+
85+
$this->privatekey = file_get_contents($privatekeyfile);
86+
$this->publickey = file_get_contents($publickeyfile);
87+
}
88+
89+
/**
90+
* Get the domain file path
91+
*/
92+
public function get_domain_path() {
93+
global $CFG;
94+
return $CFG->dataroot . '/dkim/' . $this->domain . '/';
95+
}
96+
97+
/**
98+
* Get the domain file path
99+
* @param bool $create auto create the directories
100+
*/
101+
public function get_base_path($create = false) {
102+
$certdir = $this->get_domain_path();
103+
if ($create) {
104+
mkdir($certdir, 0777, true);
105+
}
106+
return $certdir . '/' . $this->selector;
107+
}
108+
109+
/**
110+
* Get the private key file path
111+
*/
112+
public function get_private_key_path() {
113+
return $this->get_base_path() . '.private';
114+
}
115+
116+
/**
117+
* Get the public key file path
118+
*/
119+
public function get_public_key_path() {
120+
return $this->get_base_path() . '.public';
121+
}
122+
123+
/**
124+
* Get the DNS record file path
125+
*/
126+
public function get_dns_record_path() {
127+
return $this->get_base_path() . '.txt';
128+
}
129+
130+
/**
131+
* Get the domain the DKIM record should be stored at
132+
*/
133+
public function get_dns_domain() {
134+
return "{$this->selector}._domainkey.{$this->domain}";
135+
}
136+
137+
/**
138+
* Get the key of the DKIM txt record
139+
*/
140+
public function get_dns_key() {
141+
return $this->get_dns_domain() . ' IN TXT';
142+
}
143+
144+
/**
145+
* Get the value of the DKIM record
146+
*
147+
* This loads the public key and then stores the DNS record in a file.
148+
*/
149+
public function get_dns_value() {
150+
if (!empty($this->dnsrecord)) {
151+
return $this->dnsrecord;
152+
}
153+
154+
// TODO add support for records added by open dkim
155+
// These do not include the public key in the normal format, only in the DNS value format.
156+
157+
if (empty($this->publickey)) {
158+
return "ERROR: Can't find public key";
159+
}
160+
161+
$dnsvalue = 'v=DKIM1;';
162+
$dnsvalue .= ' h=' . self::DIGEST_ALG . ';'; // Hash algorythm.
163+
$dnsvalue .= ' t=s;'; // No sub domains allowed.
164+
$dnsvalue .= ' k=rsa;'; // Key type.
165+
$dnsvalue .= ' p='; // Public key.
166+
167+
$publickey = $this->publickey;
168+
$publickey = preg_replace('/^-+.*?-+$/m', '', $publickey); // Remove PEM wrapper.
169+
$publickey = str_replace(["\r", "\n"], '', $publickey); // Strip line breaks.
170+
$dnsvalue .= $publickey;
171+
172+
$this->dnsrecord = trim($dnsvalue);
173+
174+
return $this->dnsrecord;
175+
}
176+
177+
/**
178+
* Get a chunked version of the DKIM record
179+
*
180+
* Strip and split the key into smaller parts and format for DNS as many systems
181+
* don't like long TXT entries but are OK if it's split into 255-char chunks.
182+
*/
183+
public function get_dns_value_chunked() {
184+
185+
$rawvalue = $this->get_dns_value();
186+
187+
// Split into chunks.
188+
$keyparts = str_split($rawvalue, 253); // Becomes 255 when quotes are included.
189+
// Quote each chunk.
190+
foreach ($keyparts as $keypart) {
191+
$dnsvalue .= '"' . trim($keypart) . '" ';
192+
}
193+
194+
return $dnsvalue;
195+
196+
}
197+
/**
198+
* Get the alternate escaped version of the DKIM record
199+
*
200+
* Some DNS servers don't like ;(semi colon) chars unless backslash-escaped
201+
*/
202+
public function get_dns_value_escaped() {
203+
204+
$value = $this->get_dns_value_chunked();
205+
$value = str_replace(';', '\;', $value);
206+
return $value;
207+
208+
}
209+
210+
/**
211+
* Delete all info about a selector
212+
*/
213+
public function delete_selector() {
214+
$privatekeyfile = $this->get_private_key_path();
215+
$publickeyfile = $this->get_public_key_path();
216+
$dnsrecordfile = $this->get_dns_record_path();
217+
$domaindir = $this->get_domain_path();
218+
219+
@unlink($privatekeyfile);
220+
@unlink($publickeyfile);
221+
@unlink($dnsrecordfile);
222+
@rmdir($domaindir);
223+
}
224+
225+
}

classes/form/create_dkim.php

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* Create dkim selector form
19+
*
20+
* @package tool_emailutils
21+
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
22+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23+
* @author Brendan Heywood <[email protected]>
24+
*/
25+
26+
namespace tool_emailutils\form;
27+
28+
defined('MOODLE_INTERNAL') || die;
29+
30+
require_once("$CFG->libdir/formslib.php");
31+
32+
/**
33+
* Selector form
34+
*/
35+
class create_dkim extends \moodleform {
36+
37+
/**
38+
* Selector
39+
* @see moodleform::definition()
40+
*/
41+
public function definition() {
42+
43+
global $CFG;
44+
45+
$mform = $this->_form;
46+
$noreplydomain = substr($CFG->noreplyaddress, strpos($CFG->noreplyaddress, '@') + 1);
47+
48+
$group = [];
49+
50+
$group[] =& $mform->createElement('text', 'domain', array("size" => 20));
51+
$mform->setDefault("domain", $noreplydomain);
52+
$mform->setType('domain', PARAM_HOST);
53+
54+
$group[] =& $mform->createElement('text', 'selector', array("size" => 20));
55+
56+
$selector = \userdate(time(), get_string('selectordefault', 'tool_emailutils'));
57+
$mform->setDefault("selector", $selector);
58+
$mform->setType('selector', PARAM_HOST);
59+
60+
$mform->addGroup($group, 'selector', get_string('selectorcreate', 'tool_emailutils'), '', false);
61+
62+
$this->add_action_buttons(true, get_string('selectorcreatesubmit', 'tool_emailutils'));
63+
}
64+
65+
/**
66+
* Validate
67+
*
68+
* @param mixed $data date
69+
* @param mixed $files files
70+
* @return mixed errors
71+
*/
72+
public function validation($data, $files) {
73+
$errors = parent::validation($data, $files);
74+
return $errors;
75+
}
76+
}

0 commit comments

Comments
 (0)