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