settings.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. <?php
  2. /*
  3. The match section performs AND operation on different matches: for example, if you have from and rcpt in the same rule,
  4. then the rule matches only when from AND rcpt match. For similar matches, the OR rule applies: if you have multiple rcpt matches,
  5. then any of these will trigger the rule. If a rule is triggered then no more rules are matched.
  6. */
  7. header('Content-Type: text/plain');
  8. require_once "vars.inc.php";
  9. // Getting headers sent by the client.
  10. ini_set('error_reporting', 0);
  11. //$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
  12. $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
  13. $opt = [
  14. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  15. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  16. PDO::ATTR_EMULATE_PREPARES => false,
  17. ];
  18. try {
  19. $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
  20. $stmt = $pdo->query("SELECT '1' FROM `filterconf`");
  21. }
  22. catch (PDOException $e) {
  23. echo 'settings { }';
  24. exit;
  25. }
  26. // Check if db changed and return header
  27. $stmt = $pdo->prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables`
  28. WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias')
  29. AND TABLE_SCHEMA = :dbname;");
  30. $stmt->execute(array(
  31. ':dbname' => $database_name
  32. ));
  33. $db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time'];
  34. if (empty($db_update_time)) {
  35. $db_update_time = 1572048000;
  36. }
  37. if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) {
  38. header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304);
  39. exit;
  40. } else {
  41. header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200);
  42. }
  43. function parse_email($email) {
  44. if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
  45. $a = strrpos($email, '@');
  46. return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a));
  47. }
  48. function normalize_email($email) {
  49. $email = strtolower(str_replace('/', '\/', $email));
  50. $gm = "@gmail.com";
  51. if (substr_compare($email, $gm, -strlen($gm)) == 0) {
  52. $email = explode('@', $email);
  53. $email[0] = str_replace('.', '', $email[0]);
  54. $email = implode('@', $email);
  55. }
  56. $gm_alt = "@googlemail.com";
  57. if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
  58. $email = explode('@', $email);
  59. $email[0] = str_replace('.', '', $email[0]);
  60. $email[1] = str_replace('@', '', $gm);
  61. $email = implode('@', $email);
  62. }
  63. if (str_contains($email, "+")) {
  64. $email = explode('@', $email);
  65. $user = explode('+', $email[0]);
  66. $email[0] = $user[0];
  67. $email = implode('@', $email);
  68. }
  69. return $email;
  70. }
  71. function wl_by_sogo() {
  72. global $pdo;
  73. $rcpt = array();
  74. $stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info`
  75. INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id`
  76. GROUP BY `c_path2`");
  77. $sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC);
  78. while ($row = array_shift($sogo_contacts)) {
  79. foreach (explode(',', $row['contacts']) as $contact) {
  80. if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) {
  81. continue;
  82. }
  83. // Explicit from, no mime_from, no regex - envelope must match
  84. // mailcow white and blacklists also cover mime_from
  85. $rcpt[$row['user']][] = normalize_email($contact);
  86. }
  87. }
  88. return $rcpt;
  89. }
  90. function ucl_rcpts($object, $type) {
  91. global $pdo;
  92. $rcpt = array();
  93. if ($type == 'mailbox') {
  94. // Standard aliases
  95. $stmt = $pdo->prepare("SELECT `address` FROM `alias`
  96. WHERE `goto` = :object_goto
  97. AND `address` NOT LIKE '@%'");
  98. $stmt->execute(array(
  99. ':object_goto' => $object
  100. ));
  101. $standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
  102. while ($row = array_shift($standard_aliases)) {
  103. $local = parse_email($row['address'])['local'];
  104. $domain = parse_email($row['address'])['domain'];
  105. if (!empty($local) && !empty($domain)) {
  106. $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
  107. }
  108. $rcpt[] = str_replace('/', '\/', $row['address']);
  109. }
  110. // Aliases by alias domains
  111. $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
  112. LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
  113. WHERE `mailbox`.`username` = :object");
  114. $stmt->execute(array(
  115. ':object' => $object
  116. ));
  117. $by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
  118. array_filter($by_domain_aliases);
  119. while ($row = array_shift($by_domain_aliases)) {
  120. if (!empty($row['alias'])) {
  121. $local = parse_email($row['alias'])['local'];
  122. $domain = parse_email($row['alias'])['domain'];
  123. if (!empty($local) && !empty($domain)) {
  124. $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
  125. }
  126. $rcpt[] = str_replace('/', '\/', $row['alias']);
  127. }
  128. }
  129. }
  130. elseif ($type == 'domain') {
  131. // Domain self
  132. $rcpt[] = '/.*@' . $object . '/i';
  133. $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
  134. WHERE `target_domain` = :object");
  135. $stmt->execute(array(':object' => $object));
  136. $alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
  137. array_filter($alias_domains);
  138. while ($row = array_shift($alias_domains)) {
  139. $rcpt[] = '/.*@' . $row['alias_domain'] . '/i';
  140. }
  141. }
  142. return $rcpt;
  143. }
  144. ?>
  145. settings {
  146. watchdog {
  147. priority = 10;
  148. rcpt_mime = "/null@localhost/i";
  149. from_mime = "/watchdog@localhost/i";
  150. apply "default" {
  151. symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"];
  152. want_spam = yes;
  153. actions {
  154. reject = 9999.0;
  155. greylist = 9998.0;
  156. "add header" = 9997.0;
  157. }
  158. }
  159. }
  160. <?php
  161. /*
  162. // Start custom scores for users
  163. */
  164. $stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'");
  165. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  166. while ($row = array_shift($rows)) {
  167. $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
  168. ?>
  169. score_<?=$username_sane;?> {
  170. priority = 4;
  171. <?php
  172. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  173. ?>
  174. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  175. <?php
  176. }
  177. $stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
  178. WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
  179. AND `object`= :object");
  180. $stmt->execute(array(':object' => $row['object']));
  181. $spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP);
  182. ?>
  183. apply "default" {
  184. actions {
  185. reject = <?=$spamscore['highspamlevel'][0];?>;
  186. greylist = <?=$spamscore['lowspamlevel'][0] - 1;?>;
  187. "add header" = <?=$spamscore['lowspamlevel'][0];?>;
  188. }
  189. }
  190. }
  191. <?php
  192. }
  193. /*
  194. // Start SOGo contacts whitelist
  195. // Priority 4, lower than a domain whitelist (5) and lower than a mailbox whitelist (6)
  196. */
  197. foreach (wl_by_sogo() as $user => $contacts) {
  198. $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user);
  199. ?>
  200. whitelist_sogo_<?=$username_sane;?> {
  201. <?php
  202. foreach ($contacts as $contact) {
  203. ?>
  204. from = <?=json_encode($contact, JSON_UNESCAPED_SLASHES);?>;
  205. <?php
  206. }
  207. ?>
  208. priority = 4;
  209. <?php
  210. foreach (ucl_rcpts($user, 'mailbox') as $rcpt) {
  211. ?>
  212. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  213. <?php
  214. }
  215. ?>
  216. apply "default" {
  217. SOGO_CONTACT = -99.0;
  218. }
  219. symbols [
  220. "SOGO_CONTACT"
  221. ]
  222. }
  223. <?php
  224. }
  225. /*
  226. // Start whitelist
  227. */
  228. $stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'");
  229. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  230. while ($row = array_shift($rows)) {
  231. $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
  232. ?>
  233. whitelist_<?=$username_sane;?> {
  234. <?php
  235. $list_items = array();
  236. $stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
  237. WHERE `object`= :object
  238. AND `option` = 'whitelist_from'");
  239. $stmt->execute(array(':object' => $row['object']));
  240. $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
  241. foreach ($list_items as $item) {
  242. ?>
  243. from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
  244. <?php
  245. }
  246. if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
  247. ?>
  248. priority = 5;
  249. <?php
  250. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  251. ?>
  252. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  253. <?php
  254. }
  255. }
  256. else {
  257. ?>
  258. priority = 6;
  259. <?php
  260. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  261. ?>
  262. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  263. <?php
  264. }
  265. }
  266. ?>
  267. apply "default" {
  268. MAILCOW_WHITE = -999.0;
  269. }
  270. symbols [
  271. "MAILCOW_WHITE"
  272. ]
  273. }
  274. whitelist_mime_<?=$username_sane;?> {
  275. <?php
  276. foreach ($list_items as $item) {
  277. ?>
  278. from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
  279. <?php
  280. }
  281. if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
  282. ?>
  283. priority = 5;
  284. <?php
  285. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  286. ?>
  287. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  288. <?php
  289. }
  290. }
  291. else {
  292. ?>
  293. priority = 6;
  294. <?php
  295. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  296. ?>
  297. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  298. <?php
  299. }
  300. }
  301. ?>
  302. apply "default" {
  303. MAILCOW_WHITE = -999.0;
  304. }
  305. symbols [
  306. "MAILCOW_WHITE"
  307. ]
  308. }
  309. <?php
  310. }
  311. /*
  312. // Start blacklist
  313. */
  314. $stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'");
  315. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  316. while ($row = array_shift($rows)) {
  317. $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
  318. ?>
  319. blacklist_<?=$username_sane;?> {
  320. <?php
  321. $list_items = array();
  322. $stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
  323. WHERE `object`= :object
  324. AND `option` = 'blacklist_from'");
  325. $stmt->execute(array(':object' => $row['object']));
  326. $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
  327. foreach ($list_items as $item) {
  328. ?>
  329. from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
  330. <?php
  331. }
  332. if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
  333. ?>
  334. priority = 5;
  335. <?php
  336. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  337. ?>
  338. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  339. <?php
  340. }
  341. }
  342. else {
  343. ?>
  344. priority = 6;
  345. <?php
  346. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  347. ?>
  348. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  349. <?php
  350. }
  351. }
  352. ?>
  353. apply "default" {
  354. MAILCOW_BLACK = 999.0;
  355. }
  356. symbols [
  357. "MAILCOW_BLACK"
  358. ]
  359. }
  360. blacklist_header_<?=$username_sane;?> {
  361. <?php
  362. foreach ($list_items as $item) {
  363. ?>
  364. from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
  365. <?php
  366. }
  367. if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
  368. ?>
  369. priority = 5;
  370. <?php
  371. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  372. ?>
  373. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  374. <?php
  375. }
  376. }
  377. else {
  378. ?>
  379. priority = 6;
  380. <?php
  381. foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
  382. ?>
  383. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  384. <?php
  385. }
  386. }
  387. ?>
  388. apply "default" {
  389. MAILCOW_BLACK = 999.0;
  390. }
  391. symbols [
  392. "MAILCOW_BLACK"
  393. ]
  394. }
  395. <?php
  396. }
  397. /*
  398. // Start traps
  399. */
  400. ?>
  401. ham_trap {
  402. <?php
  403. foreach (ucl_rcpts('ham@localhost', 'mailbox') as $rcpt) {
  404. ?>
  405. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  406. <?php
  407. }
  408. ?>
  409. priority = 9;
  410. apply "default" {
  411. symbols_enabled = ["HISTORY_SAVE"];
  412. }
  413. symbols [
  414. "HAM_TRAP"
  415. ]
  416. }
  417. spam_trap {
  418. <?php
  419. foreach (ucl_rcpts('spam@localhost', 'mailbox') as $rcpt) {
  420. ?>
  421. rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
  422. <?php
  423. }
  424. ?>
  425. priority = 9;
  426. apply "default" {
  427. symbols_enabled = ["HISTORY_SAVE"];
  428. }
  429. symbols [
  430. "SPAM_TRAP"
  431. ]
  432. }
  433. <?php
  434. // Start additional content
  435. $stmt = $pdo->query("SELECT `id`, `content` FROM `settingsmap` WHERE `active` = '1'");
  436. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  437. while ($row = array_shift($rows)) {
  438. $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['id']);
  439. ?>
  440. additional_settings_<?=intval($row['id']);?> {
  441. <?php
  442. $content = preg_split('/\r\n|\r|\n/', $row['content']);
  443. foreach ($content as $line) {
  444. echo ' ' . $line . PHP_EOL;
  445. }
  446. ?>
  447. }
  448. <?php
  449. }
  450. ?>
  451. }