blob: 05ad25d85915853310e0fd0cbef583e72ee2ca70 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2function relayhost($_action, $_data = null) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003 global $pdo;
4 global $lang;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005 $_data_log = $_data;
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_log),
12 'msg' => 'access_denied'
13 );
14 return false;
15 }
16 $hostname = trim($_data['hostname']);
17 $username = str_replace(':', '\:', trim($_data['username']));
18 $password = str_replace(':', '\:', trim($_data['password']));
19 if (empty($hostname)) {
20 $_SESSION['return'][] = array(
21 'type' => 'danger',
22 'log' => array(__FUNCTION__, $_action, $_data_log),
23 'msg' => array('invalid_host', htmlspecialchars($host))
24 );
25 return false;
26 }
27 try {
28 $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`)
29 VALUES (:hostname, :username, :password, :active)");
30 $stmt->execute(array(
31 ':hostname' => $hostname,
32 ':username' => $username,
33 ':password' => str_replace(':', '\:', $password),
34 ':active' => '1'
35 ));
36 }
37 catch (PDOException $e) {
38 $_SESSION['return'][] = array(
39 'type' => 'danger',
40 'log' => array(__FUNCTION__, $_action, $_data_log),
41 'msg' => array('mysql_error', $e)
42 );
43 return false;
44 }
45 $_SESSION['return'][] = array(
46 'type' => 'success',
47 'log' => array(__FUNCTION__, $_action, $_data_log),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020048 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts)))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010049 );
50 break;
51 case 'edit':
52 if ($_SESSION['mailcow_cc_role'] != "admin") {
53 $_SESSION['return'][] = array(
54 'type' => 'danger',
55 'log' => array(__FUNCTION__, $_action, $_data_log),
56 'msg' => 'access_denied'
57 );
58 return false;
59 }
60 $ids = (array)$_data['id'];
61 foreach ($ids as $id) {
62 $is_now = relayhost('details', $id);
63 if (!empty($is_now)) {
64 $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname'];
65 $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username'];
66 $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password'];
67 $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
68 }
69 else {
70 $_SESSION['return'][] = array(
71 'type' => 'danger',
72 'log' => array(__FUNCTION__, $_action, $_data_log),
73 'msg' => array('relayhost_invalid', $id)
74 );
75 continue;
76 }
77 try {
78 $stmt = $pdo->prepare("UPDATE `relayhosts` SET
79 `hostname` = :hostname,
80 `username` = :username,
81 `password` = :password,
82 `active` = :active
83 WHERE `id` = :id");
84 $stmt->execute(array(
85 ':id' => $id,
86 ':hostname' => $hostname,
87 ':username' => $username,
88 ':password' => $password,
89 ':active' => $active
90 ));
91 }
92 catch (PDOException $e) {
93 $_SESSION['return'][] = array(
94 'type' => 'danger',
95 'log' => array(__FUNCTION__, $_action, $_data_log),
96 'msg' => array('mysql_error', $e)
97 );
98 continue;
99 }
100 $_SESSION['return'][] = array(
101 'type' => 'success',
102 'log' => array(__FUNCTION__, $_action, $_data_log),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200103 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames)))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100104 );
105 }
106 break;
107 case 'delete':
108 if ($_SESSION['mailcow_cc_role'] != "admin") {
109 $_SESSION['return'][] = array(
110 'type' => 'danger',
111 'log' => array(__FUNCTION__, $_action, $_data_log),
112 'msg' => 'access_denied'
113 );
114 return false;
115 }
116 $ids = (array)$_data['id'];
117 foreach ($ids as $id) {
118 try {
119 $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id");
120 $stmt->execute(array(':id' => $id));
121 $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id");
122 $stmt->execute(array(':id' => $id));
123 }
124 catch (PDOException $e) {
125 $_SESSION['return'][] = array(
126 'type' => 'danger',
127 'log' => array(__FUNCTION__, $_action, $_data_log),
128 'msg' => array('mysql_error', $e)
129 );
130 continue;
131 }
132 $_SESSION['return'][] = array(
133 'type' => 'success',
134 'log' => array(__FUNCTION__, $_action, $_data_log),
135 'msg' => array('relayhost_removed', htmlspecialchars($id))
136 );
137 }
138 break;
139 case 'get':
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200140 if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100141 return false;
142 }
143 $relayhosts = array();
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200144 $stmt = $pdo->query("SELECT `id`, `hostname`, `username`, `active` FROM `relayhosts`");
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100145 $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC);
146 return $relayhosts;
147 break;
148 case 'details':
149 if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
150 return false;
151 }
152 $relayhostdata = array();
153 $stmt = $pdo->prepare("SELECT `id`,
154 `hostname`,
155 `username`,
156 `password`,
157 `active`,
158 CONCAT(LEFT(`password`, 3), '...') AS `password_short`
159 FROM `relayhosts`
160 WHERE `id` = :id");
161 $stmt->execute(array(':id' => $_data));
162 $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC);
163 if (!empty($relayhostdata)) {
164 $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id");
165 $stmt->execute(array(':id' => $_data));
166 $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains'];
167 $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains;
168 $relayhostdata['used_by_domains'] = $used_by_domains;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200169 $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS `used_by_mailboxes` FROM `mailbox` WHERE JSON_VALUE(`attributes`, '$.relayhost') = :id");
170 $stmt->execute(array(':id' => $_data));
171 $used_by_mailboxes = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_mailboxes'];
172 $used_by_mailboxes = (empty($used_by_mailboxes)) ? '' : $used_by_mailboxes;
173 $relayhostdata['used_by_mailboxes'] = $used_by_mailboxes;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100174 }
175 return $relayhostdata;
176 break;
177 }
178}
179function transport($_action, $_data = null) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200180 global $pdo;
181 global $lang;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100182 $_data_log = $_data;
183 switch ($_action) {
184 case 'add':
185 if ($_SESSION['mailcow_cc_role'] != "admin") {
186 $_SESSION['return'][] = array(
187 'type' => 'danger',
188 'log' => array(__FUNCTION__, $_action, $_data_log),
189 'msg' => 'access_denied'
190 );
191 return false;
192 }
193 $destinations = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['destination']));
194 $active = intval($_data['active']);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200195 $is_mx_based = intval($_data['is_mx_based']);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100196 $nexthop = trim($_data['nexthop']);
197 if (filter_var($nexthop, FILTER_VALIDATE_IP)) {
198 $nexthop = '[' . $nexthop . ']';
199 }
200 preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches);
201 $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop;
202 $username = str_replace(':', '\:', trim($_data['username']));
203 $password = str_replace(':', '\:', trim($_data['password']));
204 if (empty($nexthop)) {
205 $_SESSION['return'][] = array(
206 'type' => 'danger',
207 'log' => array(__FUNCTION__, $_action, $_data_log),
208 'msg' => array('invalid_nexthop')
209 );
210 return false;
211 }
212 $transports = transport('get');
213 if (!empty($transports)) {
214 foreach ($transports as $transport) {
215 $transport_data = transport('details', $transport['id']);
216 $existing_nh[] = $transport_data['nexthop'];
217 preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]);
218 if (($transport_data['nexthop'] == $nexthop || $transport_data['nexthop'] == $next_hop_clean) && $transport_data['username'] != $username) {
219 $_SESSION['return'][] = array(
220 'type' => 'danger',
221 'log' => array(__FUNCTION__, $_action, $_data_log),
222 'msg' => 'invalid_nexthop_authenticated'
223 );
224 return false;
225 }
226 foreach ($destinations as $d_ix => &$dest) {
227 if (empty($dest)) {
228 unset($destinations[$d_ix]);
229 continue;
230 }
231 if ($transport_data['destination'] == $dest) {
232 $_SESSION['return'][] = array(
233 'type' => 'danger',
234 'log' => array(__FUNCTION__, $_action, $_data_log),
235 'msg' => array('transport_dest_exists', $dest)
236 );
237 unset($destinations[$d_ix]);
238 continue;
239 }
240 // ".domain" is a valid destination, "..domain" is not
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200241 if ($is_mx_based == 0 && (empty($dest) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $dest)) === false && $dest != '*' && filter_var($dest, FILTER_VALIDATE_EMAIL) === false))) {
242 $_SESSION['return'][] = array(
243 'type' => 'danger',
244 'log' => array(__FUNCTION__, $_action, $_data_log),
245 'msg' => array('invalid_destination', $dest)
246 );
247 unset($destinations[$d_ix]);
248 continue;
249 }
250 if ($is_mx_based == 1 && (empty($dest) || @preg_match('/' . $dest . '/', null) === false)) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100251 $_SESSION['return'][] = array(
252 'type' => 'danger',
253 'log' => array(__FUNCTION__, $_action, $_data_log),
254 'msg' => array('invalid_destination', $dest)
255 );
256 unset($destinations[$d_ix]);
257 continue;
258 }
259 }
260 }
261 }
262 $destinations = array_filter(array_values(array_unique($destinations)));
263 if (empty($destinations)) { return false; }
264 if (isset($next_hop_matches[1])) {
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100265 if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100266 $_SESSION['return'][] = array(
267 'type' => 'danger',
268 'log' => array(__FUNCTION__, $_action, $_data_log),
269 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop)
270 );
271 return false;
272 }
273 }
274 else {
275 foreach ($existing_clean_nh as $existing_clean_nh_each) {
276 if ($existing_clean_nh_each[1] == $nexthop) {
277 $_SESSION['return'][] = array(
278 'type' => 'danger',
279 'log' => array(__FUNCTION__, $_action, $_data_log),
280 'msg' => array('next_hop_interferes_any', $nexthop)
281 );
282 return false;
283 }
284 }
285 }
286 foreach ($destinations as $insert_dest) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200287 $stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `is_mx_based`, `username` , `password`, `active`)
288 VALUES (:nexthop, :destination, :is_mx_based, :username, :password, :active)");
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100289 $stmt->execute(array(
290 ':nexthop' => $nexthop,
291 ':destination' => $insert_dest,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200292 ':is_mx_based' => $is_mx_based,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100293 ':username' => $username,
294 ':password' => str_replace(':', '\:', $password),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100295 ':active' => $active
296 ));
297 }
298 $stmt = $pdo->prepare("UPDATE `transports` SET
299 `username` = :username,
300 `password` = :password
301 WHERE `nexthop` = :nexthop");
302 $stmt->execute(array(
303 ':nexthop' => $nexthop,
304 ':username' => $username,
305 ':password' => $password
306 ));
307 $_SESSION['return'][] = array(
308 'type' => 'success',
309 'log' => array(__FUNCTION__, $_action, $_data_log),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200310 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts)))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100311 );
312 break;
313 case 'edit':
314 if ($_SESSION['mailcow_cc_role'] != "admin") {
315 $_SESSION['return'][] = array(
316 'type' => 'danger',
317 'log' => array(__FUNCTION__, $_action, $_data_log),
318 'msg' => 'access_denied'
319 );
320 return false;
321 }
322 $ids = (array)$_data['id'];
323 foreach ($ids as $id) {
324 $is_now = transport('details', $id);
325 if (!empty($is_now)) {
326 $destination = (!empty($_data['destination'])) ? trim($_data['destination']) : $is_now['destination'];
327 $nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop'];
328 $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username'];
329 $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password'];
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200330 $is_mx_based = (isset($_data['is_mx_based']) && $_data['is_mx_based'] != '') ? intval($_data['is_mx_based']) : $is_now['is_mx_based'];
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100331 $active = (isset($_data['active']) && $_data['active'] != '') ? intval($_data['active']) : $is_now['active'];
332 }
333 else {
334 $_SESSION['return'][] = array(
335 'type' => 'danger',
336 'log' => array(__FUNCTION__, $_action, $_data_log),
337 'msg' => array('relayhost_invalid', $id)
338 );
339 continue;
340 }
341 preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches);
342 if (filter_var($nexthop, FILTER_VALIDATE_IP)) {
343 $nexthop = '[' . $nexthop . ']';
344 }
345 $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop;
346 $transports = transport('get');
347 if (!empty($transports)) {
348 foreach ($transports as $transport) {
349 $transport_data = transport('details', $transport['id']);
350 if ($transport['id'] == $id) {
351 continue;
352 }
353 $existing_nh[] = $transport_data['nexthop'];
354 preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]);
355 if ($transport_data['destination'] == $destination) {
356 $_SESSION['return'][] = array(
357 'type' => 'danger',
358 'log' => array(__FUNCTION__, $_action, $_data_log),
359 'msg' => 'transport_dest_exists'
360 );
361 return false;
362 }
363 }
364 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200365 if ($is_mx_based == 0 && (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*' && filter_var($destination, FILTER_VALIDATE_EMAIL) === false))) {
366 $_SESSION['return'][] = array(
367 'type' => 'danger',
368 'log' => array(__FUNCTION__, $_action, $_data_log),
369 'msg' => array('invalid_destination', $destination)
370 );
371 return false;
372 }
373 if ($is_mx_based == 1 && (empty($destination) || @preg_match('/' . $destination . '/', null) === false)) {
374 $_SESSION['return'][] = array(
375 'type' => 'danger',
376 'log' => array(__FUNCTION__, $_action, $_data_log),
377 'msg' => array('invalid_destination', $destination)
378 );
379 return false;
380 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100381 if (isset($next_hop_matches[1])) {
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100382 if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100383 $_SESSION['return'][] = array(
384 'type' => 'danger',
385 'log' => array(__FUNCTION__, $_action, $_data_log),
386 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop)
387 );
388 return false;
389 }
390 }
391 else {
392 foreach ($existing_clean_nh as $existing_clean_nh_each) {
393 if ($existing_clean_nh_each[1] == $nexthop) {
394 $_SESSION['return'][] = array(
395 'type' => 'danger',
396 'log' => array(__FUNCTION__, $_action, $_data_log),
397 'msg' => array('next_hop_interferes_any', $nexthop)
398 );
399 return false;
400 }
401 }
402 }
403 if (empty($username)) {
404 $password = '';
405 }
406 try {
407 $stmt = $pdo->prepare("UPDATE `transports` SET
408 `destination` = :destination,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200409 `is_mx_based` = :is_mx_based,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100410 `nexthop` = :nexthop,
411 `username` = :username,
412 `password` = :password,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100413 `active` = :active
414 WHERE `id` = :id");
415 $stmt->execute(array(
416 ':id' => $id,
417 ':destination' => $destination,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200418 ':is_mx_based' => $is_mx_based,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100419 ':nexthop' => $nexthop,
420 ':username' => $username,
421 ':password' => $password,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100422 ':active' => $active
423 ));
424 $stmt = $pdo->prepare("UPDATE `transports` SET
425 `username` = :username,
426 `password` = :password
427 WHERE `nexthop` = :nexthop");
428 $stmt->execute(array(
429 ':nexthop' => $nexthop,
430 ':username' => $username,
431 ':password' => $password
432 ));
433 }
434 catch (PDOException $e) {
435 $_SESSION['return'][] = array(
436 'type' => 'danger',
437 'log' => array(__FUNCTION__, $_action, $_data_log),
438 'msg' => array('mysql_error', $e)
439 );
440 continue;
441 }
442 $_SESSION['return'][] = array(
443 'type' => 'success',
444 'log' => array(__FUNCTION__, $_action, $_data_log),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200445 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames)))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100446 );
447 }
448 break;
449 case 'delete':
450 if ($_SESSION['mailcow_cc_role'] != "admin") {
451 $_SESSION['return'][] = array(
452 'type' => 'danger',
453 'log' => array(__FUNCTION__, $_action, $_data_log),
454 'msg' => 'access_denied'
455 );
456 return false;
457 }
458 $ids = (array)$_data['id'];
459 foreach ($ids as $id) {
460 try {
461 $stmt = $pdo->prepare("DELETE FROM `transports` WHERE `id`= :id");
462 $stmt->execute(array(':id' => $id));
463 }
464 catch (PDOException $e) {
465 $_SESSION['return'][] = array(
466 'type' => 'danger',
467 'log' => array(__FUNCTION__, $_action, $_data_log),
468 'msg' => array('mysql_error', $e)
469 );
470 continue;
471 }
472 $_SESSION['return'][] = array(
473 'type' => 'success',
474 'log' => array(__FUNCTION__, $_action, $_data_log),
475 'msg' => array('relayhost_removed', htmlspecialchars($id))
476 );
477 }
478 break;
479 case 'get':
480 if ($_SESSION['mailcow_cc_role'] != "admin") {
481 return false;
482 }
483 $transports = array();
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200484 $stmt = $pdo->query("SELECT `id`, `is_mx_based`, `destination`, `nexthop`, `username` FROM `transports`");
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100485 $transports = $stmt->fetchAll(PDO::FETCH_ASSOC);
486 return $transports;
487 break;
488 case 'details':
489 if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
490 return false;
491 }
492 $transportdata = array();
493 $stmt = $pdo->prepare("SELECT `id`,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200494 `is_mx_based`,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100495 `destination`,
496 `nexthop`,
497 `username`,
498 `password`,
499 `active`,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100500 CONCAT(LEFT(`password`, 3), '...') AS `password_short`
501 FROM `transports`
502 WHERE `id` = :id");
503 $stmt->execute(array(':id' => $_data));
504 $transportdata = $stmt->fetch(PDO::FETCH_ASSOC);
505 return $transportdata;
506 break;
507 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200508}