Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame^] | 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 |
|
| 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 | ];
|
| 19 | try {
|
| 20 | $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
| 21 | $stmt = $pdo->query("SELECT '1' FROM `filterconf`");
|
| 22 | }
|
| 23 | catch (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'];
|
| 37 | if (empty($db_update_time)) {
|
| 38 | $db_update_time = 1572048000;
|
| 39 | }
|
| 40 | if (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 |
|
| 48 | function 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 |
|
| 54 | function 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 |
|
| 74 | function 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 | ?>
|
| 129 | settings {
|
| 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 |
|
| 154 | while ($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 |
|
| 187 | foreach (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);
|
| 222 | while ($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);
|
| 310 | while ($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);
|
| 435 | while ($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 | }
|