blob: 29b32d5d32e3526bac341f0097ad3e0dd56acaa1 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3function dkim($_action, $_data = null, $privkey = false) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004 global $redis;
5 global $lang;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006 switch ($_action) {
7 case 'add':
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008 $key_length = intval($_data['key_size']);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009 $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim';
10 $domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['domains']));
11 $domains = array_filter($domains);
12 foreach ($domains as $domain) {
13 if (!is_valid_domain_name($domain) || !is_numeric($key_length)) {
14 $_SESSION['return'][] = array(
15 'type' => 'danger',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017 'msg' => array('dkim_domain_or_sel_invalid', $domain)
18 );
19 continue;
20 }
21 if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
22 $_SESSION['return'][] = array(
23 'type' => 'danger',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020024 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010025 'msg' => array('dkim_domain_or_sel_invalid', $domain)
26 );
27 continue;
28 }
29 if (!ctype_alnum(str_replace(['-', '_'], '', $dkim_selector))) {
30 $_SESSION['return'][] = array(
31 'type' => 'danger',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020032 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010033 'msg' => array('dkim_domain_or_sel_invalid', $domain)
34 );
35 continue;
36 }
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +010037 if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
38 $_SESSION['return'][] = array(
39 'type' => 'danger',
40 'log' => array(__FUNCTION__, $_action, $_data),
41 'msg' => array('access_denied', $domain)
42 );
43 continue;
44 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010045 $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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020065 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010066 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020079 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010080 'msg' => array('redis_error', $e)
81 );
82 continue;
83 }
84 }
85 $_SESSION['return'][] = array(
86 'type' => 'success',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020087 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010088 'msg' => array('dkim_added', $domain)
89 );
90 }
91 else {
92 $_SESSION['return'][] = array(
93 'type' => 'danger',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020094 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010095 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200105 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100106 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200115 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100116 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200131 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100132 'msg' => array('redis_error', $e)
133 );
134 continue;
135 }
136 $_SESSION['return'][] = array(
137 'type' => 'success',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200138 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100139 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200147 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100148 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200159 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100160 '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
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200170 $pem_public_key = implode('', (array)$pem_public_key_array);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100171 $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim';
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200172 $domain = $_data['domain'];
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100173 if (!is_valid_domain_name($domain)) {
174 $_SESSION['return'][] = array(
175 'type' => 'danger',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200176 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100177 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200185 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100186 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200194 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100195 'msg' => array('dkim_domain_or_sel_invalid', $domain)
196 );
197 return false;
198 }
199 try {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100200 dkim('delete', array('domains' => $domain));
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100201 $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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200208 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100209 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200221 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100222 'msg' => array('redis_error', $e)
223 );
224 return false;
225 }
226 $_SESSION['return'][] = array(
227 'type' => 'success',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200228 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100229 '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);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200254 $dkimdata['dkim_txt'] = sprintf('"%s"', implode('" "', (array)$dkim_txt_tmp ) );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100255 }
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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200273 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100274 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200289 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100290 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200298 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100299 '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',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200312 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100313 'msg' => array('redis_error', $e)
314 );
315 continue;
316 }
317 $_SESSION['return'][] = array(
318 'type' => 'success',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200319 'log' => array(__FUNCTION__, $_action, $_data),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100320 'msg' => array('dkim_removed', htmlspecialchars($domain))
321 );
322 }
323 break;
324 }
325}