functions.quarantine.inc.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. <?php
  2. function quarantine($_action, $_data = null) {
  3. global $pdo;
  4. global $redis;
  5. global $lang;
  6. $_data_log = $_data;
  7. switch ($_action) {
  8. case 'delete':
  9. if (!is_array($_data['id'])) {
  10. $ids = array();
  11. $ids[] = $_data['id'];
  12. }
  13. else {
  14. $ids = $_data['id'];
  15. }
  16. if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) {
  17. $_SESSION['return'][] = array(
  18. 'type' => 'danger',
  19. 'log' => array(__FUNCTION__, $_action, $_data_log),
  20. 'msg' => 'access_denied'
  21. );
  22. return false;
  23. }
  24. foreach ($ids as $id) {
  25. if (!is_numeric($id)) {
  26. $_SESSION['return'][] = array(
  27. 'type' => 'danger',
  28. 'log' => array(__FUNCTION__, $_action, $_data_log),
  29. 'msg' => 'access_denied'
  30. );
  31. continue;
  32. }
  33. $stmt = $pdo->prepare('SELECT `rcpt` FROM `quarantine` WHERE `id` = :id');
  34. $stmt->execute(array(':id' => $id));
  35. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  36. if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin') {
  37. $_SESSION['return'][] = array(
  38. 'type' => 'danger',
  39. 'log' => array(__FUNCTION__, $_action, $_data_log),
  40. 'msg' => 'access_denied'
  41. );
  42. continue;
  43. }
  44. else {
  45. $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id");
  46. $stmt->execute(array(
  47. ':id' => $id
  48. ));
  49. }
  50. $_SESSION['return'][] = array(
  51. 'type' => 'success',
  52. 'log' => array(__FUNCTION__, $_action, $_data_log),
  53. 'msg' => array('item_deleted', $id)
  54. );
  55. }
  56. break;
  57. case 'edit':
  58. if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) {
  59. $_SESSION['return'][] = array(
  60. 'type' => 'danger',
  61. 'log' => array(__FUNCTION__, $_action, $_data_log),
  62. 'msg' => 'access_denied'
  63. );
  64. return false;
  65. }
  66. // Edit settings
  67. if ($_data['action'] == 'settings') {
  68. if ($_SESSION['mailcow_cc_role'] != "admin") {
  69. $_SESSION['return'][] = array(
  70. 'type' => 'danger',
  71. 'log' => array(__FUNCTION__, $_action, $_data_log),
  72. 'msg' => 'access_denied'
  73. );
  74. return false;
  75. }
  76. $retention_size = $_data['retention_size'];
  77. if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') {
  78. $release_format = $_data['release_format'];
  79. }
  80. else {
  81. $release_format = 'raw';
  82. }
  83. $max_size = $_data['max_size'];
  84. $subject = $_data['subject'];
  85. $sender = $_data['sender'];
  86. $html = $_data['html'];
  87. $exclude_domains = (array)$_data['exclude_domains'];
  88. try {
  89. $redis->Set('Q_RETENTION_SIZE', intval($retention_size));
  90. $redis->Set('Q_MAX_SIZE', intval($max_size));
  91. $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains));
  92. $redis->Set('Q_RELEASE_FORMAT', $release_format);
  93. $redis->Set('Q_SENDER', $sender);
  94. $redis->Set('Q_SUBJECT', $subject);
  95. $redis->Set('Q_HTML', $html);
  96. }
  97. catch (RedisException $e) {
  98. $_SESSION['return'][] = array(
  99. 'type' => 'danger',
  100. 'log' => array(__FUNCTION__, $_action, $_data_log),
  101. 'msg' => array('redis_error', $e)
  102. );
  103. return false;
  104. }
  105. $_SESSION['return'][] = array(
  106. 'type' => 'success',
  107. 'log' => array(__FUNCTION__, $_action, $_data_log),
  108. 'msg' => 'saved_settings'
  109. );
  110. }
  111. // Release item
  112. elseif ($_data['action'] == 'release') {
  113. if (!is_array($_data['id'])) {
  114. $ids = array();
  115. $ids[] = $_data['id'];
  116. }
  117. else {
  118. $ids = $_data['id'];
  119. }
  120. foreach ($ids as $id) {
  121. if (!is_numeric($id)) {
  122. $_SESSION['return'][] = array(
  123. 'type' => 'danger',
  124. 'log' => array(__FUNCTION__, $_action, $_data_log),
  125. 'msg' => 'access_denied'
  126. );
  127. continue;
  128. }
  129. $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id');
  130. $stmt->execute(array(':id' => $id));
  131. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  132. if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) {
  133. $_SESSION['return'][] = array(
  134. 'type' => 'danger',
  135. 'msg' => 'access_denied'
  136. );
  137. continue;
  138. }
  139. $sender = (isset($row['sender'])) ? $row['sender'] : 'sender-unknown@rspamd';
  140. if (!empty(gethostbynamel('postfix-mailcow'))) {
  141. $postfix = 'postfix-mailcow';
  142. }
  143. if (!empty(gethostbynamel('postfix'))) {
  144. $postfix = 'postfix';
  145. }
  146. else {
  147. $_SESSION['return'][] = array(
  148. 'type' => 'warning',
  149. 'log' => array(__FUNCTION__, $_action, $_data_log),
  150. 'msg' => array('release_send_failed', 'Cannot determine Postfix host')
  151. );
  152. continue;
  153. }
  154. try {
  155. $release_format = $redis->Get('Q_RELEASE_FORMAT');
  156. }
  157. catch (RedisException $e) {
  158. $_SESSION['return'][] = array(
  159. 'type' => 'danger',
  160. 'log' => array(__FUNCTION__, $_action, $_data_log),
  161. 'msg' => array('redis_error', $e)
  162. );
  163. return false;
  164. }
  165. if ($release_format == 'attachment') {
  166. try {
  167. $mail = new PHPMailer(true);
  168. $mail->isSMTP();
  169. $mail->SMTPDebug = 0;
  170. $mail->SMTPOptions = array(
  171. 'ssl' => array(
  172. 'verify_peer' => false,
  173. 'verify_peer_name' => false,
  174. 'allow_self_signed' => true
  175. )
  176. );
  177. if (!empty(gethostbynamel('postfix-mailcow'))) {
  178. $postfix = 'postfix-mailcow';
  179. }
  180. if (!empty(gethostbynamel('postfix'))) {
  181. $postfix = 'postfix';
  182. }
  183. else {
  184. $_SESSION['return'][] = array(
  185. 'type' => 'warning',
  186. 'log' => array(__FUNCTION__, $_action, $_data_log),
  187. 'msg' => array('release_send_failed', 'Cannot determine Postfix host')
  188. );
  189. continue;
  190. }
  191. $mail->Host = $postfix;
  192. $mail->Port = 590;
  193. $mail->setFrom($sender);
  194. $mail->CharSet = 'UTF-8';
  195. $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']);
  196. $mail->addAddress($row['rcpt']);
  197. $mail->IsHTML(false);
  198. $msg_tmpf = tempnam("/tmp", $row['qid']);
  199. file_put_contents($msg_tmpf, $row['msg']);
  200. $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml');
  201. $mail->Body = sprintf($lang['quarantine']['release_body']);
  202. $mail->send();
  203. unlink($msg_tmpf);
  204. }
  205. catch (phpmailerException $e) {
  206. unlink($msg_tmpf);
  207. $_SESSION['return'][] = array(
  208. 'type' => 'warning',
  209. 'log' => array(__FUNCTION__, $_action, $_data_log),
  210. 'msg' => array('release_send_failed', $e->errorMessage())
  211. );
  212. continue;
  213. }
  214. }
  215. elseif ($release_format == 'raw') {
  216. $postfix_talk = array(
  217. array('220', 'HELO quarantine' . chr(10)),
  218. array('250', 'MAIL FROM: ' . $sender . chr(10)),
  219. array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)),
  220. array('250', 'DATA' . chr(10)),
  221. array('354', $row['msg'] . chr(10) . '.' . chr(10)),
  222. array('250', 'QUIT' . chr(10)),
  223. array('221', '')
  224. );
  225. // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
  226. $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
  227. if (!$smtp_connection) {
  228. $_SESSION['return'][] = array(
  229. 'type' => 'warning',
  230. 'log' => array(__FUNCTION__, $_action, $_data_log),
  231. 'msg' => 'Cannot connect to Postfix'
  232. );
  233. return false;
  234. }
  235. for ($i=0; $i < count($postfix_talk); $i++) {
  236. $smtp_resource = fgets($smtp_connection, 256);
  237. if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
  238. $ret = substr($smtp_resource, 0, 3);
  239. $ret = (empty($ret)) ? '-' : $ret;
  240. $_SESSION['return'][] = array(
  241. 'type' => 'warning',
  242. 'log' => array(__FUNCTION__, $_action, $_data_log),
  243. 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0]
  244. );
  245. return false;
  246. }
  247. if ($postfix_talk[$i][1] !== '') {
  248. fputs($smtp_connection, $postfix_talk[$i][1]);
  249. }
  250. }
  251. fclose($smtp_connection);
  252. }
  253. try {
  254. $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id");
  255. $stmt->execute(array(
  256. ':id' => $id
  257. ));
  258. }
  259. catch (PDOException $e) {
  260. $_SESSION['return'][] = array(
  261. 'type' => 'danger',
  262. 'log' => array(__FUNCTION__, $_action, $_data_log),
  263. 'msg' => array('mysql_error', $e)
  264. );
  265. continue;
  266. }
  267. $_SESSION['return'][] = array(
  268. 'type' => 'success',
  269. 'log' => array(__FUNCTION__, $_action, $_data_log),
  270. 'msg' => array('item_released', $id)
  271. );
  272. }
  273. }
  274. elseif ($_data['action'] == 'learnspam') {
  275. if (!is_array($_data['id'])) {
  276. $ids = array();
  277. $ids[] = $_data['id'];
  278. }
  279. else {
  280. $ids = $_data['id'];
  281. }
  282. foreach ($ids as $id) {
  283. if (!is_numeric($id)) {
  284. $_SESSION['return'][] = array(
  285. 'type' => 'danger',
  286. 'log' => array(__FUNCTION__, $_action, $_data_log),
  287. 'msg' => 'access_denied'
  288. );
  289. continue;
  290. }
  291. $stmt = $pdo->prepare('SELECT `msg`, `rcpt` FROM `quarantine` WHERE `id` = :id');
  292. $stmt->execute(array(':id' => $id));
  293. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  294. if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin') {
  295. $_SESSION['return'][] = array(
  296. 'type' => 'danger',
  297. 'msg' => 'access_denied'
  298. );
  299. continue;
  300. }
  301. $curl = curl_init();
  302. curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
  303. curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
  304. curl_setopt($curl, CURLOPT_POST, 1);
  305. curl_setopt($curl, CURLOPT_TIMEOUT, 30);
  306. curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain'));
  307. curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnspam");
  308. curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
  309. $response = curl_exec($curl);
  310. if (!curl_errno($curl)) {
  311. $response = json_decode($response, true);
  312. if (isset($response['error'])) {
  313. if (stripos($response['error'], 'already learned') === false) {
  314. $_SESSION['return'][] = array(
  315. 'type' => 'danger',
  316. 'log' => array(__FUNCTION__),
  317. 'msg' => array('spam_learn_error', $response['error'])
  318. );
  319. continue;
  320. }
  321. }
  322. curl_close($curl);
  323. $curl = curl_init();
  324. curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
  325. curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
  326. curl_setopt($curl, CURLOPT_POST, 1);
  327. curl_setopt($curl, CURLOPT_TIMEOUT, 30);
  328. curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11'));
  329. curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd");
  330. curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']);
  331. $response = curl_exec($curl);
  332. if (!curl_errno($curl)) {
  333. $response = json_decode($response, true);
  334. if (isset($response['error'])) {
  335. $_SESSION['return'][] = array(
  336. 'type' => 'warning',
  337. 'log' => array(__FUNCTION__),
  338. 'msg' => array('fuzzy_learn_error', $response['error'])
  339. );
  340. }
  341. curl_close($curl);
  342. try {
  343. $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id");
  344. $stmt->execute(array(
  345. ':id' => $id
  346. ));
  347. }
  348. catch (PDOException $e) {
  349. $_SESSION['return'][] = array(
  350. 'type' => 'danger',
  351. 'log' => array(__FUNCTION__, $_action, $_data_log),
  352. 'msg' => array('mysql_error', $e)
  353. );
  354. continue;
  355. }
  356. $_SESSION['return'][] = array(
  357. 'type' => 'success',
  358. 'log' => array(__FUNCTION__),
  359. 'msg' => array('qlearn_spam', $id)
  360. );
  361. continue;
  362. }
  363. else {
  364. curl_close($curl);
  365. $_SESSION['return'][] = array(
  366. 'type' => 'danger',
  367. 'log' => array(__FUNCTION__),
  368. 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl)))
  369. );
  370. continue;
  371. }
  372. curl_close($curl);
  373. $_SESSION['return'][] = array(
  374. 'type' => 'danger',
  375. 'log' => array(__FUNCTION__),
  376. 'msg' => array('learn_spam_error', 'unknown')
  377. );
  378. continue;
  379. }
  380. else {
  381. $_SESSION['return'][] = array(
  382. 'type' => 'danger',
  383. 'log' => array(__FUNCTION__),
  384. 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl)))
  385. );
  386. curl_close($curl);
  387. continue;
  388. }
  389. curl_close($curl);
  390. $_SESSION['return'][] = array(
  391. 'type' => 'danger',
  392. 'log' => array(__FUNCTION__),
  393. 'msg' => array('learn_spam_error', 'unknown')
  394. );
  395. continue;
  396. }
  397. }
  398. return true;
  399. break;
  400. case 'get':
  401. if ($_SESSION['mailcow_cc_role'] == "user") {
  402. $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` = :mbox');
  403. $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username']));
  404. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  405. while($row = array_shift($rows)) {
  406. $q_meta[] = $row;
  407. }
  408. }
  409. elseif ($_SESSION['mailcow_cc_role'] == "admin") {
  410. $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine`');
  411. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  412. while($row = array_shift($rows)) {
  413. $q_meta[] = $row;
  414. }
  415. }
  416. else {
  417. $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
  418. foreach ($domains as $domain) {
  419. $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` REGEXP :domain');
  420. $stmt->execute(array(':domain' => '@' . $domain . '$'));
  421. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  422. while($row = array_shift($rows)) {
  423. $q_meta[] = $row;
  424. }
  425. }
  426. }
  427. return $q_meta;
  428. break;
  429. case 'settings':
  430. try {
  431. if ($_SESSION['mailcow_cc_role'] == "admin") {
  432. $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true);
  433. }
  434. $settings['max_size'] = $redis->Get('Q_MAX_SIZE');
  435. $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE');
  436. $settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT');
  437. $settings['subject'] = $redis->Get('Q_SUBJECT');
  438. $settings['sender'] = $redis->Get('Q_SENDER');
  439. $settings['html'] = htmlspecialchars($redis->Get('Q_HTML'));
  440. if (empty($settings['html'])) {
  441. $settings['html'] = htmlspecialchars(file_get_contents("/templates/quarantine.tpl"));
  442. }
  443. }
  444. catch (RedisException $e) {
  445. $_SESSION['return'][] = array(
  446. 'type' => 'danger',
  447. 'log' => array(__FUNCTION__, $_action, $_data_log),
  448. 'msg' => array('redis_error', $e)
  449. );
  450. return false;
  451. }
  452. return $settings;
  453. break;
  454. case 'details':
  455. if (!is_numeric($_data) || empty($_data)) {
  456. return false;
  457. }
  458. $stmt = $pdo->prepare('SELECT `rcpt`, `symbols`, `msg`, `domain` FROM `quarantine` WHERE `id`= :id');
  459. $stmt->execute(array(':id' => $_data));
  460. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  461. if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) {
  462. return $row;
  463. }
  464. return false;
  465. break;
  466. }
  467. }