blob: 2a19ff09cacd820875b9eadd89cf2cbaa939fe72 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3function dkim($_action, $_data = null, $privkey = false) {
4 global $redis;
5 global $lang;
6 switch ($_action) {
7 case 'add':
8 if ($_SESSION['mailcow_cc_role'] != "admin") {
9 $_SESSION['return'][] = array(
10 'type' => 'danger',
11 'log' => array(__FUNCTION__, $_action, $_data, ),
12 'msg' => 'access_denied'
13 );
14 return false;
15 }
16 $key_length = intval($_data['key_size']);
17 $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim';
18 $domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['domains']));
19 $domains = array_filter($domains);
20 foreach ($domains as $domain) {
21 if (!is_valid_domain_name($domain) || !is_numeric($key_length)) {
22 $_SESSION['return'][] = array(
23 'type' => 'danger',
24 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
25 'msg' => array('dkim_domain_or_sel_invalid', $domain)
26 );
27 continue;
28 }
29 if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
30 $_SESSION['return'][] = array(
31 'type' => 'danger',
32 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
33 'msg' => array('dkim_domain_or_sel_invalid', $domain)
34 );
35 continue;
36 }
37 if (!ctype_alnum(str_replace(['-', '_'], '', $dkim_selector))) {
38 $_SESSION['return'][] = array(
39 'type' => 'danger',
40 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
41 'msg' => array('dkim_domain_or_sel_invalid', $domain)
42 );
43 continue;
44 }
45 $config = array(
46 "digest_alg" => "sha256",
47 "private_key_bits" => $key_length,
48 "private_key_type" => OPENSSL_KEYTYPE_RSA,
49 );
50 if ($keypair_ressource = openssl_pkey_new($config)) {
51 $key_details = openssl_pkey_get_details($keypair_ressource);
52 $pubKey = implode(array_slice(
53 array_filter(
54 explode(PHP_EOL, $key_details['key'])
55 ), 1, -1)
56 );
57 // Save public key and selector to redis
58 try {
59 $redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey);
60 $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
61 }
62 catch (RedisException $e) {
63 $_SESSION['return'][] = array(
64 'type' => 'danger',
65 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
66 'msg' => array('redis_error', $e)
67 );
68 continue;
69 }
70 // Export private key and save private key to redis
71 openssl_pkey_export($keypair_ressource, $privKey);
72 if (isset($privKey) && !empty($privKey)) {
73 try {
74 $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey));
75 }
76 catch (RedisException $e) {
77 $_SESSION['return'][] = array(
78 'type' => 'danger',
79 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
80 'msg' => array('redis_error', $e)
81 );
82 continue;
83 }
84 }
85 $_SESSION['return'][] = array(
86 'type' => 'success',
87 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
88 'msg' => array('dkim_added', $domain)
89 );
90 }
91 else {
92 $_SESSION['return'][] = array(
93 'type' => 'danger',
94 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
95 'msg' => array('dkim_domain_or_sel_invalid', $domain)
96 );
97 continue;
98 }
99 }
100 break;
101 case 'duplicate':
102 if ($_SESSION['mailcow_cc_role'] != "admin") {
103 $_SESSION['return'][] = array(
104 'type' => 'danger',
105 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
106 'msg' => 'access_denied'
107 );
108 return false;
109 }
110 $from_domain = $_data['from_domain'];
111 $from_domain_dkim = dkim('details', $from_domain, true);
112 if (empty($from_domain_dkim)) {
113 $_SESSION['return'][] = array(
114 'type' => 'danger',
115 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
116 'msg' => array('dkim_domain_or_sel_invalid', $from_domain)
117 );
118 continue;
119 }
120 $to_domains = (array)$_data['to_domain'];
121 $to_domains = array_filter($to_domains);
122 foreach ($to_domains as $to_domain) {
123 try {
124 $redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']);
125 $redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']);
126 $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey'])));
127 }
128 catch (RedisException $e) {
129 $_SESSION['return'][] = array(
130 'type' => 'danger',
131 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
132 'msg' => array('redis_error', $e)
133 );
134 continue;
135 }
136 $_SESSION['return'][] = array(
137 'type' => 'success',
138 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
139 'msg' => array('dkim_duplicated', $from_domain, $to_domain)
140 );
141 }
142 break;
143 case 'import':
144 if ($_SESSION['mailcow_cc_role'] != "admin") {
145 $_SESSION['return'][] = array(
146 'type' => 'danger',
147 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
148 'msg' => 'access_denied'
149 );
150 return false;
151 }
152 $private_key_input = trim($_data['private_key_file']);
153 $overwrite_existing = intval($_data['overwrite_existing']);
154 $private_key_normalized = preg_replace('~\r\n?~', "\n", $private_key_input);
155 $private_key = openssl_pkey_get_private($private_key_normalized);
156 if ($ssl_error = openssl_error_string()) {
157 $_SESSION['return'][] = array(
158 'type' => 'danger',
159 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
160 'msg' => array('private_key_error', $ssl_error)
161 );
162 return false;
163 }
164 // Explode by nl
165 $pem_public_key_array = explode(PHP_EOL, trim(openssl_pkey_get_details($private_key)['key']));
166 // Remove first and last line/item
167 array_shift($pem_public_key_array);
168 array_pop($pem_public_key_array);
169 // Implode as single string
170 $pem_public_key = implode('', $pem_public_key_array);
171 $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim';
172 $domain = $_data['domain'];
173 if (!is_valid_domain_name($domain)) {
174 $_SESSION['return'][] = array(
175 'type' => 'danger',
176 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
177 'msg' => array('dkim_domain_or_sel_invalid', $domain)
178 );
179 return false;
180 }
181 if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
182 if ($overwrite_existing == 0) {
183 $_SESSION['return'][] = array(
184 'type' => 'danger',
185 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
186 'msg' => array('dkim_domain_or_sel_exists', $domain)
187 );
188 return false;
189 }
190 }
191 if (!ctype_alnum($dkim_selector)) {
192 $_SESSION['return'][] = array(
193 'type' => 'danger',
194 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
195 'msg' => array('dkim_domain_or_sel_invalid', $domain)
196 );
197 return false;
198 }
199 try {
200 dkim('delete', $domain);
201 $redis->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key);
202 $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
203 $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized);
204 }
205 catch (RedisException $e) {
206 $_SESSION['return'][] = array(
207 'type' => 'danger',
208 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
209 'msg' => array('redis_error', $e)
210 );
211 return false;
212 }
213 unset($private_key_normalized);
214 unset($private_key);
215 unset($private_key_input);
216 try {
217 }
218 catch (RedisException $e) {
219 $_SESSION['return'][] = array(
220 'type' => 'danger',
221 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
222 'msg' => array('redis_error', $e)
223 );
224 return false;
225 }
226 $_SESSION['return'][] = array(
227 'type' => 'success',
228 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
229 'msg' => array('dkim_added', $domain)
230 );
231 return true;
232 break;
233 case 'details':
234 if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) && $_SESSION['mailcow_cc_role'] != "admin") {
235 return false;
236 }
237 $dkimdata = array();
238 if ($redis_dkim_key_data = $redis->hGet('DKIM_PUB_KEYS', $_data)) {
239 $dkimdata['pubkey'] = $redis_dkim_key_data;
240 if (strlen($dkimdata['pubkey']) < 391) {
241 $dkimdata['length'] = "1024";
242 }
243 elseif (strlen($dkimdata['pubkey']) < 736) {
244 $dkimdata['length'] = "2048";
245 }
246 elseif (strlen($dkimdata['pubkey']) < 1416) {
247 $dkimdata['length'] = "4096";
248 }
249 else {
250 $dkimdata['length'] = ">= 8192";
251 }
252 if ($GLOBALS['SPLIT_DKIM_255'] === true) {
253 $dkim_txt_tmp = str_split('v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data, 255);
254 $dkimdata['dkim_txt'] = sprintf('"%s"', implode('" "', $dkim_txt_tmp ) );
255 }
256 else {
257 $dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data;
258 }
259 $dkimdata['dkim_selector'] = $redis->hGet('DKIM_SELECTORS', $_data);
260 if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] || $privkey == true) {
261 $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data));
262 }
263 else {
264 $dkimdata['privkey'] = '';
265 }
266 }
267 return $dkimdata;
268 break;
269 case 'blind':
270 if ($_SESSION['mailcow_cc_role'] != "admin") {
271 $_SESSION['return'][] = array(
272 'type' => 'danger',
273 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
274 'msg' => 'access_denied'
275 );
276 return false;
277 }
278 $blinddkim = array();
279 foreach ($redis->hKeys('DKIM_PUB_KEYS') as $redis_dkim_domain) {
280 $blinddkim[] = $redis_dkim_domain;
281 }
282 return array_diff($blinddkim, array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')));
283 break;
284 case 'delete':
285 $domains = (array)$_data['domains'];
286 if ($_SESSION['mailcow_cc_role'] != "admin") {
287 $_SESSION['return'][] = array(
288 'type' => 'danger',
289 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
290 'msg' => 'access_denied'
291 );
292 return false;
293 }
294 foreach ($domains as $domain) {
295 if (!is_valid_domain_name($domain)) {
296 $_SESSION['return'][] = array(
297 'type' => 'danger',
298 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
299 'msg' => array('dkim_domain_or_sel_invalid', $domain)
300 );
301 continue;
302 }
303 try {
304 $selector = $redis->hGet('DKIM_SELECTORS', $domain);
305 $redis->hDel('DKIM_PUB_KEYS', $domain);
306 $redis->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain);
307 $redis->hDel('DKIM_SELECTORS', $domain);
308 }
309 catch (RedisException $e) {
310 $_SESSION['return'][] = array(
311 'type' => 'danger',
312 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
313 'msg' => array('redis_error', $e)
314 );
315 continue;
316 }
317 $_SESSION['return'][] = array(
318 'type' => 'success',
319 'log' => array(__FUNCTION__, $_action, $_data, $privkey),
320 'msg' => array('dkim_removed', htmlspecialchars($domain))
321 );
322 }
323 break;
324 }
325}