From 0a640c37581d76cd2f00936b074ef3aab9d6686c Mon Sep 17 00:00:00 2001 From: mdecimus Date: Fri, 15 Sep 2023 19:29:58 +0200 Subject: [PATCH] Sieve antispam functions, bump OpenTelemetry version --- Cargo.lock | 737 ++++++++++++-------- crates/antispam/src/import/spamassassin.rs | 103 +-- crates/jmap/Cargo.toml | 2 +- crates/smtp/Cargo.toml | 1 + crates/smtp/src/config/scripts.rs | 2 +- crates/smtp/src/scripts/exec.rs | 17 +- crates/smtp/src/scripts/functions.rs | 459 ------------ crates/smtp/src/scripts/functions/array.rs | 137 ++++ crates/smtp/src/scripts/functions/email.rs | 125 ++++ crates/smtp/src/scripts/functions/header.rs | 105 +++ crates/smtp/src/scripts/functions/html.rs | 358 ++++++++++ crates/smtp/src/scripts/functions/image.rs | 71 ++ crates/smtp/src/scripts/functions/mod.rs | 137 ++++ crates/smtp/src/scripts/functions/text.rs | 312 +++++++++ crates/utils/Cargo.toml | 8 +- resources/config/sieve/antispam.sieve | 571 ++++++++++++++- resources/config/smtp.toml | 1 - tests/src/smtp/inbound/antispam.rs | 174 ++++- 18 files changed, 2471 insertions(+), 849 deletions(-) delete mode 100644 crates/smtp/src/scripts/functions.rs create mode 100644 crates/smtp/src/scripts/functions/array.rs create mode 100644 crates/smtp/src/scripts/functions/email.rs create mode 100644 crates/smtp/src/scripts/functions/header.rs create mode 100644 crates/smtp/src/scripts/functions/html.rs create mode 100644 crates/smtp/src/scripts/functions/image.rs create mode 100644 crates/smtp/src/scripts/functions/mod.rs create mode 100644 crates/smtp/src/scripts/functions/text.rs diff --git a/Cargo.lock b/Cargo.lock index d10953f8..72e89b4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,9 +137,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" @@ -184,9 +184,9 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "argon2" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e554a8638bdc1e4eae9984845306cc95f8a9208ba8d49c3859fd958b46774d" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" dependencies = [ "base64ct", "blake2", @@ -228,7 +228,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.28", + "time", ] [[package]] @@ -256,13 +256,13 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -284,7 +284,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -295,7 +295,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -342,7 +342,7 @@ dependencies = [ "rust-ini", "serde", "thiserror", - "time 0.3.28", + "time", "url", ] @@ -435,9 +435,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" @@ -467,26 +467,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.63.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 1.0.109", -] - [[package]] name = "bindgen" version = "0.64.0" @@ -520,13 +500,13 @@ dependencies = [ "lazycell", "log", "peeking_take_while", - "prettyplease 0.2.12", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.29", + "syn 2.0.33", "which", ] @@ -652,29 +632,37 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher 0.4.4", +] + [[package]] name = "buffered-reader" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d3bea5bcc3ecc38fe5388e6bc35e6fe7bd665eb3ae9a44283e15b91ad3867d" dependencies = [ - "bzip2", - "flate2", "lazy_static", "libc", ] [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" @@ -684,9 +672,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bzip2" @@ -709,6 +697,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cast5" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cbc" version = "0.1.2" @@ -746,6 +743,15 @@ dependencies = [ "nom", ] +[[package]] +name = "cfb-mode" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -754,16 +760,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.28" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -800,9 +805,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.2" +version = "4.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" dependencies = [ "clap_builder", "clap_derive", @@ -829,7 +834,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -838,6 +843,17 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +[[package]] +name = "cmac" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" +dependencies = [ + "cipher 0.4.4", + "dbl", + "digest 0.10.7", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -991,9 +1007,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1052,6 +1068,32 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -1071,6 +1113,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array", +] + [[package]] name = "der" version = "0.7.8" @@ -1105,6 +1156,15 @@ dependencies = [ "serde", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "dialoguer" version = "0.10.4" @@ -1225,7 +1285,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -1252,6 +1312,28 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" +[[package]] +name = "eax" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" +dependencies = [ + "aead", + "cipher 0.4.4", + "cmac", + "ctr", + "subtle", +] + +[[package]] +name = "ecb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "ecdsa" version = "0.16.8" @@ -1262,7 +1344,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature", + "signature 2.1.0", "spki", ] @@ -1284,6 +1366,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "either" version = "1.9.0" @@ -1353,7 +1457,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -1462,6 +1566,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1575,7 +1685,7 @@ checksum = "83c8d52fe8b46ab822b4decdcc0d6d85aeedfc98f0d52ba2bd4aec4a97807516" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", "try_map", ] @@ -1661,7 +1771,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -2036,7 +2146,7 @@ dependencies = [ [[package]] name = "hyper-util" version = "0.0.0" -source = "git+https://github.com/hyperium/hyper-util#f898015fc9eca9f459ddac521db278d904099e89" +source = "git+https://github.com/hyperium/hyper-util#229757e565e0935a7a3b1d0f9e9ab88d9310e779" dependencies = [ "futures-channel", "futures-util", @@ -2044,7 +2154,7 @@ dependencies = [ "hyper 1.0.0-rc.4", "once_cell", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.5.4", "tokio", "tower", "tower-service", @@ -2074,6 +2184,15 @@ dependencies = [ "cc", ] +[[package]] +name = "idea" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "idna" version = "0.3.0" @@ -2094,6 +2213,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "imap" version = "0.3.7" @@ -2186,7 +2311,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.3", + "socket2 0.5.4", "widestring", "windows-sys 0.48.0", "winreg", @@ -2218,6 +2343,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -2268,7 +2402,7 @@ dependencies = [ "aes-gcm-siv", "async-stream", "async-trait", - "base64 0.21.3", + "base64 0.21.4", "bincode", "cbc", "chrono", @@ -2313,7 +2447,7 @@ dependencies = [ [[package]] name = "jmap-client" version = "0.3.0" -source = "git+https://github.com/stalwartlabs/jmap-client#a55af189d41a21cf5a51c1c69852cf6143cc8102" +source = "git+https://github.com/stalwartlabs/jmap-client#5b6595e770c75c3a5b2ae2e90ee18e35d6c6b5cf" dependencies = [ "ahash 0.8.3", "async-stream", @@ -2394,7 +2528,7 @@ dependencies = [ "diff", "ena", "is-terminal", - "itertools", + "itertools 0.10.5", "lalrpop-util", "petgraph", "regex", @@ -2465,9 +2599,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libloading" @@ -2531,9 +2665,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -2573,7 +2707,7 @@ dependencies = [ [[package]] name = "mail-auth" version = "0.3.3" -source = "git+https://github.com/stalwartlabs/mail-auth#949b8fcd91f329b424e22a3f2bbc3040869f3490" +source = "git+https://github.com/stalwartlabs/mail-auth#1605fe274fd78f16b5fdce6fad042f4b0e00846f" dependencies = [ "ahash 0.8.3", "flate2", @@ -2739,9 +2873,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.6.2" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memoffset" @@ -2809,39 +2943,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "nettle" -version = "7.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fdccf3eae7b161910d2daa2f0155ca35041322e8fe5c5f1f2c9d0b12356336" -dependencies = [ - "getrandom 0.2.10", - "libc", - "nettle-sys", - "thiserror", - "typenum", -] - -[[package]] -name = "nettle-sys" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e81c347b9002da0b6b0c4060993c280e99eb14b42ecf65a2fefcd6eb3d8a73" -dependencies = [ - "bindgen 0.63.0", - "cc", - "libc", - "pkg-config", - "tempfile", - "vcpkg", -] - [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -2958,9 +3059,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -3009,7 +3110,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -3020,18 +3121,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.27.0+1.1.1v" +version = "300.1.3+3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" +checksum = "cd2c101a165fff9935e34def4669595ab1c7847943c42be86e21503e482be107" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.92" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -3042,9 +3143,9 @@ dependencies = [ [[package]] name = "opentelemetry" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d6c3d7288a106c0a363e4b0e8d308058d56902adefb16f4936f417ffef086e" +checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" dependencies = [ "opentelemetry_api", "opentelemetry_sdk", @@ -3052,9 +3153,9 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc79add46364183ece1a4542592ca593e6421c60807232f5b8f7a31703825d" +checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b" dependencies = [ "async-trait", "bytes", @@ -3065,17 +3166,18 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c928609d087790fc936a1067bdc310ae702bdf3b090c3f281b713622c8bbde" +checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" dependencies = [ "async-trait", - "futures", - "futures-util", + "futures-core", "http", - "opentelemetry", "opentelemetry-http", "opentelemetry-proto", + "opentelemetry-semantic-conventions", + "opentelemetry_api", + "opentelemetry_sdk", "prost", "reqwest", "thiserror", @@ -3085,34 +3187,31 @@ dependencies = [ [[package]] name = "opentelemetry-proto" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61a2f56df5574508dd86aaca016c917489e589ece4141df1b5e349af8d66c28" +checksum = "b1e3f814aa9f8c905d0ee4bde026afd3b2577a97c10e1699912e3e44f0c4cbeb" dependencies = [ - "futures", - "futures-util", - "opentelemetry", + "opentelemetry_api", + "opentelemetry_sdk", "prost", "tonic", - "tonic-build", ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b02e0230abb0ab6636d18e2ba8fa02903ea63772281340ccac18e0af3ec9eeb" +checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" dependencies = [ "opentelemetry", ] [[package]] name = "opentelemetry_api" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24f96e21e7acc813c7a8394ee94978929db2bcc46cf6b5014fc612bf7760c22" +checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" dependencies = [ - "fnv", "futures-channel", "futures-util", "indexmap 1.9.3", @@ -3120,30 +3219,41 @@ dependencies = [ "once_cell", "pin-project-lite", "thiserror", + "urlencoding", ] [[package]] name = "opentelemetry_sdk" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca41c4933371b61c2a2f214bf16931499af4ec90543604ec828f7a625c09113" +checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" dependencies = [ "async-trait", "crossbeam-channel", - "dashmap", - "fnv", "futures-channel", "futures-executor", "futures-util", "once_cell", "opentelemetry_api", + "ordered-float", "percent-encoding", "rand 0.8.5", + "regex", + "serde_json", "thiserror", "tokio", "tokio-stream", ] +[[package]] +name = "ordered-float" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a54938017eacd63036332b4ae5c8a49fc8c0c1d6d629893057e4f13609edd06" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -3318,7 +3428,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -3356,7 +3466,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -3430,22 +3540,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.1.25" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "prettyplease" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" -dependencies = [ - "proc-macro2", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -3483,9 +3583,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -3500,28 +3600,6 @@ dependencies = [ "prost-derive", ] -[[package]] -name = "prost-build" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" -dependencies = [ - "bytes", - "heck", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prettyplease 0.1.25", - "prost", - "prost-types", - "regex", - "syn 1.0.109", - "tempfile", - "which", -] - [[package]] name = "prost-derive" version = "0.11.9" @@ -3529,33 +3607,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "prost-types" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" -dependencies = [ - "prost", -] - [[package]] name = "pwhash" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "419a3ad8fa9f9d445e69d9b185a24878ae6e6f55c96e4512f4a0e28cd3bc5c56" dependencies = [ - "blowfish", + "blowfish 0.7.0", "byteorder", "hmac 0.10.1", "md-5 0.9.1", "rand 0.8.5", - "sha-1", + "sha-1 0.9.8", "sha2 0.9.9", ] @@ -3720,7 +3789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c8ad6b5a6cd2f18516cde588b72797c56b884f0bd2dfdd82eb392781b85507" dependencies = [ "either", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "rayon", @@ -3790,13 +3859,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.7", + "regex-automata 0.3.8", "regex-syntax 0.7.5", ] @@ -3811,9 +3880,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", @@ -3838,7 +3907,7 @@ version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -3915,6 +3984,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "roaring" version = "0.10.2" @@ -3963,7 +4041,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", - "signature", + "signature 2.1.0", "spki", "subtle", "zeroize", @@ -4029,7 +4107,7 @@ dependencies = [ "serde_derive", "sha2 0.10.7", "thiserror", - "time 0.3.28", + "time", "tokio", "tokio-stream", "url", @@ -4068,9 +4146,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ "bitflags 2.4.0", "errno", @@ -4099,7 +4177,7 @@ checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", - "rustls-webpki 0.101.4", + "rustls-webpki 0.101.5", "sct", ] @@ -4121,14 +4199,14 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", ] [[package]] name = "rustls-webpki" -version = "0.100.2" +version = "0.100.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab" +checksum = "5f6a5fc258f1c1276dfe3016516945546e2d5383911efc0fc4f1cdc5df3a4ae3" dependencies = [ "ring", "untrusted", @@ -4136,9 +4214,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" dependencies = [ "ring", "untrusted", @@ -4254,27 +4332,51 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30efff3f9930e85b4284e76bbdad741f36412dfb1e370efd0de5866ae1a11dfc" dependencies = [ + "aes", "anyhow", - "base64 0.21.3", + "base64 0.21.4", + "block-padding", + "blowfish 0.9.1", "buffered-reader", - "bzip2", + "cast5", + "cfb-mode", "chrono", + "cipher 0.4.4", + "des", + "digest 0.10.7", "dyn-clone", - "flate2", + "eax", + "ecb", + "ecdsa", + "ed25519", + "ed25519-dalek", + "generic-array", "getrandom 0.2.10", + "idea", "idna 0.3.0", "lalrpop", "lalrpop-util", "lazy_static", "libc", + "md-5 0.10.5", "memsec", - "nettle", + "num-bigint-dig", "once_cell", + "p256", "rand 0.7.3", + "rand 0.8.5", + "rand_core 0.6.4", "regex", "regex-syntax 0.6.29", + "ripemd", + "rsa", + "sha-1 0.10.1", "sha1collisiondetection", + "sha2 0.10.7", "thiserror", + "twofish", + "typenum", + "x25519-dalek-ng", "xxhash-rust", ] @@ -4304,14 +4406,14 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -4352,7 +4454,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -4368,6 +4470,17 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha1" version = "0.10.5" @@ -4430,14 +4543,14 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "sieve-rs" version = "0.3.1" -source = "git+https://github.com/stalwartlabs/sieve#f17c085cbefd2c422d49924795d68255a6c7658c" +source = "git+https://github.com/stalwartlabs/sieve#d3f4c738e032707c5dbfcf0236fe57f0a49fbbb7" dependencies = [ "ahash 0.8.3", "bincode", @@ -4457,6 +4570,12 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + [[package]] name = "signature" version = "2.1.0" @@ -4500,6 +4619,7 @@ dependencies = [ "http-body-util", "hyper 1.0.0-rc.4", "hyper-util", + "imagesize", "lazy_static", "lru-cache", "mail-auth", @@ -4533,7 +4653,7 @@ dependencies = [ [[package]] name = "smtp-proto" version = "0.1.1" -source = "git+https://github.com/stalwartlabs/smtp-proto#434ede72159b5f9ea588b9ee8c361a3247aa2f35" +source = "git+https://github.com/stalwartlabs/smtp-proto#d2f81940d9ad74c9c03cd4bf55e681e9d94d4fe4" [[package]] name = "snafu" @@ -4570,9 +4690,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -4605,11 +4725,11 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ - "itertools", + "itertools 0.11.0", "nom", "unicode_categories", ] @@ -4716,7 +4836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" dependencies = [ "atoi", - "base64 0.21.3", + "base64 0.21.4", "bitflags 2.4.0", "byteorder", "bytes", @@ -4758,7 +4878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" dependencies = [ "atoi", - "base64 0.21.3", + "base64 0.21.4", "bitflags 2.4.0", "byteorder", "crc", @@ -4838,7 +4958,7 @@ dependencies = [ name = "stalwart-install" version = "0.3.7" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", "clap", "dialoguer", "flate2", @@ -4909,10 +5029,11 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -4929,6 +5050,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "syn" version = "1.0.109" @@ -4942,9 +5069,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" dependencies = [ "proc-macro2", "quote", @@ -5016,7 +5143,7 @@ version = "0.1.0" dependencies = [ "ahash 0.8.3", "async-trait", - "base64 0.21.3", + "base64 0.21.4", "bytes", "chrono", "csv", @@ -5060,22 +5187,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -5088,17 +5215,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.28" @@ -5175,7 +5291,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.4", "tokio-macros", "windows-sys 0.48.0", ] @@ -5198,7 +5314,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -5265,14 +5381,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.8.3" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ - "async-stream", "async-trait", "axum", - "base64 0.13.1", + "base64 0.21.4", "bytes", "futures-core", "futures-util", @@ -5284,28 +5399,12 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "prost-derive", "tokio", "tokio-stream", - "tokio-util", "tower", "tower-layer", "tower-service", "tracing", - "tracing-futures", -] - -[[package]] -name = "tonic-build" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" -dependencies = [ - "prettyplease 0.1.25", - "proc-macro2", - "prost-build", - "quote", - "syn 1.0.109", ] [[package]] @@ -5360,7 +5459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", - "time 0.3.28", + "time", "tracing-subscriber", ] @@ -5372,7 +5471,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", ] [[package]] @@ -5385,16 +5484,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.1.3" @@ -5408,12 +5497,14 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ebb87a95ea13271332df069020513ab70bdb5637ca42d6e492dc3bbbad48de" +checksum = "75327c6b667828ddc28f5e3f169036cb793c3f588d83bf0f262a7f062ffed3c8" dependencies = [ "once_cell", "opentelemetry", + "opentelemetry_sdk", + "smallvec", "tracing", "tracing-core", "tracing-log", @@ -5458,7 +5549,7 @@ dependencies = [ "ring", "rustls 0.21.7", "rustls-pemfile", - "rustls-webpki 0.101.4", + "rustls-webpki 0.101.5", "smallvec", "thiserror", "tinyvec", @@ -5545,16 +5636,25 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.16.0" +name = "twofish" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typewit" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5cee357cc77d1e02f10a3e6c4e13b8462fafab05998b62d331b7d9485589ff" +checksum = "9aa6b48ea01ae418f25260e05d1a97c5616a4e2d796dc3ba1e5a236a5100ed35" [[package]] name = "unicase" @@ -5573,9 +5673,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -5637,6 +5737,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -5715,12 +5821,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5748,7 +5848,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", "wasm-bindgen-shared", ] @@ -5782,7 +5882,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.33", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5841,7 +5941,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" dependencies = [ - "rustls-webpki 0.100.2", + "rustls-webpki 0.100.3", ] [[package]] @@ -5850,7 +5950,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ - "rustls-webpki 0.101.4", + "rustls-webpki 0.101.5", ] [[package]] @@ -5871,13 +5971,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -6074,6 +6175,18 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek-ng" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7074de8999662970c3c4c8f7f30925028dd8f4ca31ad4c055efa9cdf2ec326" +dependencies = [ + "curve25519-dalek-ng", + "rand 0.8.5", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "x509-parser" version = "0.15.1" @@ -6088,7 +6201,7 @@ dependencies = [ "oid-registry", "rusticata-macros", "thiserror", - "time 0.3.28", + "time", ] [[package]] @@ -6102,21 +6215,35 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +checksum = "bab77e97b50aee93da431f2cee7cd0f43b4d1da3c408042f2d7d164187774f0a" [[package]] name = "xxhash-rust" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" +checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" [[package]] name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.33", +] [[package]] name = "zip" @@ -6134,7 +6261,7 @@ dependencies = [ "hmac 0.12.1", "pbkdf2 0.11.0", "sha1", - "time 0.3.28", + "time", "zstd", ] diff --git a/crates/antispam/src/import/spamassassin.rs b/crates/antispam/src/import/spamassassin.rs index b2de1c7d..0198af0d 100644 --- a/crates/antispam/src/import/spamassassin.rs +++ b/crates/antispam/src/import/spamassassin.rs @@ -1660,30 +1660,30 @@ impl Display for Rule { | "check_for_spf_permerror" | "check_for_spf_softfail" | "check_for_spf_temperror" => { - let mut parts = function.split('_').rev(); + /*let mut parts = function.split('_').rev(); let result = parts.next().unwrap(); let spf = if parts.next().unwrap() == "helo" { "spf_ehlo" } else { "spf" }; - write!(f, "if string :is \"${{env.{spf}_result}}\" \"{result}\"")?; + write!(f, "if string :is \"${{env.{spf}_result}}\" \"{result}\"")?;*/ } "check_arc_signed" => { - f.write_str("if string :value \"ne\" \"${env.arc_result}\" \"none\"")?; + //f.write_str("if string :value \"ne\" \"${env.arc_result}\" \"none\"")?; } "check_arc_valid" => { - f.write_str("if string :is \"${env.arc_result}\" \"pass\"")?; + //f.write_str("if string :is \"${env.arc_result}\" \"pass\"")?; } "check_dmarc_missing" => { - f.write_str("if string :is \"${env.dmarc_policy}\" \"none\"")?; + //f.write_str("if string :is \"${env.dmarc_policy}\" \"none\"")?; } "check_dmarc_pass" => { - f.write_str("if string :is \"${env.dmarc_result}\" \"pass\"")?; + //f.write_str("if string :is \"${env.dmarc_result}\" \"pass\"")?; } "check_dmarc_none" | "check_dmarc_quarantine" | "check_dmarc_reject" => { - let policy = function.split('_').nth(2).unwrap(); - write!(f, "if allof(string :is \"${{env.dmarc_result}}\" \"fail\", string :is \"${{env.dmarc_policy}}\" \"{policy}\")")?; + //let policy = function.split('_').nth(2).unwrap(); + //write!(f, "if allof(string :is \"${{env.dmarc_result}}\" \"fail\", string :is \"${{env.dmarc_policy}}\" \"{policy}\")")?; } "check_dkim_adsp" | "check_dkim_signall" @@ -1718,17 +1718,18 @@ impl Display for Rule { f.write_str("if false")?; } "check_dkim_dependable" => { - writeln!(f, "set :local \"{}\" \"1\";", self.name)?; - return Ok(()); + //writeln!(f, "set :local \"{}\" \"1\";", self.name)?; + //return Ok(()); } "check_dkim_signed" => { - f.write_str("if string :value \"ne\" \"${env.dkim_result}\" \"none\"")?; + //f.write_str("if string :value \"ne\" \"${env.dkim_result}\" \"none\"")?; } "check_dkim_testing" => { - f.write_str("if header :contains \"DKIM-Signature\" \"t=y\"")?; + // score 0 + //f.write_str("if header :contains \"DKIM-Signature\" \"t=y\"")?; } "check_dkim_valid" => { - if params.is_empty() { + /*if params.is_empty() { f.write_str("if string :is \"${env.dkim_result}\" \"pass\"")?; } else { f.write_str("if allof(string :is \"${env.dkim_result}\" \"pass\", ")?; @@ -1746,10 +1747,10 @@ impl Display for Rule { } else { f.write_str(")")?; } - } + }*/ } "check_dkim_valid_envelopefrom" => { - f.write_str("if allof(string :is \"${env.dkim_result}\" \"pass\", string :is \"${mail_from}\" \"${from}\")")?; + //f.write_str("if allof(string :is \"${env.dkim_result}\" \"pass\", string :is \"${mail_from}\" \"${from}\")")?; } "check_for_def_dkim_welcomelist_from" | "check_for_def_dkim_whitelist_from" @@ -1768,16 +1769,18 @@ impl Display for Rule { write!(f, "if address :list \"from\" \"sa/list_{list}\"")?; } "check_for_missing_to_header" => { - write!(f, "if not exists \"to\"")?; + //write!(f, "if not exists \"to\"")?; } "check_for_to_in_subject" => { - f.write_str("foreveryline \"${header.to[*].addr[*]}\" {\n")?; + /*f.write_str("foreveryline \"${header.to[*].addr[*]}\" {\n")?; f.write_str("\tif string :contains \"${header.subject}\" \"${line}\"")?; self.fmt_match(f, 2)?; f.write_str("\t\tbreak;\n\t}\n}\n\n")?; - return Ok(()); + return Ok(());*/ } "check_blank_line_ratio" => { + /* + score = 0 let mut params = params.iter(); if let (Some(min), Some(max), Some(min_lines)) = ( @@ -1808,7 +1811,7 @@ impl Display for Rule { )?; } else { panic!("Warning: Invalid check_blank_line_ratio"); - } + }*/ } "check_language" => { f.write_str(concat!( @@ -1821,7 +1824,7 @@ impl Display for Rule { return Ok(()); } "check_body_length" => { - write!( + /*write!( f, "if eval \"body_len < {}\" ", params @@ -1829,25 +1832,25 @@ impl Display for Rule { .next() .and_then(param_to_num::) .expect("missing body length on check_body_length") - )?; + )?;*/ } "check_equal_from_domains" => { - f.write_str("if not string :is \"${mail_from.subdomain_part()}\" \"${from.subdomain_part()}\"")?; + //f.write_str("if not string :is \"${mail_from.subdomain_part()}\" \"${from.subdomain_part()}\"")?; } "check_for_no_rdns_dotcom_helo" => { - f.write_str("if not string :is \"${env.iprev_result}\" [\"pass\", \"\", \"temperror\"]")?; + //f.write_str("if not string :is \"${env.iprev_result}\" [\"pass\", \"\", \"temperror\"]")?; } "helo_ip_mismatch" => { - f.write_str(concat!( + /*f.write_str(concat!( "if allof(not string :is \"${env.iprev_ptr}\" \"\", ", "not string is \"${env.iprev_ptr}\" \"${env.helo_domain}\")" - ))?; + ))?;*/ } "subject_is_all_caps" => { - f.write_str("if eval \"thread_name.len() >= 10 && thread_name.count_words() > 1 && thread_name.is_uppercase()\"")?; + //f.write_str("if eval \"thread_name.len() >= 10 && thread_name.count_words() > 1 && thread_name.is_uppercase()\"")?; } "check_for_shifted_date" => { - let mut params = params.iter(); + /*let mut params = params.iter(); let mut range = [None; 2]; for item in range.iter_mut() { let param = params @@ -1881,10 +1884,10 @@ impl Display for Rule { } } - f.write_str("\"")?; + f.write_str("\"")?;*/ } "check_ratware_envelope_from" => { - f.write_str(concat!( + /*f.write_str(concat!( "set \"env_from_local\" \"${mail_from.local_part()}\";\n", "set \"to_local\" \"${header.to.addr.local_part()}\";\n", "set \"to_domain\" \"${header.to.addr.domain_part()}\";\n", @@ -1894,24 +1897,24 @@ impl Display for Rule { "not string :is \"${to_domain}\" \"\", ", "string :contains \"${env_from_local}\" \"${to_domain}\", ", "string :contains \"${env_from_local}\" \"${to_local}\")" - ))?; + ))?;*/ } "check_ratware_name_id" => { - f.write_str(concat!( + /*f.write_str(concat!( "if header :contains ", "[\"Message-Id\",\"Resent-Message-Id\",", "\"X-Message-Id\",\"X-Original-Message-ID\"] ", "\"${from}\"", - ))?; + ))?;*/ } "check_mailfrom_matches_rcvd" => { - f.write_str( + /*f.write_str( "if string :contains \"${env.helo_domain}\" \"${mail_from_domain}\"", - )?; + )?;*/ } "check_unresolved_template" => { - f.write_str(concat!( + /*f.write_str(concat!( "foreveryline \"${headers_raw}\" {\n", "\tif allof(string :regex \"${line}\" \"%[A-Z][A-Z_-]\", ", "not string :regex \"${line}\" \"(?i)^(?:X-VMS-To|X-UIDL|", @@ -1920,31 +1923,31 @@ impl Display for Rule { ))?; self.fmt_match(f, 2)?; f.write_str("\t\tbreak;\n\t}\n}\n\n")?; - return Ok(()); + return Ok(());*/ } "check_for_matching_env_and_hdr_from" => { - f.write_str("if string :is \"${mail_from}\" \"${from}\"")?; + //f.write_str("if string :is \"${mail_from}\" \"${from}\"")?; } "check_fromname_equals_replyto" => { - f.write_str("if string :is \"${from_name}\" \"${header.reply-to.addr}\"")?; + //f.write_str("if string :is \"${from_name}\" \"${header.reply-to.addr}\"")?; } "check_fromname_equals_to" => { - f.write_str(concat!( + /*f.write_str(concat!( "foreveryline \"${header.to[*].addr[*]}\" {\n", "\tif string :is \"${from_name}\" \"${line}\"", ))?; self.fmt_match(f, 2)?; f.write_str("\t\tbreak;\n\t}\n}\n\n")?; - return Ok(()); + return Ok(());*/ } "check_fromname_spoof" => { - f.write_str(concat!( + /*f.write_str(concat!( "if eval \"is_email(from_name) && ", "domain_name_part(from_name) != domain_name_part(from)\")", - ))?; + ))?;*/ } "check_header_count_range" => { - let mut params = params.iter(); + /*let mut params = params.iter(); let hdr_name = params .next() .expect("missing parameters for check_header_count_range") @@ -1965,10 +1968,10 @@ impl Display for Rule { )?; } else { write!(f, "if eval \"count(header.{hdr_name}[*].raw) >= {range_from} && header.{hdr_name}[*].raw.count() < {range_to}\"")?; - } + }*/ } "check_illegal_chars" => { - let mut params = params.iter(); + /*let mut params = params.iter(); let hdr_name = params .next() .expect("missing parameters for check_illegal_chars"); @@ -1993,7 +1996,7 @@ impl Display for Rule { writeln!( f, "if eval \"i_count > {count} && i_count / c_count > {ratio}\"" - )?; + )?;*/ } "check_freemail_from" => { f.write_str(concat!( @@ -2027,10 +2030,10 @@ impl Display for Rule { } "check_no_relays" => {} "check_for_forged_received_trail" => { - f.write_str(concat!( + /*f.write_str(concat!( "if allof(string :value \"${env.iprev_ptr}\" \"\", ", "string :value \"ne\" \"%{env.helo_domain}\" \"${env.iprev_ptr}\")" - ))?; + ))?;*/ } "check_for_mime_html_only" @@ -2043,7 +2046,9 @@ impl Display for Rule { | "check_msg_parse_flags" | "tvd_vertical_words" | "check_base64_length" - | "check_for_uppercase" => { + | "check_for_uppercase" + | "check_for_mime" + | "html_charset_faraway" => { // Handled externally return Ok(()); //f.write_str("if eval \"header.content-type == 'text/html'\"")?; diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml index 001ae0e6..e9549243 100644 --- a/crates/jmap/Cargo.toml +++ b/crates/jmap/Cargo.toml @@ -42,7 +42,7 @@ chrono = "0.4" dashmap = "5.4" aes = "0.8.3" cbc = { version = "0.1.2", features = ["alloc"] } -sequoia-openpgp = "1.16" +sequoia-openpgp = { version = "1.16", default-features = false, features = ["crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"] } rand = "0.8.5" rasn = "0.9.5" rasn-cms = "0.9.5" diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index d01a5702..2502dce8 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -48,6 +48,7 @@ serde_json = "1.0" num_cpus = "1.15.0" lazy_static = "1.4" whatlang = "0.16" +imagesize = "0.12" [features] test_mode = [] diff --git a/crates/smtp/src/config/scripts.rs b/crates/smtp/src/config/scripts.rs index 4e587942..d0fcbc8b 100644 --- a/crates/smtp/src/config/scripts.rs +++ b/crates/smtp/src/config/scripts.rs @@ -69,7 +69,7 @@ impl ConfigSieve for Config { Capability::Duplicate, ]) .with_capability(Capability::Plugins) - .with_capability(Capability::ForEveryLine) + .with_capability(Capability::While) .with_capability(Capability::Eval) .with_max_variable_size(102400) .with_max_header_size(10240) diff --git a/crates/smtp/src/scripts/exec.rs b/crates/smtp/src/scripts/exec.rs index 443b726c..ea73727e 100644 --- a/crates/smtp/src/scripts/exec.rs +++ b/crates/smtp/src/scripts/exec.rs @@ -38,8 +38,11 @@ impl Session { pub fn build_script_parameters(&self) -> ScriptParameters { let mut params = ScriptParameters::new() .set_variable("remote_ip", self.data.remote_ip.to_string()) - .set_variable("helo_domain", self.data.helo_domain.to_string()) - .set_variable("authenticated_as", self.data.authenticated_as.clone()) + .set_variable("helo_domain", self.data.helo_domain.to_lowercase()) + .set_variable( + "authenticated_as", + self.data.authenticated_as.to_lowercase(), + ) .set_variable( "now", SystemTime::now() @@ -65,27 +68,27 @@ impl Session { if let Some(ip_rev) = &self.data.iprev { params = params.set_variable("iprev_result", ip_rev.result().as_str()); if let Some(ptr) = ip_rev.ptr.as_ref().and_then(|addrs| addrs.first()) { - params = params.set_variable("iprev_ptr", ptr.to_string()); + params = params.set_variable("iprev_ptr", ptr.to_lowercase()); } } if let Some(mail_from) = &self.data.mail_from { params .envelope - .push((Envelope::From, mail_from.address.clone().into())); + .push((Envelope::From, mail_from.address_lcase.to_string().into())); if let Some(env_id) = &mail_from.dsn_info { params .envelope - .push((Envelope::Envid, env_id.clone().into())); + .push((Envelope::Envid, env_id.to_lowercase().into())); } if let Some(rcpt) = self.data.rcpt_to.last() { params .envelope - .push((Envelope::To, rcpt.address.clone().into())); + .push((Envelope::To, rcpt.address_lcase.to_string().into())); if let Some(orcpt) = &rcpt.dsn_info { params .envelope - .push((Envelope::Orcpt, orcpt.clone().into())); + .push((Envelope::Orcpt, orcpt.to_lowercase().into())); } } if (mail_from.flags & MAIL_RET_FULL) != 0 { diff --git a/crates/smtp/src/scripts/functions.rs b/crates/smtp/src/scripts/functions.rs deleted file mode 100644 index bb8b4332..00000000 --- a/crates/smtp/src/scripts/functions.rs +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright (c) 2023 Stalwart Labs Ltd. - * - * This file is part of Stalwart Mail Server. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * in the LICENSE file at the top-level directory of this distribution. - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * You can be released from the requirements of the AGPLv3 license by - * purchasing a commercial license. Please contact licensing@stalw.art - * for more details. -*/ - -use std::{borrow::Cow, collections::HashMap}; - -use ahash::{HashSet, HashSetExt}; -use mail_parser::{ - decoders::html::html_to_text, parsers::fields::thread::thread_name, HeaderName, HeaderValue, - MimeHeaders, -}; -use sieve::{compiler::ReceivedPart, runtime::Variable, FunctionMap}; - -pub fn register_functions() -> FunctionMap { - FunctionMap::new() - .with_function("trim", |_, v| v[0].transform(|s| Some(s.trim()))) - .with_function("len", |_, v| { - match &v[0] { - Variable::String(s) => s.len(), - Variable::StringRef(s) => s.len(), - Variable::Array(a) => a.len(), - Variable::ArrayRef(a) => a.len(), - v => v.to_string().len(), - } - .into() - }) - .with_function("is_empty", |_, v| { - match &v[0] { - Variable::String(s) => s.is_empty(), - Variable::StringRef(s) => s.is_empty(), - Variable::Integer(_) | Variable::Float(_) => false, - Variable::Array(a) => a.is_empty(), - Variable::ArrayRef(a) => a.is_empty(), - } - .into() - }) - .with_function("is_ascii", |_, v| { - match &v[0] { - Variable::String(s) => s.chars().all(|c| c.is_ascii()), - Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), - Variable::Integer(_) | Variable::Float(_) => true, - Variable::Array(a) => a.iter().all(|v| match v { - Variable::String(s) => s.chars().all(|c| c.is_ascii()), - Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), - _ => true, - }), - Variable::ArrayRef(a) => a.iter().all(|v| match v { - Variable::String(s) => s.chars().all(|c| c.is_ascii()), - Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), - _ => true, - }), - } - .into() - }) - .with_function("to_lowercase", |_, v| { - v[0].to_cow().to_lowercase().to_string().into() - }) - .with_function("to_uppercase", |_, v| { - v[0].to_cow().to_uppercase().to_string().into() - }) - .with_function("detect_language", |_, v| { - whatlang::detect_lang(v[0].to_cow().as_ref()) - .map(|l| l.code()) - .unwrap_or("unknown") - .into() - }) - .with_function("is_email", |_, v| { - is_email_valid(v[0].to_cow().as_ref()).into() - }) - .with_function("domain_part", |_, v| { - v[0].transform(|s| s.rsplit_once('@').map(|(_, d)| d.trim())) - }) - .with_function("local_part", |_, v| { - v[0].transform(|s| s.rsplit_once('@').map(|(u, _)| u.trim())) - }) - .with_function("domain_name_part", |_, v| { - v[0].transform(|s| { - s.rsplit_once('@') - .and_then(|(_, d)| d.trim().split('.').rev().nth(1)) - }) - }) - .with_function("subdomain_part", |_, v| { - v[0].to_cow() - .rsplit_once('@') - .map_or(Variable::default(), |(_, d)| { - d.split('.') - .rev() - .take(2) - .fold(String::new(), |a, b| { - if a.is_empty() { - b.to_string() - } else { - format!("{}.{}", b, a) - } - }) - .into() - }) - }) - .with_function("thread_name", |_, v| { - v[0].transform(|s| thread_name(s).into()) - }) - .with_function("html_to_text", |_, v| { - html_to_text(v[0].to_cow().as_ref()).into() - }) - .with_function("is_uppercase", |_, v| { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_uppercase()) - .into() - }) - .with_function("is_lowercase", |_, v| { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_lowercase()) - .into() - }) - .with_function("tokenize_words", |_, v| { - match &v[0] { - Variable::StringRef(s) => s - .split_whitespace() - .filter(|word| word.chars().all(|c| c.is_alphanumeric())) - .map(Variable::from) - .collect::>(), - Variable::String(s) => s - .split_whitespace() - .filter(|word| word.chars().all(|c| c.is_alphanumeric())) - .map(|word| Variable::from(word.to_string())) - .collect::>(), - v => v - .to_string() - .split_whitespace() - .filter(|word| word.chars().all(|c| c.is_alphanumeric())) - .map(|word| Variable::from(word.to_string())) - .collect::>(), - } - .into() - }) - .with_function("max_line_len", |_, v| { - 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), - } - .into() - }) - .with_function("count_spaces", |_, v| { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| c.is_whitespace()) - .count() - .into() - }) - .with_function("count_uppercase", |_, v| { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| c.is_alphabetic() && c.is_uppercase()) - .count() - .into() - }) - .with_function("count_lowercase", |_, v| { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| c.is_alphabetic() && c.is_lowercase()) - .count() - .into() - }) - .with_function("count_chars", |_, v| { - v[0].to_cow().as_ref().chars().count().into() - }) - .with_function("count_control_chars", |_, v| { - v[0].to_cow() - .as_ref() - .chars() - .filter(|c| { - matches!(c, '\u{0000}'..='\u{0008}' - | '\u{000B}' - | '\u{000C}' - | '\u{000E}'..='\u{001F}' - | '\u{007F}') - }) - .count() - .into() - }) - .with_function_args( - "eq_ignore_case", - |_, v| { - v[0].to_cow() - .eq_ignore_ascii_case(v[1].to_cow().as_ref()) - .into() - }, - 2, - ) - .with_function_args( - "received_part", - |ctx, v| { - if let (Ok(part), Some(HeaderValue::Received(rcvd))) = ( - ReceivedPart::try_from(v[1].to_cow().as_ref()), - ctx.message() - .part(ctx.part()) - .and_then(|p| { - p.headers - .iter() - .filter(|h| h.name == HeaderName::Received) - .nth((v[0].to_integer() as usize).saturating_sub(1)) - }) - .map(|h| &h.value), - ) { - part.eval(rcvd).unwrap_or_default() - } else { - Variable::default() - } - }, - 2, - ) - .with_function_args( - "cosine_similarity", - |_, v| { - let mut word_freq: HashMap, [u32; 2]> = HashMap::new(); - - for (idx, var) in v.into_iter().enumerate() { - match var { - Variable::Array(l) => { - for item in l { - word_freq.entry(item.into_cow()).or_insert([0, 0])[idx] += 1; - } - } - Variable::ArrayRef(l) => { - for item in l { - word_freq.entry(item.to_cow()).or_insert([0, 0])[idx] += 1; - } - } - _ => { - for char in var.to_cow().chars() { - word_freq.entry(char.to_string().into()).or_insert([0, 0])[idx] += - 1; - } - } - } - } - - let mut dot_product = 0; - let mut magnitude_a = 0; - let mut magnitude_b = 0; - - for (_word, count) in word_freq.iter() { - dot_product += count[0] * count[1]; - magnitude_a += count[0] * count[0]; - magnitude_b += count[1] * count[1]; - } - - if magnitude_a != 0 && magnitude_b != 0 { - dot_product as f64 / (magnitude_a as f64).sqrt() / (magnitude_b as f64).sqrt() - } else { - 0.0 - } - .into() - }, - 2, - ) - .with_function_args( - "jaccard_similarity", - |_, v| { - let mut word_freq = [HashSet::new(), HashSet::new()]; - - for (idx, var) in v.into_iter().enumerate() { - match var { - Variable::Array(l) => { - for item in l { - word_freq[idx].insert(item.into_cow()); - } - } - Variable::ArrayRef(l) => { - for item in l { - word_freq[idx].insert(item.to_cow()); - } - } - _ => { - for char in var.to_cow().chars() { - word_freq[idx].insert(char.to_string().into()); - } - } - } - } - - let intersection_size = word_freq[0].intersection(&word_freq[1]).count(); - let union_size = word_freq[0].union(&word_freq[1]).count(); - - if union_size != 0 { - intersection_size as f64 / union_size as f64 - } else { - 0.0 - } - .into() - }, - 2, - ) - .with_function_no_args("var_names", |ctx, _| { - Variable::Array( - ctx.global_variable_names() - .map(|v| Variable::from(v.to_string())) - .collect(), - ) - }) - .with_function_no_args("is_encoding_problem", |ctx, _| { - ctx.message() - .part(ctx.part()) - .map(|p| p.is_encoding_problem) - .unwrap_or_default() - .into() - }) - .with_function_no_args("is_attachment", |ctx, _| { - ctx.message().attachments.contains(&ctx.part()).into() - }) - .with_function_no_args("is_body", |ctx, _| { - (ctx.message().text_body.contains(&ctx.part()) - || ctx.message().html_body.contains(&ctx.part())) - .into() - }) - .with_function_no_args("attachment_name", |ctx, _| { - ctx.message() - .part(ctx.part()) - .and_then(|p| p.attachment_name()) - .unwrap_or_default() - .into() - }) -} - -trait ApplyString<'x> { - fn transform(&self, f: impl Fn(&str) -> Option<&str>) -> Variable<'x>; - fn transform_string>>( - &self, - f: impl Fn(&str) -> T, - ) -> Option>; -} - -impl<'x> ApplyString<'x> for Variable<'x> { - fn transform(&self, f: impl Fn(&str) -> Option<&str>) -> 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, - } - } -} - -fn is_email_valid(email: &str) -> bool { - let mut last_ch = 0; - let mut in_quote = false; - let mut at_count = 0; - let mut dot_count = 0; - let mut lp_len = 0; - let mut value = 0; - - for ch in email.bytes() { - match ch { - b'0'..=b'9' - | b'a'..=b'z' - | b'A'..=b'Z' - | b'!' - | b'#' - | b'$' - | b'%' - | b'&' - | b'\'' - | b'*' - | b'+' - | b'-' - | b'/' - | b'=' - | b'?' - | b'^' - | b'_' - | b'`' - | b'{' - | b'|' - | b'}' - | b'~' - | 0x7f..=u8::MAX => { - value += 1; - } - b'.' if !in_quote => { - if last_ch != b'.' && last_ch != b'@' && value != 0 { - value += 1; - if at_count == 1 { - dot_count += 1; - } - } else { - return false; - } - } - b'@' if !in_quote => { - at_count += 1; - lp_len = value; - value = 0; - } - b'>' | b':' | b',' | b' ' if in_quote => { - value += 1; - } - b'\"' if !in_quote || last_ch != b'\\' => { - in_quote = !in_quote; - } - b'\\' if in_quote && last_ch != b'\\' => (), - _ => { - if !in_quote { - return false; - } - } - } - - last_ch = ch; - } - - at_count == 1 && dot_count > 0 && lp_len > 0 && value > 0 -} diff --git a/crates/smtp/src/scripts/functions/array.rs b/crates/smtp/src/scripts/functions/array.rs new file mode 100644 index 00000000..05e33347 --- /dev/null +++ b/crates/smtp/src/scripts/functions/array.rs @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, +}; + +use sieve::{runtime::Variable, Context}; + +pub fn fn_count<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::Array(a) => a.len(), + Variable::ArrayRef(a) => a.len(), + _ => 1, + } + .into() +} + +pub fn fn_sort<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let is_asc = v[1].to_bool(); + let mut arr = v.into_iter().next().unwrap().into_array(); + if is_asc { + arr.sort_unstable_by(|a, b| b.cmp(a)); + } else { + arr.sort_unstable(); + } + arr.into() +} + +pub fn fn_dedup<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let arr = v.into_iter().next().unwrap().into_array(); + let mut result = Vec::with_capacity(arr.len()); + + for item in arr { + if !result.contains(&item) { + result.push(item); + } + } + + result.into() +} + +pub fn fn_cosine_similarity<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let mut word_freq: HashMap, [u32; 2]> = HashMap::new(); + + for (idx, var) in v.into_iter().enumerate() { + match var { + Variable::Array(l) => { + for item in l { + word_freq.entry(item.into_cow()).or_insert([0, 0])[idx] += 1; + } + } + Variable::ArrayRef(l) => { + for item in l { + word_freq.entry(item.to_cow()).or_insert([0, 0])[idx] += 1; + } + } + _ => { + for char in var.to_cow().chars() { + word_freq.entry(char.to_string().into()).or_insert([0, 0])[idx] += 1; + } + } + } + } + + let mut dot_product = 0; + let mut magnitude_a = 0; + let mut magnitude_b = 0; + + for (_word, count) in word_freq.iter() { + dot_product += count[0] * count[1]; + magnitude_a += count[0] * count[0]; + magnitude_b += count[1] * count[1]; + } + + if magnitude_a != 0 && magnitude_b != 0 { + dot_product as f64 / (magnitude_a as f64).sqrt() / (magnitude_b as f64).sqrt() + } else { + 0.0 + } + .into() +} + +pub fn fn_jaccard_similarity<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let mut word_freq = [HashSet::new(), HashSet::new()]; + + for (idx, var) in v.into_iter().enumerate() { + match var { + Variable::Array(l) => { + for item in l { + word_freq[idx].insert(item.into_cow()); + } + } + Variable::ArrayRef(l) => { + for item in l { + word_freq[idx].insert(item.to_cow()); + } + } + _ => { + for char in var.to_cow().chars() { + word_freq[idx].insert(char.to_string().into()); + } + } + } + } + + let intersection_size = word_freq[0].intersection(&word_freq[1]).count(); + let union_size = word_freq[0].union(&word_freq[1]).count(); + + if union_size != 0 { + intersection_size as f64 / union_size as f64 + } else { + 0.0 + } + .into() +} diff --git a/crates/smtp/src/scripts/functions/email.rs b/crates/smtp/src/scripts/functions/email.rs new file mode 100644 index 00000000..ced9db9c --- /dev/null +++ b/crates/smtp/src/scripts/functions/email.rs @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +use sieve::{runtime::Variable, Context}; + +use super::ApplyString; + +pub fn fn_is_email_valid<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let mut last_ch = 0; + let mut in_quote = false; + let mut at_count = 0; + let mut dot_count = 0; + let mut lp_len = 0; + let mut value = 0; + + for ch in v[0].to_cow().bytes() { + match ch { + b'0'..=b'9' + | b'a'..=b'z' + | b'A'..=b'Z' + | b'!' + | b'#' + | b'$' + | b'%' + | b'&' + | b'\'' + | b'*' + | b'+' + | b'-' + | b'/' + | b'=' + | b'?' + | b'^' + | b'_' + | b'`' + | b'{' + | b'|' + | b'}' + | b'~' + | 0x7f..=u8::MAX => { + value += 1; + } + b'.' if !in_quote => { + if last_ch != b'.' && last_ch != b'@' && value != 0 { + value += 1; + if at_count == 1 { + dot_count += 1; + } + } else { + return false.into(); + } + } + b'@' if !in_quote => { + at_count += 1; + lp_len = value; + value = 0; + } + b'>' | b':' | b',' | b' ' if in_quote => { + value += 1; + } + b'\"' if !in_quote || last_ch != b'\\' => { + in_quote = !in_quote; + } + b'\\' if in_quote && last_ch != b'\\' => (), + _ => { + if !in_quote { + return false.into(); + } + } + } + + last_ch = ch; + } + + (at_count == 1 && dot_count > 0 && lp_len > 0 && value > 0).into() +} + +pub fn fn_email_part<'x>(_: &'x Context<'x>, 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(), + "domain_name" => d.trim().split('.').rev().nth(1), + "host" => d.trim().split('.').next(), + "subdomain" => { + let d = d.trim(); + let mut seen_dot = false; + for (pos, ch) in d.as_bytes().iter().enumerate().rev() { + if *ch == b'.' { + if seen_dot { + return std::str::from_utf8(&d.as_bytes()[pos + 1..]) + .unwrap_or_default() + .into(); + } else { + seen_dot = true; + } + } + } + d.into() + } + _ => None, + }) + }) +} diff --git a/crates/smtp/src/scripts/functions/header.rs b/crates/smtp/src/scripts/functions/header.rs new file mode 100644 index 00000000..bfeff9e1 --- /dev/null +++ b/crates/smtp/src/scripts/functions/header.rs @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +use mail_parser::{parsers::fields::thread::thread_name, HeaderName, HeaderValue, MimeHeaders}; +use sieve::{compiler::ReceivedPart, runtime::Variable, Context}; + +use super::ApplyString; + +pub fn fn_received_part<'x>(ctx: &'x Context<'x>, v: Vec>) -> Variable<'x> { + if let (Ok(part), Some(HeaderValue::Received(rcvd))) = ( + ReceivedPart::try_from(v[1].to_cow().as_ref()), + ctx.message() + .part(ctx.part()) + .and_then(|p| { + p.headers + .iter() + .filter(|h| h.name == HeaderName::Received) + .nth((v[0].to_integer() as usize).saturating_sub(1)) + }) + .map(|h| &h.value), + ) { + part.eval(rcvd).unwrap_or_default() + } else { + Variable::default() + } +} + +pub fn fn_is_encoding_problem<'x>(ctx: &'x Context<'x>, _: Vec>) -> Variable<'x> { + ctx.message() + .part(ctx.part()) + .map(|p| p.is_encoding_problem) + .unwrap_or_default() + .into() +} + +pub fn fn_is_attachment<'x>(ctx: &'x Context<'x>, _: Vec>) -> Variable<'x> { + ctx.message().attachments.contains(&ctx.part()).into() +} + +pub fn fn_is_body<'x>(ctx: &'x Context<'x>, _: Vec>) -> Variable<'x> { + (ctx.message().text_body.contains(&ctx.part()) || ctx.message().html_body.contains(&ctx.part())) + .into() +} + +pub fn fn_attachment_name<'x>(ctx: &'x Context<'x>, _: Vec>) -> Variable<'x> { + ctx.message() + .part(ctx.part()) + .and_then(|p| p.attachment_name()) + .unwrap_or_default() + .into() +} + +pub fn fn_thread_name<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].transform(|s| thread_name(s).into()) +} + +pub fn fn_is_header_utf8_valid<'x>(ctx: &'x Context<'x>, v: Vec>) -> Variable<'x> { + ctx.message() + .part(ctx.part()) + .map(|p| { + let raw = ctx.message().raw_message(); + let mut is_valid = true; + if let Some(header_name) = HeaderName::parse(v[0].to_cow().as_ref()) { + for header in &p.headers { + if header.name == header_name + && raw + .get(header.offset_start()..header.offset_end()) + .and_then(|raw| std::str::from_utf8(raw).ok()) + .is_none() + { + is_valid = false; + break; + } + } + } else { + is_valid = raw + .get(p.raw_header_offset()..p.raw_body_offset()) + .and_then(|raw| std::str::from_utf8(raw).ok()) + .is_some(); + } + + Variable::from(is_valid) + }) + .unwrap_or(Variable::Integer(1)) +} diff --git a/crates/smtp/src/scripts/functions/html.rs b/crates/smtp/src/scripts/functions/html.rs new file mode 100644 index 00000000..f0af219a --- /dev/null +++ b/crates/smtp/src/scripts/functions/html.rs @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +use hyper::Uri; +use mail_parser::decoders::html::{add_html_token, html_to_text}; +use sieve::{runtime::Variable, Context}; + +pub fn fn_html_to_text<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + html_to_text(v[0].to_cow().as_ref()).into() +} + +pub fn fn_tokenize_html<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + html_to_tokens(v[0].to_cow().as_ref()).into() +} + +pub fn fn_html_has_tag<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].as_array() + .map(|arr| { + let token = v[1].to_cow(); + arr.iter().any(|v| { + v.to_cow() + .as_ref() + .strip_prefix('<') + .map_or(false, |tag| tag.starts_with(token.as_ref())) + }) + }) + .unwrap_or_default() + .into() +} + +pub fn fn_html_attr_int<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let t = v[0].to_cow(); + let mut dimension = None; + + if let Some(value) = get_attribute(t.as_ref(), v[1].to_cow().as_ref()) { + let value = value.trim(); + if let Some(pct) = value.strip_suffix('%') { + if let Ok(pct) = pct.trim().parse::() { + dimension = ((v[2].to_integer() * pct as i64) / 100).into(); + } + } else if let Ok(value) = value.parse::() { + dimension = (value as i64).into(); + } + } + + dimension.map(Variable::Integer).unwrap_or_default() +} + +pub fn fn_html_attr<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + get_attribute(v[0].to_cow().as_ref(), v[1].to_cow().as_ref()) + .map(|s| Variable::String(s.to_string())) + .unwrap_or_default() +} + +pub fn html_to_tokens(input: &str) -> Vec> { + let input = input.as_bytes(); + let mut iter = input.iter().enumerate(); + let mut tags = vec![]; + + let mut is_token_start = true; + let mut is_after_space = false; + let mut is_new_line = true; + + let mut token_start = 0; + let mut token_end = 0; + + let mut text = String::from("_"); + + while let Some((pos, &ch)) = iter.next() { + match ch { + b'<' => { + if !is_token_start { + add_html_token( + &mut text, + &input[token_start..token_end + 1], + is_after_space, + ); + is_after_space = false; + is_token_start = true; + } + if text.len() > 1 { + tags.push(Variable::String(text)); + text = String::from("_"); + } + + let mut tag = vec![b'<']; + if matches!(input.get(pos + 1..pos + 4), Some(b"!--")) { + let mut last_ch: u8 = 0; + for (_, &ch) in iter.by_ref() { + match ch { + b'>' if tag.len() > 3 + && matches!(tag.last(), Some(b'-')) + && matches!(tag.get(tag.len() - 2), Some(b'-')) => + { + break; + } + b' ' | b'\t' | b'\r' | b'\n' => { + if last_ch != b' ' { + tag.push(b' '); + } else { + last_ch = b' '; + } + continue; + } + _ => { + tag.push(ch); + } + } + last_ch = ch; + } + } else { + let mut in_quote = false; + let mut last_ch = b' '; + for (_, &ch) in iter.by_ref() { + match ch { + b'>' if !in_quote => { + break; + } + b'"' => { + in_quote = !in_quote; + tag.push(b'"'); + } + b' ' | b'\t' | b'\r' | b'\n' if !in_quote => { + if last_ch != b' ' { + tag.push(b' '); + last_ch = b' '; + } + continue; + } + b'/' if !in_quote => { + tag.push(b'/'); + last_ch = b' '; + continue; + } + _ => { + tag.push(if in_quote { + ch + } else { + ch.to_ascii_lowercase() + }); + } + } + last_ch = ch; + } + } + tags.push(Variable::String(String::from_utf8(tag).unwrap_or_default())); + continue; + } + b' ' | b'\t' | b'\r' | b'\n' => { + if !is_token_start { + add_html_token( + &mut text, + &input[token_start..token_end + 1], + is_after_space && !is_new_line, + ); + is_new_line = false; + } + is_after_space = true; + is_token_start = true; + continue; + } + b'&' if !is_token_start => { + add_html_token( + &mut text, + &input[token_start..token_end + 1], + is_after_space && !is_new_line, + ); + is_new_line = false; + is_token_start = true; + is_after_space = false; + } + b';' if !is_token_start => { + add_html_token( + &mut text, + &input[token_start..pos + 1], + is_after_space && !is_new_line, + ); + is_token_start = true; + is_after_space = false; + is_new_line = false; + continue; + } + _ => (), + } + + if is_token_start { + token_start = pos; + is_token_start = false; + } + token_end = pos; + } + + if !is_token_start { + add_html_token( + &mut text, + &input[token_start..token_end + 1], + is_after_space && !is_new_line, + ); + } + if text.len() > 1 { + tags.push(Variable::String(text)); + } + + tags +} + +pub fn html_img_area(arr: &[Variable<'_>]) -> u32 { + arr.iter() + .filter_map(|v| { + let t = v.to_cow(); + if t.starts_with("() { + let size = if idx == 0 { 800 } else { 600 }; + dimensions[idx] = (size * pct) / 100; + } + } else if let Ok(value) = value.parse::() { + dimensions[idx] = value; + } + } + } + + Some(dimensions[0].saturating_mul(dimensions[1])) + } else { + None + } + }) + .sum::() +} + +pub fn fn_uri_part<'x>(_: &'x Context<'x>, 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() +} + +pub fn get_attribute<'x>(tag: &'x str, attr_name: &str) -> Option<&'x str> { + let tag = tag.as_bytes(); + let attr_name = attr_name.as_bytes(); + let mut iter = tag.iter().enumerate().peekable(); + let mut in_quote = false; + let mut start_pos = usize::MAX; + let mut end_pos = usize::MAX; + + while let Some((pos, ch)) = iter.next() { + match ch { + b'=' if !in_quote => { + if start_pos != usize::MAX + && end_pos != usize::MAX + && tag + .get(start_pos..end_pos + 1) + .map_or(false, |name| name == attr_name) + { + let mut token_start = 0; + let mut token_end = 0; + + for (pos, ch) in iter.by_ref() { + match ch { + b'"' => { + if !in_quote { + token_start = pos + 1; + in_quote = true; + } else { + token_end = pos; + break; + } + } + b' ' if !in_quote => { + if token_start != 0 { + token_end = pos; + break; + } + } + _ => { + if token_start == 0 { + token_start = pos; + } + } + } + } + + return if token_start > 0 { + if token_end == 0 { + token_end = tag.len(); + } + Some(std::str::from_utf8(&tag[token_start..token_end]).unwrap_or_default()) + } else { + None + }; + } else { + start_pos = usize::MAX; + end_pos = usize::MAX; + } + } + b'"' => { + in_quote = !in_quote; + } + b' ' => { + if !in_quote && !matches!(iter.peek(), Some((_, b'='))) { + start_pos = usize::MAX; + end_pos = usize::MAX; + } + } + _ => { + if !in_quote { + if start_pos == usize::MAX { + start_pos = pos; + } + end_pos = pos; + } + } + } + } + + None +} diff --git a/crates/smtp/src/scripts/functions/image.rs b/crates/smtp/src/scripts/functions/image.rs new file mode 100644 index 00000000..a09ba710 --- /dev/null +++ b/crates/smtp/src/scripts/functions/image.rs @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +use sieve::{runtime::Variable, Context}; + +pub fn fn_img_metadata<'x>(ctx: &'x Context<'x>, v: Vec>) -> Variable<'x> { + ctx.message() + .part(ctx.part()) + .map(|p| p.contents()) + .and_then(|bytes| { + let arg = v[1].to_cow(); + match arg.as_ref() { + "type" => imagesize::image_type(bytes).ok().map(|t| { + Variable::StringRef(match t { + imagesize::ImageType::Aseprite => "aseprite", + imagesize::ImageType::Avif => "avif", + imagesize::ImageType::Bmp => "bmp", + imagesize::ImageType::Dds => "dds", + imagesize::ImageType::Exr => "exr", + imagesize::ImageType::Farbfeld => "farbfeld", + imagesize::ImageType::Gif => "gif", + imagesize::ImageType::Hdr => "hdr", + imagesize::ImageType::Heif => "heif", + imagesize::ImageType::Ico => "ico", + imagesize::ImageType::Jpeg => "jpeg", + imagesize::ImageType::Jxl => "jxl", + imagesize::ImageType::Ktx2 => "ktx2", + imagesize::ImageType::Png => "png", + imagesize::ImageType::Pnm => "pnm", + imagesize::ImageType::Psd => "psd", + imagesize::ImageType::Qoi => "qoi", + imagesize::ImageType::Tga => "tga", + imagesize::ImageType::Tiff => "tiff", + imagesize::ImageType::Vtf => "vtf", + imagesize::ImageType::Webp => "webp", + }) + }), + "width" => imagesize::blob_size(bytes) + .ok() + .map(|s| Variable::Integer(s.width as i64)), + "height" => imagesize::blob_size(bytes) + .ok() + .map(|s| Variable::Integer(s.height as i64)), + "area" => imagesize::blob_size(bytes) + .ok() + .map(|s| Variable::Integer(s.width.saturating_mul(s.height) as i64)), + _ => None, + } + }) + .unwrap_or_default() +} diff --git a/crates/smtp/src/scripts/functions/mod.rs b/crates/smtp/src/scripts/functions/mod.rs new file mode 100644 index 00000000..2cca90c1 --- /dev/null +++ b/crates/smtp/src/scripts/functions/mod.rs @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +mod array; +mod email; +mod header; +pub mod html; +mod image; +mod text; + +use sieve::{runtime::Variable, Context, FunctionMap}; + +use self::{array::*, email::*, header::*, html::*, image::*, text::*}; + +pub fn register_functions() -> FunctionMap { + FunctionMap::new() + .with_function("trim", fn_trim) + .with_function("len", fn_len) + .with_function("count", fn_count) + .with_function("is_empty", fn_is_empty) + .with_function("is_ascii", fn_is_ascii) + .with_function("to_lowercase", fn_to_lowercase) + .with_function("to_uppercase", fn_to_uppercase) + .with_function("detect_language", fn_detect_language) + .with_function("is_email", fn_is_email_valid) + .with_function("thread_name", fn_thread_name) + .with_function("html_to_text", fn_html_to_text) + .with_function("is_uppercase", fn_is_uppercase) + .with_function("is_lowercase", fn_is_lowercase) + .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) + .with_function("count_chars", fn_count_chars) + .with_function("dedup", fn_dedup) + .with_function("lines", fn_lines) + .with_function("is_header_utf8_valid", fn_is_header_utf8_valid) + .with_function("img_metadata", fn_img_metadata) + .with_function("sort", fn_sort) + .with_function_args("email_part", fn_email_part, 2) + .with_function_args("eq_ignore_case", fn_eq_ignore_case, 2) + .with_function_args("contains", fn_contains, 2) + .with_function_args("contains_ignore_case", fn_contains_ignore_case, 2) + .with_function_args("starts_with", fn_starts_with, 2) + .with_function_args("ends_with", fn_ends_with, 2) + .with_function_args("received_part", fn_received_part, 2) + .with_function_args("cosine_similarity", fn_cosine_similarity, 2) + .with_function_args("jaccard_similarity", fn_jaccard_similarity, 2) + .with_function_args("levenshtein_distance", fn_levenshtein_distance, 2) + .with_function_args("html_has_tag", fn_html_has_tag, 2) + .with_function_args("html_attr", fn_html_attr, 2) + .with_function_args("html_attr_int", fn_html_attr_int, 3) + .with_function_args("uri_part", fn_uri_part, 2) + .with_function_args("substring", fn_substring, 3) + .with_function_args("split", fn_split, 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) + .with_function_no_args("var_names", fn_is_var_names) + .with_function_no_args("attachment_name", fn_attachment_name) +} + +pub fn fn_is_empty<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::String(s) => s.is_empty(), + Variable::StringRef(s) => s.is_empty(), + Variable::Integer(_) | Variable::Float(_) => false, + Variable::Array(a) => a.is_empty(), + Variable::ArrayRef(a) => a.is_empty(), + } + .into() +} + +pub fn fn_is_var_names<'x>(ctx: &'x Context<'x>, _: Vec>) -> Variable<'x> { + Variable::Array( + ctx.global_variable_names() + .map(|v| Variable::from(v.to_string())) + .collect(), + ) +} + +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>; +} + +impl<'x> ApplyString<'x> for Variable<'x> { + fn transform(&self, f: impl Fn(&str) -> Option<&str>) -> 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, + } + } +} diff --git a/crates/smtp/src/scripts/functions/text.rs b/crates/smtp/src/scripts/functions/text.rs new file mode 100644 index 00000000..f2c6a3d2 --- /dev/null +++ b/crates/smtp/src/scripts/functions/text.rs @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +use sieve::{runtime::Variable, Context}; + +use super::ApplyString; + +pub fn fn_trim<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].transform(|s| Some(s.trim())) +} + +pub fn fn_len<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::String(s) => s.len(), + Variable::StringRef(s) => s.len(), + Variable::Array(a) => a.len(), + Variable::ArrayRef(a) => a.len(), + v => v.to_string().len(), + } + .into() +} + +pub fn fn_is_ascii<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::String(s) => s.chars().all(|c| c.is_ascii()), + Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), + Variable::Integer(_) | Variable::Float(_) => true, + Variable::Array(a) => a.iter().all(|v| match v { + Variable::String(s) => s.chars().all(|c| c.is_ascii()), + Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), + _ => true, + }), + Variable::ArrayRef(a) => a.iter().all(|v| match v { + Variable::String(s) => s.chars().all(|c| c.is_ascii()), + Variable::StringRef(s) => s.chars().all(|c| c.is_ascii()), + _ => true, + }), + } + .into() +} + +pub fn fn_to_lowercase<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow().to_lowercase().to_string().into() +} + +pub fn fn_to_uppercase<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow().to_uppercase().to_string().into() +} + +pub fn fn_is_uppercase<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .as_ref() + .chars() + .filter(|c| c.is_alphabetic()) + .all(|c| c.is_uppercase()) + .into() +} + +pub fn fn_is_lowercase<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .as_ref() + .chars() + .filter(|c| c.is_alphabetic()) + .all(|c| c.is_lowercase()) + .into() +} + +pub fn fn_tokenize_words<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::StringRef(s) => s + .split_whitespace() + .filter(|word| word.chars().all(|c| c.is_alphanumeric())) + .map(Variable::from) + .collect::>(), + Variable::String(s) => s + .split_whitespace() + .filter(|word| word.chars().all(|c| c.is_alphanumeric())) + .map(|word| Variable::from(word.to_string())) + .collect::>(), + v => v + .to_string() + .split_whitespace() + .filter(|word| word.chars().all(|c| c.is_alphanumeric())) + .map(|word| Variable::from(word.to_string())) + .collect::>(), + } + .into() +} + +pub fn fn_max_line_len<'x>(_: &'x Context<'x>, 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), + } + .into() +} + +pub fn fn_count_spaces<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .as_ref() + .chars() + .filter(|c| c.is_whitespace()) + .count() + .into() +} + +pub fn fn_count_uppercase<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .as_ref() + .chars() + .filter(|c| c.is_alphabetic() && c.is_uppercase()) + .count() + .into() +} + +pub fn fn_count_lowercase<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .as_ref() + .chars() + .filter(|c| c.is_alphabetic() && c.is_lowercase()) + .count() + .into() +} + +pub fn fn_count_chars<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow().as_ref().chars().count().into() +} + +pub fn fn_eq_ignore_case<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .eq_ignore_ascii_case(v[1].to_cow().as_ref()) + .into() +} + +pub fn fn_contains<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::String(s) => s.contains(v[1].to_cow().as_ref()), + Variable::StringRef(s) => s.contains(v[1].to_cow().as_ref()), + Variable::Array(arr) => arr.contains(&v[1]), + Variable::ArrayRef(arr) => arr.contains(&v[1]), + val => val.to_string().contains(v[1].to_cow().as_ref()), + } + .into() +} + +pub fn fn_contains_ignore_case<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let needle = v[1].to_cow(); + match &v[0] { + Variable::String(s) => s.to_lowercase().contains(&needle.to_lowercase()), + Variable::StringRef(s) => s.to_lowercase().contains(&needle.to_lowercase()), + Variable::Array(arr) => arr.iter().any(|v| match v { + Variable::String(s) => s.eq_ignore_ascii_case(needle.as_ref()), + Variable::StringRef(s) => s.eq_ignore_ascii_case(needle.as_ref()), + _ => false, + }), + Variable::ArrayRef(arr) => arr.iter().any(|v| match v { + Variable::String(s) => s.eq_ignore_ascii_case(needle.as_ref()), + Variable::StringRef(s) => s.eq_ignore_ascii_case(needle.as_ref()), + _ => false, + }), + val => val.to_string().contains(needle.as_ref()), + } + .into() +} + +pub fn fn_starts_with<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow().starts_with(v[1].to_cow().as_ref()).into() +} + +pub fn fn_ends_with<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow().ends_with(v[1].to_cow().as_ref()).into() +} + +pub fn fn_lines<'x>(_: &'x Context<'x>, mut v: Vec>) -> Variable<'x> { + match v.remove(0) { + Variable::StringRef(s) => s.lines().map(Variable::from).collect::>().into(), + Variable::String(s) => s + .lines() + .map(|s| Variable::String(s.to_string())) + .collect::>() + .into(), + val => val, + } +} + +pub fn fn_substring<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + v[0].to_cow() + .chars() + .skip(v[1].to_usize()) + .take(v[2].to_usize()) + .collect::() + .into() +} + +pub fn fn_split<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + match &v[0] { + Variable::StringRef(s) => s + .split(v[1].to_cow().as_ref()) + .map(Variable::from) + .collect::>() + .into(), + Variable::String(s) => s + .split(v[1].to_cow().as_ref()) + .map(|s| Variable::String(s.to_string())) + .collect::>() + .into(), + val => val + .to_string() + .split(v[1].to_cow().as_ref()) + .map(|s| Variable::String(s.to_string())) + .collect::>() + .into(), + } +} + +/** + * `levenshtein-rs` - levenshtein + * + * MIT licensed. + * + * Copyright (c) 2016 Titus Wormer + */ +pub fn fn_levenshtein_distance<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + let a = v[0].to_cow(); + let b = v[1].to_cow(); + + let mut result = 0; + + /* Shortcut optimizations / degenerate cases. */ + if a == b { + return result.into(); + } + + let length_a = a.chars().count(); + let length_b = b.chars().count(); + + if length_a == 0 { + return length_b.into(); + } else if length_b == 0 { + return length_a.into(); + } + + /* Initialize the vector. + * + * This is why it’s fast, normally a matrix is used, + * here we use a single vector. */ + let mut cache: Vec = (1..).take(length_a).collect(); + let mut distance_a; + let mut distance_b; + + /* Loop. */ + for (index_b, code_b) in b.chars().enumerate() { + result = index_b; + distance_a = index_b; + + for (index_a, code_a) in a.chars().enumerate() { + distance_b = if code_a == code_b { + distance_a + } else { + distance_a + 1 + }; + + distance_a = cache[index_a]; + + result = if distance_a > result { + if distance_b > result { + result + 1 + } else { + distance_b + } + } else if distance_b > distance_a { + distance_a + 1 + } else { + distance_b + }; + + cache[index_a] = result; + } + } + + result.into() +} + +pub fn fn_detect_language<'x>(_: &'x Context<'x>, v: Vec>) -> Variable<'x> { + whatlang::detect_lang(v[0].to_cow().as_ref()) + .map(|l| l.code()) + .unwrap_or("unknown") + .into() +} diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index b8058c61..41223e27 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -16,10 +16,10 @@ smtp-proto = { git = "https://github.com/stalwartlabs/smtp-proto" } mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-features = false, features = ["cram-md5", "skip-ehlo"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-appender = "0.2" -tracing-opentelemetry = "0.18.0" -opentelemetry = { version = "0.18.0", features = ["rt-tokio"] } -opentelemetry-otlp = { version = "0.11.0", features = ["http-proto", "reqwest-client"] } -opentelemetry-semantic-conventions = { version = "0.10.0" } +tracing-opentelemetry = "0.21.0" +opentelemetry = { version = "0.20.0", features = ["rt-tokio"] } +opentelemetry-otlp = { version = "0.13.0", features = ["http-proto", "reqwest-client"] } +opentelemetry-semantic-conventions = { version = "0.12.0" } dashmap = "5.4" ahash = { version = "0.8" } diff --git a/resources/config/sieve/antispam.sieve b/resources/config/sieve/antispam.sieve index 1d0bede5..d4b2951c 100644 --- a/resources/config/sieve/antispam.sieve +++ b/resources/config/sieve/antispam.sieve @@ -4,7 +4,7 @@ require [ "variables", "regex", "body", "reject", - "vnd.stalwart.foreveryline", + "vnd.stalwart.while", "vnd.stalwart.eval", "vnd.stalwart.plugins"]; @@ -12,16 +12,6 @@ set "body" "%{body.to_text}"; set "body_len" "%{len(body)}"; set "headers_raw" "%{header.*.raw}"; set "headers_text" "%{header.*.text}"; -#set "thread_name" "%{header.subject.thread_name()}"; -#set "sent_date" "%{header.date.date}"; -#set "mail_from" "%{envelope.from}"; -#if eval "mail_from.is_empty()" { -# set "mail_from" "postmaster@${env.helo_domain}"; -#} -#set "mail_from_domain" "%{mail_from.domain_part()}"; -#set "from" "%{header.from.addr}"; -#set "from_domain" "%{from.domain_part()}"; -#set "from_name" "%{header.from.name.trim()}"; # Message only has text/html MIME parts if eval "header.content-type == 'text/html'" { @@ -29,10 +19,20 @@ if eval "header.content-type == 'text/html'" { set "t.__MIME_HTML" "1"; } +# Part count set "mime_text_html_count" "0"; set "mime_text_plain_count" "0"; set "mime_part_count" "0"; +# Inline image count and area +set "mime_gif_count" "0"; +set "mime_png_count" "0"; +set "mime_jpg_count" "0"; +set "mime_img_count" "0"; +set "mime_img_area" "0"; +set "mime_gif_area" "0"; +set "mime_png_area" "0"; + foreverypart { set "ct" "%{to_lowercase(header.content-type)}"; @@ -41,7 +41,190 @@ foreverypart { set "mime_text_plain_count" "%{mime_text_plain_count + 1}"; } elsif eval "ct == 'text/html'" { set "mime_text_html_count" "%{mime_text_html_count + 1}"; - } + set "t.HTML_MESSAGE" "1"; + + # Tokenize HTML + set "html_tokens" "${tokenize_html(part.text)}"; + set "html_char_count" "0"; + set "html_space_count" "0"; + set "html_img_area" "0"; + set "in_head" "0"; + set "in_body" "0"; + set "in_center" "0"; + set "in_title" "0"; + set "in_anchor" "0"; + set "in_anchor_href" ""; + + set "i" "%{len(html_tokens)}"; + while "i" { + set "i" "%{i - 1}"; + set "line" "%{html_tokens[i]}"; + + # Tokens starting with '_' are text nodes + if eval "starts_with(line, '_')" { + if eval "in_head == 0" { + set "html_char_count" "%{html_char_count + count_chars(line) - 1}"; + set "html_space_count" "%{html_space_count + count_spaces(line)}"; + } + set "text_len" "%{len(line) - 1}"; + if eval "in_title && !is_empty(header.subject) && text_len / len(header.subject) > 3.5" { + set "t.__HTML_TITLE_SUBJ_DIFF" "1"; + } + if eval "text_len == 120" { + set "t.__HTML_TITLE_120" "1"; + } + if eval "in_anchor && + !is_empty(in_anchor_href) && + contains_ignore_case(line, 'http') && + uri_part(trim(substring(line, 1, text_len)), 'scheme_host') != in_anchor_href" { + set "t.HTTPS_HTTP_MISMATCH" "1"; + } + } elsif eval "starts_with(line, '", + "this is the actual text" + ), + vec![ + Variable::String( + concat!( + "