From 4ae0ede9ee7bda241e5b8f3d1c44c5287d7c8286 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Fri, 6 Oct 2023 16:53:50 +0200 Subject: [PATCH] Anti-SPAM DMARC, HELO, IP and RBL scripts --- Cargo.lock | 201 ++- crates/directory/src/config.rs | 173 +- crates/directory/src/lib.rs | 37 +- crates/smtp/Cargo.toml | 2 + crates/smtp/src/inbound/data.rs | 19 +- crates/smtp/src/scripts/event_loop.rs | 2 +- crates/smtp/src/scripts/exec.rs | 2 + crates/smtp/src/scripts/functions/array.rs | 2 +- crates/smtp/src/scripts/functions/email.rs | 84 +- crates/smtp/src/scripts/functions/html.rs | 7 - crates/smtp/src/scripts/functions/misc.rs | 77 + crates/smtp/src/scripts/functions/mod.rs | 61 +- crates/smtp/src/scripts/functions/text.rs | 176 +- crates/smtp/src/scripts/functions/url.rs | 122 +- crates/smtp/src/scripts/plugins/dns.rs | 61 +- crates/smtp/src/scripts/plugins/lookup.rs | 29 + crates/smtp/src/scripts/plugins/mod.rs | 6 +- crates/smtp/src/scripts/plugins/query.rs | 23 +- resources/config/lists/disposable.txt.gz | Bin 2462 -> 0 bytes .../config/lists/dmarc_whitelist_new.inc | 792 --------- resources/config/lists/free.txt.gz | Bin 18731 -> 0 bytes resources/config/lists/mid.inc | 42 - resources/config/lists/mime_types.inc | 1546 ----------------- resources/config/lists/phishing_whitelist.inc | 5 - .../config/lists/public_suffix_list.dat.gz | Bin 80969 -> 0 bytes resources/config/lists/redirectors.inc | 1076 ------------ resources/config/lists/spf_dkim_whitelist.inc | 240 --- resources/config/lists/surbl-whitelist.inc | 831 --------- resources/config/sieve/bounce.sieve | 36 +- resources/config/sieve/dmarc.sieve | 91 + resources/config/sieve/from.sieve | 26 +- resources/config/sieve/headers.sieve | 19 +- resources/config/sieve/helo.sieve | 30 + resources/config/sieve/html.sieve | 6 +- resources/config/sieve/ip.sieve | 22 + resources/config/sieve/messageid.sieve | 15 +- resources/config/sieve/mime.sieve | 61 +- resources/config/sieve/prelude.sieve | 36 + resources/config/sieve/rbl.sieve | 339 ++++ resources/config/sieve/received.sieve | 36 +- resources/config/sieve/recipient.sieve | 35 +- resources/config/sieve/replyto.sieve | 17 +- resources/config/sieve/subject.sieve | 46 +- resources/config/sieve/url.sieve | 24 +- tests/resources/smtp/antispam/dmarc.test | 151 ++ tests/resources/smtp/antispam/from.test | 43 +- tests/resources/smtp/antispam/headers.test | 21 + tests/resources/smtp/antispam/helo.test | 65 + tests/resources/smtp/antispam/ip.test | 17 + tests/resources/smtp/antispam/mime.test | 143 ++ tests/resources/smtp/antispam/rbl.test | 30 + tests/resources/smtp/antispam/received.test | 42 +- tests/resources/smtp/antispam/subject.test | 2 +- tests/resources/smtp/antispam/url.test | 17 +- .../smtp/lists/disposable-domains.txt | 3 - tests/resources/smtp/lists/free-domains.txt | 5 - tests/resources/smtp/lists/redirectors.txt | 8 - tests/src/smtp/inbound/antispam.rs | 112 +- 58 files changed, 1995 insertions(+), 5119 deletions(-) delete mode 100644 resources/config/lists/disposable.txt.gz delete mode 100644 resources/config/lists/dmarc_whitelist_new.inc delete mode 100644 resources/config/lists/free.txt.gz delete mode 100644 resources/config/lists/mid.inc delete mode 100644 resources/config/lists/mime_types.inc delete mode 100644 resources/config/lists/phishing_whitelist.inc delete mode 100644 resources/config/lists/public_suffix_list.dat.gz delete mode 100644 resources/config/lists/redirectors.inc delete mode 100644 resources/config/lists/spf_dkim_whitelist.inc delete mode 100644 resources/config/lists/surbl-whitelist.inc create mode 100644 resources/config/sieve/dmarc.sieve create mode 100644 resources/config/sieve/helo.sieve create mode 100644 resources/config/sieve/ip.sieve create mode 100644 resources/config/sieve/prelude.sieve create mode 100644 resources/config/sieve/rbl.sieve create mode 100644 tests/resources/smtp/antispam/dmarc.test create mode 100644 tests/resources/smtp/antispam/helo.test create mode 100644 tests/resources/smtp/antispam/ip.test create mode 100644 tests/resources/smtp/antispam/rbl.test delete mode 100644 tests/resources/smtp/lists/disposable-domains.txt delete mode 100644 tests/resources/smtp/lists/free-domains.txt delete mode 100644 tests/resources/smtp/lists/redirectors.txt diff --git a/Cargo.lock b/Cargo.lock index 876fbbcb..8f53946a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,7 +262,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -284,7 +284,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -295,7 +295,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -506,7 +506,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.37", + "syn 2.0.38", "which", ] @@ -665,9 +665,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -742,6 +742,17 @@ dependencies = [ "nom", ] +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfb-mode" version = "0.8.2" @@ -833,7 +844,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1039,9 +1050,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ "csv-core", "itoa", @@ -1051,9 +1062,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] @@ -1293,7 +1304,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1407,9 +1418,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" dependencies = [ "base16ct", "crypto-bigint", @@ -1465,7 +1476,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1692,7 +1703,7 @@ checksum = "83c8d52fe8b46ab822b4decdcc0d6d85aeedfc98f0d52ba2bd4aec4a97807516" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "try_map", ] @@ -1778,7 +1789,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2153,7 +2164,7 @@ dependencies = [ [[package]] name = "hyper-util" version = "0.0.0" -source = "git+https://github.com/hyperium/hyper-util#d97181a278d9c59f1d7f2713732e400440861216" +source = "git+https://github.com/hyperium/hyper-util#63e84bf0603df79bf86b733e7376f1317bf2eefe" dependencies = [ "bytes", "futures-channel", @@ -2295,6 +2306,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "infer" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199" +dependencies = [ + "cfb", +] + [[package]] name = "inout" version = "0.1.3" @@ -2594,7 +2614,7 @@ dependencies = [ "log", "nom", "percent-encoding", - "ring", + "ring 0.16.20", "rustls 0.21.7", "rustls-native-certs", "thiserror", @@ -2624,9 +2644,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "librocksdb-sys" @@ -2724,8 +2744,8 @@ dependencies = [ [[package]] name = "mail-auth" -version = "0.3.3" -source = "git+https://github.com/stalwartlabs/mail-auth#1605fe274fd78f16b5fdce6fad042f4b0e00846f" +version = "0.3.5" +source = "git+https://github.com/stalwartlabs/mail-auth#191189df4506ed81937783fb01ded8b8bb50f7cf" dependencies = [ "ahash 0.8.3", "flate2", @@ -2734,7 +2754,7 @@ dependencies = [ "mail-parser", "parking_lot", "quick-xml 0.30.0", - "ring", + "ring 0.17.0", "rustls-pemfile", "serde", "serde_json", @@ -2745,7 +2765,7 @@ dependencies = [ [[package]] name = "mail-builder" version = "0.3.1" -source = "git+https://github.com/stalwartlabs/mail-builder#1eb0b5a72211c491cbe338920e8dfd3a675d6653" +source = "git+https://github.com/stalwartlabs/mail-builder#665f46db8542a97f5b86fd1ff9ea8d74d5ab7e4f" dependencies = [ "gethostname", ] @@ -2761,8 +2781,8 @@ dependencies = [ [[package]] name = "mail-send" -version = "0.4.0" -source = "git+https://github.com/stalwartlabs/mail-send#ffa60e3f653d0f4057b7c97d103751a80adc4c12" +version = "0.4.1" +source = "git+https://github.com/stalwartlabs/mail-send#53904ce6cf4fcb9a42a92a541f5d64d4d972d6cb" dependencies = [ "base64 0.20.0", "gethostname", @@ -3129,7 +3149,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3447,7 +3467,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3485,7 +3505,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3564,7 +3584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3602,9 +3622,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" dependencies = [ "unicode-ident", ] @@ -3920,9 +3940,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "base64 0.21.4", "bytes", @@ -3947,6 +3967,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-rustls", "tokio-util", @@ -3996,11 +4017,25 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb9d44f9bf6b635117787f72416783eb7e4227aaf255e5ce739563d817176a7e" +dependencies = [ + "cc", + "getrandom 0.2.10", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -4163,9 +4198,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.15" +version = "0.38.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" +checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" dependencies = [ "bitflags 2.4.0", "errno", @@ -4181,7 +4216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", - "ring", + "ring 0.16.20", "sct", "webpki", ] @@ -4193,7 +4228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", - "ring", + "ring 0.16.20", "rustls-webpki 0.101.6", "sct", ] @@ -4225,8 +4260,8 @@ version = "0.100.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6a5fc258f1c1276dfe3016516945546e2d5383911efc0fc4f1cdc5df3a4ae3" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -4235,8 +4270,8 @@ version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -4302,8 +4337,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -4423,7 +4458,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4471,7 +4506,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4545,9 +4580,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b21f559e07218024e7e9f90f96f601825397de0e25420135f7f952453fed0b" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -4567,7 +4602,7 @@ checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "sieve-rs" version = "0.3.1" -source = "git+https://github.com/stalwartlabs/sieve#3039311edc66988223e0bc6029a23fb3f8931f94" +source = "git+https://github.com/stalwartlabs/sieve#c9288b62815610872e9f278b904e34d46124acb5" dependencies = [ "ahash 0.8.3", "bincode", @@ -4639,6 +4674,7 @@ dependencies = [ "hyper-util", "idna 0.4.0", "imagesize", + "infer", "lazy_static", "linkify", "lru-cache", @@ -4646,6 +4682,7 @@ dependencies = [ "mail-builder", "mail-parser", "mail-send", + "md5", "num_cpus", "parking_lot", "rand 0.8.5", @@ -5090,9 +5127,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -5117,6 +5154,27 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -5223,7 +5281,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -5335,7 +5393,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -5492,7 +5550,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -5578,7 +5636,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.8.5", - "ring", + "ring 0.16.20", "rustls 0.21.7", "rustls-pemfile", "rustls-webpki 0.101.6", @@ -5684,9 +5742,18 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typewit" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa6b48ea01ae418f25260e05d1a97c5616a4e2d796dc3ba1e5a236a5100ed35" +checksum = "6779a69cc5f9a7388274a0a8a353eb1c9e45195f9ae74a26690b055a7cf9592a" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" [[package]] name = "unicase" @@ -5774,6 +5841,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -5897,7 +5970,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -5931,7 +6004,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5971,8 +6044,8 @@ version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -6291,7 +6364,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] diff --git a/crates/directory/src/config.rs b/crates/directory/src/config.rs index 991f4952..1afda89d 100644 --- a/crates/directory/src/config.rs +++ b/crates/directory/src/config.rs @@ -23,7 +23,7 @@ use bb8::{ManageConnection, Pool}; use regex::Regex; -use sieve::runtime::tests::glob::GlobPattern; +use sieve::runtime::{tests::glob::GlobPattern, Variable}; use std::{ fs::File, io::{BufRead, BufReader}, @@ -45,11 +45,11 @@ use crate::{ pub trait ConfigDirectory { fn parse_directory(&self) -> utils::config::Result; - fn parse_lookup_list( + fn parse_lookup_list( &self, - key: impl AsKey, + key: K, format: LookupFormat, - ) -> utils::config::Result; + ) -> utils::config::Result; } impl ConfigDirectory for Config { @@ -102,25 +102,42 @@ impl ConfigDirectory for Config { } } else { let key = ("directory", id, "lookup", lookup_id).as_key(); - let list = match self.property::((&key, "type"))? { - Some(lookup_type) => self.parse_lookup_list( - (&key, "values"), - LookupFormat { - lookup_type, - comment: self.value((&key, "comment")).map(|s| s.to_string()), - }, - )?, - None => self.parse_lookup_list( - key, - LookupFormat { - lookup_type: LookupType::List, - comment: None, - }, - )?, - }; - - Lookup::List { list } + match self.property::((&key, "type"))? { + Some(LookupType::Map) => Lookup::Map { + map: self.parse_lookup_list( + (&key, "values"), + LookupFormat { + lookup_type: LookupType::Map, + comment: self.value((&key, "comment")).map(|s| s.to_string()), + separator: self + .value((&key, "separator")) + .map(|s| s.to_string()), + }, + )?, + }, + Some(lookup_type) => Lookup::List { + list: self.parse_lookup_list( + (&key, "values"), + LookupFormat { + lookup_type, + comment: self.value((&key, "comment")).map(|s| s.to_string()), + separator: None, + }, + )?, + }, + None => Lookup::List { + list: self.parse_lookup_list( + key, + LookupFormat { + lookup_type: LookupType::List, + comment: None, + separator: None, + }, + )?, + }, + } }; + config .lookups .insert(format!("{id}/{lookup_id}"), Arc::new(lookup)); @@ -132,12 +149,12 @@ impl ConfigDirectory for Config { Ok(config) } - fn parse_lookup_list( + fn parse_lookup_list( &self, - key: impl AsKey, + key: K, format: LookupFormat, - ) -> utils::config::Result { - let mut list = LookupList::default(); + ) -> utils::config::Result { + let mut list = T::default(); let mut last_failed = false; for (_, mut value) in self.values(key.clone()) { if let Some(new_value) = value.strip_prefix("fallback+") { @@ -208,16 +225,46 @@ impl ConfigDirectory for Config { ) })?; } else { - list.insert(value.to_string(), format.lookup_type); + list.insert(value.to_string(), &format); } } Ok(list) } } -impl LookupList { - pub fn insert(&mut self, entry: String, format: LookupType) { - match format { +pub trait InsertLine: Default { + fn insert(&mut self, entry: String, format: &LookupFormat); + fn insert_lines( + &mut self, + reader: R, + format: &LookupFormat, + decompress: bool, + ) -> Result<(), std::io::Error> { + let reader: Box = if decompress { + Box::new(flate2::read::GzDecoder::new(reader)) + } else { + Box::new(reader) + }; + + for line in BufReader::new(reader).lines() { + let line_ = line?; + let line = line_.trim(); + if !line.is_empty() + && format + .comment + .as_ref() + .map_or(true, |c| !line.starts_with(c)) + { + self.insert(line.to_string(), format); + } + } + Ok(()) + } +} + +impl InsertLine for LookupList { + fn insert(&mut self, entry: String, format: &LookupFormat) { + match format.lookup_type { LookupType::List => { self.set.insert(entry); } @@ -255,34 +302,52 @@ impl LookupList { tracing::warn!("Invalid regular expression {:?}: {}", entry, err); } }, + LookupType::Map => unreachable!(), } } +} - pub fn insert_lines( - &mut self, - reader: R, - format: &LookupFormat, - decompress: bool, - ) -> Result<(), std::io::Error> { - let reader: Box = if decompress { - Box::new(flate2::read::GzDecoder::new(reader)) - } else { - Box::new(reader) - }; +impl InsertLine for AHashMap> { + fn insert(&mut self, entry: String, format: &LookupFormat) { + let (key, value) = entry + .split_once(format.separator.as_deref().unwrap_or(" ")) + .unwrap_or((entry.as_str(), "")); + let key = key.trim(); + if key.is_empty() { + return; + } else if value.is_empty() { + self.insert(key.to_string(), Variable::default()); + return; + } + let mut has_digit = false; + let mut has_dots = false; + let mut has_other = false; - for line in BufReader::new(reader).lines() { - let line_ = line?; - let line = line_.trim(); - if !line.is_empty() - && format - .comment - .as_ref() - .map_or(true, |c| !line.starts_with(c)) - { - self.insert(line.to_string(), format.lookup_type); + for (pos, ch) in value.bytes().enumerate() { + if ch.is_ascii_digit() { + has_digit = true; + } else if ch == b'.' { + has_dots = true; + } else if pos > 0 || ch != b'-' { + has_other = true; } } - Ok(()) + + let value = if has_other || !has_digit { + Variable::String(value.to_string()) + } else if has_dots { + value + .parse() + .map(Variable::Float) + .unwrap_or_else(|_| Variable::String(value.to_string())) + } else { + value + .parse() + .map(Variable::Integer) + .unwrap_or_else(|_| Variable::String(value.to_string())) + }; + + self.insert(key.to_string(), value); } } @@ -361,12 +426,14 @@ pub enum LookupType { List, Glob, Regex, + Map, } #[derive(Debug, Clone)] pub struct LookupFormat { pub lookup_type: LookupType, pub comment: Option, + pub separator: Option, } impl Default for LookupFormat { @@ -374,6 +441,7 @@ impl Default for LookupFormat { Self { lookup_type: LookupType::Glob, comment: Default::default(), + separator: Default::default(), } } } @@ -384,6 +452,7 @@ impl ParseValue for LookupType { "list" => Ok(LookupType::List), "glob" => Ok(LookupType::Glob), "regex" => Ok(LookupType::Regex), + "map" => Ok(LookupType::Map), _ => Err(format!( "Invalid value for lookup type {key:?}: {value:?}", key = key.as_key(), diff --git a/crates/directory/src/lib.rs b/crates/directory/src/lib.rs index a9ecac41..b64b3b18 100644 --- a/crates/directory/src/lib.rs +++ b/crates/directory/src/lib.rs @@ -28,7 +28,8 @@ use bb8::RunError; use imap::ImapError; use ldap3::LdapError; use mail_send::Credentials; -use sieve::runtime::tests::glob::GlobPattern; +use sieve::runtime::{tests::glob::GlobPattern, Variable}; +use smtp_proto::IntoString; use utils::config::DynValue; pub mod cache; @@ -108,6 +109,9 @@ pub enum Lookup { List { list: LookupList, }, + Map { + map: AHashMap>, + }, } #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -174,6 +178,36 @@ impl Lookup { } } Lookup::List { list } => list.contains(item).into(), + Lookup::Map { map } => map.contains_key(item).into(), + } + } + + pub async fn lookup(&self, item: &str) -> Option> { + match self { + Lookup::Directory { directory, query } => match directory.query(query, &[item]).await { + Ok(mut result) => match result.len() { + 1 => result.pop().map(Variable::from).unwrap(), + 0 => Variable::default(), + _ => Variable::Array(result.into_iter().map(Variable::from).collect()), + } + .into(), + Err(_) => None, + }, + Lookup::List { list } => Some(list.contains(item).into()), + Lookup::Map { map } => map.get(item).cloned(), + } + } +} + +impl From for Variable<'static> { + fn from(value: QueryColumn) -> Self { + match value { + QueryColumn::Integer(v) => Variable::Integer(v), + QueryColumn::Bool(v) => Variable::Integer(i64::from(v)), + QueryColumn::Float(v) => Variable::Float(v), + QueryColumn::Text(v) => Variable::String(v), + QueryColumn::Blob(v) => Variable::String(v.into_string()), + QueryColumn::Null => Variable::StringRef(""), } } } @@ -221,6 +255,7 @@ impl Debug for Lookup { f.debug_struct("Directory").field("query", query).finish() } Self::List { list } => f.debug_struct("List").field("list", list).finish(), + Self::Map { map } => f.debug_struct("Map").field("map", &map.keys()).finish(), } } } diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index bff08522..532be3c1 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -32,6 +32,7 @@ http-body-util = "0.1.0-rc.3" form_urlencoded = "1.1.0" sha1 = "0.10" sha2 = "0.10.6" +md5 = "0.7.0" rayon = "1.5" tracing = "0.1" parking_lot = "0.12" @@ -53,6 +54,7 @@ linkify = "0.10" idna = "0.4" decancer = "1.6.1" unicode-security = "0.1.0" +infer = "0.15.0" [features] test_mode = [] diff --git a/crates/smtp/src/inbound/data.rs b/crates/smtp/src/inbound/data.rs index 078de1ac..a1c87c5b 100644 --- a/crates/smtp/src/inbound/data.rs +++ b/crates/smtp/src/inbound/data.rs @@ -30,10 +30,11 @@ use std::{ }; use mail_auth::{ - common::headers::HeaderWriter, dmarc, AuthenticatedMessage, AuthenticationResults, DkimResult, - DmarcResult, ReceivedSpf, + common::{headers::HeaderWriter, verify::VerifySignature}, + dmarc, AuthenticatedMessage, AuthenticationResults, DkimResult, DmarcResult, ReceivedSpf, }; use mail_builder::headers::{date::Date, message_id::generate_message_id_header}; +use sieve::runtime::Variable; use smtp_proto::{ MAIL_BY_RETURN, RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_NEVER, RCPT_NOTIFY_SUCCESS, }; @@ -432,6 +433,20 @@ impl Session { .map(|r| r.result().as_str()) .unwrap_or_default(), ) + .set_variable( + "dkim.domains", + dkim_output + .iter() + .filter_map(|r| { + if matches!(r.result(), DkimResult::Pass) { + r.signature() + .map(|s| Variable::String(s.domain().to_lowercase())) + } else { + None + } + }) + .collect::>(), + ) .set_variable( "dmarc.result", dmarc_result diff --git a/crates/smtp/src/scripts/event_loop.rs b/crates/smtp/src/scripts/event_loop.rs index 17a8e20c..6edcc9e3 100644 --- a/crates/smtp/src/scripts/event_loop.rs +++ b/crates/smtp/src/scripts/event_loop.rs @@ -178,7 +178,7 @@ impl SMTP { ); } } - Lookup::Directory { .. } => { + Lookup::Directory { .. } | Lookup::Map { .. } => { // Not implemented } } diff --git a/crates/smtp/src/scripts/exec.rs b/crates/smtp/src/scripts/exec.rs index d09b97b3..b961b65c 100644 --- a/crates/smtp/src/scripts/exec.rs +++ b/crates/smtp/src/scripts/exec.rs @@ -23,6 +23,7 @@ use std::{sync::Arc, time::SystemTime}; +use mail_auth::common::resolver::ToReverseName; use sieve::{runtime::Variable, Envelope, Sieve}; use smtp_proto::*; use tokio::{ @@ -42,6 +43,7 @@ impl Session { let (tls_version, tls_cipher) = self.stream.tls_version_and_cipher(); let mut params = ScriptParameters::new() .set_variable("remote_ip", self.data.remote_ip.to_string()) + .set_variable("remote_ip.reverse", self.data.remote_ip.to_reverse_name()) .set_variable("helo_domain", self.data.helo_domain.to_lowercase()) .set_variable( "authenticated_as", diff --git a/crates/smtp/src/scripts/functions/array.rs b/crates/smtp/src/scripts/functions/array.rs index 61093fcd..e9943db5 100644 --- a/crates/smtp/src/scripts/functions/array.rs +++ b/crates/smtp/src/scripts/functions/array.rs @@ -174,7 +174,7 @@ pub fn fn_winnow<'x>(_: &'x Context<'x, SieveContext>, mut v: Vec>) .iter() .filter_map(|i| { if !i.is_empty() { - i.to_owned().into() + i.clone().into() } else { None } diff --git a/crates/smtp/src/scripts/functions/email.rs b/crates/smtp/src/scripts/functions/email.rs index 6997428f..70424579 100644 --- a/crates/smtp/src/scripts/functions/email.rs +++ b/crates/smtp/src/scripts/functions/email.rs @@ -100,67 +100,71 @@ pub fn fn_is_email<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) - pub fn fn_email_part<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { v[0].transform(|s| { s.rsplit_once('@') - .and_then(|(u, d)| match v[1].to_cow().as_ref() { - "local" => u.trim().into(), - "domain" => d.trim().into(), - _ => None, + .map(|(u, d)| match v[1].to_cow().as_ref() { + "local" => Variable::StringRef(u.trim()), + "domain" => Variable::StringRef(d.trim()), + _ => Variable::default(), }) + .unwrap_or_default() }) } +#[derive(PartialEq, Eq, Clone, Copy)] +enum MatchPart { + Sld, + Tld, + Host, +} + pub fn fn_domain_part<'x>( ctx: &'x Context<'x, SieveContext>, v: Vec>, ) -> Variable<'x> { - #[derive(PartialEq, Eq)] - enum MatchPart { - Sld, - Tld, - Host, - } - let domain = v[0].to_cow(); let match_part = match v[1].to_cow().as_ref() { "sld" => MatchPart::Sld, "tld" => MatchPart::Tld, "host" => MatchPart::Host, _ => return Variable::default(), }; - let d = domain.trim().to_lowercase(); - let mut seen_dot = false; - for (pos, ch) in d.as_bytes().iter().enumerate().rev() { - if *ch == b'.' { - if seen_dot { - let maybe_domain = - std::str::from_utf8(&d.as_bytes()[pos + 1..]).unwrap_or_default(); - if !ctx.context().psl.contains(maybe_domain) { - return if match_part == MatchPart::Sld { - maybe_domain - } else { - std::str::from_utf8(&d.as_bytes()[..pos]).unwrap_or_default() + + v[0].transform(|domain| { + let d = domain.trim().to_lowercase(); + let mut seen_dot = false; + for (pos, ch) in d.as_bytes().iter().enumerate().rev() { + if *ch == b'.' { + if seen_dot { + let maybe_domain = + std::str::from_utf8(&d.as_bytes()[pos + 1..]).unwrap_or_default(); + if !ctx.context().psl.contains(maybe_domain) { + return if match_part == MatchPart::Sld { + maybe_domain + } else { + std::str::from_utf8(&d.as_bytes()[..pos]).unwrap_or_default() + } + .to_string() + .into(); } - .to_string() - .into(); + } else if match_part == MatchPart::Tld { + return std::str::from_utf8(&d.as_bytes()[pos + 1..]) + .unwrap_or_default() + .to_string() + .into(); + } else { + seen_dot = true; } - } else if match_part == MatchPart::Tld { - return std::str::from_utf8(&d.as_bytes()[pos + 1..]) - .unwrap_or_default() - .to_string() - .into(); - } else { - seen_dot = true; } } - } - if seen_dot { - if match_part == MatchPart::Sld { + if seen_dot { + if match_part == MatchPart::Sld { + d.into() + } else { + Variable::default() + } + } else if match_part == MatchPart::Host { d.into() } else { Variable::default() } - } else if match_part == MatchPart::Host { - d.into() - } else { - Variable::default() - } + }) } diff --git a/crates/smtp/src/scripts/functions/html.rs b/crates/smtp/src/scripts/functions/html.rs index 1399e448..874b5294 100644 --- a/crates/smtp/src/scripts/functions/html.rs +++ b/crates/smtp/src/scripts/functions/html.rs @@ -32,13 +32,6 @@ pub fn fn_html_to_text<'x>(_: &'x Context<'x, SieveContext>, v: Vec html_to_text(v[0].to_cow().as_ref()).into() } -pub fn fn_tokenize_html<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec>, -) -> Variable<'x> { - html_to_tokens(v[0].to_cow().as_ref()).into() -} - pub fn fn_html_has_tag<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { v[0].as_array() .map(|arr| { diff --git a/crates/smtp/src/scripts/functions/misc.rs b/crates/smtp/src/scripts/functions/misc.rs index 147fac22..5ad80087 100644 --- a/crates/smtp/src/scripts/functions/misc.rs +++ b/crates/smtp/src/scripts/functions/misc.rs @@ -21,10 +21,17 @@ * for more details. */ +use std::net::IpAddr; + +use mail_auth::common::resolver::ToReverseName; +use sha1::Sha1; +use sha2::{Sha256, Sha512}; use sieve::{runtime::Variable, Context}; use crate::config::scripts::SieveContext; +use super::ApplyString; + pub fn fn_is_empty<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { match &v[0] { Variable::String(s) => s.is_empty(), @@ -40,6 +47,76 @@ pub fn fn_is_ip_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) v[0].to_cow().parse::().is_ok().into() } +pub fn fn_is_ipv4_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .parse::() + .map_or(false, |ip| matches!(ip, IpAddr::V4(_))) + .into() +} + +pub fn fn_is_ipv6_addr<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .parse::() + .map_or(false, |ip| matches!(ip, IpAddr::V6(_))) + .into() +} + +pub fn fn_ip_reverse_name<'x>( + _: &'x Context<'x, SieveContext>, + v: Vec>, +) -> Variable<'x> { + v[0].to_cow() + .parse::() + .map(|ip| ip.to_reverse_name()) + .unwrap_or_default() + .into() +} + +pub fn fn_detect_file_type<'x>( + ctx: &'x Context<'x, SieveContext>, + v: Vec>, +) -> Variable<'x> { + ctx.message() + .part(ctx.part()) + .and_then(|p| infer::get(p.contents())) + .map(|t| { + Variable::String( + if v[0].to_cow() != "ext" { + t.mime_type() + } else { + t.extension() + } + .to_string(), + ) + }) + .unwrap_or_default() +} + +pub fn fn_hash<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + use sha1::Digest; + let hash = v[1].to_cow(); + + v[0].transform(|value| match hash.as_ref() { + "md5" => format!("{:x}", md5::compute(value.as_bytes())).into(), + "sha1" => { + let mut hasher = Sha1::new(); + hasher.update(value.as_bytes()); + format!("{:x}", hasher.finalize()).into() + } + "sha256" => { + let mut hasher = Sha256::new(); + hasher.update(value.as_bytes()); + format!("{:x}", hasher.finalize()).into() + } + "sha512" => { + let mut hasher = Sha512::new(); + hasher.update(value.as_bytes()); + format!("{:x}", hasher.finalize()).into() + } + _ => Variable::default(), + }) +} + pub fn fn_is_var_names<'x>( ctx: &'x Context<'x, SieveContext>, _: Vec>, diff --git a/crates/smtp/src/scripts/functions/mod.rs b/crates/smtp/src/scripts/functions/mod.rs index 3d9d6708..b6632e98 100644 --- a/crates/smtp/src/scripts/functions/mod.rs +++ b/crates/smtp/src/scripts/functions/mod.rs @@ -57,9 +57,6 @@ pub fn register_functions() -> FunctionMap { .with_function("is_uppercase", fn_is_uppercase) .with_function("is_lowercase", fn_is_lowercase) .with_function("has_digits", fn_has_digits) - .with_function("tokenize_words", fn_tokenize_words) - .with_function("tokenize_html", fn_tokenize_html) - .with_function("max_line_len", fn_max_line_len) .with_function("count_spaces", fn_count_spaces) .with_function("count_uppercase", fn_count_uppercase) .with_function("count_lowercase", fn_count_lowercase) @@ -69,6 +66,9 @@ pub fn register_functions() -> FunctionMap { .with_function("is_header_utf8_valid", fn_is_header_utf8_valid) .with_function("img_metadata", fn_img_metadata) .with_function("is_ip_addr", fn_is_ip_addr) + .with_function("is_ipv4_addr", fn_is_ipv4_addr) + .with_function("is_ipv6_addr", fn_is_ipv6_addr) + .with_function("ip_reverse_name", fn_ip_reverse_name) .with_function("winnow", fn_winnow) .with_function("has_zwsp", fn_has_zwsp) .with_function("has_obscured", fn_has_obscured) @@ -76,8 +76,9 @@ pub fn register_functions() -> FunctionMap { .with_function("puny_decode", fn_puny_decode) .with_function("unicode_skeleton", fn_unicode_skeleton) .with_function("cure_text", fn_cure_text) + .with_function("detect_file_type", fn_detect_file_type) .with_function_args("sort", fn_sort, 2) - .with_function_args("tokenize_url", fn_tokenize_url, 2) + .with_function_args("tokenize", fn_tokenize, 2) .with_function_args("email_part", fn_email_part, 2) .with_function_args("domain_part", fn_domain_part, 2) .with_function_args("eq_ignore_case", fn_eq_ignore_case, 2) @@ -97,9 +98,12 @@ pub fn register_functions() -> FunctionMap { .with_function_args("substring", fn_substring, 3) .with_function_args("split", fn_split, 2) .with_function_args("rsplit", fn_rsplit, 2) + .with_function_args("split_once", fn_split_once, 2) + .with_function_args("rsplit_once", fn_rsplit_once, 2) .with_function_args("strip_prefix", fn_strip_prefix, 2) .with_function_args("strip_suffix", fn_strip_suffix, 2) .with_function_args("is_intersect", fn_is_intersect, 2) + .with_function_args("hash", fn_hash, 2) .with_function_no_args("is_encoding_problem", fn_is_encoding_problem) .with_function_no_args("is_attachment", fn_is_attachment) .with_function_no_args("is_body", fn_is_body) @@ -108,36 +112,33 @@ pub fn register_functions() -> FunctionMap { } pub trait ApplyString<'x> { - fn transform(&self, f: impl Fn(&str) -> Option<&str>) -> Variable<'x>; - fn transform_string>>( - &self, - f: impl Fn(&str) -> T, - ) -> Option>; + fn transform(&self, f: impl Fn(&'_ str) -> Variable<'_>) -> Variable<'x>; } impl<'x> ApplyString<'x> for Variable<'x> { - fn transform(&self, f: impl Fn(&str) -> Option<&str>) -> Variable<'x> { + fn transform(&self, f: impl Fn(&'_ str) -> Variable<'_>) -> Variable<'x> { match self { - Variable::String(s) => { - f(s).map_or(Variable::default(), |s| Variable::from(s.to_string())) - } - Variable::StringRef(s) => f(s).map_or(Variable::default(), Variable::from), - v => f(v.to_string().as_str()) - .map_or(Variable::default(), |s| Variable::from(s.to_string())), - } - } - - fn transform_string>>( - &self, - f: impl Fn(&str) -> T, - ) -> Option> { - match self { - Variable::String(s) => Some(f(s).into()), - Variable::StringRef(s) => Some(f(s).into()), - Variable::Integer(_) - | Variable::Float(_) - | Variable::Array(_) - | Variable::ArrayRef(_) => None, + Variable::StringRef(s) => f(s), + Variable::String(s) => f(s).into_owned(), + Variable::ArrayRef(list) => list + .iter() + .map(|v| match v { + Variable::String(s) => f(s), + Variable::StringRef(s) => f(s), + v => f(v.to_cow().as_ref()).into_owned(), + }) + .collect::>() + .into(), + Variable::Array(list) => list + .iter() + .map(|v| match v { + Variable::StringRef(s) => f(s), + Variable::String(s) => f(s).into_owned(), + v => f(v.to_cow().as_ref()).into_owned(), + }) + .collect::>() + .into(), + v => f(v.to_cow().as_ref()).into_owned(), } } } diff --git a/crates/smtp/src/scripts/functions/text.rs b/crates/smtp/src/scripts/functions/text.rs index 947ad5cd..56ebaa65 100644 --- a/crates/smtp/src/scripts/functions/text.rs +++ b/crates/smtp/src/scripts/functions/text.rs @@ -23,20 +23,20 @@ use sieve::{runtime::Variable, Context}; -use crate::config::scripts::SieveContext; +use crate::{config::scripts::SieveContext, scripts::functions::url::tokenize_email}; -use super::ApplyString; +use super::{html::html_to_tokens, url::tokenize_url, ApplyString}; pub fn fn_trim<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - v[0].transform(|s| Some(s.trim())) + v[0].transform(|s| Variable::StringRef(s.trim())) } pub fn fn_trim_end<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - v[0].transform(|s| Some(s.trim_end())) + v[0].transform(|s| Variable::StringRef(s.trim_end())) } pub fn fn_trim_start<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - v[0].transform(|s| Some(s.trim_start())) + v[0].transform(|s| Variable::StringRef(s.trim_start())) } pub fn fn_len<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { @@ -50,79 +50,38 @@ pub fn fn_len<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Var .into() } -pub fn fn_to_lowercase<'x>( - _: &'x Context<'x, SieveContext>, - mut v: Vec>, -) -> Variable<'x> { - match v.remove(0) { - Variable::String(s) => s.to_lowercase().into(), - Variable::StringRef(s) => s.to_lowercase().into(), - Variable::Array(a) => a - .iter() - .map(|v| v.to_cow().to_lowercase().into()) - .collect::>() - .into(), - Variable::ArrayRef(a) => a - .iter() - .map(|v| v.to_cow().to_lowercase().into()) - .collect::>() - .into(), - v => v, - } +pub fn fn_to_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + v[0].transform(|s| Variable::String(s.to_lowercase())) } -pub fn fn_to_uppercase<'x>( - _: &'x Context<'x, SieveContext>, - mut v: Vec>, -) -> Variable<'x> { - match v.remove(0) { - Variable::String(s) => s.to_uppercase().into(), - Variable::StringRef(s) => s.to_uppercase().into(), - Variable::Array(a) => a - .iter() - .map(|v| v.to_cow().to_uppercase().into()) - .collect::>() - .into(), - Variable::ArrayRef(a) => a - .iter() - .map(|v| v.to_cow().to_uppercase().into()) - .collect::>() - .into(), - v => v, - } +pub fn fn_to_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + v[0].transform(|s| Variable::String(s.to_uppercase())) } pub fn fn_is_uppercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_uppercase()) - .into() + v[0].transform(|s| { + s.chars() + .filter(|c| c.is_alphabetic()) + .all(|c| c.is_uppercase()) + .into() + }) } pub fn fn_is_lowercase<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_lowercase()) - .into() + v[0].transform(|s| { + s.chars() + .filter(|c| c.is_alphabetic()) + .all(|c| c.is_lowercase()) + .into() + }) } pub fn fn_has_digits<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - v[0].to_cow() - .as_ref() - .chars() - .any(|c| c.is_ascii_digit()) - .into() + v[0].transform(|s| s.chars().any(|c| c.is_ascii_digit()).into()) } -pub fn fn_tokenize_words<'x>( - _: &'x Context<'x, SieveContext>, - v: Vec>, -) -> Variable<'x> { - match &v[0] { +pub fn tokenize_words<'x>(v: &Variable<'x>) -> Variable<'x> { + match v { Variable::StringRef(s) => s .split_whitespace() .filter(|word| word.chars().all(|c| c.is_alphanumeric())) @@ -143,15 +102,18 @@ pub fn fn_tokenize_words<'x>( .into() } -pub fn fn_max_line_len<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - match &v[0] { - Variable::String(s) => s.lines().map(|l| l.len()).max().unwrap_or(0), - Variable::StringRef(s) => s.lines().map(|l| l.len()).max().unwrap_or(0), - Variable::Integer(_) | Variable::Float(_) => 0, - Variable::Array(a) => a.iter().map(|v| v.to_cow().len()).max().unwrap_or(0), - Variable::ArrayRef(a) => a.iter().map(|v| v.to_cow().len()).max().unwrap_or(0), +pub fn fn_tokenize<'x>( + ctx: &'x Context<'x, SieveContext>, + mut v: Vec>, +) -> Variable<'x> { + match v[1].to_cow().as_ref() { + "html" => html_to_tokens(v[0].to_cow().as_ref()).into(), + "words" => tokenize_words(&v[0]), + "uri" | "url" => tokenize_url(ctx, v.remove(0), false), + "uri_strict" | "url_strict" => tokenize_url(ctx, v.remove(0), true), + "email" => tokenize_email(v.remove(0)), + _ => Variable::default(), } - .into() } pub fn fn_count_spaces<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { @@ -265,12 +227,20 @@ pub fn fn_substring<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) pub fn fn_strip_prefix<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { let prefix = v[1].to_cow(); - v[0].transform(|s| s.strip_prefix(prefix.as_ref())) + v[0].transform(|s| { + s.strip_prefix(prefix.as_ref()) + .map(Variable::StringRef) + .unwrap_or_default() + }) } pub fn fn_strip_suffix<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { let suffix = v[1].to_cow(); - v[0].transform(|s| s.strip_suffix(suffix.as_ref())) + v[0].transform(|s| { + s.strip_suffix(suffix.as_ref()) + .map(Variable::StringRef) + .unwrap_or_default() + }) } pub fn fn_split<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { @@ -315,6 +285,62 @@ pub fn fn_rsplit<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> } } +pub fn fn_split_once<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::StringRef(s) => s + .split_once(v[1].to_cow().as_ref()) + .map(|(a, b)| Variable::Array(vec![Variable::StringRef(a), Variable::StringRef(b)])) + .unwrap_or_default(), + Variable::String(s) => s + .split_once(v[1].to_cow().as_ref()) + .map(|(a, b)| { + Variable::Array(vec![ + Variable::String(a.to_string()), + Variable::String(b.to_string()), + ]) + }) + .unwrap_or_default(), + val => val + .to_string() + .split_once(v[1].to_cow().as_ref()) + .map(|(a, b)| { + Variable::Array(vec![ + Variable::String(a.to_string()), + Variable::String(b.to_string()), + ]) + }) + .unwrap_or_default(), + } +} + +pub fn fn_rsplit_once<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::StringRef(s) => s + .rsplit_once(v[1].to_cow().as_ref()) + .map(|(a, b)| Variable::Array(vec![Variable::StringRef(a), Variable::StringRef(b)])) + .unwrap_or_default(), + Variable::String(s) => s + .rsplit_once(v[1].to_cow().as_ref()) + .map(|(a, b)| { + Variable::Array(vec![ + Variable::String(a.to_string()), + Variable::String(b.to_string()), + ]) + }) + .unwrap_or_default(), + val => val + .to_string() + .rsplit_once(v[1].to_cow().as_ref()) + .map(|(a, b)| { + Variable::Array(vec![ + Variable::String(a.to_string()), + Variable::String(b.to_string()), + ]) + }) + .unwrap_or_default(), + } +} + /** * `levenshtein-rs` - levenshtein * diff --git a/crates/smtp/src/scripts/functions/url.rs b/crates/smtp/src/scripts/functions/url.rs index 94c69763..aaf86036 100644 --- a/crates/smtp/src/scripts/functions/url.rs +++ b/crates/smtp/src/scripts/functions/url.rs @@ -24,19 +24,22 @@ use std::net::IpAddr; use hyper::Uri; +use linkify::LinkKind; use sieve::{runtime::Variable, Context}; use crate::config::scripts::SieveContext; -pub fn fn_tokenize_url<'x>( - ctx: &'x Context<'x, SieveContext>, - mut v: Vec>, -) -> Variable<'x> { - let must_have_scheme = v[1].to_bool(); +use super::ApplyString; - match v.remove(0) { +pub fn tokenize_url<'x>( + ctx: &'x Context<'x, SieveContext>, + v: Variable<'x>, + must_have_scheme: bool, +) -> Variable<'x> { + match v { Variable::StringRef(text) => linkify::LinkFinder::new() .url_must_have_scheme(must_have_scheme) + .kinds(&[LinkKind::Url]) .links(text.as_ref()) .filter_map(|url| filter_url(url.as_str(), must_have_scheme, ctx)) .collect::>() @@ -44,6 +47,7 @@ pub fn fn_tokenize_url<'x>( v @ (Variable::String(_) | Variable::Array(_) | Variable::ArrayRef(_)) => { linkify::LinkFinder::new() .url_must_have_scheme(must_have_scheme) + .kinds(&[LinkKind::Url]) .links(v.to_cow().as_ref()) .filter_map(|url| { filter_url(url.as_str(), must_have_scheme, ctx).map(|v| v.into_owned()) @@ -55,6 +59,28 @@ pub fn fn_tokenize_url<'x>( } } +pub fn tokenize_email(v: Variable<'_>) -> Variable<'_> { + match v { + Variable::StringRef(text) => linkify::LinkFinder::new() + .email_domain_must_have_dot(true) + .kinds(&[LinkKind::Email]) + .links(text.as_ref()) + .map(|email| Variable::StringRef(email.as_str())) + .collect::>() + .into(), + v @ (Variable::String(_) | Variable::Array(_) | Variable::ArrayRef(_)) => { + linkify::LinkFinder::new() + .email_domain_must_have_dot(true) + .kinds(&[LinkKind::Email]) + .links(v.to_cow().as_ref()) + .map(|email| Variable::String(email.as_str().to_string())) + .collect::>() + .into() + } + v => v, + } +} + fn filter_url<'x, 'y>( url: &'x str, must_have_scheme: bool, @@ -84,51 +110,51 @@ fn filter_url<'x, 'y>( } pub fn fn_uri_part<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { - v[0].to_cow() - .parse::() - .ok() - .and_then(|uri| match v[1].to_cow().as_ref() { - "scheme" => uri.scheme_str().map(|s| Variable::String(s.to_lowercase())), - "host" => uri.host().map(|s| Variable::String(s.to_lowercase())), - "scheme_host" => uri - .scheme_str() - .and_then(|s| (s, uri.host()?).into()) - .map(|(s, h)| Variable::String(format!("{}://{}", s, h))), - "path" => Variable::String(uri.path().to_string()).into(), - "port" => uri.port_u16().map(|port| Variable::Integer(port as i64)), - "query" => uri.query().map(|s| Variable::String(s.to_string())), - "path_query" => uri - .path_and_query() - .map(|s| Variable::String(s.to_string())), - "authority" => uri.authority().map(|s| Variable::String(s.to_string())), - _ => None, - }) - .unwrap_or_default() + let part = v[1].to_cow(); + v[0].transform(|uri| { + uri.parse::() + .ok() + .and_then(|uri| match part.as_ref() { + "scheme" => uri.scheme_str().map(|s| Variable::String(s.to_string())), + "host" => uri.host().map(|s| Variable::String(s.to_string())), + "scheme_host" => uri + .scheme_str() + .and_then(|s| (s, uri.host()?).into()) + .map(|(s, h)| Variable::String(format!("{}://{}", s, h))), + "path" => Variable::String(uri.path().to_string()).into(), + "port" => uri.port_u16().map(|port| Variable::Integer(port as i64)), + "query" => uri.query().map(|s| Variable::String(s.to_string())), + "path_query" => uri + .path_and_query() + .map(|s| Variable::String(s.to_string())), + "authority" => uri.authority().map(|s| Variable::String(s.to_string())), + _ => None, + }) + .unwrap_or_default() + }) } -pub fn fn_puny_decode<'x>( - _: &'x Context<'x, SieveContext>, - mut v: Vec>, -) -> Variable<'x> { - let domain = v[0].to_cow(); - if domain.contains("xn--") { - let mut decoded = String::with_capacity(domain.len()); - for part in domain.split('.') { - if !decoded.is_empty() { - decoded.push('.'); - } +pub fn fn_puny_decode<'x>(_: &'x Context<'x, SieveContext>, v: Vec>) -> Variable<'x> { + v[0].transform(|domain| { + if domain.contains("xn--") { + let mut decoded = String::with_capacity(domain.len()); + for part in domain.split('.') { + if !decoded.is_empty() { + decoded.push('.'); + } - if let Some(puny) = part - .strip_prefix("xn--") - .and_then(idna::punycode::decode_to_string) - { - decoded.push_str(&puny); - } else { - decoded.push_str(part); + if let Some(puny) = part + .strip_prefix("xn--") + .and_then(idna::punycode::decode_to_string) + { + decoded.push_str(&puny); + } else { + decoded.push_str(part); + } } + decoded.into() + } else { + domain.into() } - decoded.into() - } else { - v.remove(0) - } + }) } diff --git a/crates/smtp/src/scripts/plugins/dns.rs b/crates/smtp/src/scripts/plugins/dns.rs index 057189d1..d938be6d 100644 --- a/crates/smtp/src/scripts/plugins/dns.rs +++ b/crates/smtp/src/scripts/plugins/dns.rs @@ -23,7 +23,7 @@ use std::net::IpAddr; -use mail_auth::IpLookupStrategy; +use mail_auth::{Error, IpLookupStrategy}; use sieve::{runtime::Variable, FunctionMap}; use crate::config::scripts::SieveContext; @@ -71,6 +71,21 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { .into(), Err(err) => err.short_error().into(), } + } else if record_type.eq_ignore_ascii_case("txt") { + #[cfg(feature = "test_mode")] + { + if entry.contains("origin") { + return Variable::String("23028|US|arin|2002-01-04".to_string()); + } + } + + match ctx + .handle + .block_on(ctx.core.resolvers.dns.txt_raw_lookup(entry.as_ref())) + { + Ok(result) => Variable::String(String::from_utf8(result).unwrap_or_default()), + Err(err) => err.short_error().into(), + } } else if record_type.eq_ignore_ascii_case("ptr") { if let Ok(addr) = entry.parse::() { match ctx.handle.block_on(ctx.core.resolvers.dns.ptr_lookup(addr)) { @@ -85,6 +100,14 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { Variable::default() } } else if record_type.eq_ignore_ascii_case("ipv4") { + #[cfg(feature = "test_mode")] + { + if entry.contains(".168.192.") { + let parts = entry.split('.').collect::>(); + return vec![Variable::String(format!("127.0.{}.{}", parts[1], parts[0]))].into(); + } + } + match ctx .handle .block_on(ctx.core.resolvers.dns.ipv4_lookup(entry.as_ref())) @@ -123,44 +146,56 @@ pub fn exec_exists(ctx: PluginContext<'_>) -> Variable<'static> { IpLookupStrategy::Ipv4thenIpv6, 10, )) { - Ok(result) => !result.is_empty(), - Err(_) => false, + Ok(result) => i64::from(!result.is_empty()), + Err(Error::DnsRecordNotFound(_)) => 0, + Err(_) => -1, } } else if record_type.eq_ignore_ascii_case("mx") { match ctx .handle .block_on(ctx.core.resolvers.dns.mx_lookup(entry.as_ref())) { - Ok(result) => result.iter().any(|mx| !mx.exchanges.is_empty()), - Err(_) => false, + Ok(result) => i64::from(result.iter().any(|mx| !mx.exchanges.is_empty())), + Err(Error::DnsRecordNotFound(_)) => 0, + Err(_) => -1, } } else if record_type.eq_ignore_ascii_case("ptr") { if let Ok(addr) = entry.parse::() { match ctx.handle.block_on(ctx.core.resolvers.dns.ptr_lookup(addr)) { - Ok(result) => !result.is_empty(), - Err(_) => false, + Ok(result) => i64::from(!result.is_empty()), + Err(Error::DnsRecordNotFound(_)) => 0, + Err(_) => -1, } } else { - false + -1 } } else if record_type.eq_ignore_ascii_case("ipv4") { + #[cfg(feature = "test_mode")] + { + if entry.starts_with("2.0.168.192.") { + return 1.into(); + } + } + match ctx .handle .block_on(ctx.core.resolvers.dns.ipv4_lookup(entry.as_ref())) { - Ok(result) => !result.is_empty(), - Err(_) => false, + Ok(result) => i64::from(!result.is_empty()), + Err(Error::DnsRecordNotFound(_)) => 0, + Err(_) => -1, } } else if record_type.eq_ignore_ascii_case("ipv6") { match ctx .handle .block_on(ctx.core.resolvers.dns.ipv6_lookup(entry.as_ref())) { - Ok(result) => !result.is_empty(), - Err(_) => false, + Ok(result) => i64::from(!result.is_empty()), + Err(Error::DnsRecordNotFound(_)) => 0, + Err(_) => -1, } } else { - false + -1 } .into() } diff --git a/crates/smtp/src/scripts/plugins/lookup.rs b/crates/smtp/src/scripts/plugins/lookup.rs index af8eb23d..3a677349 100644 --- a/crates/smtp/src/scripts/plugins/lookup.rs +++ b/crates/smtp/src/scripts/plugins/lookup.rs @@ -31,6 +31,10 @@ pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("lookup", plugin_id, 2); } +pub fn register_map(plugin_id: u32, fnc_map: &mut FunctionMap) { + fnc_map.set_external_function("lookup_map", plugin_id, 2); +} + pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { let lookup_id = ctx.arguments[0].to_cow(); let item = ctx.arguments[1].to_cow(); @@ -56,3 +60,28 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { false.into() } + +pub fn exec_map(ctx: PluginContext<'_>) -> Variable<'static> { + let lookup_id = ctx.arguments[0].to_cow(); + let item = ctx.arguments[1].to_cow(); + let span = ctx.span; + + if !lookup_id.is_empty() && !item.is_empty() { + if let Some(lookup) = ctx.core.sieve.lookup.get(lookup_id.as_ref()) { + return ctx + .handle + .block_on(lookup.lookup(item.as_ref())) + .unwrap_or_default(); + } else { + tracing::warn!( + parent: span, + context = "sieve:lookup", + event = "failed", + reason = "Unknown lookup id", + lookup_id = %lookup_id, + ); + } + } + + Variable::default() +} diff --git a/crates/smtp/src/scripts/plugins/mod.rs b/crates/smtp/src/scripts/plugins/mod.rs index 0ccb2171..e5b99452 100644 --- a/crates/smtp/src/scripts/plugins/mod.rs +++ b/crates/smtp/src/scripts/plugins/mod.rs @@ -46,18 +46,20 @@ pub struct PluginContext<'x> { pub arguments: Vec>, } -const PLUGINS_EXEC: [ExecPluginFnc; 6] = [ +const PLUGINS_EXEC: [ExecPluginFnc; 7] = [ query::exec, exec::exec, lookup::exec, + lookup::exec_map, dns::exec, dns::exec_exists, http::exec_header, ]; -const PLUGINS_REGISTER: [RegisterPluginFnc; 6] = [ +const PLUGINS_REGISTER: [RegisterPluginFnc; 7] = [ query::register, exec::register, lookup::register, + lookup::register_map, dns::register, dns::register_exists, http::register_header, diff --git a/crates/smtp/src/scripts/plugins/query.rs b/crates/smtp/src/scripts/plugins/query.rs index 19dafaea..d5d3447a 100644 --- a/crates/smtp/src/scripts/plugins/query.rs +++ b/crates/smtp/src/scripts/plugins/query.rs @@ -21,11 +21,8 @@ * for more details. */ -use directory::QueryColumn; -use sieve::{runtime::Variable, FunctionMap}; -use smtp_proto::IntoString; - use crate::config::scripts::SieveContext; +use sieve::{runtime::Variable, FunctionMap}; use super::PluginContext; @@ -73,23 +70,15 @@ pub fn exec(ctx: PluginContext<'_>) -> Variable<'static> { .get(..6) .map_or(false, |q| q.eq_ignore_ascii_case(b"SELECT")) { - if let Ok(query_columns) = ctx.handle.block_on(directory.query( + if let Ok(mut query_columns) = ctx.handle.block_on(directory.query( &query, ¶meters.iter().map(String::as_str).collect::>(), )) { - let mut result = Vec::with_capacity(query_columns.len()); - for column in query_columns { - result.push(match column { - QueryColumn::Integer(v) => Variable::Integer(v), - QueryColumn::Bool(v) => Variable::Integer(i64::from(v)), - QueryColumn::Float(v) => Variable::Float(v), - QueryColumn::Text(v) => Variable::String(v), - QueryColumn::Blob(v) => Variable::String(v.into_string()), - QueryColumn::Null => Variable::StringRef(""), - }); + match query_columns.len() { + 1 => query_columns.pop().map(Variable::from).unwrap(), + 0 => Variable::default(), + _ => Variable::Array(query_columns.into_iter().map(Variable::from).collect()), } - - result.into() } else { false.into() } diff --git a/resources/config/lists/disposable.txt.gz b/resources/config/lists/disposable.txt.gz deleted file mode 100644 index 17db272ecf55bfaa8a2adbed5025916c89339304..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2462 zcmV;P31RjhiwFpLW)fWh17vA)aBp*AVr*qDba-?CZCKfoq__@!&wojxwwdn!9D@b8 zFgD`_lKlF6WE)IY-{`1l9U*Vpm2bb_|7l#jy>(i36U{>DrnB?yZ8Nel6f$AmaC?9I z=j+$!XTu`*K=YF1-rwdXtzt@<80r=W!&i={?r*@SoPbaI__@R&;Y3(7BIZY14Al*%^7ZjFM}Mvq2y@zx4Q#*cR4 zD+0LP4%W>gU#b-kI}1(Ch#Nd^Em4mPWreiLOs5`NW$$w&uk-y{+on&-^cJ8GBW+pE zVTW^(=i4NCzGUx+k|o+W%w%kSbSHciTIQE_WCFQ!gO0)?ywCI~=6TO2cB55Yvfg|7 zW-b2UKTxKFI`G!cTHW-nF+IrWoT8tQz0EqITsB1)SLhpxl)^|=pDxAjD7&TXs`p3| zGO0cTZ=Z!)P1L%I^~1l-WS@3B;FvTb6|63_hbM$6+@8O}sak8O0N=sS>P{ETw}jzs z0-G`y^w^&ndC_XzQ93Fb%v=)J9l;1JSMaKGSV4bMlOIK*X5nF#&p!!RdAO^zv!mIX zAns<(-wd70!FMziK@&U~In&{*GR_OM{2fj9HM*adDkS>}r9jJE85^PCGEZVhrD?R< z21&G#jlP$m8J zNEO2GhxhbKKkI7kznVc4J+qQ}8`v%Ql-`M@%0)Sli){hM?DyHGVC$pXJ16>-2nXSi z^|k8rZUcpnv_DeE5Hlr&v|&7~J!UEA8ZB;vcPnrc zr`r(8DXpttGox}^_58v4TLv0=Wu!Fo%3|BRa@a>-IPF?b3+|_4-e^H#S$zql1q$&fZk=*?jfn3?1S> zTmJ(2SdKZ9K`%BZPi_O| zCE^M*-6l2JhYmToCv{1fC*U)tnvbSw{!Bf^q)`%bO3DlLvsA3f`s~z;zD?0RiL)DP zZHnG?NkofoI(F2;jy7|p0L6}iAm~Oe?R>hXfWkJD2&eV*=;fLz_;tdQu30J`P{|!WdS@_xr5Scp-@gQ62S>yA2VAKPGHC@ zQ4z}Z2|TjcQqf4)Z)zDYy7V^IZ~3(21y4>0BXFV5{FX_W@iJk`|5Z_vuH zhxu-d;mHm=qdPex0%g;~zAY*@bjV+!@>a)`TtHu1S`@HR+#RaC4|Y%AZEHCrq`m;f zx->to1oV8Q;}gCh_x`7e5w+nqhmW_nKlzZ=jYXMz$|q{pWMb62LIMYsSNwWMqB z%ZMsu=gWO}cIX$(iafEQ)YKe>&Cs$vhRJ~*AjAY4$$i7?9*kiV?t>Rua);QaB$ zS~3c`LL$bIIgGuhaNj)0+)`=oIRLCnshckQ=fsXg9l8R(n&=C8<}oz8Kl~mXn99K` zj;0p9a#mmd9$k;pax_e~{id$E1dQ6YId`GTs7?v(tVYj)x4@!Yd8#a4D)tNHh~-Hp zooel(QUb6%(x4G#C&RpxEr_BEbT$@8_OhE^R8FW1at$v>JJ*TJq{o(rUEYyY*~gME zE|^=iU9I$CMYA4Lx3DP36;Tq#B63-7)|5PEv04Q+k?SNFFF<h^Sb40MnDZsHHYQB(&Zy)^e1JFpGdPS3b@txy|A6LM{l$^l%sOFG=~k7ZZxVXSRPc`TAb;r58Ss z^J*|LX@+6gYpas+QSZE%G36PVRKlG%KNFVBPfTqsx_f_5(^e45U>wU4?T0oX&p2wW zGZ-Xo47PzHZ|to1$)-M^X|BS!P+l~uzWOFmD1`|$N?Uz{YO561PwBoMV#s&8CsEXa z2K(Yk-uCJO21@2D$fCNbxu6t$d@2Y8NIo;s!zsYDq?6>=Boh0&i_n9E_#k<}I%rO# zOd~=}DFzpKoXA5zM@6RHg4R2ffXS?_wfj$47bkeEm!e9Rsp6cbDV{VE+4U1(d1gV! zb_2W~73mZ{@NxQq4|#T*1zEntM`BrBf$Hn^^l|2FF^ZpTdqh(za%f&JsxG$VKHe{c z=HlIRXX6ov&u7tzpL}q+oPi(;8|Jj?5)m`tT|~c$F<*cgsa0{!_X1dkrW7cFI@GIg zkqj9KsS!@hZ{^<(Ff$`u%O_16Ca81-7pXdyrjk_$*4GMF5nr9rkxclBSDa<@%|fMv zT`FMDOD*3qKm}CL35o0D?LAMoDV2GyFFR+Y%SA?KLpaGzpa|sY_wtiKKus!>X;5&| zMIf}7=-!**zuV7}h0-j`k26`7T&lAkkP|Pc2pqkNSnF0!8k&iiP-BFelQCvJ%>krATpod^Pfl$tI?s_Evl0Vm`|D z-H2l`IrK(t`bjLEveAIr*^A@}#1}4{y2%Tp3j9CFO1@%N^EQ6_{SOaN9PtFr4cixXqGZ_E1O(*#ZwfG$)}4Dt@}o zW7~L4+0jQJxOQ60u)Ev(bH+g&Gi(6byk{P-KCw;tV7XH3D&xZVN8fd clYdq|P5)HOa;eNuThqb+02Y|3;};hI02o5e%m4rY diff --git a/resources/config/lists/dmarc_whitelist_new.inc b/resources/config/lists/dmarc_whitelist_new.inc deleted file mode 100644 index 5dddf218..00000000 --- a/resources/config/lists/dmarc_whitelist_new.inc +++ /dev/null @@ -1,792 +0,0 @@ - -# Domains with valid DMARC and DKIM -# These domains come from some top visited sites that have restrictive dmarc policy -# This file is intended for Rspamd version 1.8.1+ -# Keep sorted when editing to simplify tracks - -18f.gov -1password.com -2gis.com -4chan.org -4pda.ru -9-11commission.gov -911.gov -aberdeenshire.gov.uk -abilityone.gov -absolutbank.ru -access-board.gov -acquisition.gov -acus.gov -ada.gov -adf.gov -adidas.co.kr -adidas.com.au -adidas.com.br -adidas.com.hk -adidas.fi -adidas.fr -admongo.gov -adobe.dk -adobe.es -adobeawards.com -adp.com both:1.0 -advice.hmrc.gov.uk -aerocivil.gov.co -aerocivil.gov.co -afreximbank.com -agingstats.gov -agro.ru -ahcpr.gov -aids.gov -airbnb.co.uk -airbnb.com -airbnb.com.tr -airbnb.cz -airbnb.de -airbnb.fi -airbnb.fr -airbnb.pl -airbnb.ru -airbnb.se -airnow.gov -airtel.in -alfabank.com -alfabank.ru both:1.0 -alfastrah.ru -alibaba.com -aliexpress.com bl:1.0 -alipay.com -alkupone.ru -alzheimers.gov -amazon.co.uk both:1.0 -amazon.com both:1.0 -amazon.com.br both:1.0 -amberalert.gov -americanexpress.com both:1.0 -ameslab.gov -angus.gov.uk -anidub.com -annapolis.gov -anstaskforce.gov -apple.com.au -apple.com.cn -apps.gov -archives.gov -arctic.gov -arionbanki.is -asg.com -asic.gov.au -askona.ru -asos.com -assist.ru -atf.gov -avast.com -avg.com -avito.ru -avtoradio.ru -axisbank.com -badoo.com bl:1.0 -baltbank.ru -bank.lv -banki.ru -bankofamerica.com -barclaycard.co.uk both:1.0 -barclays.co.uk both:1.0 -barclays.com both:1.0 -battle.net -beeline.kz -beeline.ru -benefits.gov -betfaq.ru -biglion.ru -binary.com -binbank.ru -bioethics.gov -biometrics.gov -biopreferred.gov -birminghampost.net -bishopsmove.com -bitbank.cc -bjs.gov -blizzard.com -blog.gov.uk -bls.gov -bluestarindia.com -boemre.gov -bolsover.gov.uk -bolton.gov.uk -booking.com -bookmate.com -books.ru -bournemouth.gov.uk -box.com -bpa.gov -brandshop.ru -bridgend.gov.uk -brighton-hove.gov.uk -britishembassy.gov.uk -broadbandmap.gov -bromley.gov.uk -bts.gov -business.gov -caerphilly.gov.uk -caixa.gov.br both:1.0 -cambridge-news.co.uk -campaign.gov.uk -cancer.gov -cannockchasedc.gov.uk -capitalone.co.uk -cardiff.gov.uk -carecredit.com -cbp.gov -cdfifund.gov -centralbedfordshire.gov.uk -ceredigion.gov.uk -cesg.gov.uk -cfda.gov -cfo.gov -challenge.gov -change.org -chase.com -chcoc.gov -childstats.gov -cio.gov -circle.com -citibank.ae -citibank.co.in -citibank.co.uk -citibank.com.my -citibank.hu -citibank.pl -cloud.gov -cloudflare.com -cms.gov -co-operativebank.co.uk -colgate.com.br -collegedrinkingprevention.gov -companies-house.gov.uk -comuneap.gov.it -conab.gov.br both:1.0 -concerts.com -consultant.ru -contact-sys.com -copeland.gov.uk -cosla.gov.uk -courtservice.gov.uk -coventry.gov.uk -cre.gov.uk -csosa.gov -cuidadodesalud.gov -culturarecreacionydeporte.gov.co -customs.gov.my -customs.gov.ua -cybercrime.gov -dailypost.co.uk -danskebank.dk -danskebank.fi -danskebank.ie -danskebank.no -dartford.gov.uk -dartmoor.gov.uk -dataprotection.gov.uk -daventrydc.gov.uk -dellin.ru -denbighshire.gov.uk -deutsche-bank.de -deutschebank.be -deutschebank.co.in -dh.gov.uk -dhl.com -dhs.gov -diablo3.com -digital.gov -digitalgov.gov -digitalliteracy.gov -disability.gov -disability.gov.uk -dnfsb.gov -docker.com -docusign.net -doe.gov -doioig.gov -dol.gov -doleta.gov -domofond.ru -drought.gov -drugabuse.gov -dsns.gov.ua -dtv.gov -dudley.gov.uk -dyslexiaida.org -e-boks.dk -e-verify.gov -eastdunbarton.gov.uk -eaststaffsbc.gov.uk -eastsuffolk.gov.uk -ebay.be -ebay.ca -ebay.ch -ebay.co.uk both:1.0 -ebay.com -ebay.com.au -ebay.com.cn -ebay.de -ebay.es -ebay.eu -ebay.fr -ebay.in -ebay.it -ebay.se -economy.gov.tr -econsumer.gov -ed.gov -eftps.gov -ehsni.gov.uk -eia.gov -ejob.gov.tw -elance.com -eldorado.ru -email-ee.co.uk -email.tektorg.ru -emarsys.com -ems.gov -energystar.gov -erewash.gov.uk -esetnod32.ru -essex-fire.gov.uk -eubank.kz -evernote.com -everychildmatters.gov.uk -evus.gov -exist.ru -expediamail.com -facebook.com -facebookmail.com -fbi.gov -fcc.gov both:1.0 -fco.gov.uk -fdic.gov -feb.gov -federalreserve.gov -fedex.com -fedramp.gov -fedshirevets.gov -feedthefuture.gov -fema.gov -ferc.gov -fhfa.gov -fhfaoig.gov -fife.gov.uk -financialresearch.gov -financialstability.gov -firstbankpb.bank -firstbankpb.com -firstnet.gov -firstresponder.gov -fishwatch.gov -fitness.gov -flagma.ua -flamp.ru -fletc.gov -fmc.gov both:1.0 -fmcs.gov both:1.0 -foia.gov -food.gov.uk -force.com -fordlibrarymuseum.gov -foreignassistance.gov -foreigntrade.gov.tr -franklinwi.gov -ftc.gov -ftccomplaintassistant.gov -gamereactor.dk -gap.com -garant.ru -geekbrains.ru -geektimes.ru -getsmartaboutdrugs.gov -gibraltar.gov.uk -gitlab.com -globalentry.gov -globalhealth.gov -globe.gov -gloucestershire.gov.uk -goes-r.gov -gosuslugi.ru -gov.uk both:1.0 -groupon.es -groupon.hk -groupon.it -gsa.gov -gsaadvantage.gov -gsaauctions.gov -gsaig.gov -gtbank.com -guideline.gov -guidelines.gov -gwynedd.gov.uk -habr.com -hambleton.gov.uk -harp.gov -hawaiicounty.gov -hdfcbank.com -hdrezka.ag -healthcare.gov -healthypeople.gov -hertfordshire.gov.uk -hh.kz -hh.ru -highland.gov.uk -highwaycode.gov.uk -hillingdon.gov.uk -hiv.gov -hmrc.gov.uk -homeoffice.gov.uk -homesales.gov -hotels.com -hounslow.gov.uk -howto.gov -hru.gov -huduser.gov -hurricanes.gov -iba.gov.au -ice.gov -idmanagement.gov -ikea.ch -ikea.co.uk -ikea.com -ikea.de -ikea.fr -ikea.gr -ikea.nl -ikea.pl -imgur.com -incometaxindiaefiling.gov.in -ing.com -inl.gov -inlandrevenue.gov.uk -insider.co.uk -insolvency.gov.uk -instagram.com -insurekidsnow.gov -invasivespeciesinfo.gov -investor.gov -irda.gov.in -itunes.com -jccbi.gov -jd.ru -jet.com -jimmycarterlibrary.gov -job.com -johnsonsbaby.co.uk -joybuy.com -jpmorgan.com -jpmorgansecurities.com -judiciary.gov.uk -justice.gov -justice.gov.az -justice.gov.uk -jyskebank.dk -kassy.ru -kent.gov.uk -keys.openpgp.org -kids.gov -kingston.gov.uk -kivra.com -klarna.com -klarna.se -kpk.gov.pl -lacoast.gov -landsbanki.is -lanl.gov -lbhf.gov.uk -lcd.gov.uk -learningcurve.gov.uk -leeds.gov.uk -leroymerlin.es -lichfielddc.gov.uk -lincoln.gov.uk -lincolnshire.gov.uk -linkedin.com -livejournal.com -llnl.gov -lloydsbank.com -locatorplus.gov -lostfilm.tv -louisvilleco.gov -love.ru -lufthansa-group.com -lufthansa.com -mackeeper.com -mailgun.net bl:1.0 -mak.com -mandtbank.com -mcc.gov -mcga.gov.uk -mchenrycountyil.gov -mecknc.gov -mediamarkt.se -medicaid.gov -medicare.gov -medium.com -megafon.ru -megaplan.ru -mercadolibre.com.ar -mercadolivre.com.br -merseyfire.gov.uk -merthyr.gov.uk -meshok.ru -messenger.com -microsoft.net -middlesbrough.gov.uk -midlothian.gov.uk -mil.ru -mincit.gov.co -minhacienda.gov.co -minsvyaz.ru -mintic.gov.co -mirrorfootball.co.uk -mkb.ru -mlg.ru -mlg.tv -mns.gov.ua -mod.gov.az -molisa.gov.vn -mos.ru -mosoblbank.ru -mosreg.ru -motinfo.gov.uk -movavi.com -msha.gov -mspb.gov -msport.gov.pl -murfreesborotn.gov -mvideo.ru -mxtoolbox.com -mymoney.gov -myplate.gov -myra.gov -myshared.ru -n-kesteven.gov.uk -n-somerset.gov.uk -nads.gov.ua -nalog.ru -namus.gov -nasa.gov -nationalarchives.gov.uk -nationalservice.gov -nationsreportcard.gov -nbr.gov.bd -nbtbank.com -ncifcrf.gov -ncpw.gov -nctb.gov.bd -ne-derbyshire.gov.uk -nea.gov -nelincs.gov.uk -neobux.com -neolane.net -netflix.com -newegg.com -newmoney.gov -nga.gov -ngu.gov.ua -nhtsa.gov -nic.ru -nidw.gov.bd -nij.gov -nio.gov.uk -niscc.gov.uk -nist.gov -nixonlibrary.gov -nkh.gov.hu -noaa.gov -nordea.dk -nordea.com -nordea.fi -nordea.no -nordea.se -north-ayrshire.gov.uk -north-norfolk.gov.uk -northlincs.gov.uk -norwich.gov.uk -notifications.service.gov.uk -nottinghamcity.gov.uk -nrc-gateway.gov -nrc.gov -nrel.gov -nsf.gov -nsopr.gov -nsopw.gov -nwtrb.gov -oculus.com -ofcm.gov -office.com -officemag.ru -ok.ru -omb.gov -ombudsman.gov.tr -onedrive.com -onguardonline.gov -opengl.org -openinternet.gov -ordsvy.gov.uk -ornl.gov -oshrc.gov -osti.gov -oxfordshire.gov.uk -ozon.ru -paauditor.gov -paccar.com -paddle8.com -pandemicflu.gov -passport.gov.uk -payeer.com -paymentaccuracy.gov -paypal-community.com -paypal.be both:1.0 -paypal.cn both:1.0 -paypal.co.il both:1.0 -paypal.co.uk both:1.0 -paypal.com both:1.0 -paypal.com.au both:1.0 -paypal.com.br both:1.0 -paypal.com.mx both:1.0 -paypal.de both:1.0 -paypal.dk both:1.0 -paypal.es both:1.0 -paypal.fr both:1.0 -paypal.nl both:1.0 -paypal.se both:1.0 -pbgc.gov -pc.gov.au -pch.com -penanghill.gov.my -pepfar.gov -performance.gov -pinterest.co.kr -pinterest.com -pinterest.de -pinterest.jp -pinterest.se -pkc.gov.uk -planeta.ru -platron.ru -plymouth.gov.uk -pm.gov.uk -pmf.gov -pmi.gov -pncbank.com -pokerstars.com -pokerstars.fr -pokerstars.it -pokerstars.net -priorbank.by -privatbank.ua -prospertx.gov -prostocash.com -provident.bank -psbank.ru -psc.gov -punjab.gov.in -puzzle-english.com -qiwi.com -qiwi.ru -rabota.ru -rbkc.gov.uk -ready.gov -reaganlibrary.gov -redbridge.gov.uk -reddit.com -reebok.es -reebok.nl -reginfo.gov -regulations.gov -reisebank.de -renfrewshire.gov.uk -rentonwa.gov -reportband.gov -rgs.ru -richmond.gov.uk -rivers.gov -rkn.gov.ru -ros.gov.uk -roseltorg.ru -rostelecom.ru -roundrocktexas.gov -royalmail.com -rozetka.com.ua -rt.com -rt.ru -rushcliffe.gov.uk -rutubeinfo.ru -sacn.gov.uk -safercar.gov -samhsa.gov -sanmarcostx.gov -sberbank.ru -sberbank-ast.ru -sbir.gov -sbis.ru -scality.com -scdhhs.gov -science360.gov -sciencebase.gov -scijinks.gov -sec.gov -secretservice.gov -section508.gov -semnan.ac.ir -senate.gov -sendgrid.net bl:1 -seniorcorps.gov -serpro.gov.br both:1.0 -service.gov.uk -sftool.gov -shetland.gov.uk -shropshire-cc.gov.uk -shutterstock.com -sigtarp.gov -sk.ru -skat.dk -skatteverket.se -skbbank.ru -skittles.com -skydio.com -skype.com -slideshare.com -smart.gov -smida.gov.ua -smokefree.gov -snickers.com -solardecathlon.gov -sourceforge.net -south-ayrshire.gov.uk -sovest.ru -spbrealty.ru -sportmaster.ru -squarespace.com -sravni.ru -srs.gov -staffordbc.gov.uk -stat.gov.az -stedmundsbury.gov.uk -sthelens.gov.uk -stihl.ru -stopalcoholabuse.gov -stopfraud.gov -studentloans.gov -subscribe.ru -suffolkcc.gov.uk -suncorpbank.com.au -sundaymirror.co.uk -sunlight.net -superjob.ru -surestart.gov.uk -sutton.gov.uk -swansea.gov.uk -swift.com -symantec.com -synologynotification.com -taobao.com -tatar.ru -tauntondeane.gov.uk -tda.gov.uk -tdk.gov.tr -tdscpc.gov.in -telework.gov -tenders.gov.au -textmagic.com -tfhrc.gov -thebell.io -thecoolspot.gov -thinkroadsafety.gov.uk -tiaabank.com -ticketland.ru -tinder.com -tinkoff.ru -tomsk.gov.ru -torbay.gov.uk -tradingstandards.gov.uk -treas.gov -trial-sport.ru -tsa.gov -tst.gov.br both:1.0 -tuba.gov.tr -turystyka.gov.pl -tutu.ru -twitch.tv -twitter.com -twix.com -uber.com -ucarecdn.com -ucrdatatool.gov -udall.gov -ukvisas.gov.uk -ulmart.ru -unicor.gov -uniras.gov.uk -ups.com -uralairlines.ru -us-cert.gov -usa.gov -usadf.gov -usaid.gov -usap.gov -uscg.gov -usconsulate.gov -usmission.gov -usphs.gov -uspis.gov -usps.com -usps.gov -ustreas.gov -utair.ru -utkonos.ru -vaccines.gov -valeofglamorgan.gov.uk -verizonwireless.com -vigoda.ru -visa.co.uk -visa.com -visa.com.ar -visa.com.br both:1.0 -visa.com.cn -visa.com.tw -visa.pl -vistacampus.gov -vk.com bl:1.0 -vkrugudruzei.ru -vkusnyblog.ru -vmc.gov.in -voa.gov.uk -volunteer.gov -vote.gov -walsall.gov.uk -wandsworth.gov.uk -wartimecontracting.gov -warwickdc.gov.uk -wealden.gov.uk -wellingtonfl.gov -west-lindsey.gov.uk -westernunion.com -westernunion.ru -westlothian.gov.uk -whatsapp.com -whistleblowers.gov -wirral.gov.uk -wlga.gov.uk -womenshealth.gov -wrexham.gov.uk -wrigley.com -wrp.gov -yandex-team.ru -york.gov.uk -youla.ru -youth.gov -youthrules.gov -youtube.com -zcts.ru -zendesk.com -zionsbank.com -zomato.com diff --git a/resources/config/lists/free.txt.gz b/resources/config/lists/free.txt.gz deleted file mode 100644 index 79b9ffef27ff6cd41555173a895bdc132728bd42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18731 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 au-AnXlXc#q7Ws=0HT-`?$c&57+W-K^MP=mx diff --git a/resources/config/lists/mid.inc b/resources/config/lists/mid.inc deleted file mode 100644 index d3ec5ce8..00000000 --- a/resources/config/lists/mid.inc +++ /dev/null @@ -1,42 +0,0 @@ -# Known invalid or missed Message-IDs -# 'domain' 'Message-ID regexp' - -1c-bitrix.ru -alibaba.com /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}-0$/ -aliexpress.com /^(?:(?:[0-9]{14,16}|[a-z]{4}(?:COWS|UTT)_[0-9]{3,6}_(?:(?:[0-9]{7,8}|0)_)?)[$])?[a-f0-9]{32}$/ -beeline.ru /^<[A-z0-9+]{18}>$/ -cdek.ru -citilink.ru -noreply.esphere.ru -etpgpb.ru -noreply.etprf.ru -freshports.org -rkn.gov.ru -garant.ru -is-zakupki.com -kassir.ru -mail-us.jira.com /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/ -mirtesen.ru -fcod.nalog.ru -www.nalog.ru -otc.ru -news.ozon.ru /^[A-z0-9+/]{32}$/ -sender.ozon.ru /^[A-z0-9+/]{32}$/ -professionali.ru -psbank.ru -qiwi.ru -robokassa.ru -client.rostelecom.ru -sberbank-ast.ru -taxcom.ru -crm.taxcom.ru -wildberries.ru /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/ -promo.wildberries.ru /^[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}$/ -taxi.yandex.ru /^[a-f0-9]{32}$/ -mail.youdo.com -online.zcts.ru -mail.authorize.net -craigslist.org -maersk.com -monster.com -teamviewer.com diff --git a/resources/config/lists/mime_types.inc b/resources/config/lists/mime_types.inc deleted file mode 100644 index 9821faa7..00000000 --- a/resources/config/lists/mime_types.inc +++ /dev/null @@ -1,1546 +0,0 @@ -# perl -ne 'if(/([^\d.\s\[o][^\d.\[\]\s\/A-Z]{3,}\/[^\s\/]+)/) { print "$1 0\n"; }' -application/1d-interleaved-parityfec 0 -application/3gpdash-qoe-report+xml 0 -application/3gpp-ims+xml 0 -application/A2L 0 -application/AML 0 -application/ATF 0 -application/ATFX 0 -application/ATXML 0 -application/CALS-1840 0 -application/CDFX+XML 0 -application/CEA 0 -application/CSTAdata+xml 0 -application/DCD 0 -application/DII 0 -application/DIT 0 -application/EDI-X12 0 -application/EDI-consent 0 -application/EDIFACT 0 -application/EmergencyCallData.Comment+xml 0 -application/EmergencyCallData.DeviceInfo+xml 0 -application/EmergencyCallData.ProviderInfo+xml 0 -application/EmergencyCallData.ServiceInfo+xml 0 -application/EmergencyCallData.SubscriberInfo+xml 0 -application/H224 0 -application/IOTP 0 -application/ISUP 0 -application/LXF 0 -application/MF4 0 -application/ODA 0 -application/ODX 0 -application/PDX 0 -application/QSIG 0 -application/SGML 0 -application/activemessage 0 -application/alto-costmap+json 0 -application/alto-costmapfilter+json 0 -application/alto-directory+json 0 -application/alto-endpointcost+json 0 -application/alto-endpointcostparams+json 0 -application/alto-endpointprop+json 0 -application/alto-endpointpropparams+json 0 -application/alto-error+json 0 -application/alto-networkmap+json 0 -application/alto-networkmapfilter+json 0 -application/andrew-inset 0 -application/applefile 0 -application/atom+xml 0 -application/atomcat+xml 0 -application/atomdeleted+xml 0 -application/atomicmail 0 -application/atomsvc+xml 0 -application/auth-policy+xml 0 -application/bacnet-xdd+zip 0 -application/batch-SMTP 0 -application/beep+xml 0 -application/calendar+json 0 -application/calendar+xml 0 -application/call-completion 0 -application/cbor 0 -application/ccmp+xml 0 -application/ccxml+xml 0 -application/cdmi-capability 0 -application/cdmi-container 0 -application/cdmi-domain 0 -application/cdmi-object 0 -application/cdmi-queue 0 -application/cdni 0 -application/cea-2018+xml 0 -application/cellml+xml 0 -application/cfw 0 -application/cms 0 -application/cnrp+xml 0 -application/coap-group+json 0 -application/commonground 0 -application/conference-info+xml 0 -application/cpl+xml 0 -application/csrattrs 0 -application/csta+xml 0 -application/csvm+json 0 -application/cybercash 0 -application/dash+xml 0 -application/dashdelta 0 -application/davmount+xml 0 -application/dca-rft 0 -application/dec-dx 0 -application/dialog-info+xml 0 -application/dicom 0 -application/dns 0 -application/dskpp+xml 0 -application/dssc+der 0 -application/dssc+xml 0 -application/dvcs 0 -application/ecmascript 0 -application/emotionml+xml 0 -application/encaprtp 0 -application/epp+xml 0 -application/epub+zip 0 -application/eshop 0 -application/example 0 -application/fastinfoset 0 -application/fastsoap 0 -application/fdt+xml 0 -application/fits 0 -application/font-sfnt 0 -application/font-tdpfr 0 -application/font-woff 0 -application/framework-attributes+xml 0 -application/gzip 0 -application/held+xml 0 -application/http 0 -application/hyperstudio 0 -application/ibe-key-request+xml 0 -application/ibe-pkg-reply+xml 0 -application/ibe-pp-data 0 -application/ics 0 -application/iges 0 -application/im-iscomposing+xml 0 -application/index 0 -application/index-obj 0 -application/index.cmd 0 -application/index.response 0 -application/index.vnd 0 -application/inkml+xml 0 -application/ipfix 0 -application/ipp 0 -application/its+xml 0 -application/javascript 0 -application/jose 0 -application/jose+json 0 -application/jrd+json 0 -application/json 0 -application/json-patch+json 0 -application/json-seq 0 -application/jwk+json 0 -application/jwk-set+json 0 -application/jwt 0 -application/kpml-request+xml 0 -application/kpml-response+xml 0 -application/ld+json 0 -application/link-format 0 -application/load-control+xml 0 -application/lost+xml 0 -application/lostsync+xml 0 -application/mac-binhex40 0 -application/macwriteii 0 -application/mads+xml 0 -application/marc 0 -application/marcxml+xml 0 -application/mathematica 0 -application/mbms-associated-procedure-description+xml 0 -application/mbms-deregister+xml 0 -application/mbms-envelope+xml 0 -application/mbms-msk+xml 0 -application/mbms-msk-response+xml 0 -application/mbms-protection-description+xml 0 -application/mbms-reception-report+xml 0 -application/mbms-register+xml 0 -application/mbms-register-response+xml 0 -application/mbms-schedule+xml 0 -application/mbms-user-service-description+xml 0 -application/mbox 0 -application/media-policy-dataset+xml 0 -application/media_control+xml 0 -application/mediaservercontrol+xml 0 -application/merge-patch+json 0 -application/metalink4+xml 0 -application/mets+xml 0 -application/mikey 0 -application/mods+xml 0 -application/moss-keys 0 -application/moss-signature 0 -application/mosskey-data 0 -application/mosskey-request 0 -application/mp21 0 -application/mp4 0 -application/mpeg4-generic 0 -application/mpeg4-iod 0 -application/mpeg4-iod-xmt 0 -application/mrb-consumer+xml 0 -application/mrb-publish+xml 0 -application/msc-ivr+xml 0 -application/msc-mixer+xml 0 -application/msword 0 -application/mxf 0 -application/nasdata 0 -application/news-checkgroups 0 -application/news-groupinfo 0 -application/news-transmission 0 -application/nlsml+xml 0 -application/nss 0 -application/ocsp-request 0 -application/ocsp-response 0 -application/octet-stream 0 -application/oebps-package+xml 0 -application/ogg 0 -application/oxps 0 -application/p2p-overlay+xml 0 -application/patch-ops-error+xml 0 -application/pdf 0 -application/pgp-encrypted 0 -application/pgp-signature 0 -application/pidf+xml 0 -application/pidf-diff+xml 0 -application/pkcs10 0 -application/pkcs12 0 -application/pkcs7-mime 0 -application/pkcs7-signature 0 -application/pkcs8 0 -application/pkix-attr-cert 0 -application/pkix-cert 0 -application/pkix-crl 0 -application/pkix-pkipath 0 -application/pkixcmp 0 -application/pls+xml 0 -application/poc-settings+xml 0 -application/postscript 0 -application/ppsp-tracker+json 0 -application/provenance+xml 0 -application/prs.alvestrand.titrax-sheet 0 -application/prs.cww 0 -application/prs.hpub+zip 0 -application/prs.nprend 0 -application/prs.plucker 0 -application/prs.rdf-xml-crypt 0 -application/prs.xsf+xml 0 -application/pskc+xml 0 -application/rar 0 -application/raptorfec 0 -application/rdap+json 0 -application/rdf+xml 0 -application/reginfo+xml 0 -application/relax-ng-compact-syntax 0 -application/remote-printing 0 -application/reputon+json 0 -application/resource-lists+xml 0 -application/resource-lists-diff+xml 0 -application/rfc+xml 0 -application/riscos 0 -application/rlmi+xml 0 -application/rls-services+xml 0 -application/rpki-ghostbusters 0 -application/rpki-manifest 0 -application/rpki-roa 0 -application/rpki-updown 0 -application/rtf 0 -application/rtploopback 0 -application/rtx 0 -application/samlassertion+xml 0 -application/samlmetadata+xml 0 -application/sbml+xml 0 -application/scaip+xml 0 -application/scim+json 0 -application/scvp-cv-request 0 -application/scvp-cv-response 0 -application/scvp-vp-request 0 -application/scvp-vp-response 0 -application/sdp 0 -application/sep+xml 0 -application/sep-exi 0 -application/session-info 0 -application/set-payment 0 -application/set-payment-initiation 0 -application/set-registration 0 -application/set-registration-initiation 0 -application/sgml-open-catalog 0 -application/shf+xml 0 -application/sieve 0 -application/simple-filter+xml 0 -application/simple-message-summary 0 -application/simpleSymbolContainer 0 -application/slate 0 -application/smil+xml 0 -application/smpte336m 0 -application/soap+fastinfoset 0 -application/soap+xml 0 -application/spirits-event+xml 0 -application/sql 0 -application/srgs 0 -application/srgs+xml 0 -application/sru+xml 0 -application/ssml+xml 0 -application/tamp-apex-update 0 -application/tamp-apex-update-confirm 0 -application/tamp-community-update 0 -application/tamp-community-update-confirm 0 -application/tamp-error 0 -application/tamp-sequence-adjust 0 -application/tamp-sequence-adjust-confirm 0 -application/tamp-status-query 0 -application/tamp-status-response 0 -application/tamp-update 0 -application/tamp-update-confirm 0 -application/tei+xml 0 -application/thraud+xml 0 -application/timestamp-query 0 -application/timestamp-reply 0 -application/timestamped-data 0 -application/ttml+xml 0 -application/tve-trigger 0 -application/ulpfec 0 -application/urc-grpsheet+xml 0 -application/urc-ressheet+xml 0 -application/urc-targetdesc+xml 0 -application/urc-uisocketdesc+xml 0 -application/vcard+json 0 -application/vcard+xml 0 -application/vemmi 0 -application/vnd-acucobol 0 -application/vnd-curl 0 -application/vnd-dart 0 -application/vnd-dxr 0 -application/vnd-fdf 0 -application/vnd-mif 0 -application/vnd-sema 0 -application/vnd-wap-wmlc 0 -application/vnd.3M.Post-it-Notes 0 -application/vnd.3gpp-prose+xml 0 -application/vnd.3gpp-prose-pc3ch+xml 0 -application/vnd.3gpp.SRVCC-info+xml 0 -application/vnd.3gpp.access-transfer-events+xml 0 -application/vnd.3gpp.bsf+xml 0 -application/vnd.3gpp.mid-call+xml 0 -application/vnd.3gpp.pic-bw-large 0 -application/vnd.3gpp.pic-bw-small 0 -application/vnd.3gpp.pic-bw-var 0 -application/vnd.3gpp.sms 0 -application/vnd.3gpp.srvcc-ext+xml 0 -application/vnd.3gpp.state-and-event-info+xml 0 -application/vnd.3gpp.ussd+xml 0 -application/vnd.3gpp2.bcmcsinfo+xml 0 -application/vnd.3gpp2.sms 0 -application/vnd.3gpp2.tcap 0 -application/vnd.FloGraphIt 0 -application/vnd.HandHeld-Entertainment+xml 0 -application/vnd.Kinar 0 -application/vnd.MFER 0 -application/vnd.Mobius.DAF 0 -application/vnd.Mobius.DIS 0 -application/vnd.Mobius.MBK 0 -application/vnd.Mobius.MQY 0 -application/vnd.Mobius.MSL 0 -application/vnd.Mobius.PLC 0 -application/vnd.Mobius.TXF 0 -application/vnd.Quark.QuarkXPress 0 -application/vnd.SimTech-MindMapper 0 -application/vnd.accpac.simply.aso 0 -application/vnd.accpac.simply.imp 0 -application/vnd.acucorp 0 -application/vnd.adobe.flash-movie 0 -application/vnd.adobe.formscentral.fcdt 0 -application/vnd.adobe.fxp 0 -application/vnd.adobe.partial-upload 0 -application/vnd.adobe.xdp+xml 0 -application/vnd.adobe.xfdf 0 -application/vnd.aether.imp 0 -application/vnd.ah-barcode 0 -application/vnd.ahead.space 0 -application/vnd.airzip.filesecure.azf 0 -application/vnd.airzip.filesecure.azs 0 -application/vnd.americandynamics.acc 0 -application/vnd.amiga.ami 0 -application/vnd.amundsen.maze+xml 0 -application/vnd.anki 0 -application/vnd.anser-web-certificate-issue-initiation 0 -application/vnd.antix.game-component 0 -application/vnd.apache.thrift.binary 0 -application/vnd.apache.thrift.compact 0 -application/vnd.apache.thrift.json 0 -application/vnd.api+json 0 -application/vnd.apple.installer+xml 0 -application/vnd.apple.mpegurl 0 -application/vnd.arastra.swi 0 -application/vnd.aristanetworks.swi 0 -application/vnd.artsquare 0 -application/vnd.astraea-software.iota 0 -application/vnd.audiograph 0 -application/vnd.autopackage 0 -application/vnd.avistar+xml 0 -application/vnd.balsamiq.bmml+xml 0 -application/vnd.balsamiq.bmpr 0 -application/vnd.bekitzur-stech+json 0 -application/vnd.biopax.rdf+xml 0 -application/vnd.blueice.multipass 0 -application/vnd.bluetooth.ep.oob 0 -application/vnd.bluetooth.le.oob 0 -application/vnd.bmi 0 -application/vnd.businessobjects 0 -application/vnd.cab-jscript 0 -application/vnd.canon-cpdl 0 -application/vnd.canon-lips 0 -application/vnd.cendio.thinlinc.clientconf 0 -application/vnd.century-systems.tcp_stream 0 -application/vnd.chemdraw+xml 0 -application/vnd.chipnuts.karaoke-mmd 0 -application/vnd.cinderella 0 -application/vnd.cirpack.isdn-ext 0 -application/vnd.citationstyles.style+xml 0 -application/vnd.claymore 0 -application/vnd.cloanto.rp9 0 -application/vnd.clonk.c4group 0 -application/vnd.cluetrust.cartomobile-config 0 -application/vnd.cluetrust.cartomobile-config-pkg 0 -application/vnd.coffeescript 0 -application/vnd.collection+json 0 -application/vnd.collection.doc+json 0 -application/vnd.collection.next+json 0 -application/vnd.commerce-battelle 0 -application/vnd.commonspace 0 -application/vnd.contact.cmsg 0 -application/vnd.cosmocaller 0 -application/vnd.crick.clicker 0 -application/vnd.crick.clicker.keyboard 0 -application/vnd.crick.clicker.palette 0 -application/vnd.crick.clicker.template 0 -application/vnd.crick.clicker.wordbank 0 -application/vnd.criticaltools.wbs+xml 0 -application/vnd.ctc-posml 0 -application/vnd.ctct.ws+xml 0 -application/vnd.cups-pdf 0 -application/vnd.cups-postscript 0 -application/vnd.cups-ppd 0 -application/vnd.cups-raster 0 -application/vnd.cups-raw 0 -application/vnd.cyan.dean.root+xml 0 -application/vnd.cybank 0 -application/vnd.data-vision.rdz 0 -application/vnd.debian.binary-package 0 -application/vnd.dece-zip 0 -application/vnd.dece.data 0 -application/vnd.dece.ttml+xml 0 -application/vnd.dece.unspecified 0 -application/vnd.denovo.fcselayout-link 0 -application/vnd.desmume-movie 0 -application/vnd.dir-bi.plate-dl-nosuffix 0 -application/vnd.dm.delegation+xml 0 -application/vnd.dna 0 -application/vnd.document+json 0 -application/vnd.dolby.mobile.1 0 -application/vnd.dolby.mobile.2 0 -application/vnd.doremir.scorecloud-binary-document 0 -application/vnd.dpgraph 0 -application/vnd.dreamfactory 0 -application/vnd.drive+json 0 -application/vnd.dtg.local 0 -application/vnd.dtg.local-html 0 -application/vnd.dtg.local.flash 0 -application/vnd.dvb.ait 0 -application/vnd.dvb.dvbj 0 -application/vnd.dvb.esgcontainer 0 -application/vnd.dvb.ipdcdftnotifaccess 0 -application/vnd.dvb.ipdcesgaccess 0 -application/vnd.dvb.ipdcesgaccess2 0 -application/vnd.dvb.ipdcesgpdd 0 -application/vnd.dvb.ipdcroaming 0 -application/vnd.dvb.iptv.alfec-base 0 -application/vnd.dvb.iptv.alfec-enhancement 0 -application/vnd.dvb.notif-aggregate-root+xml 0 -application/vnd.dvb.notif-container+xml 0 -application/vnd.dvb.notif-generic+xml 0 -application/vnd.dvb.notif-ia-msglist+xml 0 -application/vnd.dvb.notif-ia-registration-request+xml 0 -application/vnd.dvb.notif-ia-registration-response+xml 0 -application/vnd.dvb.notif-init+xml 0 -application/vnd.dvb.pfr 0 -application/vnd.dvb_service 0 -application/vnd.dynageo 0 -application/vnd.dzr 0 -application/vnd.easykaraoke.cdgdownload 0 -application/vnd.ecdis-update 0 -application/vnd.ecowin.chart 0 -application/vnd.ecowin.filerequest 0 -application/vnd.ecowin.fileupdate 0 -application/vnd.ecowin.series 0 -application/vnd.ecowin.seriesrequest 0 -application/vnd.ecowin.seriesupdate 0 -application/vnd.emclient.accessrequest+xml 0 -application/vnd.enliven 0 -application/vnd.enphase.envoy 0 -application/vnd.eprints.data+xml 0 -application/vnd.epson.esf 0 -application/vnd.epson.msf 0 -application/vnd.epson.quickanime 0 -application/vnd.epson.salt 0 -application/vnd.epson.ssf 0 -application/vnd.ericsson.quickcall 0 -application/vnd.eszigno3+xml 0 -application/vnd.etsi.aoc+xml 0 -application/vnd.etsi.asic-e+zip 0 -application/vnd.etsi.asic-s+zip 0 -application/vnd.etsi.cug+xml 0 -application/vnd.etsi.iptvcommand+xml 0 -application/vnd.etsi.iptvdiscovery+xml 0 -application/vnd.etsi.iptvprofile+xml 0 -application/vnd.etsi.iptvsad-bc+xml 0 -application/vnd.etsi.iptvsad-cod+xml 0 -application/vnd.etsi.iptvsad-npvr+xml 0 -application/vnd.etsi.iptvservice+xml 0 -application/vnd.etsi.iptvsync+xml 0 -application/vnd.etsi.iptvueprofile+xml 0 -application/vnd.etsi.mcid+xml 0 -application/vnd.etsi.mheg5 0 -application/vnd.etsi.overload-control-policy-dataset+xml 0 -application/vnd.etsi.pstn+xml 0 -application/vnd.etsi.sci+xml 0 -application/vnd.etsi.simservs+xml 0 -application/vnd.etsi.timestamp-token 0 -application/vnd.etsi.tsl+xml 0 -application/vnd.etsi.tsl.der 0 -application/vnd.eudora.data 0 -application/vnd.ezpix-album 0 -application/vnd.ezpix-package 0 -application/vnd.f-secure.mobile 0 -application/vnd.fastcopy-disk-image 0 -application/vnd.fdsn.mseed 0 -application/vnd.fdsn.seed 0 -application/vnd.ffsns 0 -application/vnd.filmit.zfc 0 -application/vnd.fints 0 -application/vnd.firemonkeys.cloudcell 0 -application/vnd.fluxtime.clip 0 -application/vnd.font-fontforge-sfd 0 -application/vnd.framemaker 0 -application/vnd.frogans.fnc 0 -application/vnd.frogans.ltf 0 -application/vnd.fsc.weblaunch 0 -application/vnd.fujitsu.oasys 0 -application/vnd.fujitsu.oasys2 0 -application/vnd.fujitsu.oasys3 0 -application/vnd.fujitsu.oasysgp 0 -application/vnd.fujitsu.oasysprs 0 -application/vnd.fujixerox.ART-EX 0 -application/vnd.fujixerox.ART4 0 -application/vnd.fujixerox.HBPL 0 -application/vnd.fujixerox.ddd 0 -application/vnd.fujixerox.docuworks 0 -application/vnd.fujixerox.docuworks.binder 0 -application/vnd.fujixerox.docuworks.container 0 -application/vnd.fut-misnet 0 -application/vnd.fuzzysheet 0 -application/vnd.genomatix.tuxedo 0 -application/vnd.geo+json 0 -application/vnd.geocube+xml 0 -application/vnd.geogebra.file 0 -application/vnd.geogebra.tool 0 -application/vnd.geometry-explorer 0 -application/vnd.geonext 0 -application/vnd.geoplan 0 -application/vnd.geospace 0 -application/vnd.gerber 0 -application/vnd.globalplatform.card-content-mgt 0 -application/vnd.globalplatform.card-content-mgt-response 0 -application/vnd.gmx 0 -application/vnd.google-earth.kml+xml 0 -application/vnd.google-earth.kmz 0 -application/vnd.gov.sk.e-form+xml 0 -application/vnd.gov.sk.e-form+zip 0 -application/vnd.gov.sk.xmldatacontainer+xml 0 -application/vnd.grafeq 0 -application/vnd.gridmp 0 -application/vnd.groove-account 0 -application/vnd.groove-help 0 -application/vnd.groove-identity-message 0 -application/vnd.groove-injector 0 -application/vnd.groove-tool-message 0 -application/vnd.groove-tool-template 0 -application/vnd.groove-vcard 0 -application/vnd.hal+json 0 -application/vnd.hal+xml 0 -application/vnd.hbci 0 -application/vnd.hcl-bireports 0 -application/vnd.hdt 0 -application/vnd.heroku+json 0 -application/vnd.hhe.lesson-player 0 -application/vnd.hp-HPGL 0 -application/vnd.hp-PCL 0 -application/vnd.hp-PCLXL 0 -application/vnd.hp-hpid 0 -application/vnd.hp-hps 0 -application/vnd.hp-jlyt 0 -application/vnd.httphone 0 -application/vnd.hydrostatix.sof-data 0 -application/vnd.hyperdrive+json 0 -application/vnd.hzn-3d-crossword 0 -application/vnd.ibm.MiniPay 0 -application/vnd.ibm.afplinedata 0 -application/vnd.ibm.electronic-media 0 -application/vnd.ibm.modcap 0 -application/vnd.ibm.rights-management 0 -application/vnd.ibm.secure-container 0 -application/vnd.iccprofile 0 -application/vnd.ieee.1905 0 -application/vnd.igloader 0 -application/vnd.immervision-ivp 0 -application/vnd.immervision-ivu 0 -application/vnd.ims.imsccv1p1 0 -application/vnd.ims.imsccv1p2 0 -application/vnd.ims.imsccv1p3 0 -application/vnd.ims.lis.v2.result+json 0 -application/vnd.ims.lti.v2.toolconsumerprofile+json 0 -application/vnd.ims.lti.v2.toolproxy+json 0 -application/vnd.ims.lti.v2.toolproxy.id+json 0 -application/vnd.ims.lti.v2.toolsettings+json 0 -application/vnd.ims.lti.v2.toolsettings.simple+json 0 -application/vnd.informedcontrol.rms+xml 0 -application/vnd.infotech.project 0 -application/vnd.infotech.project+xml 0 -application/vnd.innopath.wamp.notification 0 -application/vnd.insors.igm 0 -application/vnd.intercon.formnet 0 -application/vnd.intergeo 0 -application/vnd.intertrust.digibox 0 -application/vnd.intertrust.nncp 0 -application/vnd.intu.qbo 0 -application/vnd.intu.qfx 0 -application/vnd.iptc.g2.catalogitem+xml 0 -application/vnd.iptc.g2.conceptitem+xml 0 -application/vnd.iptc.g2.knowledgeitem+xml 0 -application/vnd.iptc.g2.newsitem+xml 0 -application/vnd.iptc.g2.newsmessage+xml 0 -application/vnd.iptc.g2.packageitem+xml 0 -application/vnd.iptc.g2.planningitem+xml 0 -application/vnd.ipunplugged.rcprofile 0 -application/vnd.irepository.package+xml 0 -application/vnd.is-xpr 0 -application/vnd.isac.fcs 0 -application/vnd.jam 0 -application/vnd.japannet-directory-service 0 -application/vnd.japannet-jpnstore-wakeup 0 -application/vnd.japannet-payment-wakeup 0 -application/vnd.japannet-registration 0 -application/vnd.japannet-registration-wakeup 0 -application/vnd.japannet-setstore-wakeup 0 -application/vnd.japannet-verification 0 -application/vnd.japannet-verification-wakeup 0 -application/vnd.jcp.javame.midlet-rms 0 -application/vnd.jisp 0 -application/vnd.joost.joda-archive 0 -application/vnd.jsk.isdn-ngn 0 -application/vnd.kahootz 0 -application/vnd.kde.karbon 0 -application/vnd.kde.kchart 0 -application/vnd.kde.kformula 0 -application/vnd.kde.kivio 0 -application/vnd.kde.kontour 0 -application/vnd.kde.kpresenter 0 -application/vnd.kde.kspread 0 -application/vnd.kde.kword 0 -application/vnd.kenameaapp 0 -application/vnd.kidspiration 0 -application/vnd.koan 0 -application/vnd.kodak-descriptor 0 -application/vnd.las.las+xml 0 -application/vnd.liberty-request+xml 0 -application/vnd.llamagraphics.life-balance.desktop 0 -application/vnd.llamagraphics.life-balance.exchange+xml 0 -application/vnd.lotus-1-2-3 0 -application/vnd.lotus-approach 0 -application/vnd.lotus-freelance 0 -application/vnd.lotus-notes 0 -application/vnd.lotus-organizer 0 -application/vnd.lotus-screencam 0 -application/vnd.lotus-wordpro 0 -application/vnd.macports.portpkg 0 -application/vnd.mapbox-vector-tile 0 -application/vnd.marlin.drm.actiontoken+xml 0 -application/vnd.marlin.drm.conftoken+xml 0 -application/vnd.marlin.drm.license+xml 0 -application/vnd.marlin.drm.mdcf 0 -application/vnd.mason+json 0 -application/vnd.maxmind.maxmind-db 0 -application/vnd.mcd 0 -application/vnd.medcalcdata 0 -application/vnd.mediastation.cdkey 0 -application/vnd.meridian-slingshot 0 -application/vnd.mfmp 0 -application/vnd.micro+json 0 -application/vnd.micrografx-igx 0 -application/vnd.micrografx.flo 0 -application/vnd.microsoft.portable-executable 0 -application/vnd.miele+json 0 -application/vnd.minisoft-hp3000-save 0 -application/vnd.mitsubishi.misty-guard.trustweb 0 -application/vnd.mophun.application 0 -application/vnd.mophun.certificate 0 -application/vnd.motorola.flexsuite 0 -application/vnd.motorola.flexsuite.adsi 0 -application/vnd.motorola.flexsuite.fis 0 -application/vnd.motorola.flexsuite.gotap 0 -application/vnd.motorola.flexsuite.kmr 0 -application/vnd.motorola.flexsuite.ttc 0 -application/vnd.motorola.flexsuite.wem 0 -application/vnd.motorola.iprm 0 -application/vnd.mozilla.xul+xml 0 -application/vnd.ms-3mfdocument 0 -application/vnd.ms-PrintDeviceCapabilities+xml 0 -application/vnd.ms-artgalry 0 -application/vnd.ms-asf 0 -application/vnd.ms-cab-compressed 0 -application/vnd.ms-excel 0 -application/vnd.ms-excel.addin.macroEnabled.12 0 -application/vnd.ms-excel.sheet.binary.macroEnabled.12 0 -application/vnd.ms-excel.sheet.macroEnabled.12 0 -application/vnd.ms-excel.template.macroEnabled.12 0 -application/vnd.ms-fontobject 0 -application/vnd.ms-htmlhelp 0 -application/vnd.ms-ims 0 -application/vnd.ms-lrm 0 -application/vnd.ms-office.activeX+xml 0 -application/vnd.ms-officetheme 0 -application/vnd.ms-playready.initiator+xml 0 -application/vnd.ms-powerpoint 0 -application/vnd.ms-powerpoint.addin.macroEnabled.12 0 -application/vnd.ms-powerpoint.presentation.macroEnabled.12 0 -application/vnd.ms-powerpoint.slide.macroEnabled.12 0 -application/vnd.ms-powerpoint.slideshow.macroEnabled.12 0 -application/vnd.ms-powerpoint.template.macroEnabled.12 0 -application/vnd.ms-project 0 -application/vnd.ms-tnef 0 -application/vnd.ms-windows.devicepairing 0 -application/vnd.ms-windows.nwprinting.oob 0 -application/vnd.ms-windows.printerpairing 0 -application/vnd.ms-windows.wsd.oob 0 -application/vnd.ms-wmdrm.lic-chlg-req 0 -application/vnd.ms-wmdrm.lic-resp 0 -application/vnd.ms-wmdrm.meter-chlg-req 0 -application/vnd.ms-wmdrm.meter-resp 0 -application/vnd.ms-word.document.macroEnabled.12 0 -application/vnd.ms-word.template.macroEnabled.12 0 -application/vnd.ms-works 0 -application/vnd.ms-wpl 0 -application/vnd.ms-xpsdocument 0 -application/vnd.msa-disk-image 0 -application/vnd.mseq 0 -application/vnd.msign 0 -application/vnd.multiad.creator 0 -application/vnd.multiad.creator.cif 0 -application/vnd.music-niff 0 -application/vnd.musician 0 -application/vnd.muvee.style 0 -application/vnd.mynfc 0 -application/vnd.ncd.control 0 -application/vnd.ncd.reference 0 -application/vnd.nervana 0 -application/vnd.netfpx 0 -application/vnd.neurolanguage.nlu 0 -application/vnd.nintendo.nitro.rom 0 -application/vnd.nintendo.snes.rom 0 -application/vnd.nitf 0 -application/vnd.noblenet-directory 0 -application/vnd.noblenet-sealer 0 -application/vnd.noblenet-web 0 -application/vnd.nokia.catalogs 0 -application/vnd.nokia.conml+wbxml 0 -application/vnd.nokia.conml+xml 0 -application/vnd.nokia.iSDS-radio-presets 0 -application/vnd.nokia.iptv.config+xml 0 -application/vnd.nokia.landmark+wbxml 0 -application/vnd.nokia.landmark+xml 0 -application/vnd.nokia.landmarkcollection+xml 0 -application/vnd.nokia.n-gage.ac+xml 0 -application/vnd.nokia.n-gage.data 0 -application/vnd.nokia.n-gage.symbian.install 0 -application/vnd.nokia.ncd 0 -application/vnd.nokia.pcd+wbxml 0 -application/vnd.nokia.pcd+xml 0 -application/vnd.nokia.radio-preset 0 -application/vnd.nokia.radio-presets 0 -application/vnd.novadigm.EDM 0 -application/vnd.novadigm.EDX 0 -application/vnd.novadigm.EXT 0 -application/vnd.ntt-local.content-share 0 -application/vnd.ntt-local.file-transfer 0 -application/vnd.ntt-local.ogw_remote-access 0 -application/vnd.ntt-local.sip-ta_remote 0 -application/vnd.ntt-local.sip-ta_tcp_stream 0 -application/vnd.oasis.opendocument.chart 0 -application/vnd.oasis.opendocument.chart-template 0 -application/vnd.oasis.opendocument.database 0 -application/vnd.oasis.opendocument.formula 0 -application/vnd.oasis.opendocument.formula-template 0 -application/vnd.oasis.opendocument.graphics 0 -application/vnd.oasis.opendocument.graphics-template 0 -application/vnd.oasis.opendocument.image 0 -application/vnd.oasis.opendocument.image-template 0 -application/vnd.oasis.opendocument.presentation 0 -application/vnd.oasis.opendocument.presentation-template 0 -application/vnd.oasis.opendocument.spreadsheet 0 -application/vnd.oasis.opendocument.spreadsheet-template 0 -application/vnd.oasis.opendocument.text 0 -application/vnd.oasis.opendocument.text-master 0 -application/vnd.oasis.opendocument.text-template 0 -application/vnd.oasis.opendocument.text-web 0 -application/vnd.obn 0 -application/vnd.oftn.l10n+json 0 -application/vnd.oipf.contentaccessdownload+xml 0 -application/vnd.oipf.contentaccessstreaming+xml 0 -application/vnd.oipf.cspg-hexbinary 0 -application/vnd.oipf.dae.svg+xml 0 -application/vnd.oipf.dae.xhtml+xml 0 -application/vnd.oipf.mippvcontrolmessage+xml 0 -application/vnd.oipf.pae.gem 0 -application/vnd.oipf.spdiscovery+xml 0 -application/vnd.oipf.spdlist+xml 0 -application/vnd.oipf.ueprofile+xml 0 -application/vnd.oipf.userprofile+xml 0 -application/vnd.olpc-sugar 0 -application/vnd.oma-scws-config 0 -application/vnd.oma-scws-http-request 0 -application/vnd.oma-scws-http-response 0 -application/vnd.oma.bcast.associated-procedure-parameter+xml 0 -application/vnd.oma.bcast.drm-trigger+xml 0 -application/vnd.oma.bcast.imd+xml 0 -application/vnd.oma.bcast.ltkm 0 -application/vnd.oma.bcast.notification+xml 0 -application/vnd.oma.bcast.provisioningtrigger 0 -application/vnd.oma.bcast.sgboot 0 -application/vnd.oma.bcast.sgdd+xml 0 -application/vnd.oma.bcast.sgdu 0 -application/vnd.oma.bcast.simple-symbol-container 0 -application/vnd.oma.bcast.smartcard-trigger+xml 0 -application/vnd.oma.bcast.sprov+xml 0 -application/vnd.oma.bcast.stkm 0 -application/vnd.oma.cab-address-book+xml 0 -application/vnd.oma.cab-feature-handler+xml 0 -application/vnd.oma.cab-pcc+xml 0 -application/vnd.oma.cab-subs-invite+xml 0 -application/vnd.oma.cab-user-prefs+xml 0 -application/vnd.oma.dcd 0 -application/vnd.oma.dcdc 0 -application/vnd.oma.dd2+xml 0 -application/vnd.oma.drm.risd+xml 0 -application/vnd.oma.group-usage-list+xml 0 -application/vnd.oma.pal+xml 0 -application/vnd.oma.poc.detailed-progress-report+xml 0 -application/vnd.oma.poc.final-report+xml 0 -application/vnd.oma.poc.groups+xml 0 -application/vnd.oma.poc.invocation-descriptor+xml 0 -application/vnd.oma.poc.optimized-progress-report+xml 0 -application/vnd.oma.push 0 -application/vnd.oma.scidm.messages+xml 0 -application/vnd.oma.xcap-directory+xml 0 -application/vnd.omads-email+xml 0 -application/vnd.omads-file+xml 0 -application/vnd.omads-folder+xml 0 -application/vnd.omaloc-supl-init 0 -application/vnd.openblox.game+xml 0 -application/vnd.openblox.game-binary 0 -application/vnd.openeye.oeb 0 -application/vnd.openxmlformats-officedocument.custom-properties+xml 0 -application/vnd.openxmlformats-officedocument.customXmlProperties+xml 0 -application/vnd.openxmlformats-officedocument.drawing+xml 0 -application/vnd.openxmlformats-officedocument.drawingml.chart+xml 0 -application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml 0 -application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml 0 -application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml 0 -application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml 0 -application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml 0 -application/vnd.openxmlformats-officedocument.extended-properties+xml 0 -application/vnd.openxmlformats-officedocument.presentationml-template 0 -application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.comments+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.presProps+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.presentation 0 -application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.slide 0 -application/vnd.openxmlformats-officedocument.presentationml.slide+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.slideshow 0 -application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.tags+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.template.main+xml 0 -application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml-template 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml 0 -application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml 0 -application/vnd.openxmlformats-officedocument.theme+xml 0 -application/vnd.openxmlformats-officedocument.themeOverride+xml 0 -application/vnd.openxmlformats-officedocument.vmlDrawing 0 -application/vnd.openxmlformats-officedocument.wordprocessingml-template 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.document 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml 0 -application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml 0 -application/vnd.openxmlformats-package.core-properties+xml 0 -application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml 0 -application/vnd.openxmlformats-package.relationships+xml 0 -application/vnd.oracle.resource+json 0 -application/vnd.orange.indata 0 -application/vnd.osa.netdeploy 0 -application/vnd.osgeo.mapguide.package 0 -application/vnd.osgi.bundle 0 -application/vnd.osgi.dp 0 -application/vnd.osgi.subsystem 0 -application/vnd.otps.ct-kip+xml 0 -application/vnd.oxli.countgraph 0 -application/vnd.pagerduty+json 0 -application/vnd.palm 0 -application/vnd.panoply 0 -application/vnd.paos+xml 0 -application/vnd.pawaafile 0 -application/vnd.pcos 0 -application/vnd.pg.format 0 -application/vnd.pg.osasli 0 -application/vnd.piaccess.application-licence 0 -application/vnd.picsel 0 -application/vnd.pmi.widget 0 -application/vnd.poc.group-advertisement+xml 0 -application/vnd.pocketlearn 0 -application/vnd.powerbuilder6 0 -application/vnd.powerbuilder6-s 0 -application/vnd.powerbuilder7 0 -application/vnd.powerbuilder7-s 0 -application/vnd.powerbuilder75 0 -application/vnd.powerbuilder75-s 0 -application/vnd.preminet 0 -application/vnd.previewsystems.box 0 -application/vnd.proteus.magazine 0 -application/vnd.publishare-delta-tree 0 -application/vnd.pvi.ptid1 0 -application/vnd.pwg-multiplexed 0 -application/vnd.pwg-xhtml-print+xml 0 -application/vnd.qualcomm.brew-app-res 0 -application/vnd.quobject-quoxdocument 0 -application/vnd.radisys.moml+xml 0 -application/vnd.radisys.msml+xml 0 -application/vnd.radisys.msml-audit+xml 0 -application/vnd.radisys.msml-audit-conf+xml 0 -application/vnd.radisys.msml-audit-conn+xml 0 -application/vnd.radisys.msml-audit-dialog+xml 0 -application/vnd.radisys.msml-audit-stream+xml 0 -application/vnd.radisys.msml-conf+xml 0 -application/vnd.radisys.msml-dialog+xml 0 -application/vnd.radisys.msml-dialog-base+xml 0 -application/vnd.radisys.msml-dialog-fax-detect+xml 0 -application/vnd.radisys.msml-dialog-fax-sendrecv+xml 0 -application/vnd.radisys.msml-dialog-group+xml 0 -application/vnd.radisys.msml-dialog-speech+xml 0 -application/vnd.radisys.msml-dialog-transform+xml 0 -application/vnd.rainstor.data 0 -application/vnd.rapid 0 -application/vnd.realvnc.bed 0 -application/vnd.recordare.musicxml 0 -application/vnd.recordare.musicxml+xml 0 -application/vnd.renlearn.rlprint 0 -application/vnd.rig.cryptonote 0 -application/vnd.route66.link66+xml 0 -application/vnd.rs-274x 0 -application/vnd.ruckus.download 0 -application/vnd.s3sms 0 -application/vnd.sailingtracker.track 0 -application/vnd.sbm.cid 0 -application/vnd.sbm.mid2 0 -application/vnd.scribus 0 -application/vnd.sealed-doc 0 -application/vnd.sealed-eml 0 -application/vnd.sealed-mht 0 -application/vnd.sealed-ppt 0 -application/vnd.sealed-tiff 0 -application/vnd.sealed-xls 0 -application/vnd.sealed.3df 0 -application/vnd.sealed.csf 0 -application/vnd.sealed.net 0 -application/vnd.sealedmedia.softseal-html 0 -application/vnd.sealedmedia.softseal-pdf 0 -application/vnd.seemail 0 -application/vnd.semd 0 -application/vnd.semf 0 -application/vnd.shana.informed.formdata 0 -application/vnd.shana.informed.formtemplate 0 -application/vnd.shana.informed.interchange 0 -application/vnd.shana.informed.package 0 -application/vnd.siren+json 0 -application/vnd.smaf 0 -application/vnd.smart.notebook 0 -application/vnd.smart.teacher 0 -application/vnd.software602.filler.form+xml 0 -application/vnd.software602.filler.form-xml-zip 0 -application/vnd.solent.sdkm+xml 0 -application/vnd.spotfire.dxp 0 -application/vnd.spotfire.sfs 0 -application/vnd.sss-cod 0 -application/vnd.sss-dtf 0 -application/vnd.sss-ntf 0 -application/vnd.stepmania.package 0 -application/vnd.stepmania.stepchart 0 -application/vnd.street-stream 0 -application/vnd.sun.wadl+xml 0 -application/vnd.sus-calendar 0 -application/vnd.svd 0 -application/vnd.swiftview-ics 0 -application/vnd.syncml+xml 0 -application/vnd.syncml.dm+wbxml 0 -application/vnd.syncml.dm+xml 0 -application/vnd.syncml.dm.notification 0 -application/vnd.syncml.dmddf+wbxml 0 -application/vnd.syncml.dmddf+xml 0 -application/vnd.syncml.dmtnds+wbxml 0 -application/vnd.syncml.dmtnds+xml 0 -application/vnd.syncml.ds.notification 0 -application/vnd.tao.intent-module-archive 0 -application/vnd.tcpdump.pcap 0 -application/vnd.tmd.mediaflex.api+xml 0 -application/vnd.tml 0 -application/vnd.tmobile-livetv 0 -application/vnd.trid.tpt 0 -application/vnd.triscape.mxs 0 -application/vnd.trueapp 0 -application/vnd.truedoc 0 -application/vnd.ubisoft.webplayer 0 -application/vnd.ufdl 0 -application/vnd.uiq.theme 0 -application/vnd.umajin 0 -application/vnd.unity 0 -application/vnd.uoml+xml 0 -application/vnd.uplanet.alert 0 -application/vnd.uplanet.alert-wbxml 0 -application/vnd.uplanet.bearer-choice 0 -application/vnd.uplanet.bearer-choice-wbxml 0 -application/vnd.uplanet.cacheop 0 -application/vnd.uplanet.cacheop-wbxml 0 -application/vnd.uplanet.channel 0 -application/vnd.uplanet.channel-wbxml 0 -application/vnd.uplanet.list 0 -application/vnd.uplanet.list-wbxml 0 -application/vnd.uplanet.listcmd 0 -application/vnd.uplanet.listcmd-wbxml 0 -application/vnd.uplanet.signal 0 -application/vnd.uri-map 0 -application/vnd.valve.source.material 0 -application/vnd.vcx 0 -application/vnd.vd-study 0 -application/vnd.vectorworks 0 -application/vnd.verimatrix.vcas 0 -application/vnd.vidsoft.vidconference 0 -application/vnd.visio 0 -application/vnd.visionary 0 -application/vnd.vividence.scriptfile 0 -application/vnd.vsf 0 -application/vnd.wap-slc 0 -application/vnd.wap-wbxml 0 -application/vnd.wap.sic 0 -application/vnd.wap.wmlscriptc 0 -application/vnd.webturbo 0 -application/vnd.wfa.p2p 0 -application/vnd.wfa.wsc 0 -application/vnd.windows.devicepairing 0 -application/vnd.wmc 0 -application/vnd.wmf.bootstrap 0 -application/vnd.wolfram.mathematica 0 -application/vnd.wolfram.mathematica.package 0 -application/vnd.wolfram.player 0 -application/vnd.wordperfect 0 -application/vnd.wqd 0 -application/vnd.wrq-hp3000-labelled 0 -application/vnd.wt.stf 0 -application/vnd.wv.csp+wbxml 0 -application/vnd.wv.csp+xml 0 -application/vnd.wv.ssp+xml 0 -application/vnd.xacml+json 0 -application/vnd.xara 0 -application/vnd.xfdl 0 -application/vnd.xfdl.webform 0 -application/vnd.xmi+xml 0 -application/vnd.xmpie.cpkg 0 -application/vnd.xmpie.dpkg 0 -application/vnd.xmpie.plan 0 -application/vnd.xmpie.ppkg 0 -application/vnd.xmpie.xlim 0 -application/vnd.yamaha.hv-dic 0 -application/vnd.yamaha.hv-script 0 -application/vnd.yamaha.hv-voice 0 -application/vnd.yamaha.openscoreformat 0 -application/vnd.yamaha.openscoreformat.osfpvg+xml 0 -application/vnd.yamaha.remote-setup 0 -application/vnd.yamaha.smaf-audio 0 -application/vnd.yamaha.smaf-phrase 0 -application/vnd.yamaha.through-ngn 0 -application/vnd.yamaha.tunnel-udpencap 0 -application/vnd.yaoweme 0 -application/vnd.yellowriver-custom-menu 0 -application/vnd.zul 0 -application/vnd.zzazz.deck+xml 0 -application/voicexml+xml 0 -application/vq-rtcpxr 0 -application/watcherinfo+xml 0 -application/whoispp-query 0 -application/whoispp-response 0 -application/wita 0 -application/wordperfect5.1 0 -application/wsdl+xml 0 -application/wspolicy+xml 0 -application/x-excel 0 -application/x-gzip 0 -application/x-msdownload 0 -application/x-pkcs12 0 -application/x-www-form-urlencoded 0 -application/x-tar 0 -application/x-x509-ca-cert 0 -application/x-7z-compressed 0 -application/x-zip-compressed 0 -application/x400-bp 0 -application/xacml+xml 0 -application/xcap-att+xml 0 -application/xcap-caps+xml 0 -application/xcap-diff+xml 0 -application/xcap-el+xml 0 -application/xcap-error+xml 0 -application/xcap-ns+xml 0 -application/xcon-conference-info+xml 0 -application/xcon-conference-info-diff+xml 0 -application/xenc+xml 0 -application/xhtml+xml 0 -application/xml 0 -application/xml-dtd 0 -application/xml-external-parsed-entity 0 -application/xml-patch+xml 0 -application/xmpp+xml 0 -application/xop+xml 0 -application/xv+xml 0 -application/yang 0 -application/yin+xml 0 -application/zip 0 -application/zlib 0 -audio/1d-interleaved-parityfec 0 -audio/32kadpcm 0 -audio/3gpp 0 -audio/3gpp2 0 -audio/AMR 0 -audio/AMR-WB 0 -audio/ATRAC-ADVANCED-LOSSLESS 0 -audio/ATRAC-X 0 -audio/ATRAC3 0 -audio/BV16 0 -audio/BV32 0 -audio/CN 0 -audio/DAT12 0 -audio/DV 0 -audio/DVI4 0 -audio/EVRC 0 -audio/EVRC-QCP 0 -audio/EVRC0 0 -audio/EVRC1 0 -audio/EVRCB 0 -audio/EVRCB0 0 -audio/EVRCB1 0 -audio/EVRCNW 0 -audio/EVRCNW0 0 -audio/EVRCNW1 0 -audio/EVRCWB 0 -audio/EVRCWB0 0 -audio/EVRCWB1 0 -audio/EVS 0 -audio/G711-0 0 -audio/G719 0 -audio/G722 0 -audio/G7221 0 -audio/G723 0 -audio/G726-16 0 -audio/G726-24 0 -audio/G726-32 0 -audio/G726-40 0 -audio/G728 0 -audio/G729 0 -audio/G729D 0 -audio/G729E 0 -audio/GSM 0 -audio/GSM-EFR 0 -audio/GSM-HR-08 0 -audio/L16 0 -audio/L20 0 -audio/L24 0 -audio/L8 0 -audio/LPC 0 -audio/MP4A-LATM 0 -audio/MPA 0 -audio/PCMA 0 -audio/PCMA-WB 0 -audio/PCMU 0 -audio/PCMU-WB 0 -audio/RED 0 -audio/SMV 0 -audio/SMV-QCP 0 -audio/SMV0 0 -audio/UEMCLIP 0 -audio/VDVI 0 -audio/VMR-WB 0 -audio/ac3 0 -audio/amr-wb+ 0 -audio/aptx 0 -audio/asc 0 -audio/basic 0 -audio/clearmode 0 -audio/dls 0 -audio/dsr-es201108 0 -audio/dsr-es202050 0 -audio/dsr-es202211 0 -audio/dsr-es202212 0 -audio/eac3 0 -audio/encaprtp 0 -audio/example 0 -audio/fwdred 0 -audio/iLBC 0 -audio/ip-mr_v2.5 0 -audio/mobile-xmf 0 -audio/mp4 0 -audio/mpa-robust 0 -audio/mpeg 0 -audio/mpeg3 0 -audio/mpeg4-generic 0 -audio/ogg 0 -audio/opus 0 -audio/prs.sid 0 -audio/qcelp 0 -audio/raptorfec 0 -audio/rtp-enc-aescm128 0 -audio/rtp-midi 0 -audio/rtploopback 0 -audio/rtx 0 -audio/sp-midi 0 -audio/speex 0 -audio/t140c 0 -audio/t38 0 -audio/telephone-event 0 -audio/tone 0 -audio/ulpfec 0 -audio/vnd.3gpp.iufp 0 -audio/vnd.4SB 0 -audio/vnd.CELP 0 -audio/vnd.audiokoz 0 -audio/vnd.cisco.nse 0 -audio/vnd.cmles.radio-events 0 -audio/vnd.cns.anp1 0 -audio/vnd.cns.inf1 0 -audio/vnd.dece.audio 0 -audio/vnd.digital-winds 0 -audio/vnd.dlna.adts 0 -audio/vnd.dolby.heaac.1 0 -audio/vnd.dolby.heaac.2 0 -audio/vnd.dolby.mlp 0 -audio/vnd.dolby.mps 0 -audio/vnd.dolby.pl2 0 -audio/vnd.dolby.pl2x 0 -audio/vnd.dolby.pl2z 0 -audio/vnd.dolby.pulse.1 0 -audio/vnd.dra 0 -audio/vnd.dts 0 -audio/vnd.dts.hd 0 -audio/vnd.dvb.file 0 -audio/vnd.everad.plj 0 -audio/vnd.hns.audio 0 -audio/vnd.lucent.voice 0 -audio/vnd.ms-playready.media.pya 0 -audio/vnd.nokia.mobile-xmf 0 -audio/vnd.nortel.vbk 0 -audio/vnd.nuera.ecelp4800 0 -audio/vnd.nuera.ecelp7470 0 -audio/vnd.nuera.ecelp9600 0 -audio/vnd.octel.sbc 0 -audio/vnd.rhetorex.32kadpcm 0 -audio/vnd.rip 0 -audio/vnd.sealedmedia.softseal-mpeg 0 -audio/vnd.vmx.cvsd 0 -audio/vnd.wave 0 -audio/vorbis 0 -audio/vorbis-config 0 -audio/wav 0 -audio/x-mpeg-3 0 -audio/x-wav 0 -image/bmp 0 -image/cgm 0 -image/example 0 -image/fits 0 -image/g3fax 0 -image/gif 0 -image/jp2 0 -image/jpeg 0 -image/jpm 0 -image/jpx 0 -image/naplps 0 -image/png 0 -image/prs.btif 0 -image/prs.pti 0 -image/pwg-raster 0 -image/t38 0 -image/tiff 0 -image/tiff-fx 0 -image/vnd-djvu 0 -image/vnd-svf 0 -image/vnd-wap-wbmp 0 -image/vnd.adobe.photoshop 0 -image/vnd.airzip.accelerator.azv 0 -image/vnd.cns.inf2 0 -image/vnd.dece.graphic 0 -image/vnd.dvb.subtitle 0 -image/vnd.dwg 0 -image/vnd.dxf 0 -image/vnd.fastbidsheet 0 -image/vnd.fpx 0 -image/vnd.fst 0 -image/vnd.fujixerox.edmics-mmr 0 -image/vnd.fujixerox.edmics-rlc 0 -image/vnd.globalgraphics.pgb 0 -image/vnd.microsoft.icon 0 -image/vnd.mix 0 -image/vnd.mozilla.apng 0 -image/vnd.ms-modi 0 -image/vnd.net-fpx 0 -image/vnd.radiance 0 -image/vnd.sealed-png 0 -image/vnd.sealedmedia.softseal-gif 0 -image/vnd.sealedmedia.softseal-jpg 0 -image/vnd.tencent.tap 0 -image/vnd.valve.source.texture 0 -image/vnd.xiff 0 -image/vnd.zbrush.pcx 0 -image/wmf 0 -image/x-wmf 0 -message/CPIM 0 -message/delivery-status 0 -message/disposition-notification 0 -message/example 0 -message/feedback-report 0 -message/global 0 -message/global-delivery-status 0 -message/global-disposition-notification 0 -message/global-headers 0 -message/http 0 -message/imdn+xml 0 -message/news 0 -message/rfc822 0 -message/rfc822-headers 0 -message/s-http 0 -message/sip 0 -message/sipfrag 0 -message/tracking-status 0 -message/vnd.si.simp 0 -message/vnd.wfa.wsc 0 -model/example 0 -model/iges 0 -model/vnd-dwf 0 -model/vnd.collada+xml 0 -model/vnd.flatland.3dml 0 -model/vnd.gdl 0 -model/vnd.gs-gdl 0 -model/vnd.gtw 0 -model/vnd.moml+xml 0 -model/vnd.mts 0 -model/vnd.opengex 0 -model/vnd.parasolid.transmit-binary 0 -model/vnd.parasolid.transmit-text 0 -model/vnd.valve.source.compiled-map 0 -model/vnd.vtu 0 -model/x3d+fastinfoset 0 -model/x3d+xml 0 -model/x3d-vrml 0 -multipart/alternative -1 -multipart/mixed -1 -multipart/appledouble 2 -multipart/byteranges 2 -multipart/encrypted -2 -multipart/example 2 -multipart/form-data 2 -multipart/header-set 2 -multipart/related -1 -multipart/report 0 -multipart/signed -2 -multipart/voice-message 2 -multipart/x-mixed-replace 2 -text/1d-interleaved-parityfec 0 -text/RED 0 -text/SGML 0 -text/cache-manifest 0 -text/calendar 0 -text/css 0 -text/csv 0 -text/csv-schema 0 -text/directory 0 -text/dns 0 -text/encaprtp 0 -text/example 0 -text/fwdred 0 -text/grammar-ref-list 0 -text/html 0 -text/jcr-cnd 0 -text/markdown 0 -text/mizar 0 -text/n3 0 -text/parameters 0 -text/provenance-notation 0 -text/prs.fallenstein.rst 0 -text/prs.lines.tag 0 -text/plain -1 -text/raptorfec 0 -text/rfc822-headers 0 -text/rtf 0 -text/rtp-enc-aescm128 0 -text/rtploopback 0 -text/rtx 0 -text/t140 0 -text/tab-separated-values 0 -text/troff 0 -text/turtle 0 -text/ulpfec 0 -text/uri-list 0 -text/vcard 0 -text/vnd-a 0 -text/vnd-curl 0 -text/vnd.DMClientScript 0 -text/vnd.IPTC.NITF 0 -text/vnd.IPTC.NewsML 0 -text/vnd.abc 0 -text/vnd.debian.copyright 0 -text/vnd.dvb.subtitle 0 -text/vnd.esmertec.theme-descriptor 0 -text/vnd.fly 0 -text/vnd.fmi.flexstor 0 -text/vnd.graphviz 0 -text/vnd.in3d.3dml 0 -text/vnd.in3d.spot 0 -text/vnd.latex-z 0 -text/vnd.motorola.reflex 0 -text/vnd.ms-mediapackage 0 -text/vnd.net2phone.commcenter.command 0 -text/vnd.radisys.msml-basic-layout 0 -text/vnd.si.uricatalogue 0 -text/vnd.sun.j2me.app-descriptor 0 -text/vnd.trolltech.linguist 0 -text/vnd.wap-wml 0 -text/vnd.wap.si 0 -text/vnd.wap.sl 0 -text/vnd.wap.wmlscript 0 -text/xml 0 -text/xml-external-parsed-entity 0 -text/x-vcard 0 -video/1d-interleaved-parityfec 0 -video/3gpp 0 -video/3gpp-tt 0 -video/3gpp2 0 -video/BMPEG 0 -video/BT656 0 -video/CelB 0 -video/DV 0 -video/H261 0 -video/H263 0 -video/H263-1998 0 -video/H263-2000 0 -video/H264 0 -video/H264-RCDO 0 -video/H264-SVC 0 -video/H265 0 -video/JPEG 0 -video/MP1S 0 -video/MP2P 0 -video/MP2T 0 -video/MP4V-ES 0 -video/MPV 0 -video/SMPTE292M 0 -video/VP8 0 -video/encaprtp 0 -video/example 0 -video/iso.segment 0 -video/jpeg2000 0 -video/mj2 0 -video/mp4 0 -video/mpeg4-generic 0 -video/nv 0 -video/ogg 0 -video/pointer 0 -video/quicktime 0 -video/raptorfec 0 -video/rtp-enc-aescm128 0 -video/rtploopback 0 -video/rtx 0 -video/ulpfec 0 -video/vc1 0 -video/vnd-mpegurl 0 -video/vnd-vivo 0 -video/vnd.CCTV 0 -video/vnd.dece-mp4 0 -video/vnd.dece.hd 0 -video/vnd.dece.mobile 0 -video/vnd.dece.pd 0 -video/vnd.dece.sd 0 -video/vnd.dece.video 0 -video/vnd.directv-mpeg 0 -video/vnd.directv.mpeg-tts 0 -video/vnd.dlna.mpeg-tts 0 -video/vnd.dvb.file 0 -video/vnd.fvt 0 -video/vnd.hns.video 0 -video/vnd.iptvforum.1dparityfec-1010 0 -video/vnd.iptvforum.1dparityfec-2005 0 -video/vnd.iptvforum.2dparityfec-1010 0 -video/vnd.iptvforum.2dparityfec-2005 0 -video/vnd.iptvforum.ttsavc 0 -video/vnd.iptvforum.ttsmpeg2 0 -video/vnd.motorola.video 0 -video/vnd.motorola.videop 0 -video/vnd.ms-playready.media.pyv 0 -video/vnd.nokia.interleaved-multimedia 0 -video/vnd.nokia.videovoip 0 -video/vnd.objectvideo 0 -video/vnd.radgamettools.bink 0 -video/vnd.radgamettools.smacker 0 -video/vnd.sealed-swf 0 -video/vnd.sealed.mpeg1 0 -video/vnd.sealed.mpeg4 0 -video/vnd.sealedmedia.softseal-mov 0 -video/vnd.uvvu-mp4 0 diff --git a/resources/config/lists/phishing_whitelist.inc b/resources/config/lists/phishing_whitelist.inc deleted file mode 100644 index 4679bcdc..00000000 --- a/resources/config/lists/phishing_whitelist.inc +++ /dev/null @@ -1,5 +0,0 @@ -# Used to exclude domains and tlds from phishing checks as they have known legal use - -protect-*.mimecast.com -*.safelinks.protection.outlook.com -urldefense.proofpoint.com diff --git a/resources/config/lists/public_suffix_list.dat.gz b/resources/config/lists/public_suffix_list.dat.gz deleted file mode 100644 index c916d9decdd7302b5d41ea210b67dfe6abbe8eff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80969 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 Q_ct-FS07T55qqG`{ibY2NC=eLL zqm3TH5I=P{#h3=Yc1nP8O6sH>s@SL?pk~`7UC^sa#;i@UXrUeIxXD_TJgp|aFFnfn{Tmqp9z5s)0GQJgQMF&$TR{IeoRm{~U=30^5#8&NPj7&zf8PJHN zEfH!HVL?P^55?UB70(Je!D6nDhY#5hvn2uJ5k}?0@5Vh0H*C;=)MigVyAt?D;J7k0 zmqj29^)cdkG{77^y7*Z`<27cdh-brMW?Rv#*5DE&fpAKWfB~k}K8bQhZ|=fFm{q!E zQ()mI6X)A!EdXXAngk0EYQjr>fHV}ni5}6$OI!d9zDO6bwMdw_bWZjKsQLv!H5orV z!8$5yNpBD?c42L_X=DUYw_RW~TKf3G29HUI3^6nnED+>6p=}pqprVYTMwuaONNm^z z7-?kAyoPQ-Gdw9e7IGg028!h3azk(FID&=d%BtpInUo=ox@-ZIQ5?2200X~Gc*v`Y zt1zH$wPT9dafsuH0_65?HqBXI&1~6_f{l46)B7%sKSqh|Mh%PeB*JbSuvupwCNAhx zGQ4LD>rN^zLE4Kre!5yPSn)<#DL3{J+mcZm#|OluIk#BY6d+4E=G{2PMQardxwu9x z2$IG5cR>tI3=2Q*VU3d{JEhIsV>VUPl0B?);s^ZH7V+4Og>msSrbrr*1p1uR7uP5@ z=jD_njcXSaJ(EBF*a`?s3v~6G>MQt1L4QkW)8RIqdj6imihy`XsZP$yq zK?$0ohj4CH%*7ZEzCz&Gh=l{DSuieXVZ4lMvH<6WHx6sHuxN&ei0uV!P%Z$JWoA$b z?{VH<5K!^ZlM4^a=GNfp&>8C4LXUVTqc#~ptvf>~(rLk9{oEu`%sI*yOz(Y^A}Ff3 z*f@PaDM>@k;DC#Qh%-@<(b{XHxZ_Er)t*3{2ay&?!b7;}IeRvwY9#)$K;r6%Sk+1x zJTu$Q%`iiLtVcXH%tb~p+T&QJi88_Om(PrY&XqBX*5Ay|*5rcVoKZ&rx(Zyxqk5Q_ z_b!NH3`O$*Hsx5G3#hq-_LmT=pB%l&Voc&xV2-2rD03~9zFj=j2$HZEVRJBrEZepT z=EU4j9#iTJ<1Rro_Gg=)Lwa0YSm{kaXPhQj$P?Mnq_lH*IE0KUP8gT0_SlJ8b%q^8~pu* z49q*!3>Q^%FlOqDej}?mPmb;4D{;*QWG=E9BjvnoB?v{1vJC963E^IB~-ub-EgyF)V$vkuTZTV$8YQHd~ zmHH(STD!aGKK?+(!qB*0x+L6C1ooq*``cZ-^)fD z&>~k6>-Vr0z>d*v)5Z)gE@t*5v>9WunD@XIz{HI?;ch`-gvRRL&AXJedaEJ;U6m$| z;pG6caHtgK8(`JZN@)QGUir>Ii@7CidUs%NW(=Kh&RKZcYFJoUL{rTI&YZ1cRpxk>421P~VDMECQ3&#Z*X>yo(Lz7O#$LX0kCN6|WmW>GNjaFGK zJo^#xCVpa|g-zV}i$pC*FZ!fEaIP(IO&_-yidWL%v<#R~v&Rtb<3vx5GQLmNV3G(9 zX|~UkeQ+>Rpc)Az2VSfu;0{;6In?rUGu>Dh>_NZ*k=|gjU9fCF9;e3y+T)kbTgBdh zSXH5bo< zU|u#U+Jli|&+hbj5};p2)>Gt` zBggUK7;UZ;K;u!z9E7&dO$I_Q%VpJt;&fM3U?aEegU6hWA^Zc zSupxVZCaQU*sB*)@d+)?Y@kBj#>^I*jLLglC=`rvj##v8n=QPFm_D5DT_E;uEW8tS z{Dh-TvW3Up#DygbFL|EMCdUAd&^}55w;ban=0g$-5~OtocpeXIi@UT*1oASG`5Yr{ z76;Eb#w>1MMf@o&bpq5U_26kWt3N&A4|Js9DQOj4K+lY$Q;#u=9=nM5TXYdJ#gFqI zOu`$DyJ|7y;wMHr4LG_HW7qII_b%D@J@exL*MGD}eJA?em?-V@o8O{Xc z1zUo#qZj)%0ky~wv@KFV^Q4@DSwcjn$e1+*8YChsD4$|U(;i9pRB1$i$|r_)BW?rF zZxHdhV{Dy(&?!b5Ff#&XCOfZV2xqUEpO1KE2yKHb*eOqkiJ9HQ%4#3o#_esIl_EsT zFrXLAj=e~LQ^L}gY@BU7Cb^7h(c94<_LvcWY$W2(W7=w{(r|K`0iCr6ohzI0lCGRUB^4s(>4?M3gBe1K za(VY`@t>tQjvNgQ9U(9PP?{pB#{AL%jQ%ig3=Mb$cHyxj+?Z4hbd#ncNAB!ia4PYRgK0AeUl`8-u-Q_ChD`Q? zU%j+I^4=e=g)$61d_`6`FwILQhLi|d7@i{rjG=j=%h~CA*=)Amm_IcSEbN|XWnC^B>CnhSWsD@VHP8^ zDviNL&{rN(s5Lsxf)2yrE>(hpD9|jNyv~X5ji?ObUP?jbt>YNvqu3e7D;S5BwE3+( zRZWIdf`o-m41%gV<%t=Uu`X>DM`v#?>Wja|L{*YDEDbo!GkT>OBA66#DPJ~Jh6t%A z-XKce%*MfZCHYqIao+?BbPV=>d6;0Z?m&%`K$;J)U4Y8e_=WEnr`Ny76ryb(YLy7R zd=wA0$9$MIB8cW?^wY%Pm{lQfCthh>Ko_z2#XOsJ4Q)5gFTwkx)I3xI=!a_x;(BUf zbj0kbbEl;jC0c_q5Z@+6wQ$%*)6%I~rbv(lOAnt8>Q?|TVy=uiC=5`yc1kc%T`8c^D?jt3huDk=kV}&Q-5EDnUOzIoB4+RkJ3uG)P0hTo zmQJ9Ph})h0L;ed0in0rgn@Q|R>W-N|flaa8Mo-5~JAPkp=1``Gw0KQI@6vH*_c88^CQUO}q^;t|)UqJpn)CEI(Wp5HdC1oD) z4d;YKQ*r_D1^f&cMAA|y;iT3V=qzGR_~*k%O=Q6`xF9uXGbaYa%A$?IpcA47o0`&Sj1utsDmS2pJ8-MxI zji@F22qFmU8L_yAk64;TA|6-kKrV=XIjkLX3_2=7lBaMr*~bZ^|FaMYU=vp1%a{?M z@T_*sk}og@XnKf&GyQ=50?=urnCF=(LX!CCh~pzhF?M3-TLj_SFJ+fQVKF`kntmb( zKOv8PgapC^9}B?l^Ry6)yCTBydlm^#3*j_F25DBg0HME5SpqpQi7Eqdzb684MR6XJ z)IF5~NxJv}0GGySFXOaXDh4}-w*(i|6!@Kph|iLtFFM71pN$uj^sF&x%p?H2xFcjp z8`F?NUwD31I_29e!kDp5Tv6JfacMEg64V!k$7yV@((JPb$8#$1s8wo}0c0JdP&Tyi zTs51c2}ar|LU7xiybK98CgXfan|w@%=qZ)Bc|#_J{QChhmw%X7r zXQq+#0_V*7g|2aa24WZwkfIT^@TOllI!QraBS}E*HD%z*#!PuXYXtp1K_3GSCMtWW-K*=6(K#U0fL4t@7%R0o;n3>9LA#pucP8c=K3A zJg&oM4%Lw=qZ0Fhu)OiG_4m{4PToqMi^)4U`PNjzyWLcSj)`f=PMSbm#j(`WLOyNc2XWQfFBbx5kKz7 zhylHXg0}!}xohHt9T}N{!`mrJ4D6GV`EJ^Hk2O-l})LuZj7qw`-Ii@*loi-cu#4Efje6UeeC%!q2_lmfJK z*s2^OEfiyTayKa{%3=})25ychow5UrZFC)EAT5A!bR-o*+1s;hN0?T1))qWJTqKOs zYmp?V@vvjUHzMV)6$4PvIy!||sup10PKiI9TdTC7fs7%Ywip=~`{XH7Fs9EiE7FER z&yP3JES z)A_YY@GGz)#>Xg@@A1H*$3<1>zM7#1zbL2Tlp8*myYM_{Oqx%4Gky%OZsvF94b$j` zWq06=gcEWLK<=1@rE?)EOADbCaw0B2fA=ru`8z)mkkrqcR5pn*WJ#X*;A@@2eBe1Q zG3|RT9KJ4tR_%7)+%(|qBa3#*4Hk~2-bVu6Mynt9(TpZ0D5&o=o z^yO+??L};MTc$#RKgtvL17x>1D5`@@K<~OKiafF zMpq(ulp_=eT9TQeH@s(l5)p%6gN4~De9@b~p%*xG=2fV00#M`HWMS#uOl-gzAhRTJ zir(-DT+6OD83CFM{Dr5pGZD`Q&CG{SL&QLn0#jU?*08sB<|kgF2A-{qq{b$??Y&Gh zJH{M^Gc#|Q1Oz{i5HWU%OW&gij9-P-aXShBHcIqybyj{6P|^lZF~*qpGhZOk^$A9f zzzAvR++GSyV+znYiUdIqSDubPq!MF%bS6PaZC81EtHke8GM+^asz{`GshwAVK-7i9xT)~N1Dp>b@$fSf^uvNP7mPQ2WWnfk=FGR$t_z0~eZ=CCzla^8)Wf%EW}p|mvR;7T zgPRc){3b)5xe)sDYE(FrWDfIHTsS+8U`NgDAm(W^hP*DEA!7wP zO=85qIzJ=yJ=mG!-4aTff84{@EwFVcWB{=8RT9N_5;+M|y!CAmA*xSuyLbrqlC)`) zNWGt@VubuYF`_~(1L6-a&y5yv)1+zPBIQb~cDyKCAR`E+cuLx5zzr&$JU41DlL7E= zh$aY~3rY~))Q`KyvXIR`z9Q%i%NU^-wR1png%d1vO#=Fp!YNFjFHbO#35mzuVysz) zPK4nb*j|1XbQYHcR@{3FPiMgrB!2=wXOHfJ1O)+-bzV98idCOSgnp7>fLx$iK6tY8 zGDYLE≥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 0" { - let "from_name" "to_lowercase(trim(header.from.name))"; - let "from_addr" "to_lowercase(trim(header.from.addr))"; - let "from_local" "email_part(from_addr, 'local')"; - let "from_domain" "email_part(from_addr, 'domain')"; - let "from_domain_sld" "domain_part(from_domain, 'sld')"; let "from_raw" "to_lowercase(header.from.raw)"; if eval "from_count > 1" { @@ -42,8 +37,8 @@ if eval "from_count > 0" { if eval "is_email(from_name)" { let "from_name_sld" "domain_part(email_part(from_name, 'domain'), 'sld')"; if eval "(!t.FROM_INVALID && from_domain_sld != from_name_sld) || - (!is_empty(envelope.from) && domain_part(email_part(envelope.from, 'domain'), 'sld') != from_name_sld) || - (is_empty(envelope.from) && domain_part(env.helo_domain, 'sld') != from_name_sld)" { + (!is_empty(envelope.from) && envfrom_domain_sld != from_name_sld) || + (is_empty(envelope.from) && helo_domain_sld != from_name_sld)" { let "t.SPOOF_DISPLAY_NAME" "1"; } else { let "t.FROM_NEQ_DISPLAY_NAME" "1"; @@ -69,7 +64,7 @@ if eval "from_count > 0" { eq_ignore_case(from_addr, envelope.from)) || (t.HFILTER_FROM_BOUNCE && !is_empty(from_domain) && - domain_part(from_domain, 'sld') == domain_part(env.helo_domain, 'sld'))" { + from_domain_sld == helo_domain_sld)" { let "t.FROM_EQ_ENVFROM" "1"; } elsif eval "!t.FROM_INVALID" { let "t.FORGED_SENDER" "1"; @@ -80,11 +75,10 @@ if eval "from_count > 0" { let "t.TAGGED_FROM" "1"; } - let "to" "header.to:cc[*].addr[*]"; - if eval "count(to) == 1" { - if eval "eq_ignore_case(to[0], from_addr)" { + if eval "count(recipients_to) + count(recipients_cc) == 1" { + if eval "eq_ignore_case(recipients_to[0], from_addr)" { let "t.TO_EQ_FROM" "1"; - } elsif eval "eq_ignore_case(email_part(to[0], 'domain'), from_domain)" { + } elsif eval "eq_ignore_case(email_part(recipients_to[0], 'domain'), from_domain)" { let "t.TO_DOM_EQ_FROM_DOM" "1"; } } @@ -123,20 +117,24 @@ if eval "from_count > 0" { if eval "!is_empty(envelope.from)" { if eval "is_email(envelope.from)" { - if eval "contains(service_accounts, email_part(envelope.from, 'local'))" { + if eval "contains(service_accounts, envfrom_local)" { let "t.ENVFROM_SERVICE_ACCT" "1"; } } else { let "t.ENVFROM_INVALID" "1"; } - let "envfrom_domain_sld" "domain_part(email_part(envelope.from, 'domain'), 'sld')"; if eval "!is_empty(envfrom_domain_sld)" { if eval "lookup('spam/free-domains', envfrom_domain_sld)" { let "t.FREEMAIL_ENVFROM" "1"; } elsif eval "lookup('spam/disposable-domains', envfrom_domain_sld)" { let "t.DISPOSABLE_ENVFROM" "1"; } + + # Mail from no resolve to A or MX + if eval "!dns_exists(envfrom_domain, 'mx') && !dns_exists(envfrom_domain, 'ip')" { + let "t.HFILTER_FROMHOST_NORES_A_OR_MX" "1"; + } } # Read confirmation address is different to return path diff --git a/resources/config/sieve/headers.sieve b/resources/config/sieve/headers.sieve index f0f67e3e..74ed94b6 100644 --- a/resources/config/sieve/headers.sieve +++ b/resources/config/sieve/headers.sieve @@ -1,5 +1,22 @@ +# Mailing list scores +let "ml_score" "count(header.List-Id:List-Archive:List-Owner:List-Help:List-Post:X-Loop:List-Subscribe:List-Unsubscribe[*].exists) * 0.125"; +eval "print('ml_score: ' + ml_score)"; +if eval "ml_score < 1" { + if eval "header.List-Id.exists" { + let "ml_score" "ml_score + 0.50"; + } + if eval "header.List-Subscribe.exists && header.List-Unsubscribe.exists" { + let "ml_score" "ml_score + 0.25"; + } + if eval "header.Precedence.exists && (eq_ignore_case(header.Precedence, 'list') || eq_ignore_case(header.Precedence, 'bulk'))" { + let "ml_score" "ml_score + 0.25"; + } +} +if eval "ml_score >= 1" { + let "t.MAILLIST" "1"; +} - +# X-Priority if eval "header.x-priority.exists" { let "xp" "header.x-priority"; if eval "xp == 0" { diff --git a/resources/config/sieve/helo.sieve b/resources/config/sieve/helo.sieve new file mode 100644 index 00000000..870197b1 --- /dev/null +++ b/resources/config/sieve/helo.sieve @@ -0,0 +1,30 @@ +if eval "!is_ip_addr(env.helo_domain)" { + let "helo" "env.helo_domain"; + + if eval "contains(helo, '.')" { + if eval "!is_empty(env.iprev.ptr) && !eq_ignore_case(helo, env.iprev.ptr)" { + # Unknown client hostname (PTR or FCrDNS verification failed) + let "t.HFILTER_HOSTNAME_UNKNOWN" "1"; + } + if eval "!dns_exists(helo, 'ip') && !dns_exists(helo, 'mx')" { + # Helo no resolve to A or MX + let "t.HFILTER_HELO_NORES_A_OR_MX" "1"; + } + } else { + if eval "contains(helo, 'user')" { + # HELO contains 'user' + let "t.RCVD_HELO_USER" "1"; + } + + # Helo not FQDN + let "t.HFILTER_HELO_NOT_FQDN" "1"; + } +} else { + # Helo host is bare ip + let "t.HFILTER_HELO_BAREIP" "1"; + + if eval "env.helo_domain != env.remote_ip" { + # Helo A IP != hostname IP + let "t.HFILTER_HELO_IP_A" "1"; + } +} diff --git a/resources/config/sieve/html.sieve b/resources/config/sieve/html.sieve index 3ba05d4c..f64320bf 100644 --- a/resources/config/sieve/html.sieve +++ b/resources/config/sieve/html.sieve @@ -8,7 +8,7 @@ foreverypart { if eval "eq_ignore_case(header.content-type, 'text/html')" { # Tokenize HTML let "is_body_part" "is_body()"; - let "html_tokens" "tokenize_html(part.text)"; + let "html_tokens" "tokenize(part.text, 'html')"; let "html_tokens_len" "len(html_tokens)"; let "html_char_count" "0"; let "html_space_count" "0"; @@ -35,9 +35,9 @@ foreverypart { let "html_space_count" "html_space_count + count_spaces(token)"; let "text" "to_lowercase(trim(strip_prefix(token, '_')))"; - let "html_words" "html_words + len(tokenize_words(text))"; + let "html_words" "html_words + len(tokenize(text, 'words'))"; - let "uris" "tokenize_url(text, false)"; + let "uris" "tokenize(text, 'uri')"; if eval "!is_empty(uris)" { let "has_uri" "1"; diff --git a/resources/config/sieve/ip.sieve b/resources/config/sieve/ip.sieve new file mode 100644 index 00000000..542c9be6 --- /dev/null +++ b/resources/config/sieve/ip.sieve @@ -0,0 +1,22 @@ +# Reverse ip checks +if eval "env.iprev.result != ''" { + if eval "ends_with(env.iprev.result, 'error')" { + let "t.RDNS_DNSFAIL" "1"; + } elsif eval "env.iprev.result == 'fail'" { + let "t.RDNS_NONE" "1"; + } +} + +# Lookup ASN +let "asn_lookup" ""; +if eval "len(env.remote_ip.reverse) <= 15" { + let "asn_lookup" "env.remote_ip.reverse + '.origin.asn.cymru.com'"; +} else { + let "asn_lookup" "env.remote_ip.reverse + '.origin.asn6.cymru.com'"; +} +let "asn_lookup" "split(dns_query(asn_lookup, 'txt'), '|')"; +let "asn" "asn_lookup[0]"; +let "country" "asn_lookup[2]"; + +#eval "print('ASN: ' + asn + ' (' + country + ')')"; + diff --git a/resources/config/sieve/messageid.sieve b/resources/config/sieve/messageid.sieve index 77a1421e..a55f0ac7 100644 --- a/resources/config/sieve/messageid.sieve +++ b/resources/config/sieve/messageid.sieve @@ -22,15 +22,15 @@ if eval "!is_empty(mid_raw)" { } # From address present in Message-ID checks - let "from_lcase" "to_lowercase(header.from.addr)"; - if eval "is_empty(from_lcase)" { - let "from_lcase" "envelope.from"; + let "sender" "from_addr"; + if eval "is_empty(sender)" { + let "sender" "envelope.from"; } - if eval "!is_empty(from_lcase)" { - if eval "contains(mid_lcase, from_lcase)" { + if eval "!is_empty(sender)" { + if eval "contains(mid_lcase, sender)" { let "t.MID_CONTAINS_FROM" "1"; } else { - let "from_domain" "email_part(from_lcase, 'domain')"; + let "from_domain" "email_part(sender, 'domain')"; let "mid_sld" "domain_part(mid_rhs, 'sld')"; if eval "mid_rhs == from_domain" { @@ -42,12 +42,11 @@ if eval "!is_empty(mid_raw)" { } # To/Cc addresses present in Message-ID checks - let "recipients" "winnow(header.to:cc:bcc[*].addr[*])"; let "recipients_len" "count(recipients)"; let "i" "0"; while "i < recipients_len" { - let "rcpt" "to_lowercase(recipients[i])"; + let "rcpt" "recipients[i]"; let "i" "i + 1"; if eval "contains(mid_lcase, rcpt)" { let "t.MID_CONTAINS_TO" "1"; diff --git a/resources/config/sieve/mime.sieve b/resources/config/sieve/mime.sieve index 8137c610..eb8accc6 100644 --- a/resources/config/sieve/mime.sieve +++ b/resources/config/sieve/mime.sieve @@ -10,6 +10,7 @@ let "has_text_part" "0"; let "is_encrypted" "0"; foreverypart { + let "content_type" "to_lowercase(header.content-type)"; let "type" "to_lowercase(header.content-type.type)"; let "subtype" "to_lowercase(header.content-type.subtype)"; let "cte" "header.content-transfer-encoding"; @@ -41,13 +42,13 @@ foreverypart { if eval "!has_plain_part && ma_ct == 'text/plain'" { let "text_part" "part.text"; - let "text_part_words" "tokenize_words(text_part)"; - let "text_part_uris" "count(tokenize_url(text_part, true))"; + let "text_part_words" "tokenize(text_part, 'words')"; + let "text_part_uris" "count(tokenize(text_part, 'uri_strict'))"; let "has_plain_part" "1"; } elsif eval "!has_html_part && ma_ct == 'text/html'" { let "html_part" "html_to_text(part.text)"; - let "html_part_words" "tokenize_words(html_part)"; - let "html_part_uris" "count(tokenize_url(html_part, true))"; + let "html_part_words" "tokenize(html_part, 'words')"; + let "html_part_uris" "count(tokenize(html_part, 'uri_strict'))"; let "has_html_part" "1"; } } @@ -149,9 +150,61 @@ foreverypart { if eval "part_is_attachment" { # Has a MIME attachment let "t.HAS_ATTACHMENT" "1"; + + # Detect and compare mime type + let "detected_mime_type" "detect_file_type('mime')"; + if eval "!is_empty(detected_mime_type)" { + if eval "detected_mime_type == content_type" { + # Known content-type + let "t.MIME_GOOD" "1"; + } elsif eval "content_type != 'application/octet-stream'" { + # Known bad content-type + let "t.MIME_BAD" "1"; + } + } } + + # Analyze attachment name + let "attach_name" "attachment_name()"; + if eval "!is_empty(attach_name)" { + if eval "has_obscured(attach_name)" { + let "t.MIME_BAD_UNICODE" "1"; + } + let "name_parts" "rsplit(to_lowercase(attach_name), '.')"; + if eval "count(name_parts) > 1" { + let "ext_type" "lookup_map('spam/mime-types', name_parts[0])"; + if eval "!is_empty(ext_type)" { + let "ext_type_double" "lookup_map('spam/mime-types', name_parts[1])"; + if eval "contains(ext_type, 'BAD')" { + # Bad extension + if eval "contains(ext_type_double, 'BAD')" { + let "t.MIME_DOUBLE_BAD_EXTENSION" "1"; + } else { + let "t.MIME_BAD_EXTENSION" "1"; + } + } + if eval "contains(ext_type, 'AR') && contains(ext_type_double, 'AR')" { + # Archive in archive + let "t.MIME_ARCHIVE_IN_ARCHIVE" "1"; + } + + if eval "contains(ext_type, '/') && + content_type != 'application/octet-stream' && + !contains(split(ext_type, '|'), content_type)" { + # Invalid attachment mime type + let "t.MIME_BAD_ATTACHMENT" "1"; + } + } + } + } + } if eval "has_text_part && (t.ENCRYPTED_SMIME || t.SIGNED_SMIME || t.ENCRYPTED_PGP || t.SIGNED_PGP)" { let "t.BOGUS_ENCRYPTED_AND_TEXT" "1"; } + +# Check for mixed script in body +if eval "!is_single_script(text_body)" { + let "t.R_MIXED_CHARSET" "1"; +} diff --git a/resources/config/sieve/prelude.sieve b/resources/config/sieve/prelude.sieve new file mode 100644 index 00000000..74f723a4 --- /dev/null +++ b/resources/config/sieve/prelude.sieve @@ -0,0 +1,36 @@ +# Convert body to plain text +let "text_body" "body.to_text"; + +# Obtain all URLs in the body +let "body_urls" "tokenize(text_body, 'uri')"; + +# Obtain all URLs in href and src attributes +let "html_body_urls" "html_attrs(body.html, '', ['href', 'src'])"; + +# Obtain all URLs in the subject, combine them with all other URLs and remove duplicates +let "urls" "dedup(tokenize(header.subject, 'uri') + body_urls + html_body_urls)"; + +# Obtain thread name and subject +let "subject_lc" "to_lowercase(header.subject)"; +let "thread_name" "thread_name(header.subject)"; + +# Obtain all recipients +let "recipients" "to_lowercase(header.to:cc:bcc[*].addr[*])"; +let "recipients_clean" "winnow(dedup(recipients))"; +let "recipients_to" "header.to[*].addr[*]"; +let "recipients_cc" "header.cc[*].addr[*]"; + +# Obtain From parts +let "from_name" "to_lowercase(trim(header.from.name))"; +let "from_addr" "to_lowercase(trim(header.from.addr))"; +let "from_local" "email_part(from_addr, 'local')"; +let "from_domain" "email_part(from_addr, 'domain')"; +let "from_domain_sld" "domain_part(from_domain, 'sld')"; + +# Obtain Envelope From parts +let "envfrom_local" "email_part(envelope.from, 'local')"; +let "envfrom_domain" "email_part(envelope.from, 'domain')"; +let "envfrom_domain_sld" "domain_part(envfrom_domain, 'sld')"; + +# Obtain HELO domain SLD +let "helo_domain_sld" "domain_part(env.helo_domain, 'sld')"; diff --git a/resources/config/sieve/rbl.sieve b/resources/config/sieve/rbl.sieve new file mode 100644 index 00000000..9502f08e --- /dev/null +++ b/resources/config/sieve/rbl.sieve @@ -0,0 +1,339 @@ + +# Validate IP addresses +let "ip_addresses" "dedup(winnow([ env.remote_ip ] + header.received[*].rcvd.ip + header.received[*].rcvd.from.ip + header.received[*].rcvd.by.ip))"; +let "ip_addresses_len" "count(ip_addresses)"; +let "i" "0"; + +while "i < ip_addresses_len" { + let "ip_address" "ip_addresses[i]"; + let "is_from_addr" "i == 0"; + let "i" "i + 1"; + + if eval "ip_address == '127.0.0.1' || ip_address == '::1'" { + continue; + } + + # Do not check more than 10 IP addresses + if eval "i >= 10" { + break; + } + + let "ip_reverse" "ip_reverse_name(ip_address)"; + let "is_ip_v4" "len(ip_reverse) <= 15"; + + # Query SPAMHAUS + let "result" "rsplit_once(dns_query(ip_reverse + '.zen.spamhaus.org', 'ipv4')[0], '.')"; + if eval "result[0] == '127.0.0'" { + let "result" "result[1]"; + + if eval "result == 2" { + if eval "is_from_addr" { + let "t.RBL_SPAMHAUS_SBL" "1"; + } else { + let "t.RECEIVED_SPAMHAUS_SBL" "1"; + } + } elsif eval "result == 3" { + if eval "is_from_addr" { + let "t.RBL_SPAMHAUS_CSS" "1"; + } else { + let "t.RECEIVED_SPAMHAUS_CSS" "1"; + } + } elsif eval "result >= 4 && result <= 7" { + if eval "is_from_addr" { + let "t.RBL_SPAMHAUS_XBL" "1"; + } else { + let "t.RECEIVED_SPAMHAUS_XBL" "1"; + } + } elsif eval "result == 9" { + if eval "is_from_addr" { + let "t.RBL_SPAMHAUS_DROP" "1"; + } else { + let "t.RECEIVED_SPAMHAUS_PBL" "1"; + } + } elsif eval "result == 10 || result == 11" { + if eval "is_from_addr" { + let "t.RBL_SPAMHAUS_PBL" "1"; + } else { + let "t.RECEIVED_SPAMHAUS_PBL" "1"; + } + } elsif eval "result == 254" { + if eval "is_from_addr" { + let "t.RBL_SPAMHAUS_BLOCKED_OPENRESOLVER" "1"; + } else { + let "t.RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER" "1"; + } + } elsif eval "result == 255" { + if eval "is_from_addr" { + let "t.RBL_SPAMHAUS_BLOCKED" "1"; + } else { + let "t.RECEIVED_SPAMHAUS_BLOCKED" "1"; + } + } else { + # Unrecognized result + let "t.RBL_SPAMHAUS" "1"; + } + } + + if eval "is_from_addr" { + # Query IP reputation at Mailspike + let "result" "rsplit_once(dns_query(ip_reverse + '.rep.mailspike.net', 'ipv4')[0], '.')"; + if eval "result[0] == '127.0.0'" { + let "result" "result[1]"; + + if eval "result == 10" { + let "t.RBL_MAILSPIKE_WORST" "1"; + } elsif eval "result == 11" { + let "t.RBL_MAILSPIKE_VERYBAD" "1"; + } elsif eval "result == 12" { + let "t.RBL_MAILSPIKE_BAD" "1"; + } elsif eval "result >= 13 && result <= 16" { + let "t.RWL_MAILSPIKE_NEUTRAL" "1"; + } elsif eval "result == 17" { + let "t.RWL_MAILSPIKE_POSSIBLE" "1"; + } elsif eval "result == 18" { + let "t.RWL_MAILSPIKE_GOOD" "1"; + } elsif eval "result == 19" { + let "t.RWL_MAILSPIKE_VERYGOOD" "1"; + } elsif eval "result == 20" { + let "t.RWL_MAILSPIKE_EXCELLENT" "1"; + } + } + + # Query SenderScore + if eval "dns_exists(ip_reverse + '.bl.score.senderscore.com', 'ipv4')" { + let "t.RBL_SENDERSCORE" "1"; + } + + # Query SpamEatingMonkey + if eval "is_ip_v4 && dns_exists(ip_reverse + '.bl.spameatingmonkey.net', 'ipv4')" { + let "t.RBL_SEM" "1"; + } elsif eval "!is_ip_v4 && dns_exists(ip_reverse + '.bl.ipv6.spameatingmonkey.net', 'ipv4')" { + let "t.RBL_SEM_IPV6" "1"; + } + + # Query VirusFree + if eval "dns_query(ip_reverse + '.bip.virusfree.cz', 'ipv4')[0] == '127.0.0.2'" { + let "t.RBL_VIRUSFREE_BOTNET" "1"; + } + + # Query NiX + if eval "dns_exists(ip_reverse + '.ix.dnsbl.manitu.net', 'ipv4')" { + let "t.RBL_NIXSPAM" "1"; + } + + # Query Spamcop + if eval "dns_exists(ip_reverse + '.bl.spamcop.net', 'ipv4')" { + let "t.RBL_SPAMCOP" "1"; + } + + # Query Barracuda + if eval "dns_exists(ip_reverse + '.b.barracudacentral.org', 'ipv4')" { + let "t.RBL_BARRACUDA" "1"; + } + } + + # Query Blocklist.de + if eval "dns_exists(ip_reverse + '.bl.blocklist.de', 'ipv4')" { + if eval "is_from_addr" { + let "t.RBL_BLOCKLISTDE" "1"; + } else { + let "t.RECEIVED_BLOCKLISTDE" "1"; + } + } + + # Query DNSWL + let "result" "rsplit_once(dns_query(ip_reverse + '.list.dnswl.org', 'ipv4')[0], '.')"; + if eval "starts_with(result[0], '127.0.')" { + let "result" "result[1]"; + + if eval "result == 0" { + let "t.RCVD_IN_DNSWL_NONE" "1"; + } elsif eval "result == 1" { + let "t.RCVD_IN_DNSWL_LOW" "1"; + } elsif eval "result == 2" { + let "t.RCVD_IN_DNSWL_MED" "1"; + } elsif eval "result == 3" { + let "t.RCVD_IN_DNSWL_HI" "1"; + } elsif eval "result == 255" { + let "t.DNSWL_BLOCKED" "1"; + } + } +} + +# Validate domain names +let "emails" "dedup(winnow(to_lowercase([header.from, header.reply-to, envelope.from] + tokenize(text_body, 'email'))))"; +let "emails_len" "count(emails)"; +let "domains" "dedup(winnow(to_lowercase([ env.helo_domain, env.iprev.ptr ] + email_part(emails, 'domain') + puny_decode(uri_part(urls, 'host')))))"; +let "domains_len" "count(domains)"; +let "i" "0"; + +while "i < domains_len" { + let "domain" "domains[i]"; + let "i" "i + 1"; + + if eval "!contains(domain, '.') || is_ip_addr(domain)" { + continue; + } + + # Do not check more than 10 domain names + if eval "i >= 10" { + break; + } + + # Query SpamHaus DBL + let "result" "rsplit_once(dns_query(domain + '.dbl.spamhaus.org', 'ipv4')[0], '.')"; + if eval "result[0] == '127.0.0'" { + let "result" "result[1]"; + + if eval "result == 2" { + let "t.DBL_SPAM" ""; + } elsif eval "result == 4" { + let "t.DBL_PHISH" "1"; + } elsif eval "result == 5" { + let "t.DBL_MALWARE" "1"; + } elsif eval "result == 6" { + let "t.DBL_BOTNET" "1"; + } elsif eval "result == 102" { + let "t.DBL_ABUSE" "1"; + } elsif eval "result == 103" { + let "t.DBL_ABUSE_REDIR" "1"; + } elsif eval "result == 104" { + let "t.DBL_ABUSE_PHISH" "1"; + } elsif eval "result == 105" { + let "t.DBL_ABUSE_MALWARE" "1"; + } elsif eval "result == 106" { + let "t.DBL_ABUSE_BOTNET" "1"; + } elsif eval "result == 254" { + let "t.DBL_BLOCKED_OPENRESOLVER" "1"; + } elsif eval "result == 255" { + let "t.DBL_BLOCKED" "1"; + } + } + + # Query SURBL multi + let "result" "rsplit_once(dns_query(domain + '.multi.surbl.org', 'ipv4')[0], '.')"; + if eval "result[0] == '127.0.0'" { + let "result" "result[1]"; + + if eval "result == 128" { + let "t.CRACKED_SURBL" "1"; + } elsif eval "result == 64" { + let "t.ABUSE_SURBL" "1"; + } elsif eval "result == 16" { + let "t.MW_SURBL_MULTI" "1"; + } elsif eval "result == 8" { + let "t.PH_SURBL_MULTI" "1"; + } elsif eval "result == 1" { + let "t.SURBL_BLOCKED" "1"; + } + } + + # Query URIBL multi + let "result" "rsplit_once(dns_query(domain + '.multi.uribl.com', 'ipv4')[0], '.')"; + if eval "result[0] == '127.0.0'" { + let "result" "result[1]"; + + if eval "result == 1" { + let "t.URIBL_BLOCKED" "1"; + } elsif eval "result == 2" { + let "t.URIBL_BLACK" "1"; + } elsif eval "result == 4" { + let "t.URIBL_GREY" "1"; + } elsif eval "result == 8" { + let "t.URIBL_RED" "1"; + } + } + + # Query SpamEatingMonkey URIBL + if eval "dns_query(domain + '.uribl.spameatingmonkey.net', 'ipv4')[0] == '127.0.0.2'" { + let "t.SEM_URIBL" "1"; + } + + # Query SpamEatingMonkey FRESH15 + if eval "dns_query(domain + '.fresh15.spameatingmonkey.net', 'ipv4')[0] == '127.0.0.2'" { + let "t.SEM_URIBL_FRESH15" "1"; + } + +} + +# Check DKIM domains that passed validation +let "i" "count(env.dkim.domains)"; +while "i > 0" { + let "i" "i - 1"; + + # Query DNSWL + let "result" "rsplit_once(dns_query(env.dkim.domains[i] + '.dwl.dnswl.org', 'ipv4')[0], '.')"; + if eval "result[0] == '127.0.0'" { + let "result" "result[1]"; + + if eval "result == 0" { + let "t.DWL_DNSWL_NONE" "1"; + } elsif eval "result == 1" { + let "t.DWL_DNSWL_LOW" "1"; + } elsif eval "result == 2" { + let "t.DWL_DNSWL_MED" "1"; + } elsif eval "result == 3" { + let "t.DWL_DNSWL_HI" "1"; + } elsif eval "result == 255" { + let "t.DWL_DNSWL_BLOCKED" "1"; + } + } +} + +# Validate email addresses +let "i" "0"; +while "i < emails_len" { + let "email" "emails[i]"; + let "i" "i + 1"; + + if eval "!contains(email, '@')" { + continue; + } + + # Do not check more than 10 email addresses + if eval "i >= 10" { + break; + } + + # Query MSBL EBL + let "result" "rsplit_once(dns_query(hash(email, 'sha1') + '.ebl.msbl.org', 'ipv4')[0], '.')"; + if eval "result[1] == 2 || result[1] == 3" { + if eval "result[0] == '127.0.0'" { + let "t.MSBL_EBL" "1"; + } elsif eval "result[0] == '127.0.1'" { + let "t.MSBL_EBL_GREY" "1"; + } + } +} + + +# Validate URL hashes +let "i" "0"; +let "urls_len" "count(urls)"; +while "i < urls_len" { + let "url" "urls[i]"; + let "i" "i + 1"; + + # Do not check more than 10 URLs + if eval "i >= 10" { + break; + } + + # Query SURBL HASHBL + let "result" "rsplit_once(dns_query(hash(url, 'md5') + '.hashbl.surbl.org', 'ipv4')[0], '.')"; + if eval "starts_with(result[0], '127.0.')" { + let "result" "result[1]"; + + if eval "result == 8" { + let "t.SURBL_HASHBL_PHISH" "1"; + } elsif eval "result == 16" { + let "t.SURBL_HASHBL_MALWARE" "1"; + } elsif eval "result == 64" { + let "t.SURBL_HASHBL_ABUSE" "1"; + } elsif eval "result == 128" { + let "t.SURBL_HASHBL_CRACKED" "1"; + } elsif eval "result[0] == '127.0.1'" { + let "t.SURBL_HASHBL_EMAIL" "1"; + } + } +} diff --git a/resources/config/sieve/received.sieve b/resources/config/sieve/received.sieve index 2a884d9f..3c14d57e 100644 --- a/resources/config/sieve/received.sieve +++ b/resources/config/sieve/received.sieve @@ -28,24 +28,9 @@ if eval "!is_ascii(rcvd_raw)" { let "t.RCVD_ILLEGAL_CHARS" "1"; } -# HELO contains 'user' -if eval "eq_ignore_case(env.helo_domain, 'user')" { - let "t.RCVD_HELO_USER" "1"; -} - -# Received from an IP address rather than a FQDN -if eval "is_ip_addr(env.helo_domain)" { - let "t.RCVD_IP_SPAM" "1"; -} - -# Received: HELO and IP do not match, but should -if eval "!is_empty(env.iprev.ptr) && !eq_ignore_case(env.helo_domain, env.iprev.ptr)" { - let "t.RCVD_HELO_IP_MISMATCH" "1"; -} - let "i" "0"; -let "recipients" "header.to:cc:bcc[*].addr[*]"; let "tls_count" "0"; +let "rcvd_from_ip" "0"; while "i < rcvd_count" { let "i" "i + 1"; let "helo_domain" "received_part(i, 'from')"; @@ -72,9 +57,9 @@ while "i < rcvd_count" { let "t.RCVD_HELO_USER" "1"; } - if eval "!t.RCVD_IP_SPAM && !is_empty(received_part(i, 'from.ip'))" { + if eval "!is_empty(received_part(i, 'from.ip'))" { # Received from an IP address rather than a FQDN - let "t.RCVD_IP_SPAM" "1"; + let "rcvd_from_ip" "rcvd_from_ip + 1"; } if eval "!is_empty(received_part(i, 'tls'))" { @@ -83,6 +68,21 @@ while "i < rcvd_count" { } } +if eval "rcvd_from_ip >= 2 || (rcvd_from_ip == 1 && is_ip_addr(env.helo_domain))" { + # Has two or more Received headers containing bare IP addresses + let "t.RCVD_DOUBLE_IP_SPAM" "1"; +} + +if eval "rcvd_count == 0" { + # One received header in a message (currently zero but one header will be added later by the MTA) + let "t.ONCE_RECEIVED" "1"; + + # Message has been directly delivered from MUA to local MX + if eval "header.User-Agent.exists || header.X-Mailer.exists" { + let "t.DIRECT_TO_MX" "1"; + } +} + # Received with TLS checks if eval "rcvd_count > 0 && tls_count == rcvd_count && !is_empty(env.tls.version)" { let "t.RCVD_TLS_ALL" "1"; diff --git a/resources/config/sieve/recipient.sieve b/resources/config/sieve/recipient.sieve index 93aed0b5..ad4d2640 100644 --- a/resources/config/sieve/recipient.sieve +++ b/resources/config/sieve/recipient.sieve @@ -17,9 +17,7 @@ if eval "!is_empty(to_raw)" { let "t.MISSING_TO" "1"; } -let "rcpt_addr" "dedup(to_lowercase(header.to:cc:bcc[*].addr[*]))"; -let "rcpt_addr_clean" "winnow(rcpt_addr)"; -let "rcpt_count" "count(rcpt_addr_clean)"; +let "rcpt_count" "count(recipients_clean)"; if eval "rcpt_count > 0" { if eval "rcpt_count == 1" { @@ -39,15 +37,14 @@ if eval "rcpt_count > 0" { } let "rcpt_name" "to_lowercase(header.to:cc:bcc[*].name[*])"; - let "i" "count(rcpt_addr)"; + let "i" "count(recipients)"; let "to_dn_count" "0"; let "to_dn_eq_addr_count" "0"; let "to_match_envrcpt" "0"; - let "subject" "to_lowercase(thread_name(header.subject))"; while "i != 0" { let "i" "i - 1"; - let "addr" "rcpt_addr[i]"; + let "addr" "recipients[i]"; if eval "!is_empty(addr)" { let "name" "rcpt_name[i]"; @@ -70,9 +67,9 @@ if eval "rcpt_count > 0" { # Check if the local part is present in the subject let "local_part" "email_part(addr, 'local')"; if eval "!is_empty(local_part)" { - if eval "contains(subject, addr)" { + if eval "contains(subject_lc, addr)" { let "t.RCPT_ADDR_IN_SUBJECT" "1"; - } elsif eval "len(local_part) > 3 && contains(subject, local_part)" { + } elsif eval "len(local_part) > 3 && contains(subject_lc, local_part)" { let "t.RCPT_LOCAL_IN_SUBJECT" "1"; } @@ -84,7 +81,7 @@ if eval "rcpt_count > 0" { # Check if it is an into to info if eval "!t.INFO_TO_INFO_LU && local_part == 'info' && - eq_ignore_case(email_part(header.from.addr, 'local'), 'info') && + from_local == 'info' && header.List-Unsubscribe.exists" { let "t.INFO_TO_INFO_LU" "1"; } @@ -93,15 +90,15 @@ if eval "rcpt_count > 0" { let "domain" "domain_part(email_part(addr, 'domain'), 'sld')"; if eval "!is_empty(domain)" { if eval "lookup('spam/free-domains', domain)" { - if eval "!t.FREEMAIL_TO && contains_ignore_case(header.to[*].addr[*], addr)" { + if eval "!t.FREEMAIL_TO && contains_ignore_case(recipients_to, addr)" { let "t.FREEMAIL_TO" "1"; - } elsif eval "!t.FREEMAIL_CC && contains_ignore_case(header.cc[*].addr[*], addr)" { + } elsif eval "!t.FREEMAIL_CC && contains_ignore_case(recipients_cc, addr)" { let "t.FREEMAIL_CC" "1"; } } elsif eval "lookup('spam/disposable-domains', domain)" { - if eval "!t.DISPOSABLE_TO && contains_ignore_case(header.to[*].addr[*], addr)" { + if eval "!t.DISPOSABLE_TO && contains_ignore_case(recipients_to, addr)" { let "t.DISPOSABLE_TO" "1"; - } elsif eval "!t.DISPOSABLE_CC && contains_ignore_case(header.cc[*].addr[*], addr)" { + } elsif eval "!t.DISPOSABLE_CC && contains_ignore_case(recipients_cc, addr)" { let "t.DISPOSABLE_CC" "1"; } } @@ -136,7 +133,7 @@ if eval "rcpt_count > 0" { let "i" "i - 1"; let "env_rcpt" "envelope.to[i]"; - if eval "!contains(rcpt_addr, env_rcpt) && env_rcpt != envelope.from" { + if eval "!contains(recipients, env_rcpt) && env_rcpt != envelope.from" { let "t.FORGED_RECIPIENTS" "1"; break; } @@ -147,13 +144,13 @@ if eval "rcpt_count > 0" { # Message from bounce and over 1 recipient if eval "rcpt_count > 1 && (is_empty(envelope.from) || - starts_with(envelope.from, 'postmaster@') || - starts_with(envelope.from, 'mailer-daemon@'))" { + envfrom_local == 'postmaster' || + envfrom_local == 'mailer-daemon')" { let "t.HFILTER_RCPT_BOUNCEMOREONE" "1"; } # Check for sorted recipients - if eval "rcpt_count >= 7 && sort(rcpt_addr_clean, false) == rcpt_addr_clean" { + if eval "rcpt_count >= 7 && sort(recipients_clean, false) == recipients_clean" { let "t.SORTED_RECIPS" "1"; } @@ -168,8 +165,8 @@ if eval "rcpt_count > 0" { let "j" "i"; while "j" { let "j" "j - 1"; - let "a" "rcpt_addr_clean[i]"; - let "b" "rcpt_addr_clean[j]"; + let "a" "recipients_clean[i]"; + let "b" "recipients_clean[j]"; if eval "levenshtein_distance(email_part(a, 'local'), email_part(b, 'local')) < 3" { let "hits" "hits + 1"; diff --git a/resources/config/sieve/replyto.sieve b/resources/config/sieve/replyto.sieve index 71e73819..45e41b23 100644 --- a/resources/config/sieve/replyto.sieve +++ b/resources/config/sieve/replyto.sieve @@ -1,4 +1,3 @@ - let "rto_raw" "to_lowercase(header.reply-to.raw)"; if eval "!is_empty(rto_raw)" { let "rto_addr" "to_lowercase(header.reply-to.addr)"; @@ -6,18 +5,16 @@ if eval "!is_empty(rto_raw)" { if eval "is_email(rto_addr)" { let "t.HAS_REPLYTO" "1"; - let "rto_domain" "domain_part(email_part(rto_addr, 'domain'), 'sld')"; - let "from_addr" "to_lowercase(header.from.addr)"; - let "from_domain" "domain_part(email_part(from_addr, 'domain'), 'sld')"; + let "rto_domain_sld" "domain_part(email_part(rto_addr, 'domain'), 'sld')"; if eval "eq_ignore_case(header.reply-to, header.from)" { let "t.REPLYTO_EQ_FROM" "1"; } else { - if eval "rto_domain == from_domain" { + if eval "rto_domain_sld == from_domain_sld" { let "t.REPLYTO_DOM_EQ_FROM_DOM" "1"; } else { let "is_from_list" "!is_empty(header.List-Unsubscribe:List-Id:X-To-Get-Off-This-List:X-List:Auto-Submitted[*])"; - if eval "!is_from_list && contains_ignore_case(header.to:cc:bcc[*].addr[*], rto_addr)" { + if eval "!is_from_list && contains_ignore_case(recipients_clean, rto_addr)" { let "t.REPLYTO_EQ_TO_ADDR" "1"; } else { let "t.REPLYTO_DOM_NEQ_FROM_DOM" "1"; @@ -32,7 +29,7 @@ if eval "!is_empty(rto_raw)" { while "i != 0" { let "i" "i - 1"; - if eval "domain_part(email_part(envelope.to[i], 'domain'), 'sld') == from_domain" { + if eval "domain_part(email_part(envelope.to[i], 'domain'), 'sld') == from_domain_sld" { let "found_domain" "1"; break; } @@ -49,12 +46,12 @@ if eval "!is_empty(rto_raw)" { } } - if eval "lookup('spam/free-domains', rto_domain)" { + if eval "lookup('spam/free-domains', rto_domain_sld)" { let "t.FREEMAIL_REPLYTO" "1"; - if eval "rto_domain != from_domain && lookup('spam/free-domains', from_domain)" { + if eval "rto_domain_sld != from_domain_sld && lookup('spam/free-domains', from_domain_sld)" { let "t.FREEMAIL_REPLYTO_NEQ_FROM_DOM" "1"; } - } elsif eval "lookup('spam/disposable-domains', rto_domain)" { + } elsif eval "lookup('spam/disposable-domains', rto_domain_sld)" { let "t.DISPOSABLE_REPLYTO" "1"; } diff --git a/resources/config/sieve/subject.sieve b/resources/config/sieve/subject.sieve index a9c7db01..7683c234 100644 --- a/resources/config/sieve/subject.sieve +++ b/resources/config/sieve/subject.sieve @@ -1,80 +1,78 @@ -let "thread_name" "thread_name(header.subject)"; -let "subject_lcase" "to_lowercase(header.subject)"; -let "raw_subject_lcase" "to_lowercase(header.subject.raw)"; -let "is_ascii_subject" "is_ascii(subject_lcase)"; +let "raw_subject_lc" "to_lowercase(header.subject.raw)"; +let "is_ascii_subject" "is_ascii(subject_lc)"; -if eval "len(thread_name) >= 10 && count(tokenize_words(thread_name)) > 1 && is_uppercase(thread_name)" { +if eval "len(thread_name) >= 10 && count(tokenize(thread_name, 'words')) > 1 && is_uppercase(thread_name)" { # Subject contains mostly capital letters let "t.SUBJ_ALL_CAPS" "1"; } if eval "count_chars(thread_name) > 200" { # Subject is very long - let "t.SUBJ_VERY_LONG" "1"; + let "t.LONG_SUBJ" "1"; } -if eval "!is_empty(tokenize_url(subject_lcase, true))" { +if eval "!is_empty(tokenize(subject_lc, 'uri_strict'))" { # Subject contains a URL let "t.URL_IN_SUBJECT" "1"; } -if eval "!is_ascii(raw_subject_lcase) && !env.param.smtputf8 && env.param.body != '8bitmime' && env.param.body != 'binarymime'" { +if eval "!is_ascii(raw_subject_lc) && !env.param.smtputf8 && env.param.body != '8bitmime' && env.param.body != 'binarymime'" { # Subject needs encoding let "t.SUBJECT_NEEDS_ENCODING" "1"; } -if not exists "Subject" { +if eval "!header.Subject.exists" { # Missing subject header let "t.MISSING_SUBJECT" "1"; -} elsif eval "is_empty(trim(subject_lcase))" { +} elsif eval "is_empty(trim(subject_lc))" { # Subject is empty let "t.EMPTY_SUBJECT" "1"; } -if eval "is_ascii(subject_lcase) && contains(raw_subject_lcase, '=?') && contains(raw_subject_lcase, '?=')" { - if eval "contains(raw_subject_lcase, '?q?')" { +if eval "is_ascii(subject_lc) && contains(raw_subject_lc, '=?') && contains(raw_subject_lc, '?=')" { + if eval "contains(raw_subject_lc, '?q?')" { # Subject header is unnecessarily encoded in quoted-printable let "t.SUBJ_EXCESS_QP" "1"; - } elsif eval "contains(raw_subject_lcase, '?b?')" { + } elsif eval "contains(raw_subject_lc, '?b?')" { # Subject header is unnecessarily encoded in base64 let "t.SUBJ_EXCESS_BASE64" "1"; } } -if eval "starts_with(subject_lcase, 're:') && is_empty(header.in-reply-to) && is_empty(header.references)" { +if eval "starts_with(subject_lc, 're:') && is_empty(header.in-reply-to) && is_empty(header.references)" { # Fake reply let "t.FAKE_REPLY" "1"; } -let "subject_lcase_trim" "trim_end(subject_lcase)"; -if eval "subject_lcase != subject_lcase_trim" { +let "subject_lc_trim" "trim_end(subject_lc)"; +if eval "subject_lc != subject_lc_trim" { # Subject ends with space characters let "t.SUBJECT_ENDS_SPACES" "1"; } -if eval "contains(subject_lcase, '$') || - contains(subject_lcase, '€') || - contains(subject_lcase, '£') || - contains(subject_lcase, '¥')" { +if eval "contains(subject_lc, '$') || + contains(subject_lc, '€') || + contains(subject_lc, '£') || + contains(subject_lc, '¥')" { # Subject contains currency symbols let "t.SUBJECT_HAS_CURRENCY" "1"; } -if eval "ends_with(subject_lcase_trim, '!')" { +if eval "ends_with(subject_lc_trim, '!')" { # Subject ends with an exclamation mark let "t.SUBJECT_ENDS_EXCLAIM" "1"; -} elsif eval "ends_with(subject_lcase_trim, '?')" { +} elsif eval "ends_with(subject_lc_trim, '?')" { # Subject ends with a question mark let "t.SUBJECT_ENDS_QUESTION" "1"; } -if eval "contains(subject_lcase_trim, '!')" { +if eval "contains(subject_lc_trim, '!')" { # Subject contains an exclamation mark let "t.SUBJECT_HAS_EXCLAIM" "1"; } -if eval "contains(subject_lcase_trim, '?')" { +if eval "contains(subject_lc_trim, '?')" { # Subject contains a question mark let "t.SUBJECT_HAS_QUESTION" "1"; } diff --git a/resources/config/sieve/url.sieve b/resources/config/sieve/url.sieve index 5373f8cc..2377b8a7 100644 --- a/resources/config/sieve/url.sieve +++ b/resources/config/sieve/url.sieve @@ -1,9 +1,4 @@ -let "text_body" "body.to_text"; -let "body_urls" "tokenize_url(text_body, false)"; -let "html_body_urls" "html_attrs(body.html, '', ['href', 'src'])"; -let "urls" "dedup(tokenize_url(header.subject, false) + body_urls + html_body_urls)"; - -if eval "(count(body_urls) == 1 || count(html_body_urls) == 1) && count(tokenize_words(text_body)) == 0" { +if eval "(count(body_urls) == 1 || count(html_body_urls) == 1) && count(tokenize(text_body, 'words')) == 0" { let "t.HFILTER_URL_ONLY" "1"; } @@ -62,12 +57,8 @@ while "i > 0" { if eval "!is_ip" { if eval "!is_ascii(host)" { let "host_cured" "cure_text(host)"; - if eval "host_lc != host_cured" { - if eval "dns_exists(host_cured, 'ip')" { - let "t.OMOGRAPH_URL" "1"; - } else { - let "t.CONFUSABLE_URL" "1"; - } + if eval "host_lc != host_cured && dns_exists(host_cured, 'ip')" { + let "t.OMOGRAPH_URL" "1"; } if eval "!is_single_script(host)" { @@ -106,6 +97,15 @@ while "i > 0" { # Message contains URI with a hidden path let "t.URI_HIDDEN_PATH" "1"; } + + # Phishing checks + if eval "lookup('spam/phishing-open', url)" { + let "t.PHISHED_OPENPHISH" "1"; + } + if eval "lookup('spam/phishing-tank', url)" { + let "t.PHISHED_PHISHTANK" "1"; + } + } else { # URL could not be parsed let "t.R_SUSPICIOUS_URL" "1"; diff --git a/tests/resources/smtp/antispam/dmarc.test b/tests/resources/smtp/antispam/dmarc.test new file mode 100644 index 00000000..247e9109 --- /dev/null +++ b/tests/resources/smtp/antispam/dmarc.test @@ -0,0 +1,151 @@ +expect DMARC_NA R_SPF_NA R_DKIM_NA ARC_NA + +Subject: test + +Test + + +spf.result pass +dkim.result pass +arc.result pass +dmarc.result pass +expect DKIM_SIGNED ARC_SIGNED R_DKIM_ALLOW R_SPF_ALLOW ARC_ALLOW DMARC_POLICY_ALLOW + +DKIM-Signature: abc +ARC-Seal: xyz +Subject: test + +Test + + +spf.result fail +dkim.result fail +arc.result fail +dmarc.result fail +dmarc.policy quarantine +expect R_SPF_FAIL ARC_REJECT R_DKIM_REJECT DMARC_POLICY_QUARANTINE + +Subject: test + +Test + + +spf.result neutral +dkim.result temperror +arc.result permerror +dmarc.result fail +dmarc.policy reject +expect R_DKIM_TEMPFAIL R_SPF_NEUTRAL ARC_INVALID DMARC_POLICY_REJECT + +Subject: test + +Test + + +spf.result softfail +dkim.result permerror +arc.result temperror +dmarc.result permerror +expect ARC_DNSFAIL DMARC_BAD_POLICY R_DKIM_PERMFAIL R_SPF_SOFTFAIL + +Subject: test + +Test + +dmarc.result pass +dkim.result pass +spf.result pass +expect ALLOWLIST_DMARC DMARC_POLICY_ALLOW R_DKIM_ALLOW R_SPF_ALLOW ARC_NA + +From: user@dmarc-allow.org +Subject: test + +Test + + +dmarc.result fail +dkim.result fail +spf.result fail +expect BLOCKLIST_DMARC DMARC_POLICY_SOFTFAIL R_DKIM_REJECT R_SPF_FAIL ARC_NA + +From: user@dmarc-allow.org +Subject: test + +Test + + +dkim.result pass +dkim.domains spf-dkim-allow.org +spf.result pass +expect ALLOWLIST_SPF_DKIM R_DKIM_ALLOW R_SPF_ALLOW ARC_NA DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test + +dkim.result pass +spf.result pass +arc.result pass +expect ALLOWLIST_SPF_DKIM R_DKIM_ALLOW R_SPF_ALLOW ARC_ALLOW DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test + +spf.result pass +dkim.result fail +expect ALLOWLIST_SPF BLOCKLIST_DKIM R_DKIM_REJECT R_SPF_ALLOW ARC_NA DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test + +spf.result pass +dkim.result temperror +expect ALLOWLIST_SPF R_DKIM_TEMPFAIL R_SPF_ALLOW ARC_NA DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test + +dkim.result pass +dkim.domains spf-dkim-allow.org +spf.result fail +expect BLOCKLIST_SPF ALLOWLIST_DKIM R_DKIM_ALLOW R_SPF_FAIL ARC_NA DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test + +dkim.result pass +dkim.domains spf-dkim-allow.org +spf.result temperror +expect ALLOWLIST_DKIM R_DKIM_ALLOW R_SPF_DNSFAIL ARC_NA DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test + +dkim.result fail +spf.result fail +expect BLOCKLIST_SPF_DKIM R_DKIM_REJECT R_SPF_FAIL ARC_NA DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test + +dkim.result temperror +spf.result temperror +expect R_DKIM_TEMPFAIL R_SPF_DNSFAIL ARC_NA DMARC_NA + +From: user@spf-dkim-allow.org +Subject: test + +Test diff --git a/tests/resources/smtp/antispam/from.test b/tests/resources/smtp/antispam/from.test index 237a3002..6f115c6f 100644 --- a/tests/resources/smtp/antispam/from.test +++ b/tests/resources/smtp/antispam/from.test @@ -1,14 +1,14 @@ expect MISSING_FROM -X-From: hello@world.com +X-From: hello@domain.org Test -envelope_from hello@world.com +envelope_from hello@domain.org expect MULTIPLE_FROM FROM_EQ_ENVFROM FROM_NO_DN -From: hello@world.com -From: hello@world.com +From: hello@domain.org +From: hello@domain.org Test @@ -19,52 +19,52 @@ From: test Test -envelope_from www-data@domain.com +envelope_from www-data@domain.org expect FROM_SERVICE_ACCT ENVFROM_SERVICE_ACCT FROM_HAS_DN FROM_EQ_ENVFROM -From: "WWW DATA" +From: "WWW DATA" Test -envelope_from hello@world.com +envelope_from hello@domain.org expect FROM_DN_EQ_ADDR FROM_EQ_ENVFROM -From: "hello@world.com" +From: "hello@domain.org" Test -envelope_from hello@world.com +envelope_from hello@domain.org expect SPOOF_DISPLAY_NAME FROM_EQ_ENVFROM FROM_HAS_DN -From: "hello@otherworld.com" +From: "hello@otherdomain.org" Test -envelope_from hello@world.co.uk +envelope_from hello@domain.co.uk expect FROM_NEQ_DISPLAY_NAME FROM_EQ_ENVFROM FROM_HAS_DN -From: "hello@other.world.co.uk" +From: "hello@other.domain.co.uk" Test -helo_domain mx.world.co.uk +helo_domain mx.domain.co.uk expect FROM_EQ_ENVFROM FROM_NEQ_DISPLAY_NAME FROM_HAS_DN HFILTER_FROM_BOUNCE -From: "postmaster@mx.world.co.uk" +From: "postmaster@mx.domain.co.uk" Test -helo_domain mx.world.co.uk +helo_domain mx.domain.co.uk expect FROM_EQ_ENVFROM FROM_HAS_DN HFILTER_FROM_BOUNCE -From: "Mailer Daemon" +From: "Mailer Daemon" Test -envelope_from mrspammer@domain.com +envelope_from mrspammer@domain.org expect FROM_NAME_HAS_TITLE FROM_NAME_EXCESS_SPACE FROM_EQ_ENVFROM FROM_HAS_DN -From: "Mr. Money Maker" +From: "Mr. Money Maker" Test @@ -172,3 +172,10 @@ expect DISPOSABLE_ENVFROM DISPOSABLE_FROM FROM_EQ_ENVFROM FROM_NO_DN From: hello@custom.disposable.org Test + +envelope_from hello@nomx.org +expect HFILTER_FROMHOST_NORES_A_OR_MX FROM_EQ_ENVFROM FROM_NO_DN + +From: hello@nomx.org + +Test diff --git a/tests/resources/smtp/antispam/headers.test b/tests/resources/smtp/antispam/headers.test index fa1eea47..c496aac1 100644 --- a/tests/resources/smtp/antispam/headers.test +++ b/tests/resources/smtp/antispam/headers.test @@ -56,3 +56,24 @@ expect HEADER_EMPTY_DELIMITER Subject:test Test + +expect MAILLIST + +List-Archive: 1 +List-Owner: 1 +List-Help: 1 +List-Post: 1 +X-Loop: 1 +List-Id: 1 +Subject: test + +Test + +expect MAILLIST HAS_LIST_UNSUB + +List-Id: 1 +List-Subscribe: 1 +List-Unsubscribe: 1 +Subject: test + +Test diff --git a/tests/resources/smtp/antispam/helo.test b/tests/resources/smtp/antispam/helo.test new file mode 100644 index 00000000..046d00fb --- /dev/null +++ b/tests/resources/smtp/antispam/helo.test @@ -0,0 +1,65 @@ +helo_domain localhost +expect HFILTER_HELO_NOT_FQDN + +Subject: test + +test + +helo_domain user +expect RCVD_HELO_USER HFILTER_HELO_NOT_FQDN + +Subject: test + +test + +helo_domain 8.8.8.8 +remote_ip 8.8.8.8 +expect HFILTER_HELO_BAREIP + +Subject: test + +test + +helo_domain 8.8.8.8 +remote_ip 1.1.1.1 +expect HFILTER_HELO_IP_A HFILTER_HELO_BAREIP + +Subject: test + +test + +helo_domain domain.org +iprev.ptr domain.org +remote_ip 1.1.1.1 +expect + +Subject: test + +test + +helo_domain domain.org +iprev.ptr otherdomain.org +remote_ip 1.1.1.1 +expect HFILTER_HOSTNAME_UNKNOWN + +Subject: test + +test + +helo_domain otherdomain.org +iprev.ptr otherdomain.org +remote_ip 1.1.1.1 +expect HFILTER_HELO_NORES_A_OR_MX + +Subject: test + +test + +helo_domain otherdomain.org +iprev.ptr otherdomain.net +remote_ip 1.1.1.1 +expect HFILTER_HELO_NORES_A_OR_MX HFILTER_HOSTNAME_UNKNOWN + +Subject: test + +test diff --git a/tests/resources/smtp/antispam/ip.test b/tests/resources/smtp/antispam/ip.test new file mode 100644 index 00000000..12ecb160 --- /dev/null +++ b/tests/resources/smtp/antispam/ip.test @@ -0,0 +1,17 @@ +remote_ip 8.8.8.8 +iprev.result temperror +expect RDNS_DNSFAIL + +Subject: test + +Test + + +remote_ip 8.8.8.8 +iprev.result fail +expect RDNS_NONE + +Subject: test + +Test + diff --git a/tests/resources/smtp/antispam/mime.test b/tests/resources/smtp/antispam/mime.test index 22a8ebee..07c925ba 100644 --- a/tests/resources/smtp/antispam/mime.test +++ b/tests/resources/smtp/antispam/mime.test @@ -352,3 +352,146 @@ Content-Transfer-Encoding: 7bit this is a test --boundary-- + +expect R_MIXED_CHARSET + +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit +MIME-Version: 1.0 + +Tést 孔子 + + +expect MIME_BAD_EXTENSION MIME_GOOD HAS_ATTACHMENT + +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="boundary" + +--boundary +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +simple text + +--boundary +Content-Type: text/html; charset="utf-8" +Content-Disposition: attachment; filename="test.html" +Content-Transfer-Encoding: 8bit + + +

hello world

+ +--boundary-- + + +expect MIME_BAD_ATTACHMENT HAS_ATTACHMENT + +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="boundary" + +--boundary +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +simple text + +--boundary +Content-Type: text/x-plain; charset="utf-8" +Content-Disposition: attachment; filename="test.txt" +Content-Transfer-Encoding: 8bit + +hello world +--boundary-- + + +expect HAS_ATTACHMENT + +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="boundary" + +--boundary +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +simple text + +--boundary +Content-Type: text/plain; charset="utf-8" +Content-Disposition: attachment; filename="test.txt" +Content-Transfer-Encoding: 8bit + +hello world +--boundary-- + + +expect MIME_DOUBLE_BAD_EXTENSION MIME_GOOD HAS_ATTACHMENT + +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="boundary" + +--boundary +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +simple text + +--boundary +Content-Type: text/html; charset="utf-8" +Content-Disposition: attachment; filename="test.html.html" +Content-Transfer-Encoding: 8bit + + +

hello world

+ +--boundary-- + + +expect MIME_ARCHIVE_IN_ARCHIVE MIME_GOOD HAS_ATTACHMENT + +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="boundary" + +--boundary +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +simple text + +--boundary +Content-Type: application/zip +Content-Disposition: attachment; filename="test.zip.zip" +Content-Transfer-Encoding: base64 + +UEsDBAoAAAAAALN6RlcAAAAAAAAAAAAAAAAIABwAdGVzdC5iaW5VVAkAA+IJI +GXiCSBldXgLAAEE9QEAAAQUAAAAUEsBAh4DCgAAAAAAs3pGVwAAAAAAAAAAAA +AAAAgAGAAAAAAAAAAAAKSBAAAAAHRlc3QuYmluVVQFAAPiCSBldXgLAAEE9QE +AAAQUAAAAUEsFBgAAAAABAAEATgAAAEIAAAAAAA== +--boundary-- + +expect MIME_BAD HAS_ATTACHMENT + +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="boundary" + +--boundary +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +simple text + +--boundary +Content-Type: image/png +Content-Disposition: attachment; filename="test.png" +Content-Transfer-Encoding: base64 + +UEsDBAoAAAAAALN6RlcAAAAAAAAAAAAAAAAIABwAdGVzdC5iaW5VVAkAA+IJI +GXiCSBldXgLAAEE9QEAAAQUAAAAUEsBAh4DCgAAAAAAs3pGVwAAAAAAAAAAAA +AAAAgAGAAAAAAAAAAAAKSBAAAAAHRlc3QuYmluVVQFAAPiCSBldXgLAAEE9QE +AAAQUAAAAUEsFBgAAAAABAAEATgAAAEIAAAAAAA== +--boundary-- diff --git a/tests/resources/smtp/antispam/rbl.test b/tests/resources/smtp/antispam/rbl.test new file mode 100644 index 00000000..cc5a47f3 --- /dev/null +++ b/tests/resources/smtp/antispam/rbl.test @@ -0,0 +1,30 @@ +remote_ip 192.168.0.1 +expect RCVD_IN_DNSWL_LOW RBL_SPAMHAUS + +Subject: test + +test + + +remote_ip 192.168.0.2 +expect RBL_SENDERSCORE RBL_NIXSPAM RBL_SEM RBL_SPAMHAUS_SBL RBL_BARRACUDA RBL_BLOCKLISTDE RBL_VIRUSFREE_BOTNET RBL_SPAMCOP RCVD_IN_DNSWL_MED + +Subject: test + +test + + +remote_ip 192.168.0.14 +expect RWL_MAILSPIKE_NEUTRAL RECEIVED_SPAMHAUS_SBL RBL_SPAMHAUS RECEIVED_SPAMHAUS_XBL RECEIVED_BLOCKLISTDE RCVD_IN_DNSWL_MED + +Received: from Agni (localhost [192.168.0.5]) (TLS: TLSv1/SSLv3, 168bits,DES-CBC3-SHA) by agni.forevermore.net + with esmtp; Mon, 28 Oct 2002 14:48:52 -0800 +Received: from [192.168.0.14] (79.sub-174-252-72.myvzw.com [192.168.0.8]) by mx.google.com + with ESMTPS id m16sm345129qck.28.2011.06.15.07.42.02 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 15 Jun 2011 07:42:08 -0700 (PDT) +Received: from user (192.168.0.2) by DB6PR07MB3384.eurprd07.prod.outlook.com ([192.168.0.2]) + with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.1143.11; Thu, 13 Sep 2018 14:47:44 +0000 +Subject: test + +test + + diff --git a/tests/resources/smtp/antispam/received.test b/tests/resources/smtp/antispam/received.test index afdc7bb1..8f430440 100644 --- a/tests/resources/smtp/antispam/received.test +++ b/tests/resources/smtp/antispam/received.test @@ -29,39 +29,10 @@ Received: from BAY0-HMR08.bay0.hótmail.com (bay0-hmr08.bay0.hótmail.com [65.54 with ESMTP id h2DBpvs24047 for ; Thu, 13 Mar 2003 11:51:57 GMT test - -helo_domain user -expect RCVD_HELO_USER RCVD_COUNT_ZERO RCVD_NO_TLS_LAST -Subject: test - -test - -helo_domain 100.21.30.40 -expect RCVD_IP_SPAM RCVD_COUNT_ZERO RCVD_NO_TLS_LAST - -Subject: test - -test - -iprev.ptr host.otherdomain.com -helo_domain HOST.DOMAIN.COM -expect RCVD_HELO_IP_MISMATCH RCVD_COUNT_ZERO RCVD_NO_TLS_LAST - -Subject: test - -test - -iprev.ptr host.domain.com -helo_domain HOST.DOMAIN.COM -expect RCVD_COUNT_ZERO RCVD_NO_TLS_LAST - -Subject: test - -test tls.version TLVv1.3 -expect RCVD_TLS_ALL RCVD_HELO_USER RCVD_IP_SPAM FORGED_RCVD_TRAIL PREVIOUSLY_DELIVERED RCVD_COUNT_FIVE +expect RCVD_TLS_ALL RCVD_HELO_USER RCVD_DOUBLE_IP_SPAM FORGED_RCVD_TRAIL PREVIOUSLY_DELIVERED RCVD_COUNT_FIVE Received: from Agni (localhost [::ffff:127.0.0.1]) (TLS: TLSv1/SSLv3, 168bits,DES-CBC3-SHA) by agni.forevermore.net with esmtp; Mon, 28 Oct 2002 14:48:52 -0800 @@ -69,9 +40,18 @@ Received: from [10.231.252.223] (79.sub-174-252-72.myvzw.com [174.252.72.79]) by with ESMTPS id m16sm345129qck.28.2011.06.15.07.42.02 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 15 Jun 2011 07:42:08 -0700 (PDT) Received: from user (10.175.233.33) by DB6PR07MB3384.eurprd07.prod.outlook.com (10.175.234.11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.1143.11; Thu, 13 Sep 2018 14:47:44 +0000 -Received: from wmail.tana.it (wmail.tana.it [94.198.96.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) +Received: from [94.198.96.74] (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 10B7AC151535 for ; Mon, 28 Aug 2023 02:21:23 -0700 (PDT) To: user@domain.com Subject: test test + + +expect DIRECT_TO_MX ONCE_RECEIVED RCVD_COUNT_ZERO RCVD_NO_TLS_LAST + +To: user@domain.com +X-Mailer: MUA +Subject: test + +test diff --git a/tests/resources/smtp/antispam/subject.test b/tests/resources/smtp/antispam/subject.test index d0896762..945015e0 100644 --- a/tests/resources/smtp/antispam/subject.test +++ b/tests/resources/smtp/antispam/subject.test @@ -4,7 +4,7 @@ Subject: HELLO WORLD Test -expect SUBJ_VERY_LONG +expect LONG_SUBJ Subject: this is an extremely long subject line that should be truncated to 80 characters and folded diff --git a/tests/resources/smtp/antispam/url.test b/tests/resources/smtp/antispam/url.test index 56a5d39e..a8489afb 100644 --- a/tests/resources/smtp/antispam/url.test +++ b/tests/resources/smtp/antispam/url.test @@ -16,13 +16,13 @@ Subject: test my site is https://192.168.1.1 -expect CONFUSABLE_URL +expect OMOGRAPH_URL Subject: test my site is https://xn--youtue-tg7b.com -expect R_MIXED_CHARSET_URL CONFUSABLE_URL +expect R_MIXED_CHARSET_URL Subject: test @@ -40,9 +40,9 @@ Subject: nested redirect login to https://redirect.com/?https://redirect.org/?https://redirect.net/?https://redirect.io/?https://redirect.me/?https://redirect.com -expect REDIRECTOR_URL CONFUSABLE_URL +expect REDIRECTOR_URL OMOGRAPH_URL -Subject: redirect to confusable +Subject: redirect to omograph login to https://www.redirect.com/?https://xn--twiter-507b.com @@ -73,9 +73,12 @@ Subject: plain test http://url.com/Well-known/../assetlinks.json http://wp.com/WP-content/content.pdf + -expect REDIRECTOR_URL +expect PHISHED_OPENPHISH PHISHED_PHISHTANK -Subject: test +Subject: plain test + +https://phishing-open.org +https://phishing-tank.com -go to my site https://tinyurl.com/4fbkcb6m diff --git a/tests/resources/smtp/lists/disposable-domains.txt b/tests/resources/smtp/lists/disposable-domains.txt deleted file mode 100644 index b28ff8da..00000000 --- a/tests/resources/smtp/lists/disposable-domains.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Disposable domains -guerrillamail.com -*.disposable.org diff --git a/tests/resources/smtp/lists/free-domains.txt b/tests/resources/smtp/lists/free-domains.txt deleted file mode 100644 index 7fc45cab..00000000 --- a/tests/resources/smtp/lists/free-domains.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Free domains -gmail.com -googlemail.com -yahoomail.com -*.freemail.org diff --git a/tests/resources/smtp/lists/redirectors.txt b/tests/resources/smtp/lists/redirectors.txt deleted file mode 100644 index 0d31b158..00000000 --- a/tests/resources/smtp/lists/redirectors.txt +++ /dev/null @@ -1,8 +0,0 @@ -bit.ly -redirect.io -redirect.me -redirect.org -redirect.com -redirect.net -t.ly -tinyurl.com diff --git a/tests/src/smtp/inbound/antispam.rs b/tests/src/smtp/inbound/antispam.rs index f0218cad..41058e7f 100644 --- a/tests/src/smtp/inbound/antispam.rs +++ b/tests/src/smtp/inbound/antispam.rs @@ -10,7 +10,7 @@ use std::{ use crate::smtp::session::TestSession; use ahash::AHashMap; use directory::config::ConfigDirectory; -use mail_auth::{dmarc::Policy, DkimResult, DmarcResult, IprevResult, SpfResult}; +use mail_auth::{dmarc::Policy, DkimResult, DmarcResult, IprevResult, SpfResult, MX}; use sieve::runtime::Variable; use smtp::{ config::{scripts::ConfigSieve, ConfigContext, IfBlock}, @@ -57,17 +57,48 @@ type = "memory" [directory."spam".lookup."free-domains"] type = "glob" comment = '#' -values = ["file://%LIST_PATH%/free-domains.txt"] +values = ["gmail.com", "googlemail.com", "yahoomail.com", "*.freemail.org"] [directory."spam".lookup."disposable-domains"] type = "glob" comment = '#' -values = ["file://%LIST_PATH%/disposable-domains.txt"] +values = ["guerrillamail.com", "*.disposable.org"] [directory."spam".lookup."redirectors"] type = "glob" comment = '#' -values = ["file://%LIST_PATH%/redirectors.txt"] +values = ["bit.ly", "redirect.io", "redirect.me", "redirect.org", + "redirect.com", "redirect.net", "t.ly", "tinyurl.com"] + +[directory."spam".lookup."dmarc-allow"] +type = "glob" +comment = '#' +values = ["dmarc-allow.org"] + +[directory."spam".lookup."spf-dkim-allow"] +type = "glob" +comment = '#' +values = ["spf-dkim-allow.org"] + +[directory."spam".lookup."mime-types"] +type = "map" +comment = '#' +values = ["html text/html|BAD", + "pdf application/pdf|NZ", + "txt text/plain|message/disposition-notification|text/rfc822-headers", + "zip AR", + "js BAD|NZ", + "hta BAD|NZ"] + +[directory."spam".lookup."phishing-open"] +type = "glob" +comment = '#' +values = ["*://phishing-open.org", "*://phishing-open.com"] + +[directory."spam".lookup."phishing-tank"] +type = "glob" +comment = '#' +values = ["*://phishing-tank.com", "*://phishing-tank.org"] [resolver] public-suffix = "file://%LIST_PATH%/public-suffix.dat" @@ -98,6 +129,10 @@ async fn antispam() { "mime", "headers", "url", + "dmarc", + "ip", + "helo", + "rbl", ]; let mut core = SMTP::test(); let qr = core.init_test_queue("smtp_antispam_test"); @@ -118,14 +153,11 @@ async fn antispam() { .to_path_buf() .join("resources") .join("config") - .join("sieve") - .to_str() - .unwrap() - .to_string(); + .join("sieve"); + let prelude = fs::read_to_string(base_path.join("prelude.sieve")).unwrap(); for test_name in tests { - config.push_str(&format!( - "{test_name} = \"file://{base_path}/{test_name}.sieve\"\n" - )); + let script = fs::read_to_string(base_path.join(format!("{test_name}.sieve"))).unwrap(); + config.push_str(&format!("{test_name} = '''{prelude}\n{script}\n'''\n")); } // Parse config @@ -137,16 +169,28 @@ async fn antispam() { config.rcpt.relay = IfBlock::new(true); // Add mock DNS entries - core.resolvers.dns.ipv4_add( - "bank.com", - vec!["127.0.0.1".parse().unwrap()], - Instant::now() + Duration::from_secs(100), - ); - core.resolvers.dns.ipv4_add( - "apple.com", - vec!["127.0.0.1".parse().unwrap()], - Instant::now() + Duration::from_secs(100), - ); + for domain in ["bank.com", "apple.com", "youtube.com", "twitter.com"] { + core.resolvers.dns.ipv4_add( + domain, + vec!["127.0.0.1".parse().unwrap()], + Instant::now() + Duration::from_secs(100), + ); + } + for mx in [ + "domain.org", + "domain.co.uk", + "gmail.com", + "custom.disposable.org", + ] { + core.resolvers.dns.mx_add( + mx, + vec![MX { + exchanges: vec!["127.0.0.1".parse().unwrap()], + preference: 10, + }], + Instant::now() + Duration::from_secs(100), + ); + } let core = Arc::new(core); @@ -167,7 +211,7 @@ async fn antispam() { while has_more { let mut message = String::new(); let mut in_params = true; - let mut variables = HashMap::new(); + let mut variables: HashMap> = HashMap::new(); let mut expected_variables = AHashMap::new(); // Build session @@ -193,19 +237,29 @@ async fn antispam() { "spf.result" | "spf_ehlo.result" => { variables.insert( param.to_string(), - SpfResult::from_str(value).as_str().to_string(), + SpfResult::from_str(value).as_str().to_string().into(), ); } "iprev.result" => { variables.insert( param.to_string(), - IprevResult::from_str(value).as_str().to_string(), + IprevResult::from_str(value).as_str().to_string().into(), ); } "dkim.result" | "arc.result" => { variables.insert( param.to_string(), - DkimResult::from_str(value).as_str().to_string(), + DkimResult::from_str(value).as_str().to_string().into(), + ); + } + "dkim.domains" => { + variables.insert( + param.to_string(), + value + .split_ascii_whitespace() + .map(|s| Variable::String(s.to_string())) + .collect::>() + .into(), ); } "envelope_from" => { @@ -218,18 +272,18 @@ async fn antispam() { .push(SessionAddress::new(value.to_string())); } "iprev.ptr" | "dmarc.from" => { - variables.insert(param.to_string(), value.to_string()); + variables.insert(param.to_string(), value.to_string().into()); } "dmarc.result" => { variables.insert( param.to_string(), - DmarcResult::from_str(value).as_str().to_string(), + DmarcResult::from_str(value).as_str().to_string().into(), ); } "dmarc.policy" => { variables.insert( param.to_string(), - Policy::from_str(value).as_str().to_string(), + Policy::from_str(value).as_str().to_string().into(), ); } "expect" => { @@ -249,7 +303,7 @@ async fn antispam() { })); } _ if param.starts_with("param.") | param.starts_with("tls.") => { - variables.insert(param.to_string(), value.to_string()); + variables.insert(param.to_string(), value.to_string().into()); } _ => panic!("Invalid parameter {param:?}"), }