Skip to content

Commit 6c95313

Browse files
Added DKIM manager page #7
1 parent 925e1fc commit 6c95313

File tree

7 files changed

+566
-0
lines changed

7 files changed

+566
-0
lines changed

classes/dkim_manager.php

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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+
* @package tool_emailutils
19+
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
20+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21+
* @author Brendan Heywood <[email protected]>
22+
*/
23+
24+
namespace tool_emailutils;
25+
26+
/**
27+
* This loads, verifies and can auto create DKIM pairs of certificates
28+
*
29+
* Code largely adapted from PHPMailer
30+
*/
31+
class dkim_manager {
32+
33+
protected $domain;
34+
35+
protected $selector;
36+
37+
protected $privatekey;
38+
39+
protected $publickey;
40+
41+
protected $dnsrecord;
42+
43+
const DIGEST_ALG = 'sha256';
44+
45+
/**
46+
* Create or load the certificates for a domain and selector
47+
*
48+
*/
49+
public function __construct($domain, $selector, $autocreate = false) {
50+
$this->domain = $domain;
51+
$this->selector = $selector;
52+
53+
$privatekeyfile = $this->get_private_key_path();
54+
$publickeyfile = $this->get_public_key_path();
55+
56+
if (!file_exists($privatekeyfile) && $autocreate) {
57+
58+
$this->get_base_path(true);
59+
// Create a 2048-bit RSA key with an SHA256 digest.
60+
$pk = openssl_pkey_new(
61+
[
62+
'digest_alg' => self::DIGEST_ALG,
63+
'private_key_bits' => 2048,
64+
'private_key_type' => OPENSSL_KEYTYPE_RSA,
65+
]
66+
);
67+
68+
// Save both keys.
69+
openssl_pkey_export_to_file($pk, $privatekeyfile);
70+
$details = openssl_pkey_get_details($pk);
71+
file_put_contents($publickeyfile, $details['key']);
72+
}
73+
74+
$this->privatekey = file_get_contents($privatekeyfile);
75+
$this->publickey = file_get_contents($publickeyfile);
76+
}
77+
78+
public function get_domain_path() {
79+
global $CFG;
80+
return $CFG->dataroot . '/dkim/' . $this->domain . '/';
81+
}
82+
83+
public function get_base_path($create = false) {
84+
$certdir = $this->get_domain_path();
85+
if ($create) {
86+
mkdir($certdir, 0777, true);
87+
}
88+
return $certdir . '/' . $this->selector;
89+
}
90+
91+
public function get_private_key_path() {
92+
return $this->get_base_path() . '.private';
93+
}
94+
95+
public function get_public_key_path() {
96+
return $this->get_base_path() . '.public';
97+
}
98+
99+
public function get_dns_record_path() {
100+
return $this->get_base_path() . '.txt';
101+
}
102+
103+
/**
104+
* Get the domain the DKIM record should be stored at
105+
*/
106+
public function get_dns_domain() {
107+
return "{$this->selector}._domainkey.{$this->domain}";
108+
}
109+
110+
/**
111+
* Get the key of the DKIM txt record
112+
*/
113+
public function get_dns_key() {
114+
return $this->get_dns_domain() . ' IN TXT';
115+
}
116+
117+
/**
118+
* Get the value of the DKIM record
119+
*
120+
* This loads the public key and then stores the DNS record in a file.
121+
*/
122+
public function get_dns_value() {
123+
if (!empty($this->dnsrecord)) {
124+
return $this->dnsrecord;
125+
}
126+
127+
// TODO add support for records added by open dkim
128+
// These do not include the public key in the normal format, only in the DNS value format.
129+
130+
if (empty($this->publickey)) {
131+
return "ERROR: Can't find public key";
132+
}
133+
134+
$dnsvalue = 'v=DKIM1;';
135+
$dnsvalue .= ' h=' . self::DIGEST_ALG . ';'; // Hash algorythm.
136+
$dnsvalue .= ' t=s;'; // No sub domains allowed.
137+
$dnsvalue .= ' k=rsa;'; // Key type.
138+
$dnsvalue .= ' p='; // Public key.
139+
140+
$publickey = $this->publickey;
141+
$publickey = preg_replace('/^-+.*?-+$/m', '', $publickey); // Remove PEM wrapper.
142+
$publickey = str_replace(["\r", "\n"], '', $publickey); // Strip line breaks.
143+
$dnsvalue .= $publickey;
144+
145+
$this->dnsrecord = trim($dnsvalue);
146+
147+
return $this->dnsrecord;
148+
}
149+
150+
/**
151+
* Get a chunked version of the DKIM record
152+
*
153+
* Strip and split the key into smaller parts and format for DNS as many systems
154+
* don't like long TXT entries but are OK if it's split into 255-char chunks.
155+
*/
156+
public function get_dns_value_chunked() {
157+
158+
$rawvalue = $this->get_dns_value();
159+
160+
// Split into chunks.
161+
$keyparts = str_split($rawvalue, 253); // Becomes 255 when quotes are included.
162+
// Quote each chunk.
163+
foreach ($keyparts as $keypart) {
164+
$dnsvalue .= '"' . trim($keypart) . '" ';
165+
}
166+
167+
return $dnsvalue;
168+
169+
}
170+
/**
171+
* Get the alternate escaped version of the DKIM record
172+
*
173+
* Some DNS servers don't like ;(semi colon) chars unless backslash-escaped
174+
*/
175+
public function get_dns_value_escaped() {
176+
177+
$value = $this->get_dns_value_chunked();
178+
$value = str_replace(';', '\;', $value);
179+
return $value;
180+
181+
}
182+
183+
/**
184+
* Delete all info about a selector
185+
*/
186+
public function delete_selector() {
187+
$privatekeyfile = $this->get_private_key_path();
188+
$publickeyfile = $this->get_public_key_path();
189+
$dnsrecordfile = $this->get_dns_record_path();
190+
$domaindir = $this->get_domain_path();
191+
192+
@unlink($privatekeyfile);
193+
@unlink($publickeyfile);
194+
@unlink($dnsrecordfile);
195+
@rmdir($domaindir);
196+
}
197+
198+
}

classes/form/create_dkim.php

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
* @package tool_emailutils
19+
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
20+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21+
* @author Brendan Heywood <[email protected]>
22+
*/
23+
24+
namespace tool_emailutils\form;
25+
26+
require_once("$CFG->libdir/formslib.php");
27+
28+
class create_dkim extends \moodleform {
29+
30+
public function definition() {
31+
32+
global $CFG;
33+
34+
$mform = $this->_form;
35+
$noreplydomain = substr($CFG->noreplyaddress, strpos($CFG->noreplyaddress, '@') + 1);
36+
37+
$group = [];
38+
39+
$group[] =& $mform->createElement('text', 'domain', array("size" => 20));
40+
$mform->setDefault("domain", $noreplydomain);
41+
$mform->setType('domain', PARAM_HOST);
42+
43+
$group[] =& $mform->createElement('text', 'selector', array("size" => 20));
44+
45+
$selector = \userdate(time(), get_string('selectordefault', 'tool_emailutils'));
46+
$mform->setDefault("selector", $selector);
47+
$mform->setType('selector', PARAM_HOST);
48+
49+
$mform->addGroup($group, 'selector', get_string('selectorcreate', 'tool_emailutils'), '', false);
50+
51+
$this->add_action_buttons(true, get_string('selectorcreatesubmit', 'tool_emailutils'));
52+
}
53+
54+
public function validation($data, $files) {
55+
$errors = parent::validation($data, $files);
56+
return $errors;
57+
}
58+
}

0 commit comments

Comments
 (0)