blob: f4f49de4939199eb8a15d960d17dc4301651c4e8 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2use PHPMailer\PHPMailer\PHPMailer;
3use PHPMailer\PHPMailer\SMTP;
4use PHPMailer\PHPMailer\Exception;
5function quarantine($_action, $_data = null) {
6 global $pdo;
7 global $redis;
8 global $lang;
9 $_data_log = $_data;
10 switch ($_action) {
11 case 'quick_delete':
12 // Dont return results, just log
13 $hash = trim($_data);
14 if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) {
15 logger(array('return' => array(
16 array(
17 'type' => 'danger',
18 'log' => array(__FUNCTION__, $_action, $_data_log),
19 'msg' => 'access_denied'
20 )
21 )));
22 return false;
23 }
24 $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
25 WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
26 AND user_acl.quarantine = 1
27 AND rcpt IN (SELECT username FROM mailbox)');
28 $stmt->execute(array(':hash' => $hash));
29 $row = $stmt->fetch(PDO::FETCH_ASSOC);
30 if (empty($row['id']) || !is_numeric($row['id'])) {
31 logger(array('return' => array(
32 array(
33 'type' => 'danger',
34 'log' => array(__FUNCTION__, $_action, $_data_log),
35 'msg' => 'access_denied'
36 )
37 )));
38 return false;
39 }
40 else {
41 $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id");
42 $stmt->execute(array(
43 ':id' => $row['id']
44 ));
45 }
46 logger(array('return' => array(
47 array(
48 'type' => 'success',
49 'log' => array(__FUNCTION__, $_action, $_data_log),
50 'msg' => array('item_deleted', $row['id'])
51 )
52 )));
53 break;
54 case 'quick_release':
55 // Dont return results, just log
56 $hash = trim($_data);
57 if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) {
58 logger(array('return' => array(
59 array(
60 'type' => 'danger',
61 'log' => array(__FUNCTION__, $_action, $_data_log),
62 'msg' => 'access_denied'
63 )
64 )));
65 return false;
66 }
67 $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
68 WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
69 AND `user_acl`.`quarantine` = 1
70 AND `username` IN (SELECT `username` FROM `mailbox`)');
71 $stmt->execute(array(':hash' => $hash));
72 $row = $stmt->fetch(PDO::FETCH_ASSOC);
73 if (empty($row['id']) || !is_numeric($row['id'])) {
74 logger(array('return' => array(
75 array(
76 'type' => 'danger',
77 'log' => array(__FUNCTION__, $_action, $_data_log),
78 'msg' => 'access_denied'
79 )
80 )));
81 return false;
82 }
83 else {
84 $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id');
85 $stmt->execute(array(':id' => $row['id']));
86 $detail_row = $stmt->fetch(PDO::FETCH_ASSOC);
87 $sender = !empty($detail_row['sender']) ? $detail_row['sender'] : 'sender-unknown@rspamd';
88 if (!empty(gethostbynamel('postfix-mailcow'))) {
89 $postfix = 'postfix-mailcow';
90 }
91 if (!empty(gethostbynamel('postfix'))) {
92 $postfix = 'postfix';
93 }
94 else {
95 logger(array('return' => array(
96 array(
97 'type' => 'warning',
98 'log' => array(__FUNCTION__, $_action, $_data_log),
99 'msg' => array('release_send_failed', 'Cannot determine Postfix host')
100 )
101 )));
102 return false;
103 }
104 try {
105 $release_format = $redis->Get('Q_RELEASE_FORMAT');
106 }
107 catch (RedisException $e) {
108 logger(array('return' => array(
109 array(
110 'type' => 'danger',
111 'log' => array(__FUNCTION__, $_action, $_data_log),
112 'msg' => array('redis_error', $e)
113 )
114 )));
115 return false;
116 }
117 if ($release_format == 'attachment') {
118 try {
119 $mail = new PHPMailer(true);
120 $mail->isSMTP();
121 $mail->SMTPDebug = 0;
122 $mail->SMTPOptions = array(
123 'ssl' => array(
124 'verify_peer' => false,
125 'verify_peer_name' => false,
126 'allow_self_signed' => true
127 )
128 );
129 if (!empty(gethostbynamel('postfix-mailcow'))) {
130 $postfix = 'postfix-mailcow';
131 }
132 if (!empty(gethostbynamel('postfix'))) {
133 $postfix = 'postfix';
134 }
135 else {
136 logger(array('return' => array(
137 array(
138 'type' => 'warning',
139 'log' => array(__FUNCTION__, $_action, $_data_log),
140 'msg' => array('release_send_failed', 'Cannot determine Postfix host')
141 )
142 )));
143 return false;
144 }
145 $mail->Host = $postfix;
146 $mail->Port = 590;
147 $mail->setFrom($sender);
148 $mail->CharSet = 'UTF-8';
149 $mail->Subject = sprintf($lang['quarantine']['release_subject'], $detail_row['qid']);
150 $mail->addAddress($detail_row['rcpt']);
151 $mail->IsHTML(false);
152 $msg_tmpf = tempnam("/tmp", $detail_row['qid']);
153 file_put_contents($msg_tmpf, $detail_row['msg']);
154 $mail->addAttachment($msg_tmpf, $detail_row['qid'] . '.eml');
155 $mail->Body = sprintf($lang['quarantine']['release_body']);
156 $mail->send();
157 unlink($msg_tmpf);
158 }
159 catch (phpmailerException $e) {
160 unlink($msg_tmpf);
161 logger(array('return' => array(
162 array(
163 'type' => 'warning',
164 'log' => array(__FUNCTION__, $_action, $_data_log),
165 'msg' => array('release_send_failed', $e->errorMessage())
166 )
167 )));
168 return false;
169 }
170 }
171 elseif ($release_format == 'raw') {
172 $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $detail_row['msg']);
173 $postfix_talk = array(
174 array('220', 'HELO quarantine' . chr(10)),
175 array('250', 'MAIL FROM: ' . $sender . chr(10)),
176 array('250', 'RCPT TO: ' . $detail_row['rcpt'] . chr(10)),
177 array('250', 'DATA' . chr(10)),
178 array('354', $detail_row['msg'] . chr(10) . '.' . chr(10)),
179 array('250', 'QUIT' . chr(10)),
180 array('221', '')
181 );
182 // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
183 $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
184 if (!$smtp_connection) {
185 logger(array('return' => array(
186 array(
187 'type' => 'warning',
188 'log' => array(__FUNCTION__, $_action, $_data_log),
189 'msg' => 'Cannot connect to Postfix'
190 )
191 )));
192 return false;
193 }
194 for ($i=0; $i < count($postfix_talk); $i++) {
195 $smtp_resource = fgets($smtp_connection, 256);
196 if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
197 $ret = substr($smtp_resource, 0, 3);
198 $ret = (empty($ret)) ? '-' : $ret;
199 logger(array('return' => array(
200 array(
201 'type' => 'warning',
202 'log' => array(__FUNCTION__, $_action, $_data_log),
203 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0]
204 )
205 )));
206 return false;
207 }
208 if ($postfix_talk[$i][1] !== '') {
209 fputs($smtp_connection, $postfix_talk[$i][1]);
210 }
211 }
212 fclose($smtp_connection);
213 }
214 $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id");
215 $stmt->execute(array(
216 ':id' => $row['id']
217 ));
218 }
219 logger(array('return' => array(
220 array(
221 'type' => 'success',
222 'log' => array(__FUNCTION__, $_action, $_data_log),
223 'msg' => array('item_released', $hash)
224 )
225 )));
226 break;
227 case 'delete':
228 if (!is_array($_data['id'])) {
229 $ids = array();
230 $ids[] = $_data['id'];
231 }
232 else {
233 $ids = $_data['id'];
234 }
235 if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) {
236 $_SESSION['return'][] = array(
237 'type' => 'danger',
238 'log' => array(__FUNCTION__, $_action, $_data_log),
239 'msg' => 'access_denied'
240 );
241 return false;
242 }
243 foreach ($ids as $id) {
244 if (!is_numeric($id)) {
245 $_SESSION['return'][] = array(
246 'type' => 'danger',
247 'log' => array(__FUNCTION__, $_action, $_data_log),
248 'msg' => 'access_denied'
249 );
250 continue;
251 }
252 $stmt = $pdo->prepare('SELECT `rcpt` FROM `quarantine` WHERE `id` = :id');
253 $stmt->execute(array(':id' => $id));
254 $row = $stmt->fetch(PDO::FETCH_ASSOC);
255 if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin') {
256 $_SESSION['return'][] = array(
257 'type' => 'danger',
258 'log' => array(__FUNCTION__, $_action, $_data_log),
259 'msg' => 'access_denied'
260 );
261 continue;
262 }
263 else {
264 $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id");
265 $stmt->execute(array(
266 ':id' => $id
267 ));
268 }
269 $_SESSION['return'][] = array(
270 'type' => 'success',
271 'log' => array(__FUNCTION__, $_action, $_data_log),
272 'msg' => array('item_deleted', $id)
273 );
274 }
275 break;
276 case 'edit':
277 if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) {
278 $_SESSION['return'][] = array(
279 'type' => 'danger',
280 'log' => array(__FUNCTION__, $_action, $_data_log),
281 'msg' => 'access_denied'
282 );
283 return false;
284 }
285 // Edit settings
286 if ($_data['action'] == 'settings') {
287 if ($_SESSION['mailcow_cc_role'] != "admin") {
288 $_SESSION['return'][] = array(
289 'type' => 'danger',
290 'log' => array(__FUNCTION__, $_action, $_data_log),
291 'msg' => 'access_denied'
292 );
293 return false;
294 }
295 $retention_size = $_data['retention_size'];
296 if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') {
297 $release_format = $_data['release_format'];
298 }
299 else {
300 $release_format = 'raw';
301 }
302 $max_size = $_data['max_size'];
303 if ($_data['max_score'] == "") {
304 $max_score = '';
305 }
306 else {
307 $max_score = floatval($_data['max_score']);
308 }
309 $max_age = intval($_data['max_age']);
310 $subject = $_data['subject'];
311 if (!filter_var($_data['bcc'], FILTER_VALIDATE_EMAIL)) {
312 $bcc = '';
313 }
314 else {
315 $bcc = $_data['bcc'];
316 }
317 if (!filter_var($_data['redirect'], FILTER_VALIDATE_EMAIL)) {
318 $redirect = '';
319 }
320 else {
321 $redirect = $_data['redirect'];
322 }
323 if (!filter_var($_data['sender'], FILTER_VALIDATE_EMAIL)) {
324 $sender = '';
325 }
326 else {
327 $sender = $_data['sender'];
328 }
329 $html = $_data['html_tmpl'];
330 if ($max_age <= 0) {
331 $max_age = 365;
332 }
333 $exclude_domains = (array)$_data['exclude_domains'];
334 try {
335 $redis->Set('Q_RETENTION_SIZE', intval($retention_size));
336 $redis->Set('Q_MAX_SIZE', intval($max_size));
337 $redis->Set('Q_MAX_SCORE', $max_score);
338 $redis->Set('Q_MAX_AGE', $max_age);
339 $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains));
340 $redis->Set('Q_RELEASE_FORMAT', $release_format);
341 $redis->Set('Q_SENDER', $sender);
342 $redis->Set('Q_BCC', $bcc);
343 $redis->Set('Q_REDIRECT', $redirect);
344 $redis->Set('Q_SUBJ', $subject);
345 $redis->Set('Q_HTML', $html);
346 }
347 catch (RedisException $e) {
348 $_SESSION['return'][] = array(
349 'type' => 'danger',
350 'log' => array(__FUNCTION__, $_action, $_data_log),
351 'msg' => array('redis_error', $e)
352 );
353 return false;
354 }
355 $_SESSION['return'][] = array(
356 'type' => 'success',
357 'log' => array(__FUNCTION__, $_action, $_data_log),
358 'msg' => 'saved_settings'
359 );
360 }
361 // Release item
362 elseif ($_data['action'] == 'release' || $_data['action'] == 'learnham') {
363 if (!is_array($_data['id'])) {
364 $ids = array();
365 $ids[] = $_data['id'];
366 }
367 else {
368 $ids = $_data['id'];
369 }
370 foreach ($ids as $id) {
371 if (!is_numeric($id)) {
372 $_SESSION['return'][] = array(
373 'type' => 'danger',
374 'log' => array(__FUNCTION__, $_action, $_data_log),
375 'msg' => 'access_denied'
376 );
377 continue;
378 }
379 $stmt = $pdo->prepare('SELECT `msg`, `action`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id');
380 $stmt->execute(array(':id' => $id));
381 $row = $stmt->fetch(PDO::FETCH_ASSOC);
382 if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) {
383 $_SESSION['return'][] = array(
384 'type' => 'danger',
385 'log' => array(__FUNCTION__, $_action, $_data_log),
386 'msg' => 'access_denied'
387 );
388 continue;
389 }
390 $sender = !empty($row['sender']) ? $row['sender'] : 'sender-unknown@rspamd';
391 if (!empty(gethostbynamel('postfix-mailcow'))) {
392 $postfix = 'postfix-mailcow';
393 }
394 if (!empty(gethostbynamel('postfix'))) {
395 $postfix = 'postfix';
396 }
397 else {
398 $_SESSION['return'][] = array(
399 'type' => 'warning',
400 'log' => array(__FUNCTION__, $_action, $_data_log),
401 'msg' => array('release_send_failed', 'Cannot determine Postfix host')
402 );
403 continue;
404 }
405 try {
406 $release_format = $redis->Get('Q_RELEASE_FORMAT');
407 }
408 catch (RedisException $e) {
409 $_SESSION['return'][] = array(
410 'type' => 'danger',
411 'log' => array(__FUNCTION__, $_action, $_data_log),
412 'msg' => array('redis_error', $e)
413 );
414 return false;
415 }
416 if ($release_format == 'attachment') {
417 try {
418 $mail = new PHPMailer(true);
419 $mail->isSMTP();
420 $mail->SMTPDebug = 0;
421 $mail->SMTPOptions = array(
422 'ssl' => array(
423 'verify_peer' => false,
424 'verify_peer_name' => false,
425 'allow_self_signed' => true
426 )
427 );
428 if (!empty(gethostbynamel('postfix-mailcow'))) {
429 $postfix = 'postfix-mailcow';
430 }
431 if (!empty(gethostbynamel('postfix'))) {
432 $postfix = 'postfix';
433 }
434 else {
435 $_SESSION['return'][] = array(
436 'type' => 'warning',
437 'log' => array(__FUNCTION__, $_action, $_data_log),
438 'msg' => array('release_send_failed', 'Cannot determine Postfix host')
439 );
440 continue;
441 }
442 $mail->Host = $postfix;
443 $mail->Port = 590;
444 $mail->setFrom($sender);
445 $mail->CharSet = 'UTF-8';
446 $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']);
447 $mail->addAddress($row['rcpt']);
448 $mail->IsHTML(false);
449 $msg_tmpf = tempnam("/tmp", $row['qid']);
450 file_put_contents($msg_tmpf, $row['msg']);
451 $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml');
452 $mail->Body = sprintf($lang['quarantine']['release_body']);
453 $mail->send();
454 unlink($msg_tmpf);
455 }
456 catch (phpmailerException $e) {
457 unlink($msg_tmpf);
458 $_SESSION['return'][] = array(
459 'type' => 'warning',
460 'log' => array(__FUNCTION__, $_action, $_data_log),
461 'msg' => array('release_send_failed', $e->errorMessage())
462 );
463 continue;
464 }
465 }
466 elseif ($release_format == 'raw') {
467 $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $row['msg']);
468 $postfix_talk = array(
469 array('220', 'HELO quarantine' . chr(10)),
470 array('250', 'MAIL FROM: ' . $sender . chr(10)),
471 array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)),
472 array('250', 'DATA' . chr(10)),
473 array('354', str_replace("\n.", '', $row['msg']) . chr(10) . '.' . chr(10)),
474 array('250', 'QUIT' . chr(10)),
475 array('221', '')
476 );
477 // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
478 $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
479 if (!$smtp_connection) {
480 $_SESSION['return'][] = array(
481 'type' => 'warning',
482 'log' => array(__FUNCTION__, $_action, $_data_log),
483 'msg' => 'Cannot connect to Postfix'
484 );
485 return false;
486 }
487 for ($i=0; $i < count($postfix_talk); $i++) {
488 $smtp_resource = fgets($smtp_connection, 256);
489 if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
490 $ret = substr($smtp_resource, 0, 3);
491 $ret = (empty($ret)) ? '-' : $ret;
492 $_SESSION['return'][] = array(
493 'type' => 'warning',
494 'log' => array(__FUNCTION__, $_action, $_data_log),
495 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0]
496 );
497 return false;
498 }
499 if ($postfix_talk[$i][1] !== '') {
500 fputs($smtp_connection, $postfix_talk[$i][1]);
501 }
502 }
503 fclose($smtp_connection);
504 }
505 $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id");
506 $stmt->execute(array(
507 ':id' => $id
508 ));
509 $_SESSION['return'][] = array(
510 'type' => 'success',
511 'log' => array(__FUNCTION__, $_action, $_data_log),
512 'msg' => array('item_released', $id)
513 );
514 // Item was released and deleted from quarantine, now learning ham
515 $curl = curl_init();
516 curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
517 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
518 curl_setopt($curl, CURLOPT_POST, 1);
519 curl_setopt($curl, CURLOPT_TIMEOUT, 30);
520 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain'));
521 curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnham");
522 curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
523 $response = curl_exec($curl);
524 if (!curl_errno($curl)) {
525 $response = json_decode($response, true);
526 if (isset($response['error'])) {
527 if (stripos($response['error'], 'already learned') === false) {
528 $_SESSION['return'][] = array(
529 'type' => 'danger',
530 'log' => array(__FUNCTION__, $_action, $_data_log),
531 'msg' => array('ham_learn_error', $response['error'])
532 );
533 continue;
534 }
535 }
536 curl_close($curl);
537 $curl = curl_init();
538 curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
539 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
540 curl_setopt($curl, CURLOPT_POST, 1);
541 curl_setopt($curl, CURLOPT_TIMEOUT, 30);
542 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11'));
543 curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel");
544 curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
545 // It is most likely not a spam hash, so we ignore any error/warning response
546 // $response = curl_exec($curl);
547 if (!curl_errno($curl)) {
548 curl_close($curl);
549 $curl = curl_init();
550 curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
551 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
552 curl_setopt($curl, CURLOPT_POST, 1);
553 curl_setopt($curl, CURLOPT_TIMEOUT, 30);
554 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13'));
555 curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd");
556 curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
557 $response = curl_exec($curl);
558 curl_exec($curl);
559 if (!curl_errno($curl)) {
560 $response = json_decode($response, true);
561 if (isset($response['error'])) {
562 if (stripos($response['error'], 'No content to generate fuzzy') === false) {
563 $_SESSION['return'][] = array(
564 'type' => 'warning',
565 'log' => array(__FUNCTION__, $_action, $_data_log),
566 'msg' => array('fuzzy_learn_error', $response['error'])
567 );
568 }
569 }
570 }
571 curl_close($curl);
572 $_SESSION['return'][] = array(
573 'type' => 'success',
574 'log' => array(__FUNCTION__, $_action, $_data_log),
575 'msg' => array('learned_ham', $id)
576 );
577 continue;
578 }
579 else {
580 curl_close($curl);
581 $_SESSION['return'][] = array(
582 'type' => 'danger',
583 'log' => array(__FUNCTION__, $_action, $_data_log),
584 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl)))
585 );
586 continue;
587 }
588 curl_close($curl);
589 $_SESSION['return'][] = array(
590 'type' => 'danger',
591 'log' => array(__FUNCTION__, $_action, $_data_log),
592 'msg' => array('ham_learn_error', 'unknown')
593 );
594 continue;
595 }
596 else {
597 $_SESSION['return'][] = array(
598 'type' => 'danger',
599 'log' => array(__FUNCTION__, $_action, $_data_log),
600 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl)))
601 );
602 curl_close($curl);
603 continue;
604 }
605 curl_close($curl);
606 $_SESSION['return'][] = array(
607 'type' => 'danger',
608 'log' => array(__FUNCTION__, $_action, $_data_log),
609 'msg' => array('ham_learn_error', 'unknown')
610 );
611 continue;
612 }
613 }
614 elseif ($_data['action'] == 'learnspam') {
615 if (!is_array($_data['id'])) {
616 $ids = array();
617 $ids[] = $_data['id'];
618 }
619 else {
620 $ids = $_data['id'];
621 }
622 foreach ($ids as $id) {
623 if (!is_numeric($id)) {
624 $_SESSION['return'][] = array(
625 'type' => 'danger',
626 'log' => array(__FUNCTION__, $_action, $_data_log),
627 'msg' => 'access_denied'
628 );
629 continue;
630 }
631 $stmt = $pdo->prepare('SELECT `msg`, `rcpt`, `action` FROM `quarantine` WHERE `id` = :id');
632 $stmt->execute(array(':id' => $id));
633 $row = $stmt->fetch(PDO::FETCH_ASSOC);
634 if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) {
635 $_SESSION['return'][] = array(
636 'type' => 'danger',
637 'log' => array(__FUNCTION__, $_action, $_data_log),
638 'msg' => 'access_denied'
639 );
640 continue;
641 }
642 $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id");
643 $stmt->execute(array(
644 ':id' => $id
645 ));
646 $curl = curl_init();
647 curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
648 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
649 curl_setopt($curl, CURLOPT_POST, 1);
650 curl_setopt($curl, CURLOPT_TIMEOUT, 30);
651 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain'));
652 curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnspam");
653 curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
654 $response = curl_exec($curl);
655 if (!curl_errno($curl)) {
656 $response = json_decode($response, true);
657 if (isset($response['error'])) {
658 if (stripos($response['error'], 'already learned') === false) {
659 $_SESSION['return'][] = array(
660 'type' => 'danger',
661 'log' => array(__FUNCTION__, $_action, $_data_log),
662 'msg' => array('spam_learn_error', $response['error'])
663 );
664 continue;
665 }
666 }
667 curl_close($curl);
668 $curl = curl_init();
669 curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
670 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
671 curl_setopt($curl, CURLOPT_POST, 1);
672 curl_setopt($curl, CURLOPT_TIMEOUT, 30);
673 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13'));
674 curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel");
675 curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
676 // It is most likely not a spam hash, so we ignore any error/warning response
677 // $response = curl_exec($curl);
678 if (!curl_errno($curl)) {
679 curl_close($curl);
680 $curl = curl_init();
681 curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
682 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
683 curl_setopt($curl, CURLOPT_POST, 1);
684 curl_setopt($curl, CURLOPT_TIMEOUT, 30);
685 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11'));
686 curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd");
687 curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
688 $response = curl_exec($curl);
689 curl_exec($curl);
690 if (!curl_errno($curl)) {
691 $response = json_decode($response, true);
692 if (isset($response['error'])) {
693 if (stripos($response['error'], 'No content to generate fuzzy') === false) {
694 $_SESSION['return'][] = array(
695 'type' => 'warning',
696 'log' => array(__FUNCTION__, $_action, $_data_log),
697 'msg' => array('fuzzy_learn_error', $response['error'])
698 );
699 }
700 }
701 }
702 curl_close($curl);
703 $_SESSION['return'][] = array(
704 'type' => 'success',
705 'log' => array(__FUNCTION__, $_action, $_data_log),
706 'msg' => array('qlearn_spam', $id)
707 );
708 continue;
709 }
710 else {
711 curl_close($curl);
712 $_SESSION['return'][] = array(
713 'type' => 'danger',
714 'log' => array(__FUNCTION__, $_action, $_data_log),
715 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl)))
716 );
717 continue;
718 }
719 curl_close($curl);
720 $_SESSION['return'][] = array(
721 'type' => 'danger',
722 'log' => array(__FUNCTION__, $_action, $_data_log),
723 'msg' => array('spam_learn_error', 'unknown')
724 );
725 continue;
726 }
727 else {
728 $_SESSION['return'][] = array(
729 'type' => 'danger',
730 'log' => array(__FUNCTION__, $_action, $_data_log),
731 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl)))
732 );
733 curl_close($curl);
734 continue;
735 }
736 curl_close($curl);
737 $_SESSION['return'][] = array(
738 'type' => 'danger',
739 'log' => array(__FUNCTION__, $_action, $_data_log),
740 'msg' => array('spam_learn_error', 'unknown')
741 );
742 continue;
743 }
744 }
745 return true;
746 break;
747 case 'get':
748 if ($_SESSION['mailcow_cc_role'] == "user") {
749 $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` = :mbox');
750 $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username']));
751 $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
752 while($row = array_shift($rows)) {
753 $q_meta[] = $row;
754 }
755 }
756 elseif ($_SESSION['mailcow_cc_role'] == "admin") {
757 $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine`');
758 $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
759 while($row = array_shift($rows)) {
760 $q_meta[] = $row;
761 }
762 }
763 else {
764 $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
765 foreach ($domains as $domain) {
766 $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` REGEXP :domain');
767 $stmt->execute(array(':domain' => '@' . $domain . '$'));
768 $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
769 while($row = array_shift($rows)) {
770 $q_meta[] = $row;
771 }
772 }
773 }
774 return $q_meta;
775 break;
776 case 'settings':
777 try {
778 if ($_SESSION['mailcow_cc_role'] == "admin") {
779 $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true);
780 }
781 $settings['max_size'] = $redis->Get('Q_MAX_SIZE');
782 $settings['max_score'] = $redis->Get('Q_MAX_SCORE');
783 $settings['max_age'] = $redis->Get('Q_MAX_AGE');
784 $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE');
785 $settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT');
786 $settings['subject'] = $redis->Get('Q_SUBJ');
787 $settings['sender'] = $redis->Get('Q_SENDER');
788 $settings['bcc'] = $redis->Get('Q_BCC');
789 $settings['redirect'] = $redis->Get('Q_REDIRECT');
790 $settings['html_tmpl'] = htmlspecialchars($redis->Get('Q_HTML'));
791 if (empty($settings['html_tmpl'])) {
792 $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl"));
793 }
794 }
795 catch (RedisException $e) {
796 $_SESSION['return'][] = array(
797 'type' => 'danger',
798 'log' => array(__FUNCTION__, $_action, $_data_log),
799 'msg' => array('redis_error', $e)
800 );
801 return false;
802 }
803 return $settings;
804 break;
805 case 'details':
806 if (!is_numeric($_data) || empty($_data)) {
807 return false;
808 }
809 $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `id`= :id');
810 $stmt->execute(array(':id' => $_data));
811 $row = $stmt->fetch(PDO::FETCH_ASSOC);
812 if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) || $_SESSION['mailcow_cc_role'] == 'admin') {
813 return $row;
814 }
815 logger(array('return' => array(
816 array(
817 'type' => 'danger',
818 'log' => array(__FUNCTION__, $_action, $_data_log),
819 'msg' => 'access_denied'
820 )
821 )));
822 return false;
823 break;
824 case 'hash_details':
825 $hash = trim($_data);
826 if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) {
827 logger(array('return' => array(
828 array(
829 'type' => 'danger',
830 'log' => array(__FUNCTION__, $_action, $_data_log),
831 'msg' => 'access_denied'
832 )
833 )));
834 return false;
835 }
836 $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash');
837 $stmt->execute(array(':hash' => $hash));
838 return $stmt->fetch(PDO::FETCH_ASSOC);
839 break;
840 }
841}