mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2024-11-28 09:07:32 +00:00
Migrate to new Sieve version
This commit is contained in:
parent
758280837d
commit
cc582b05f9
44 changed files with 1379 additions and 666 deletions
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -33,9 +33,8 @@ body:
|
|||
label: Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- v0.3.1
|
||||
- v0.3.0
|
||||
- v0.2.0 or lower
|
||||
- v0.3.x
|
||||
- v0.2.x or lower
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
344
Cargo.lock
generated
344
Cargo.lock
generated
|
@ -4,9 +4,9 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.20.0"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
@ -123,24 +123,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is-terminal",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
||||
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
|
@ -162,9 +161,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "1.0.2"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
|
||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.48.0",
|
||||
|
@ -220,7 +219,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.25",
|
||||
"time 0.3.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -334,7 +333,7 @@ dependencies = [
|
|||
"rust-ini",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"time 0.3.25",
|
||||
"time 0.3.28",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -394,9 +393,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.68"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
|
||||
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
|
@ -427,9 +426,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
|||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.2"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
|
@ -735,9 +734,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.82"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
@ -824,9 +823,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.22"
|
||||
version = "4.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b417ae4361bca3f5de378294fc7472d3c4ed86a5ef9f49e93ae722f432aae8d2"
|
||||
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -835,9 +834,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.22"
|
||||
version = "4.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c90dc0f0e42c64bff177ca9d7be6fcc9ddb0f26a6e062174a61c84dd6c644d4"
|
||||
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -847,9 +846,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.3.12"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
|
||||
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
@ -859,9 +858,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
|
@ -888,6 +887,12 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||
|
||||
[[package]]
|
||||
name = "const_panic"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
|
@ -1142,9 +1147,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.0"
|
||||
version = "5.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d"
|
||||
checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.14.0",
|
||||
|
@ -1186,9 +1191,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
|
||||
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1467,9 +1472,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
|||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.32"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
|
||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
@ -1857,7 +1862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-targets 0.48.4",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1883,9 +1888,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.27.3"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
|
||||
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
|
@ -1906,9 +1911,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.20"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
|
||||
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -2326,7 +2331,7 @@ dependencies = [
|
|||
"socket2 0.5.3",
|
||||
"widestring",
|
||||
"windows-sys 0.48.0",
|
||||
"winreg 0.50.0",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2405,7 +2410,7 @@ dependencies = [
|
|||
"aes-gcm-siv",
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bincode",
|
||||
"cbc",
|
||||
"chrono",
|
||||
|
@ -2511,18 +2516,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "konst"
|
||||
version = "0.2.19"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4"
|
||||
checksum = "030400e39b2dff8beaa55986a17e0014ad657f569ca92426aafcb5e8e71faee7"
|
||||
dependencies = [
|
||||
"konst_macro_rules",
|
||||
"const_panic",
|
||||
"konst_kernel",
|
||||
"typewit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "konst_macro_rules"
|
||||
version = "0.2.19"
|
||||
name = "konst_kernel"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
|
||||
checksum = "3376133edc39f027d551eb77b077c2865a0ef252b2e7d0dd6b6dc303db95d8b5"
|
||||
dependencies = [
|
||||
"typewit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
|
@ -2858,9 +2868,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
|
@ -2930,16 +2940,15 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
|||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.2"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
|
||||
checksum = "abbbc55ad7b13aac85f9401c796dcda1b864e07fcad40ad47792eaa8932ea502"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset 0.7.1",
|
||||
"pin-utils",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2964,9 +2973,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
|
@ -3051,9 +3060,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
|
||||
checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -3081,11 +3090,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.56"
|
||||
version = "0.10.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e"
|
||||
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.0",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
|
@ -3122,9 +3131,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.91"
|
||||
version = "0.9.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
|
||||
checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -3297,7 +3306,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall 0.3.5",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.4",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3375,12 +3384,12 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
|||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
|
||||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3390,7 +3399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "27e1f8e085bfa9b85763fe3ddaacbe90a09cd847b3833129153a6cb063bbe132"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bitfield",
|
||||
"block-padding",
|
||||
"blowfish 0.9.1",
|
||||
|
@ -3509,9 +3518,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
@ -3548,9 +3557,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
|||
|
||||
[[package]]
|
||||
name = "platforms"
|
||||
version = "3.0.2"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630"
|
||||
checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8"
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
|
@ -3566,9 +3575,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e"
|
||||
checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
|
@ -3790,9 +3799,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rasn"
|
||||
version = "0.8.2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b8d6d2a0e8ed2c702de8b7bcc6c867faa8fe662d5fa87d6dbcae945fe276004"
|
||||
checksum = "fbe5ce12835340cdd47a8abf36aabc02ce2d2a1d88e29d6c5126b5e7bf95eb53"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitvec",
|
||||
|
@ -3812,9 +3821,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rasn-cms"
|
||||
version = "0.8.2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a7d66ec2276b9a8cee3bf3dc2d051895f009f1bffabd114871d44d3b215702b"
|
||||
checksum = "8ae05ee5eb8b58bcbbb7d5d28270281b9aa305b9a6aa271cac54f3a967f11fa2"
|
||||
dependencies = [
|
||||
"rasn",
|
||||
"rasn-pkix",
|
||||
|
@ -3822,9 +3831,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rasn-derive"
|
||||
version = "0.8.2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52573d02ccf5e01c7d42b6009365728046a58202ec4001724b034815c9c35d3f"
|
||||
checksum = "43c8ad6b5a6cd2f18516cde588b72797c56b884f0bd2dfdd82eb392781b85507"
|
||||
dependencies = [
|
||||
"either",
|
||||
"itertools",
|
||||
|
@ -3837,9 +3846,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rasn-pkix"
|
||||
version = "0.8.2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ff08da0c6502e4d4e961450e3d291f47c2e2896b788436f294e5cacef57dc1"
|
||||
checksum = "57b82b75a434efa7dbb6ff898c9504586e3adffa9e445336edd9c0431c27b4c2"
|
||||
dependencies = [
|
||||
"rasn",
|
||||
]
|
||||
|
@ -3897,14 +3906,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.3"
|
||||
version = "1.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
|
||||
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.6",
|
||||
"regex-syntax 0.7.4",
|
||||
"regex-automata 0.3.7",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3918,13 +3927,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
|
||||
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.4",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3935,17 +3944,17 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.18"
|
||||
version = "0.11.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
|
||||
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
|
@ -3977,8 +3986,8 @@ dependencies = [
|
|||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.22.6",
|
||||
"winreg 0.10.1",
|
||||
"webpki-roots 0.25.2",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4145,7 +4154,7 @@ dependencies = [
|
|||
"serde_derive",
|
||||
"sha2 0.10.7",
|
||||
"thiserror",
|
||||
"time 0.3.25",
|
||||
"time 0.3.28",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
|
@ -4193,9 +4202,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.8"
|
||||
version = "0.38.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
|
||||
checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
|
@ -4224,7 +4233,7 @@ checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
|
|||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-webpki 0.101.3",
|
||||
"rustls-webpki 0.101.4",
|
||||
"sct",
|
||||
]
|
||||
|
||||
|
@ -4246,14 +4255,14 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.100.1"
|
||||
version = "0.100.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
|
||||
checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
|
@ -4261,9 +4270,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.3"
|
||||
version = "0.101.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0"
|
||||
checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
|
@ -4387,9 +4396,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.183"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -4405,9 +4414,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.183"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -4522,9 +4531,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
checksum = "b6805d8ff0f66aa61fb79a97a51ba210dcae753a797336dea8a36a3168196fab"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
@ -4544,14 +4553,14 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
|||
[[package]]
|
||||
name = "sieve-rs"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/stalwartlabs/sieve#f9c01ba6947d73855fdd645b17c9a5d347724ee3"
|
||||
source = "git+https://github.com/stalwartlabs/sieve#cea1451c13006016cff96aacfb88290318576e96"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"bincode",
|
||||
"fancy-regex",
|
||||
"mail-builder",
|
||||
"mail-parser",
|
||||
"phf",
|
||||
"regex",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -4576,15 +4585,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
@ -4822,7 +4831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
@ -4864,7 +4873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"crc",
|
||||
|
@ -4944,7 +4953,7 @@ dependencies = [
|
|||
name = "stalwart-install"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"clap",
|
||||
"dialoguer",
|
||||
"flate2",
|
||||
|
@ -5081,9 +5090,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.7.1"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
|
||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
|
@ -5109,7 +5118,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"async-trait",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.3",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"csv",
|
||||
|
@ -5194,9 +5203,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.25"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
|
||||
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
|
@ -5213,9 +5222,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.11"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
|
||||
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
@ -5455,7 +5464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"time 0.3.25",
|
||||
"time 0.3.28",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
|
@ -5654,10 +5663,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
name = "typewit"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
checksum = "3e5cee357cc77d1e02f10a3e6c4e13b8462fafab05998b62d331b7d9485589ff"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
@ -5725,9 +5740,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
|||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 0.4.0",
|
||||
|
@ -5886,9 +5901,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078"
|
||||
checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
|
@ -5932,7 +5947,7 @@ version = "0.23.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
|
||||
dependencies = [
|
||||
"rustls-webpki 0.100.1",
|
||||
"rustls-webpki 0.100.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5941,7 +5956,7 @@ version = "0.24.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888"
|
||||
dependencies = [
|
||||
"rustls-webpki 0.101.3",
|
||||
"rustls-webpki 0.101.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6011,7 +6026,7 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.4",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6029,7 +6044,7 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.4",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6049,17 +6064,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d92ecb8ae0317859f509f17b19adc74b0763b0fa3b085dea8ed01085c8dac222"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.4",
|
||||
"windows_aarch64_msvc 0.48.4",
|
||||
"windows_i686_gnu 0.48.4",
|
||||
"windows_i686_msvc 0.48.4",
|
||||
"windows_x86_64_gnu 0.48.4",
|
||||
"windows_x86_64_gnullvm 0.48.4",
|
||||
"windows_x86_64_msvc 0.48.4",
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6070,9 +6085,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d14b0ee96970be7108701212f097ce67ca772fd84cb0ffbc86d26a94e77ba929"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
|
@ -6082,9 +6097,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1332277d49f440c8fc6014941e320ee47ededfcce10cb272728470f56cc092c9"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
|
@ -6094,9 +6109,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d992130ac399d56f02c20564e9975ac5ba08cb25cb832849bbc0d736a101abe5"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
|
@ -6106,9 +6121,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "962e96d0fa4b4773c63977977ea6564f463fb10e34a6e07360428b53ae7a3f71"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
|
@ -6118,9 +6133,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30652a53018a48a9735fbc2986ff0446c37bc8bed0d3f98a0ed4d04cdb80027e"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
|
@ -6130,9 +6145,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5bb3f0331abfe1a95af56067f1e64b3791b55b5373b03869560b6025de809bf"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
|
@ -6142,18 +6157,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.4"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd1df36d9fd0bbe4849461de9b969f765170f4e0f90497d580a235d515722b10"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
|
@ -6200,7 +6206,7 @@ dependencies = [
|
|||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.25",
|
||||
"time 0.3.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6260,7 +6266,7 @@ dependencies = [
|
|||
"hmac 0.12.1",
|
||||
"pbkdf2 0.11.0",
|
||||
"sha1",
|
||||
"time 0.3.25",
|
||||
"time 0.3.28",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
|
|
|
@ -3,7 +3,5 @@ name = "antispam"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
fancy-regex = "0.11.0"
|
||||
|
|
|
@ -14,8 +14,6 @@ struct Rule {
|
|||
description: HashMap<String, String>,
|
||||
priority: i32,
|
||||
flags: Vec<TestFlag>,
|
||||
forward_score_pos: f64,
|
||||
forward_score_neg: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
@ -171,7 +169,21 @@ impl Rule {
|
|||
|
||||
impl Ord for Rule {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let this_score = self.score();
|
||||
match self.priority.cmp(&other.priority) {
|
||||
std::cmp::Ordering::Equal => {
|
||||
match other
|
||||
.score()
|
||||
.abs()
|
||||
.partial_cmp(&self.score().abs())
|
||||
.unwrap()
|
||||
{
|
||||
std::cmp::Ordering::Equal => other.name.cmp(&self.name),
|
||||
x => x,
|
||||
}
|
||||
}
|
||||
x => x,
|
||||
}
|
||||
/*let this_score = self.score();
|
||||
let other_score = other.score();
|
||||
|
||||
let this_is_negative = this_score < 0.0;
|
||||
|
@ -204,7 +216,7 @@ impl Ord for Rule {
|
|||
}
|
||||
x => x,
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1090,13 +1090,13 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool) {
|
|||
// Generate script
|
||||
let mut script = String::from(concat!(
|
||||
"require [\"variables\", \"include\", \"regex\", \"body\", \"vnd.stalwart.plugins\"];\n\n",
|
||||
"global \"score\";\n",
|
||||
"global \"spam_score\";\n",
|
||||
"set \"score\" \"0.0\";\n",
|
||||
"set \"spam_score\" \"0.0\";\n",
|
||||
"set \"awl_factor\" \"0.5\";\n",
|
||||
"\n"
|
||||
));
|
||||
let mut rules_iter = rules_sorted.iter();
|
||||
|
||||
while let Some(&rule) = rules_iter.next() {
|
||||
for rule in rules_sorted {
|
||||
if rule.score() == 0.0 && !tests_linked.contains(&rule.name) {
|
||||
if do_warn {
|
||||
eprintln!("Warning: Test {} is never linked to.", rule.name);
|
||||
|
@ -1105,7 +1105,7 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool) {
|
|||
}
|
||||
|
||||
// Calculate forward scores
|
||||
let (score_pos, score_neg) =
|
||||
/*let (score_pos, score_neg) =
|
||||
rules_iter
|
||||
.clone()
|
||||
.fold((0.0, 0.0), |(acc_pos, acc_neg), rule| {
|
||||
|
@ -1120,7 +1120,7 @@ pub fn import_spamassassin(path: PathBuf, extension: String, do_warn: bool) {
|
|||
});
|
||||
let mut rule = rule.clone();
|
||||
rule.forward_score_neg = score_neg;
|
||||
rule.forward_score_pos = score_pos;
|
||||
rule.forward_score_pos = score_pos;*/
|
||||
|
||||
write!(&mut script, "{rule}").unwrap();
|
||||
}
|
||||
|
@ -1281,11 +1281,25 @@ impl Display for Rule {
|
|||
RuleType::Uri { pattern } => {
|
||||
write!(f, "if match_uri {:?}", pattern)?;
|
||||
}
|
||||
RuleType::Eval { function, params } => {
|
||||
RuleType::Eval { function, params } => match function.as_str() {
|
||||
"check_from_in_auto_welcomelist" | "check_from_in_auto_whitelist" => {
|
||||
f.write_str(concat!(
|
||||
"query :set [\"awl_score\", \"awl_count\"] \"SELECT score, count FROM awl WHERE from = ? AND ip = ?\" [\"${envelope.from}\", \"%{env.remote_ip}\"];\n",
|
||||
"if eval \"awl_count > 0\" {\n",
|
||||
"\tquery \"UPDATE awl SET score += ?, count += 1 WHERE from = ? AND ip = ?\" [\"%{score}\", \"${envelope.from}\", \"%{env.remote_ip}\"];\n",
|
||||
"\tset \"score\" \"%{score + ((awl_score / awl_count) - score) * awl_factor}\";\n",
|
||||
"} else {\n",
|
||||
"\tquery \"INSERT OR IGNORE INTO (score, count, from, ip) (?, 1, ?, ?)\" [\"%{score}\", \"${envelope.from}\", \"%{env.remote_ip}\"];\n",
|
||||
"}\n\n",
|
||||
))?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
write!(f, "if {function}")?;
|
||||
for param in params {
|
||||
f.write_str(" ")?;
|
||||
if let Some(param) = param.strip_prefix('\'').and_then(|v| v.strip_suffix('\''))
|
||||
if let Some(param) =
|
||||
param.strip_prefix('\'').and_then(|v| v.strip_suffix('\''))
|
||||
{
|
||||
write!(f, "\"{param}\"")?;
|
||||
} else if param.starts_with('\"') {
|
||||
|
@ -1295,6 +1309,7 @@ impl Display for Rule {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
RuleType::Meta { expr } => {
|
||||
write!(f, "if eval {:?}", expr.expr.trim())?;
|
||||
}
|
||||
|
@ -1303,7 +1318,7 @@ impl Display for Rule {
|
|||
}
|
||||
}
|
||||
|
||||
write!(f, " {{\n\tset :local \"{}\" \"1\";\n", self.name)?;
|
||||
writeln!(f, " {{\n\tset :local \"{}\" \"1\";", self.name)?;
|
||||
|
||||
for (var_name, pos) in &self.captured_vars {
|
||||
writeln!(f, "\tset :local \"{}\" \"${{{}}}\";", var_name, pos)?;
|
||||
|
@ -1319,9 +1334,9 @@ impl Display for Rule {
|
|||
f.write_str(" - ")?;
|
||||
(-score).fmt(f)?;
|
||||
}
|
||||
f.write_str("}\";\n\t")?;
|
||||
f.write_str("}\";\n")?;
|
||||
|
||||
if score > 0.0 {
|
||||
/*if score > 0.0 {
|
||||
if self.forward_score_neg != 0.0 {
|
||||
write!(
|
||||
f,
|
||||
|
@ -1340,7 +1355,7 @@ impl Display for Rule {
|
|||
} else {
|
||||
f.write_str("if eval \"score < spam_score\"")?;
|
||||
}
|
||||
f.write_str(" {\n\t\treturn;\n\t}\n")?;
|
||||
f.write_str(" {\n\t\treturn;\n\t}\n")?;*/
|
||||
}
|
||||
|
||||
f.write_str("}\n\n")
|
||||
|
|
|
@ -65,17 +65,17 @@ pub fn replace_tags(
|
|||
|
||||
pub fn fix_broken_regex(value: &str) -> &str {
|
||||
match value {
|
||||
r#"/[\042\223\224\262\263\271]{2}\S{0,16}[\042\223\224\262\263\271]{2}/"# => {
|
||||
r"/[\042\223\224\262\263\271]{2}\S{0,16}[\042\223\224\262\263\271]{2}/" => {
|
||||
//r#"[\"\u{93}\u{94}\u{B2}\u{B3}\u{B9}]{2}\S{0,16}[\"\u{93}\u{94}\u{B2}\u{B3}\u{B9}]{2}"#
|
||||
r#"/[\x22\x93\x94\xB2\xB3\xB9]{2}\S{0,16}[\x22\x93\x94\xB2\xB3\xB9]{2}/"#
|
||||
r"/[\x22\x93\x94\xB2\xB3\xB9]{2}\S{0,16}[\x22\x93\x94\xB2\xB3\xB9]{2}/"
|
||||
}
|
||||
r#"/\b_{0,3}d[_\W]?[i1!|l\xEC-\xEF][_\W]?d[_\W]?r[_\W][e3\xE8-\xEB[_\W]?xx?_{0,3}\b/i"# => {
|
||||
r#"/\b_{0,3}d[_\W]?[i1!|l\xEC-\xEF][_\W]?d[_\W]?r[_\W][e3\xE8-\xEB][_\W]?xx?_{0,3}\b/i"#
|
||||
r"/\b_{0,3}d[_\W]?[i1!|l\xEC-\xEF][_\W]?d[_\W]?r[_\W][e3\xE8-\xEB[_\W]?xx?_{0,3}\b/i" => {
|
||||
r"/\b_{0,3}d[_\W]?[i1!|l\xEC-\xEF][_\W]?d[_\W]?r[_\W][e3\xE8-\xEB][_\W]?xx?_{0,3}\b/i"
|
||||
}
|
||||
r#"/<!--(?:\s{1,10}[-\w'"]{1,40}){100}/im"# => r#"/<!--(?:\s{1,10}[-\w'"]{1,40}){5}/im"#,
|
||||
r#"/\015/"# => r#"/\x0D/"#,
|
||||
r#"/[({[<][. ]*(?-i:\xbc\xba[. ]*\xc0\xce[. ]*)?(?-i:\xb1\xa4(?:[. ]*|[\x00-\x7f]{0,3})\xb0\xed|\xc1\xa4[. ]*\xba\xb8|\xc8\xab[. ]*\xba\xb8)[. ]*[)}\]>]/"# => {
|
||||
r#"/[\(\{\[\<][. ]*(?-i:\xbc\xba[. ]*\xc0\xce[. ]*)?(?-i:\xb1\xa4(?:[. ]*|[\x00-\x7f]{0,3})\xb0\xed|\xc1\xa4[. ]*\xba\xb8|\xc8\xab[. ]*\xba\xb8)[. ]*[\)\}\]\>]/"#
|
||||
r"/\015/" => r"/\x0D/",
|
||||
r"/[({[<][. ]*(?-i:\xbc\xba[. ]*\xc0\xce[. ]*)?(?-i:\xb1\xa4(?:[. ]*|[\x00-\x7f]{0,3})\xb0\xed|\xc1\xa4[. ]*\xba\xb8|\xc8\xab[. ]*\xba\xb8)[. ]*[)}\]>]/" => {
|
||||
r"/[\(\{\[\<][. ]*(?-i:\xbc\xba[. ]*\xc0\xce[. ]*)?(?-i:\xb1\xa4(?:[. ]*|[\x00-\x7f]{0,3})\xb0\xed|\xc1\xa4[. ]*\xba\xb8|\xc8\xab[. ]*\xba\xb8)[. ]*[\)\}\]\>]/"
|
||||
}
|
||||
_ => value,
|
||||
}
|
||||
|
@ -152,15 +152,15 @@ mod test {
|
|||
fn import_regex() {
|
||||
for (expr, result, vars) in [
|
||||
(
|
||||
r#"m{<img\b[^>]{0,100}\ssrc=.?https?://[^>]{6,80}(?:\?[^>]{8}|[^a-z](?![a-f]{3}|20\d\d[01]\d[0-3]\d)[0-9a-f]{8})}i"#,
|
||||
r#"(?i)<img\b[^>]{0,100}\ssrc=.?https?://[^>]{6,80}(?:\?[^>]{8}|[^a-z](?![a-f]{3}|20\d\d[01]\d[0-3]\d)[0-9a-f]{8})"#,
|
||||
r"m{<img\b[^>]{0,100}\ssrc=.?https?://[^>]{6,80}(?:\?[^>]{8}|[^a-z](?![a-f]{3}|20\d\d[01]\d[0-3]\d)[0-9a-f]{8})}i",
|
||||
r"(?i)<img\b[^>]{0,100}\ssrc=.?https?://[^>]{6,80}(?:\?[^>]{8}|[^a-z](?![a-f]{3}|20\d\d[01]\d[0-3]\d)[0-9a-f]{8})",
|
||||
vec![],
|
||||
),
|
||||
(r#"/\bhoodia\b/i"#, r#"(?i)\bhoodia\b"#, vec![]),
|
||||
(r#"/\bCurrent Price:/"#, r#"\bCurrent Price:"#, vec![]),
|
||||
(r"/\bhoodia\b/i", r"(?i)\bhoodia\b", vec![]),
|
||||
(r"/\bCurrent Price:/", r"\bCurrent Price:", vec![]),
|
||||
(
|
||||
r#"m|^https?://storage\.cloud\.google\.com/.{4,128}\#%{GB_TO_ADDR}|i"#,
|
||||
r#"(?i)^https?://storage\.cloud\.google\.com/.{4,128}\#${GB_TO_ADDR}"#,
|
||||
r"m|^https?://storage\.cloud\.google\.com/.{4,128}\#%{GB_TO_ADDR}|i",
|
||||
r"(?i)^https?://storage\.cloud\.google\.com/.{4,128}\#${GB_TO_ADDR}",
|
||||
vec!["GB_TO_ADDR"],
|
||||
),
|
||||
] {
|
||||
|
|
8
crates/directory/src/cache/lookup.rs
vendored
8
crates/directory/src/cache/lookup.rs
vendored
|
@ -23,7 +23,7 @@
|
|||
|
||||
use mail_send::Credentials;
|
||||
|
||||
use crate::{Directory, Principal};
|
||||
use crate::{Directory, Principal, QueryColumn};
|
||||
|
||||
use super::CachedDirectory;
|
||||
|
||||
|
@ -71,7 +71,11 @@ impl<T: Directory> Directory for CachedDirectory<T> {
|
|||
self.inner.expn(address).await
|
||||
}
|
||||
|
||||
async fn query(&self, query: &str, params: &[&str]) -> crate::Result<bool> {
|
||||
async fn lookup(&self, query: &str, params: &[&str]) -> crate::Result<bool> {
|
||||
self.inner.lookup(query, params).await
|
||||
}
|
||||
|
||||
async fn query(&self, query: &str, params: &[&str]) -> crate::Result<Vec<QueryColumn>> {
|
||||
self.inner.query(query, params).await
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
use mail_send::Credentials;
|
||||
use smtp_proto::{AUTH_CRAM_MD5, AUTH_LOGIN, AUTH_OAUTHBEARER, AUTH_PLAIN, AUTH_XOAUTH2};
|
||||
|
||||
use crate::{Directory, DirectoryError, Principal};
|
||||
use crate::{Directory, DirectoryError, Principal, QueryColumn};
|
||||
|
||||
use super::{ImapDirectory, ImapError};
|
||||
|
||||
|
@ -98,7 +98,11 @@ impl Directory for ImapDirectory {
|
|||
Err(DirectoryError::unsupported("imap", "expn"))
|
||||
}
|
||||
|
||||
async fn query(&self, _query: &str, _params: &[&str]) -> crate::Result<bool> {
|
||||
async fn lookup(&self, _query: &str, _params: &[&str]) -> crate::Result<bool> {
|
||||
Err(DirectoryError::unsupported("imap", "lookup"))
|
||||
}
|
||||
|
||||
async fn query(&self, _query: &str, _params: &[&str]) -> crate::Result<Vec<QueryColumn>> {
|
||||
Err(DirectoryError::unsupported("imap", "query"))
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
use ldap3::{ResultEntry, Scope, SearchEntry};
|
||||
use mail_send::Credentials;
|
||||
|
||||
use crate::{Directory, Principal, Type};
|
||||
use crate::{Directory, Principal, QueryColumn, Type};
|
||||
|
||||
use super::{LdapDirectory, LdapMappings};
|
||||
|
||||
|
@ -239,11 +239,55 @@ impl Directory for LdapDirectory {
|
|||
Ok(emails)
|
||||
}
|
||||
|
||||
async fn query(&self, query: &str, params: &[&str]) -> crate::Result<bool> {
|
||||
async fn lookup(&self, query: &str, params: &[&str]) -> crate::Result<bool> {
|
||||
self.query_(query, params)
|
||||
.await
|
||||
.map(|entry| entry.is_some())
|
||||
}
|
||||
|
||||
async fn query(&self, query: &str, params: &[&str]) -> crate::Result<Vec<QueryColumn>> {
|
||||
self.query_(query, params).await.map(|entry| {
|
||||
if let Some(entry) = entry {
|
||||
let mut object = String::new();
|
||||
for (attr, values) in SearchEntry::construct(entry).attrs {
|
||||
for value in values {
|
||||
object.push_str(&attr);
|
||||
object.push(':');
|
||||
object.push_str(&value);
|
||||
object.push('\n');
|
||||
}
|
||||
}
|
||||
vec![QueryColumn::Text(object)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.streaming_search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
&self.mappings.filter_domains.build(domain),
|
||||
Vec::<String>::new(),
|
||||
)
|
||||
.await?
|
||||
.next()
|
||||
.await
|
||||
.map(|entry| entry.is_some())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl LdapDirectory {
|
||||
async fn query_(&self, query: &str, params: &[&str]) -> crate::Result<Option<ResultEntry>> {
|
||||
let mut conn = self.pool.get().await?;
|
||||
tracing::trace!(context = "directory", event = "query", query = query, params = ?params);
|
||||
|
||||
Ok(if !params.is_empty() {
|
||||
if !params.is_empty() {
|
||||
let mut expanded_query = String::with_capacity(query.len() + params.len() * 2);
|
||||
for (pos, item) in query.split('?').enumerate() {
|
||||
if pos > 0 {
|
||||
|
@ -270,29 +314,10 @@ impl Directory for LdapDirectory {
|
|||
.await
|
||||
}?
|
||||
.next()
|
||||
.await?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.streaming_search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
&self.mappings.filter_domains.build(domain),
|
||||
Vec::<String>::new(),
|
||||
)
|
||||
.await?
|
||||
.next()
|
||||
.await
|
||||
.map(|entry| entry.is_some())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl LdapDirectory {
|
||||
async fn find_principal(&self, filter: &str) -> crate::Result<Option<Principal>> {
|
||||
let (rs, _res) = self
|
||||
.pool
|
||||
|
|
|
@ -80,13 +80,24 @@ pub trait Directory: Sync + Send {
|
|||
async fn rcpt(&self, address: &str) -> crate::Result<bool>;
|
||||
async fn vrfy(&self, address: &str) -> Result<Vec<String>>;
|
||||
async fn expn(&self, address: &str) -> Result<Vec<String>>;
|
||||
async fn query(&self, query: &str, params: &[&str]) -> Result<bool>;
|
||||
async fn lookup(&self, query: &str, params: &[&str]) -> Result<bool>;
|
||||
async fn query(&self, query: &str, params: &[&str]) -> Result<Vec<QueryColumn>>;
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum QueryColumn {
|
||||
Integer(i64),
|
||||
Bool(bool),
|
||||
Float(f64),
|
||||
Text(String),
|
||||
Blob(Vec<u8>),
|
||||
Null,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Lookup {
|
||||
Directory {
|
||||
|
@ -101,10 +112,12 @@ pub enum Lookup {
|
|||
impl Lookup {
|
||||
pub async fn contains(&self, item: &str) -> Option<bool> {
|
||||
match self {
|
||||
Lookup::Directory { directory, query } => match directory.query(query, &[item]).await {
|
||||
Lookup::Directory { directory, query } => {
|
||||
match directory.lookup(query, &[item]).await {
|
||||
Ok(result) => result.into(),
|
||||
Err(_) => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
Lookup::List { list } => list.contains(item).into(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
use mail_send::Credentials;
|
||||
|
||||
use crate::{Directory, DirectoryError, Principal};
|
||||
use crate::{Directory, DirectoryError, Principal, QueryColumn};
|
||||
|
||||
use super::{EmailType, MemoryDirectory};
|
||||
|
||||
|
@ -132,7 +132,11 @@ impl Directory for MemoryDirectory {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
async fn query(&self, _query: &str, _params: &[&str]) -> crate::Result<bool> {
|
||||
async fn lookup(&self, _query: &str, _params: &[&str]) -> crate::Result<bool> {
|
||||
Err(DirectoryError::unsupported("memory", "lookp"))
|
||||
}
|
||||
|
||||
async fn query(&self, _query: &str, _params: &[&str]) -> crate::Result<Vec<QueryColumn>> {
|
||||
Err(DirectoryError::unsupported("memory", "query"))
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
use mail_send::{smtp::AssertReply, Credentials};
|
||||
use smtp_proto::Severity;
|
||||
|
||||
use crate::{Directory, DirectoryError, Principal};
|
||||
use crate::{Directory, DirectoryError, Principal, QueryColumn};
|
||||
|
||||
use super::{SmtpClient, SmtpDirectory};
|
||||
|
||||
|
@ -93,7 +93,11 @@ impl Directory for SmtpDirectory {
|
|||
.await
|
||||
}
|
||||
|
||||
async fn query(&self, _query: &str, _params: &[&str]) -> crate::Result<bool> {
|
||||
async fn lookup(&self, _query: &str, _params: &[&str]) -> crate::Result<bool> {
|
||||
Err(DirectoryError::unsupported("smtp", "lookup"))
|
||||
}
|
||||
|
||||
async fn query(&self, _query: &str, _params: &[&str]) -> crate::Result<Vec<QueryColumn>> {
|
||||
Err(DirectoryError::unsupported("smtp", "query"))
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
|
||||
use futures::TryStreamExt;
|
||||
use mail_send::Credentials;
|
||||
use sqlx::{any::AnyRow, Column, Row};
|
||||
use sqlx::{any::AnyRow, postgres::any::AnyTypeInfoKind, Column, Row};
|
||||
|
||||
use crate::{Directory, Principal, Type};
|
||||
use crate::{Directory, Principal, QueryColumn, Type};
|
||||
|
||||
use super::{SqlDirectory, SqlMappings};
|
||||
|
||||
|
@ -154,18 +154,42 @@ impl Directory for SqlDirectory {
|
|||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn query(&self, query: &str, params: &[&str]) -> crate::Result<bool> {
|
||||
tracing::trace!(context = "directory", event = "query", query = query, params = ?params);
|
||||
let mut q = sqlx::query(query);
|
||||
for param in params {
|
||||
q = q.bind(param);
|
||||
async fn lookup(&self, query: &str, params: &[&str]) -> crate::Result<bool> {
|
||||
self.query_(query, params).await.map(|row| row.is_some())
|
||||
}
|
||||
|
||||
q.fetch(&self.pool)
|
||||
.try_next()
|
||||
.await
|
||||
.map(|r| r.is_some())
|
||||
.map_err(Into::into)
|
||||
async fn query(&self, query: &str, params: &[&str]) -> crate::Result<Vec<QueryColumn>> {
|
||||
self.query_(query, params).await.map(|row| {
|
||||
if let Some(row) = row {
|
||||
let mut columns = Vec::with_capacity(row.columns().len());
|
||||
for col in row.columns() {
|
||||
let idx = col.ordinal();
|
||||
columns.push(match col.type_info().kind() {
|
||||
AnyTypeInfoKind::Null => QueryColumn::Null,
|
||||
AnyTypeInfoKind::Bool => {
|
||||
QueryColumn::Bool(row.try_get(idx).unwrap_or_default())
|
||||
}
|
||||
AnyTypeInfoKind::SmallInt
|
||||
| AnyTypeInfoKind::Integer
|
||||
| AnyTypeInfoKind::BigInt => {
|
||||
QueryColumn::Integer(row.try_get(idx).unwrap_or_default())
|
||||
}
|
||||
AnyTypeInfoKind::Real | AnyTypeInfoKind::Double => {
|
||||
QueryColumn::Float(row.try_get(idx).unwrap_or_default())
|
||||
}
|
||||
AnyTypeInfoKind::Text => {
|
||||
QueryColumn::Text(row.try_get(idx).unwrap_or_default())
|
||||
}
|
||||
AnyTypeInfoKind::Blob => {
|
||||
QueryColumn::Blob(row.try_get(idx).unwrap_or_default())
|
||||
}
|
||||
});
|
||||
}
|
||||
columns
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
|
@ -179,6 +203,18 @@ impl Directory for SqlDirectory {
|
|||
}
|
||||
}
|
||||
|
||||
impl SqlDirectory {
|
||||
async fn query_(&self, query: &str, params: &[&str]) -> crate::Result<Option<AnyRow>> {
|
||||
tracing::trace!(context = "directory", event = "query", query = query, params = ?params);
|
||||
let mut q = sqlx::query(query);
|
||||
for param in params {
|
||||
q = q.bind(param);
|
||||
}
|
||||
|
||||
q.fetch(&self.pool).try_next().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl SqlMappings {
|
||||
pub fn row_to_principal(&self, row: AnyRow) -> crate::Result<Principal> {
|
||||
let mut principal = Principal::default();
|
||||
|
|
|
@ -291,6 +291,7 @@ impl SessionData {
|
|||
{
|
||||
account
|
||||
} else {
|
||||
#[allow(clippy::unnecessary_literal_unwrap)]
|
||||
return Err(StatusResponse::no(format!(
|
||||
"Shared account '{}' not found.",
|
||||
prefix.unwrap_or_default()
|
||||
|
|
|
@ -34,10 +34,6 @@ pub enum JSONPointer {
|
|||
Path(Vec<JSONPointer>),
|
||||
}
|
||||
|
||||
pub trait JSONPointerEval {
|
||||
fn eval_json_pointer(&self, ptr: &JSONPointer) -> Option<Vec<u64>>;
|
||||
}
|
||||
|
||||
enum TokenType {
|
||||
Unknown,
|
||||
Number,
|
||||
|
|
|
@ -44,9 +44,9 @@ aes = "0.8.3"
|
|||
cbc = { version = "0.1.2", features = ["alloc"] }
|
||||
pgp = "0.10.2"
|
||||
rand = "0.8.5"
|
||||
rasn = "0.8.2"
|
||||
rasn-cms = "0.8.2"
|
||||
rasn-pkix = "0.8.2"
|
||||
rasn = "0.9.5"
|
||||
rasn-cms = "0.9.5"
|
||||
rasn-pkix = "0.9.5"
|
||||
rsa = "0.9.2"
|
||||
async-trait = "0.1.68"
|
||||
|
||||
|
|
|
@ -382,7 +382,7 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
Event::ListContains { .. }
|
||||
| Event::Execute { .. }
|
||||
| Event::Plugin { .. }
|
||||
| Event::Notify { .. }
|
||||
| Event::SetEnvelope { .. } => {
|
||||
// Not allowed
|
||||
|
|
|
@ -25,7 +25,10 @@ use std::time::Duration;
|
|||
|
||||
use sieve::{compiler::grammar::Capability, Compiler, Runtime};
|
||||
|
||||
use crate::core::{SieveConfig, SieveCore};
|
||||
use crate::{
|
||||
core::{SieveConfig, SieveCore},
|
||||
scripts::plugins::RegisterSievePlugins,
|
||||
};
|
||||
use utils::config::{utils::AsKey, Config};
|
||||
|
||||
use super::ConfigContext;
|
||||
|
@ -39,14 +42,15 @@ impl ConfigSieve for Config {
|
|||
// Allocate compiler and runtime
|
||||
let compiler = Compiler::new()
|
||||
.with_max_string_size(52428800)
|
||||
.with_max_string_size(10240)
|
||||
.with_max_variable_name_size(100)
|
||||
.with_max_nested_blocks(50)
|
||||
.with_max_nested_tests(50)
|
||||
.with_max_nested_foreverypart(10)
|
||||
.with_max_local_variables(128)
|
||||
.with_max_local_variables(8192)
|
||||
.with_max_header_size(10240)
|
||||
.with_max_includes(10);
|
||||
.with_max_includes(10)
|
||||
.register_plugins();
|
||||
|
||||
let mut runtime = Runtime::new()
|
||||
.without_capabilities([
|
||||
Capability::FileInto,
|
||||
|
@ -60,7 +64,7 @@ impl ConfigSieve for Config {
|
|||
Capability::ImapSieve,
|
||||
Capability::Duplicate,
|
||||
])
|
||||
.with_capability(Capability::Execute)
|
||||
.with_capability(Capability::Plugins)
|
||||
.with_max_variable_size(102400)
|
||||
.with_max_header_size(10240)
|
||||
.with_valid_notification_uri("mailto")
|
||||
|
@ -135,17 +139,7 @@ impl ConfigSieve for Config {
|
|||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
sign,
|
||||
db: if let Some(db) = self.value("sieve.use-directory") {
|
||||
if let Some(db) = ctx.directory.directories.get(db) {
|
||||
Some(db.clone())
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Directory {db:?} not found for key \"sieve.use-directory\"."
|
||||
));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
directories: ctx.directory.directories.clone(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ use self::throttle::{Limiter, ThrottleKey, ThrottleKeyHasherBuilder};
|
|||
pub mod if_block;
|
||||
pub mod management;
|
||||
pub mod params;
|
||||
pub mod scripts;
|
||||
pub mod throttle;
|
||||
pub mod worker;
|
||||
|
||||
|
@ -115,7 +114,7 @@ pub struct SieveConfig {
|
|||
pub from_name: String,
|
||||
pub return_path: String,
|
||||
pub sign: Vec<Arc<DkimSigner>>,
|
||||
pub db: Option<Arc<dyn Directory>>,
|
||||
pub directories: AHashMap<String, Arc<dyn Directory>>,
|
||||
}
|
||||
|
||||
pub struct Resolvers {
|
||||
|
|
|
@ -44,12 +44,13 @@ use tokio::{
|
|||
|
||||
use crate::{
|
||||
config::DNSBL_FROM,
|
||||
core::{scripts::ScriptResult, Session, SessionAddress, State},
|
||||
core::{Session, SessionAddress, State},
|
||||
queue::{self, DomainPart, Message, SimpleEnvelope},
|
||||
reporting::analysis::AnalyzeReport,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
|
||||
use super::IsTls;
|
||||
use super::{AuthResult, IsTls};
|
||||
|
||||
impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> {
|
||||
pub async fn queue_message(&mut self) -> Cow<'static, [u8]> {
|
||||
|
@ -204,7 +205,7 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> {
|
|||
}
|
||||
|
||||
// Verify DMARC
|
||||
match &self.data.spf_mail_from {
|
||||
let dmarc_result = match &self.data.spf_mail_from {
|
||||
Some(spf_output) if dmarc.verify() => {
|
||||
let dmarc_output = self
|
||||
.core
|
||||
|
@ -232,6 +233,17 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> {
|
|||
|
||||
// Add to DMARC output to the Authentication-Results header
|
||||
auth_results = auth_results.with_dmarc_result(&dmarc_output);
|
||||
let dmarc_result = if dmarc_output.spf_result() == &DmarcResult::Pass
|
||||
|| dmarc_output.dkim_result() == &DmarcResult::Pass
|
||||
{
|
||||
DmarcResult::Pass
|
||||
} else if dmarc_output.spf_result() != &DmarcResult::None {
|
||||
dmarc_output.spf_result().clone()
|
||||
} else if dmarc_output.dkim_result() != &DmarcResult::None {
|
||||
dmarc_output.dkim_result().clone()
|
||||
} else {
|
||||
DmarcResult::None
|
||||
};
|
||||
|
||||
if !rejected {
|
||||
tracing::debug!(parent: &self.span,
|
||||
|
@ -271,9 +283,11 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> {
|
|||
(&b"550 5.7.1 Email rejected per DMARC policy.\r\n"[..]).into()
|
||||
};
|
||||
}
|
||||
|
||||
dmarc_result.into()
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Analyze reports
|
||||
if self.is_report() {
|
||||
|
@ -397,13 +411,35 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> {
|
|||
|
||||
// Sieve filtering
|
||||
if let Some(script) = dc.script.eval(self).await {
|
||||
match self
|
||||
.run_script(
|
||||
script.clone(),
|
||||
Some(edited_message.as_ref().unwrap_or(&raw_message).clone()),
|
||||
let params = self
|
||||
.build_script_parameters()
|
||||
.with_message(edited_message.as_ref().unwrap_or(&raw_message).clone())
|
||||
.set_variable("from", auth_message.from().to_string())
|
||||
.set_variable(
|
||||
"arc",
|
||||
arc_output
|
||||
.as_ref()
|
||||
.map(|a| a.result().as_str())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
.set_variable(
|
||||
"dkim",
|
||||
dkim_output
|
||||
.iter()
|
||||
.find(|r| matches!(r.result(), DkimResult::Pass))
|
||||
.or_else(|| dkim_output.first())
|
||||
.map(|r| r.result().as_str())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.set_variable(
|
||||
"dmarc",
|
||||
dmarc_result
|
||||
.as_ref()
|
||||
.map(|a| a.as_str())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
match self.run_script(script.clone(), params).await {
|
||||
ScriptResult::Accept { modifications } => {
|
||||
if !modifications.is_empty() {
|
||||
self.data.apply_sieve_modifications(modifications)
|
||||
|
|
|
@ -25,7 +25,8 @@ use std::{net::IpAddr, time::SystemTime};
|
|||
|
||||
use crate::{
|
||||
config::{DNSBL_EHLO, DNSBL_IP},
|
||||
core::{scripts::ScriptResult, Session},
|
||||
core::Session,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
use mail_auth::spf::verify::HasLabels;
|
||||
use smtp_proto::*;
|
||||
|
@ -96,10 +97,13 @@ impl<T: AsyncWrite + AsyncRead + IsTls + Unpin> Session<T> {
|
|||
|
||||
// Sieve filtering
|
||||
if let Some(script) = self.core.session.config.ehlo.script.eval(self).await {
|
||||
if let ScriptResult::Reject(message) = self.run_script(script.clone(), None).await {
|
||||
if let ScriptResult::Reject(message) = self
|
||||
.run_script(script.clone(), self.build_script_parameters())
|
||||
.await
|
||||
{
|
||||
tracing::debug!(parent: &self.span,
|
||||
context = "ehlo",
|
||||
event = "sieve-reject",
|
||||
context = "sieve",
|
||||
event = "reject",
|
||||
domain = &self.data.helo_domain,
|
||||
reason = message);
|
||||
|
||||
|
|
|
@ -29,8 +29,9 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
|||
|
||||
use crate::{
|
||||
config::{DNSBL_IPREV, DNSBL_RETURN_PATH},
|
||||
core::{scripts::ScriptResult, Session, SessionAddress},
|
||||
core::{Session, SessionAddress},
|
||||
queue::DomainPart,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
|
||||
use super::IsTls;
|
||||
|
@ -139,7 +140,10 @@ impl<T: AsyncWrite + AsyncRead + Unpin + IsTls> Session<T> {
|
|||
|
||||
// Sieve filtering
|
||||
if let Some(script) = self.core.session.config.mail.script.eval(self).await {
|
||||
match self.run_script(script.clone(), None).await {
|
||||
match self
|
||||
.run_script(script.clone(), self.build_script_parameters())
|
||||
.await
|
||||
{
|
||||
ScriptResult::Accept { modifications } => {
|
||||
if !modifications.is_empty() {
|
||||
tracing::debug!(parent: &self.span,
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
use mail_auth::{
|
||||
arc::ArcSet, dkim::Signature, ArcOutput, AuthenticatedMessage, AuthenticationResults,
|
||||
DkimResult, DmarcResult, IprevResult, SpfResult,
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::server::TlsStream;
|
||||
|
@ -131,3 +132,58 @@ impl DkimSigner {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AuthResult {
|
||||
fn as_str(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl AuthResult for SpfResult {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
SpfResult::Pass => "pass",
|
||||
SpfResult::Fail => "fail",
|
||||
SpfResult::SoftFail => "softfail",
|
||||
SpfResult::Neutral => "neutral",
|
||||
SpfResult::None => "none",
|
||||
SpfResult::TempError => "temperror",
|
||||
SpfResult::PermError => "permerror",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthResult for IprevResult {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
IprevResult::Pass => "pass",
|
||||
IprevResult::Fail(_) => "fail",
|
||||
IprevResult::TempError(_) => "temperror",
|
||||
IprevResult::PermError(_) => "permerror",
|
||||
IprevResult::None => "none",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthResult for DkimResult {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
DkimResult::Pass => "pass",
|
||||
DkimResult::None => "none",
|
||||
DkimResult::Neutral(_) => "neutral",
|
||||
DkimResult::Fail(_) => "fail",
|
||||
DkimResult::PermError(_) => "permerror",
|
||||
DkimResult::TempError(_) => "temperror",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthResult for DmarcResult {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
DmarcResult::Pass => "pass",
|
||||
DmarcResult::Fail(_) => "fail",
|
||||
DmarcResult::TempError(_) => "temperror",
|
||||
DmarcResult::PermError(_) => "permerror",
|
||||
DmarcResult::None => "none",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,9 @@ use smtp_proto::{
|
|||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::{
|
||||
core::{scripts::ScriptResult, Session, SessionAddress},
|
||||
core::{Session, SessionAddress},
|
||||
queue::DomainPart,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
|
||||
impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
|
||||
|
@ -88,7 +89,10 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
|
|||
if rcpt_script.is_some() || !self.core.session.config.rcpt.rewrite.is_empty() {
|
||||
// Sieve filtering
|
||||
if let Some(script) = rcpt_script {
|
||||
match self.run_script(script.clone(), None).await {
|
||||
match self
|
||||
.run_script(script.clone(), self.build_script_parameters())
|
||||
.await
|
||||
{
|
||||
ScriptResult::Accept { modifications } => {
|
||||
if !modifications.is_empty() {
|
||||
tracing::debug!(parent: &self.span,
|
||||
|
|
|
@ -31,10 +31,9 @@ use tokio_rustls::server::TlsStream;
|
|||
use utils::listener::SessionManager;
|
||||
|
||||
use crate::{
|
||||
core::{
|
||||
scripts::ScriptResult, Session, SessionData, SessionParameters, SmtpSessionManager, State,
|
||||
},
|
||||
core::{Session, SessionData, SessionParameters, SmtpSessionManager, State},
|
||||
queue, reporting,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
|
||||
use super::IsTls;
|
||||
|
@ -118,7 +117,10 @@ impl<T: AsyncRead + AsyncWrite + IsTls + Unpin> Session<T> {
|
|||
|
||||
// Sieve filtering
|
||||
if let Some(script) = self.core.session.config.connect.script.eval(self).await {
|
||||
if let ScriptResult::Reject(message) = self.run_script(script.clone(), None).await {
|
||||
if let ScriptResult::Reject(message) = self
|
||||
.run_script(script.clone(), self.build_script_parameters())
|
||||
.await
|
||||
{
|
||||
tracing::debug!(parent: &self.span,
|
||||
context = "connect",
|
||||
event = "sieve-reject",
|
||||
|
|
|
@ -47,6 +47,7 @@ pub mod inbound;
|
|||
pub mod outbound;
|
||||
pub mod queue;
|
||||
pub mod reporting;
|
||||
pub mod scripts;
|
||||
|
||||
pub static USER_AGENT: &str = concat!("StalwartSMTP/", env!("CARGO_PKG_VERSION"),);
|
||||
pub static DAEMON_NAME: &str = concat!("Stalwart SMTP v", env!("CARGO_PKG_VERSION"),);
|
||||
|
|
146
crates/smtp/src/scripts/envelope.rs
Normal file
146
crates/smtp/src/scripts/envelope.rs
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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::Envelope;
|
||||
use smtp_proto::{
|
||||
MAIL_BY_NOTIFY, MAIL_BY_RETURN, MAIL_BY_TRACE, MAIL_RET_FULL, MAIL_RET_HDRS, RCPT_NOTIFY_DELAY,
|
||||
RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_NEVER, RCPT_NOTIFY_SUCCESS,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core::{SessionAddress, SessionData},
|
||||
queue::DomainPart,
|
||||
};
|
||||
|
||||
impl SessionData {
|
||||
pub fn apply_sieve_modifications(&mut self, modifications: Vec<(Envelope, String)>) {
|
||||
for (envelope, value) in modifications {
|
||||
match envelope {
|
||||
Envelope::From => {
|
||||
let (address, address_lcase, domain) = if value.contains('@') {
|
||||
let address_lcase = value.to_lowercase();
|
||||
let domain = address_lcase.domain_part().to_string();
|
||||
(value, address_lcase, domain)
|
||||
} else if value.is_empty() {
|
||||
(String::new(), String::new(), String::new())
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.address = address;
|
||||
mail_from.address_lcase = address_lcase;
|
||||
mail_from.domain = domain;
|
||||
} else {
|
||||
self.mail_from = SessionAddress {
|
||||
address,
|
||||
address_lcase,
|
||||
domain,
|
||||
flags: 0,
|
||||
dsn_info: None,
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
Envelope::To => {
|
||||
if value.contains('@') {
|
||||
let address_lcase = value.to_lowercase();
|
||||
let domain = address_lcase.domain_part().to_string();
|
||||
if let Some(rcpt_to) = self.rcpt_to.last_mut() {
|
||||
rcpt_to.address = value;
|
||||
rcpt_to.address_lcase = address_lcase;
|
||||
rcpt_to.domain = domain;
|
||||
} else {
|
||||
self.rcpt_to.push(SessionAddress {
|
||||
address: value,
|
||||
address_lcase,
|
||||
domain,
|
||||
flags: 0,
|
||||
dsn_info: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::ByMode => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.flags &= !(MAIL_BY_NOTIFY | MAIL_BY_RETURN);
|
||||
if value == "N" {
|
||||
mail_from.flags |= MAIL_BY_NOTIFY;
|
||||
} else if value == "R" {
|
||||
mail_from.flags |= MAIL_BY_RETURN;
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::ByTrace => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
if value == "T" {
|
||||
mail_from.flags |= MAIL_BY_TRACE;
|
||||
} else {
|
||||
mail_from.flags &= !MAIL_BY_TRACE;
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::Notify => {
|
||||
if let Some(rcpt_to) = self.rcpt_to.last_mut() {
|
||||
rcpt_to.flags &= !(RCPT_NOTIFY_DELAY
|
||||
| RCPT_NOTIFY_FAILURE
|
||||
| RCPT_NOTIFY_SUCCESS
|
||||
| RCPT_NOTIFY_NEVER);
|
||||
if value == "NEVER" {
|
||||
rcpt_to.flags |= RCPT_NOTIFY_NEVER;
|
||||
} else {
|
||||
for value in value.split(',') {
|
||||
match value.trim() {
|
||||
"SUCCESS" => rcpt_to.flags |= RCPT_NOTIFY_SUCCESS,
|
||||
"FAILURE" => rcpt_to.flags |= RCPT_NOTIFY_FAILURE,
|
||||
"DELAY" => rcpt_to.flags |= RCPT_NOTIFY_DELAY,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::Ret => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.flags &= !(MAIL_RET_FULL | MAIL_RET_HDRS);
|
||||
if value == "FULL" {
|
||||
mail_from.flags |= MAIL_RET_FULL;
|
||||
} else if value == "HDRS" {
|
||||
mail_from.flags |= MAIL_RET_HDRS;
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::Orcpt => {
|
||||
if let Some(rcpt_to) = self.rcpt_to.last_mut() {
|
||||
rcpt_to.dsn_info = value.into();
|
||||
}
|
||||
}
|
||||
Envelope::Envid => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.dsn_info = value.into();
|
||||
}
|
||||
}
|
||||
Envelope::ByTimeAbsolute | Envelope::ByTimeRelative => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,111 +21,33 @@
|
|||
* for more details.
|
||||
*/
|
||||
|
||||
use std::{borrow::Cow, process::Command, sync::Arc, time::Duration};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use directory::Lookup;
|
||||
use mail_auth::common::headers::HeaderWriter;
|
||||
use sieve::{
|
||||
compiler::grammar::actions::action_redirect::{ByMode, ByTime, Notify, NotifyItem, Ret},
|
||||
CommandType, Envelope, Event, Input, MatchAs, Recipient, Sieve,
|
||||
Event, Input, MatchAs, Recipient, Sieve,
|
||||
};
|
||||
use smtp_proto::{
|
||||
MAIL_BY_NOTIFY, MAIL_BY_RETURN, MAIL_BY_TRACE, MAIL_RET_FULL, MAIL_RET_HDRS, RCPT_NOTIFY_DELAY,
|
||||
RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_NEVER, RCPT_NOTIFY_SUCCESS,
|
||||
MAIL_BY_TRACE, MAIL_RET_FULL, MAIL_RET_HDRS, RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE,
|
||||
RCPT_NOTIFY_NEVER, RCPT_NOTIFY_SUCCESS,
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
runtime::Handle,
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::{
|
||||
core::SMTP,
|
||||
queue::{DomainPart, InstantFromTimestamp, Message},
|
||||
};
|
||||
|
||||
use crate::queue::{DomainPart, InstantFromTimestamp, Message};
|
||||
|
||||
use super::{Session, SessionAddress, SessionData, SMTP};
|
||||
|
||||
pub enum ScriptResult {
|
||||
Accept {
|
||||
modifications: Vec<(Envelope, String)>,
|
||||
},
|
||||
Replace {
|
||||
message: Vec<u8>,
|
||||
modifications: Vec<(Envelope, String)>,
|
||||
},
|
||||
Reject(String),
|
||||
Discard,
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
|
||||
pub async fn run_script(
|
||||
&self,
|
||||
script: Arc<Sieve>,
|
||||
message: Option<Arc<Vec<u8>>>,
|
||||
) -> ScriptResult {
|
||||
let core = self.core.clone();
|
||||
let span = self.span.clone();
|
||||
|
||||
// Set environment variables
|
||||
let mut vars_env: AHashMap<String, Cow<str>> = AHashMap::with_capacity(6);
|
||||
vars_env.insert(
|
||||
"remote_ip".to_string(),
|
||||
self.data.remote_ip.to_string().into(),
|
||||
);
|
||||
vars_env.insert(
|
||||
"helo_domain".to_string(),
|
||||
self.data.helo_domain.clone().into(),
|
||||
);
|
||||
vars_env.insert(
|
||||
"authenticated_as".to_string(),
|
||||
self.data.authenticated_as.clone().into(),
|
||||
);
|
||||
|
||||
// Set envelope
|
||||
let envelope = if let Some(mail_from) = &self.data.mail_from {
|
||||
let mut envelope: Vec<(Envelope, Cow<str>)> = Vec::with_capacity(6);
|
||||
envelope.push((Envelope::From, mail_from.address.clone().into()));
|
||||
if let Some(env_id) = &mail_from.dsn_info {
|
||||
envelope.push((Envelope::Envid, env_id.clone().into()));
|
||||
}
|
||||
if let Some(rcpt) = self.data.rcpt_to.last() {
|
||||
envelope.push((Envelope::To, rcpt.address.clone().into()));
|
||||
if let Some(orcpt) = &rcpt.dsn_info {
|
||||
envelope.push((Envelope::Orcpt, orcpt.clone().into()));
|
||||
}
|
||||
}
|
||||
if (mail_from.flags & MAIL_RET_FULL) != 0 {
|
||||
envelope.push((Envelope::Ret, "FULL".into()));
|
||||
} else if (mail_from.flags & MAIL_RET_HDRS) != 0 {
|
||||
envelope.push((Envelope::Ret, "HDRS".into()));
|
||||
}
|
||||
if (mail_from.flags & MAIL_BY_NOTIFY) != 0 {
|
||||
envelope.push((Envelope::ByMode, "N".into()));
|
||||
} else if (mail_from.flags & MAIL_BY_RETURN) != 0 {
|
||||
envelope.push((Envelope::ByMode, "R".into()));
|
||||
}
|
||||
envelope
|
||||
} else {
|
||||
Vec::with_capacity(0)
|
||||
};
|
||||
|
||||
let handle = Handle::current();
|
||||
self.core
|
||||
.spawn_worker(move || {
|
||||
core.run_script_blocking(script, vars_env, envelope, message, handle, span)
|
||||
})
|
||||
.await
|
||||
.unwrap_or(ScriptResult::Accept {
|
||||
modifications: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
use super::{plugins::PluginContext, ScriptParameters, ScriptResult};
|
||||
|
||||
impl SMTP {
|
||||
fn run_script_blocking(
|
||||
pub fn run_script_blocking(
|
||||
&self,
|
||||
script: Arc<Sieve>,
|
||||
vars_env: AHashMap<String, Cow<'static, str>>,
|
||||
envelope: Vec<(Envelope, Cow<'static, str>)>,
|
||||
message: Option<Arc<Vec<u8>>>,
|
||||
params: ScriptParameters,
|
||||
handle: Handle,
|
||||
span: tracing::Span,
|
||||
) -> ScriptResult {
|
||||
|
@ -133,9 +55,9 @@ impl SMTP {
|
|||
let mut instance = self
|
||||
.sieve
|
||||
.runtime
|
||||
.filter(message.as_deref().map_or(b"", |m| &m[..]))
|
||||
.with_vars_env(vars_env)
|
||||
.with_envelope_list(envelope)
|
||||
.filter(params.message.as_deref().map_or(b"", |m| &m[..]))
|
||||
.with_vars_env(params.variables)
|
||||
.with_envelope_list(params.envelope)
|
||||
.with_user_address(&self.sieve.config.from_addr)
|
||||
.with_user_full_name(&self.sieve.config.from_name);
|
||||
let mut input = Input::script("__script", script);
|
||||
|
@ -145,6 +67,8 @@ impl SMTP {
|
|||
let mut modifications = vec![];
|
||||
let mut keep_id = usize::MAX;
|
||||
|
||||
let mut plugin_data = AHashMap::new();
|
||||
|
||||
// Start event loop
|
||||
while let Some(result) = instance.run(input) {
|
||||
match result {
|
||||
|
@ -193,54 +117,18 @@ impl SMTP {
|
|||
}
|
||||
}
|
||||
}
|
||||
Event::Execute {
|
||||
command_type,
|
||||
command,
|
||||
Event::Plugin { id, arguments } => {
|
||||
input = self.run_plugin_blocking(
|
||||
id,
|
||||
PluginContext {
|
||||
span: &span,
|
||||
handle: &handle,
|
||||
core: self,
|
||||
data: &mut plugin_data,
|
||||
arguments,
|
||||
} => match command_type {
|
||||
CommandType::Query => {
|
||||
if let Some(db) = &self.sieve.config.db {
|
||||
let result = handle.block_on(db.query(
|
||||
&command,
|
||||
&arguments.iter().map(String::as_str).collect::<Vec<_>>(),
|
||||
));
|
||||
|
||||
input = if command
|
||||
.as_bytes()
|
||||
.get(..6)
|
||||
.map_or(false, |q| q.eq_ignore_ascii_case(b"SELECT"))
|
||||
{
|
||||
result.unwrap_or(false).into()
|
||||
} else {
|
||||
result.is_ok().into()
|
||||
};
|
||||
} else {
|
||||
tracing::warn!(
|
||||
parent: &span,
|
||||
context = "sieve",
|
||||
event = "config-error",
|
||||
reason = "No directory configured",
|
||||
);
|
||||
input = false.into();
|
||||
}
|
||||
}
|
||||
CommandType::Binary => {
|
||||
match Command::new(command).args(arguments).output() {
|
||||
Ok(result) => {
|
||||
input = result.status.success().into();
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
parent: &span,
|
||||
context = "sieve",
|
||||
event = "execute-failed",
|
||||
reason = %err,
|
||||
);
|
||||
input = false.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
Event::Keep { message_id, .. } => {
|
||||
keep_id = message_id;
|
||||
input = true.into();
|
||||
|
@ -484,116 +372,3 @@ impl SMTP {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionData {
|
||||
pub fn apply_sieve_modifications(&mut self, modifications: Vec<(Envelope, String)>) {
|
||||
for (envelope, value) in modifications {
|
||||
match envelope {
|
||||
Envelope::From => {
|
||||
let (address, address_lcase, domain) = if value.contains('@') {
|
||||
let address_lcase = value.to_lowercase();
|
||||
let domain = address_lcase.domain_part().to_string();
|
||||
(value, address_lcase, domain)
|
||||
} else if value.is_empty() {
|
||||
(String::new(), String::new(), String::new())
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.address = address;
|
||||
mail_from.address_lcase = address_lcase;
|
||||
mail_from.domain = domain;
|
||||
} else {
|
||||
self.mail_from = SessionAddress {
|
||||
address,
|
||||
address_lcase,
|
||||
domain,
|
||||
flags: 0,
|
||||
dsn_info: None,
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
Envelope::To => {
|
||||
if value.contains('@') {
|
||||
let address_lcase = value.to_lowercase();
|
||||
let domain = address_lcase.domain_part().to_string();
|
||||
if let Some(rcpt_to) = self.rcpt_to.last_mut() {
|
||||
rcpt_to.address = value;
|
||||
rcpt_to.address_lcase = address_lcase;
|
||||
rcpt_to.domain = domain;
|
||||
} else {
|
||||
self.rcpt_to.push(SessionAddress {
|
||||
address: value,
|
||||
address_lcase,
|
||||
domain,
|
||||
flags: 0,
|
||||
dsn_info: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::ByMode => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.flags &= !(MAIL_BY_NOTIFY | MAIL_BY_RETURN);
|
||||
if value == "N" {
|
||||
mail_from.flags |= MAIL_BY_NOTIFY;
|
||||
} else if value == "R" {
|
||||
mail_from.flags |= MAIL_BY_RETURN;
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::ByTrace => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
if value == "T" {
|
||||
mail_from.flags |= MAIL_BY_TRACE;
|
||||
} else {
|
||||
mail_from.flags &= !MAIL_BY_TRACE;
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::Notify => {
|
||||
if let Some(rcpt_to) = self.rcpt_to.last_mut() {
|
||||
rcpt_to.flags &= !(RCPT_NOTIFY_DELAY
|
||||
| RCPT_NOTIFY_FAILURE
|
||||
| RCPT_NOTIFY_SUCCESS
|
||||
| RCPT_NOTIFY_NEVER);
|
||||
if value == "NEVER" {
|
||||
rcpt_to.flags |= RCPT_NOTIFY_NEVER;
|
||||
} else {
|
||||
for value in value.split(',') {
|
||||
match value.trim() {
|
||||
"SUCCESS" => rcpt_to.flags |= RCPT_NOTIFY_SUCCESS,
|
||||
"FAILURE" => rcpt_to.flags |= RCPT_NOTIFY_FAILURE,
|
||||
"DELAY" => rcpt_to.flags |= RCPT_NOTIFY_DELAY,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::Ret => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.flags &= !(MAIL_RET_FULL | MAIL_RET_HDRS);
|
||||
if value == "FULL" {
|
||||
mail_from.flags |= MAIL_RET_FULL;
|
||||
} else if value == "HDRS" {
|
||||
mail_from.flags |= MAIL_RET_HDRS;
|
||||
}
|
||||
}
|
||||
}
|
||||
Envelope::Orcpt => {
|
||||
if let Some(rcpt_to) = self.rcpt_to.last_mut() {
|
||||
rcpt_to.dsn_info = value.into();
|
||||
}
|
||||
}
|
||||
Envelope::Envid => {
|
||||
if let Some(mail_from) = &mut self.mail_from {
|
||||
mail_from.dsn_info = value.into();
|
||||
}
|
||||
}
|
||||
Envelope::ByTimeAbsolute | Envelope::ByTimeRelative => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
114
crates/smtp/src/scripts/exec.rs
Normal file
114
crates/smtp/src/scripts/exec.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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::sync::Arc;
|
||||
|
||||
use sieve::{Envelope, Sieve};
|
||||
use smtp_proto::{MAIL_BY_NOTIFY, MAIL_BY_RETURN, MAIL_RET_FULL, MAIL_RET_HDRS};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
runtime::Handle,
|
||||
};
|
||||
|
||||
use crate::{core::Session, inbound::AuthResult};
|
||||
|
||||
use super::{ScriptParameters, ScriptResult};
|
||||
|
||||
impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
|
||||
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(
|
||||
"spf",
|
||||
self.data
|
||||
.spf_mail_from
|
||||
.as_ref()
|
||||
.map(|r| r.result().as_str())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.set_variable(
|
||||
"spf_ehlo",
|
||||
self.data
|
||||
.spf_ehlo
|
||||
.as_ref()
|
||||
.map(|r| r.result().as_str())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.set_variable(
|
||||
"iprev",
|
||||
self.data
|
||||
.iprev
|
||||
.as_ref()
|
||||
.map(|r| r.result().as_str())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
if let Some(mail_from) = &self.data.mail_from {
|
||||
params
|
||||
.envelope
|
||||
.push((Envelope::From, mail_from.address.clone().into()));
|
||||
if let Some(env_id) = &mail_from.dsn_info {
|
||||
params
|
||||
.envelope
|
||||
.push((Envelope::Envid, env_id.clone().into()));
|
||||
}
|
||||
if let Some(rcpt) = self.data.rcpt_to.last() {
|
||||
params
|
||||
.envelope
|
||||
.push((Envelope::To, rcpt.address.clone().into()));
|
||||
if let Some(orcpt) = &rcpt.dsn_info {
|
||||
params
|
||||
.envelope
|
||||
.push((Envelope::Orcpt, orcpt.clone().into()));
|
||||
}
|
||||
}
|
||||
if (mail_from.flags & MAIL_RET_FULL) != 0 {
|
||||
params.envelope.push((Envelope::Ret, "FULL".into()));
|
||||
} else if (mail_from.flags & MAIL_RET_HDRS) != 0 {
|
||||
params.envelope.push((Envelope::Ret, "HDRS".into()));
|
||||
}
|
||||
if (mail_from.flags & MAIL_BY_NOTIFY) != 0 {
|
||||
params.envelope.push((Envelope::ByMode, "N".into()));
|
||||
} else if (mail_from.flags & MAIL_BY_RETURN) != 0 {
|
||||
params.envelope.push((Envelope::ByMode, "R".into()));
|
||||
}
|
||||
}
|
||||
|
||||
params
|
||||
}
|
||||
|
||||
pub async fn run_script(&self, script: Arc<Sieve>, params: ScriptParameters) -> ScriptResult {
|
||||
let core = self.core.clone();
|
||||
let span = self.span.clone();
|
||||
|
||||
let handle = Handle::current();
|
||||
self.core
|
||||
.spawn_worker(move || core.run_script_blocking(script, params, handle, span))
|
||||
.await
|
||||
.unwrap_or(ScriptResult::Accept {
|
||||
modifications: vec![],
|
||||
})
|
||||
}
|
||||
}
|
83
crates/smtp/src/scripts/mod.rs
Normal file
83
crates/smtp/src/scripts/mod.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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, sync::Arc};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use sieve::{runtime::Variable, Envelope};
|
||||
|
||||
pub mod envelope;
|
||||
pub mod event_loop;
|
||||
pub mod exec;
|
||||
pub mod plugins;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScriptResult {
|
||||
Accept {
|
||||
modifications: Vec<(Envelope, String)>,
|
||||
},
|
||||
Replace {
|
||||
message: Vec<u8>,
|
||||
modifications: Vec<(Envelope, String)>,
|
||||
},
|
||||
Reject(String),
|
||||
Discard,
|
||||
}
|
||||
|
||||
pub struct ScriptParameters {
|
||||
message: Option<Arc<Vec<u8>>>,
|
||||
variables: AHashMap<Cow<'static, str>, Variable<'static>>,
|
||||
envelope: Vec<(Envelope, Variable<'static>)>,
|
||||
}
|
||||
|
||||
impl ScriptParameters {
|
||||
pub fn new() -> Self {
|
||||
ScriptParameters {
|
||||
variables: AHashMap::with_capacity(10),
|
||||
envelope: Vec::with_capacity(6),
|
||||
message: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_message(self, message: Arc<Vec<u8>>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_variable(
|
||||
mut self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
value: impl Into<Variable<'static>>,
|
||||
) -> Self {
|
||||
self.variables.insert(name.into(), value.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ScriptParameters {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
66
crates/smtp/src/scripts/plugins/exec.rs
Normal file
66
crates/smtp/src/scripts/plugins/exec.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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::process::Command;
|
||||
|
||||
use sieve::{Compiler, Input};
|
||||
|
||||
use super::PluginContext;
|
||||
|
||||
pub fn register(plugin_id: u32, compiler: &mut Compiler) {
|
||||
compiler
|
||||
.register_plugin("exec")
|
||||
.with_id(plugin_id)
|
||||
.with_string_argument()
|
||||
.with_string_array_argument();
|
||||
}
|
||||
|
||||
pub fn exec(ctx: PluginContext<'_>) -> Input {
|
||||
let span = ctx.span;
|
||||
let mut arguments = ctx.arguments.into_iter();
|
||||
match Command::new(
|
||||
arguments
|
||||
.next()
|
||||
.and_then(|a| a.unwrap_string())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.args(
|
||||
arguments
|
||||
.next()
|
||||
.and_then(|a| a.unwrap_string_array())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.output()
|
||||
{
|
||||
Ok(result) => result.status.success().into(),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
parent: span,
|
||||
context = "sieve",
|
||||
event = "execute-failed",
|
||||
reason = %err,
|
||||
);
|
||||
false.into()
|
||||
}
|
||||
}
|
||||
}
|
67
crates/smtp/src/scripts/plugins/mod.rs
Normal file
67
crates/smtp/src/scripts/plugins/mod.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* You can be released from the requirements of the AGPLv3 license by
|
||||
* purchasing a commercial license. Please contact licensing@stalw.art
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
pub mod exec;
|
||||
pub mod query;
|
||||
|
||||
use ahash::AHashMap;
|
||||
use sieve::{compiler::Number, Compiler, Input, PluginArgument};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::core::SMTP;
|
||||
|
||||
type RegisterPluginFnc = fn(u32, &mut Compiler) -> ();
|
||||
type ExecPluginFnc = fn(PluginContext<'_>) -> Input;
|
||||
|
||||
pub struct PluginContext<'x> {
|
||||
pub span: &'x tracing::Span,
|
||||
pub handle: &'x Handle,
|
||||
pub core: &'x SMTP,
|
||||
pub data: &'x mut AHashMap<String, String>,
|
||||
pub arguments: Vec<PluginArgument<String, Number>>,
|
||||
}
|
||||
|
||||
const PLUGINS_EXEC: [ExecPluginFnc; 2] = [query::exec, exec::exec];
|
||||
const PLUGINS_REGISTER: [RegisterPluginFnc; 2] = [query::register, exec::register];
|
||||
|
||||
pub trait RegisterSievePlugins {
|
||||
fn register_plugins(self) -> Self;
|
||||
}
|
||||
|
||||
impl RegisterSievePlugins for Compiler {
|
||||
fn register_plugins(mut self) -> Self {
|
||||
for (i, fnc) in PLUGINS_REGISTER.iter().enumerate() {
|
||||
fnc(i as u32, &mut self);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SMTP {
|
||||
pub fn run_plugin_blocking(&self, id: u32, ctx: PluginContext<'_>) -> Input {
|
||||
PLUGINS_EXEC
|
||||
.get(id as usize)
|
||||
.map(|fnc| fnc(ctx))
|
||||
.unwrap_or(false.into())
|
||||
}
|
||||
}
|
150
crates/smtp/src/scripts/plugins/query.rs
Normal file
150
crates/smtp/src/scripts/plugins/query.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 directory::QueryColumn;
|
||||
use sieve::{runtime::Variable, Compiler, Input, PluginArgument, SetVariable};
|
||||
use smtp_proto::IntoString;
|
||||
|
||||
use super::PluginContext;
|
||||
|
||||
pub fn register(plugin_id: u32, compiler: &mut Compiler) {
|
||||
compiler
|
||||
.register_plugin("query")
|
||||
.with_id(plugin_id)
|
||||
.with_tagged_string_argument("use")
|
||||
.with_tagged_variable_array_argument("set")
|
||||
.with_string_argument()
|
||||
.with_string_array_argument();
|
||||
}
|
||||
|
||||
pub fn exec(ctx: PluginContext<'_>) -> Input {
|
||||
let span = ctx.span;
|
||||
let mut arguments = ctx.arguments.into_iter();
|
||||
let query = arguments
|
||||
.next()
|
||||
.and_then(|a| a.unwrap_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
if query.is_empty() {
|
||||
tracing::warn!(
|
||||
parent: span,
|
||||
context = "sieve:query",
|
||||
event = "invalid",
|
||||
reason = "Empty query string",
|
||||
);
|
||||
return false.into();
|
||||
}
|
||||
|
||||
let parameters = arguments
|
||||
.next()
|
||||
.and_then(|a| a.unwrap_string_array())
|
||||
.unwrap_or_default();
|
||||
let mut directory = None;
|
||||
let mut set_variables = vec![];
|
||||
|
||||
while let Some(arg) = arguments.next() {
|
||||
if let PluginArgument::Tag(tag_id) = arg {
|
||||
match tag_id {
|
||||
0 => {
|
||||
let name = arguments
|
||||
.next()
|
||||
.and_then(|a| a.unwrap_string())
|
||||
.unwrap_or_default();
|
||||
if let Some(directory_) = ctx.core.sieve.config.directories.get(&name) {
|
||||
directory = Some(directory_);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
parent: span,
|
||||
context = "sieve:query",
|
||||
event = "failed",
|
||||
reason = "Unknown directory",
|
||||
directory = %name,
|
||||
);
|
||||
return false.into();
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
set_variables = arguments
|
||||
.next()
|
||||
.and_then(|a| a.unwrap_variable_array())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let directory = if let Some(directory) = directory {
|
||||
directory
|
||||
} else if let Some(directory) = ctx.core.sieve.config.directories.values().next() {
|
||||
directory
|
||||
} else {
|
||||
tracing::warn!(
|
||||
parent: span,
|
||||
context = "sieve:query",
|
||||
event = "failed",
|
||||
reason = "No directory configured",
|
||||
);
|
||||
return false.into();
|
||||
};
|
||||
|
||||
if !set_variables.is_empty() {
|
||||
if let Ok(result) = ctx.handle.block_on(directory.query(
|
||||
&query,
|
||||
¶meters.iter().map(String::as_str).collect::<Vec<_>>(),
|
||||
)) {
|
||||
let mut list = vec![];
|
||||
for (name, value) in set_variables.into_iter().zip(result) {
|
||||
list.push(SetVariable {
|
||||
name,
|
||||
value: match value {
|
||||
QueryColumn::Integer(v) => Variable::Integer(v),
|
||||
QueryColumn::Bool(v) => Variable::Integer(i64::from(v)),
|
||||
QueryColumn::Float(v) => Variable::Float(v),
|
||||
QueryColumn::Text(v) => Variable::String(v),
|
||||
QueryColumn::Blob(v) => Variable::String(v.into_string()),
|
||||
QueryColumn::Null => Variable::StringRef(""),
|
||||
},
|
||||
});
|
||||
}
|
||||
Input::variables(list)
|
||||
} else {
|
||||
false.into()
|
||||
}
|
||||
} else {
|
||||
let result = ctx.handle.block_on(directory.lookup(
|
||||
&query,
|
||||
¶meters.iter().map(String::as_str).collect::<Vec<_>>(),
|
||||
));
|
||||
|
||||
if query
|
||||
.as_bytes()
|
||||
.get(..6)
|
||||
.map_or(false, |q| q.eq_ignore_ascii_case(b"SELECT"))
|
||||
{
|
||||
result.unwrap_or(false).into()
|
||||
} else {
|
||||
result.is_ok().into()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -190,7 +190,7 @@ impl<K: Eq + PartialEq, V> IntoIterator for VecMap<K, V> {
|
|||
type IntoIter = std::iter::Zip<std::vec::IntoIter<K>, std::vec::IntoIter<V>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.k.into_iter().zip(self.v.into_iter())
|
||||
self.k.into_iter().zip(self.v)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -362,12 +362,12 @@ mail = '''
|
|||
}
|
||||
'''
|
||||
rcpt = '''
|
||||
require ["variables", "vnd.stalwart.execute", "envelope", "reject"];
|
||||
require ["variables", "vnd.stalwart.plugins", "envelope", "reject"];
|
||||
|
||||
set "triplet" "${env.remote_ip}.${envelope.from}.${envelope.to}";
|
||||
|
||||
if not execute :query "SELECT EXISTS(SELECT 1 FROM greylist WHERE addr=? LIMIT 1)" ["${triplet}"] {
|
||||
execute :query "INSERT INTO greylist (addr) VALUES (?)" ["${triplet}"];
|
||||
if not query "SELECT EXISTS(SELECT 1 FROM greylist WHERE addr=? LIMIT 1)" ["${triplet}"] {
|
||||
query "INSERT INTO greylist (addr) VALUES (?)" ["${triplet}"];
|
||||
reject "422 4.2.2 Greylisted, please try again in a few moments.";
|
||||
}
|
||||
'''
|
||||
|
|
32
tests/resources/smtp/sieve/awl.sieve
Normal file
32
tests/resources/smtp/sieve/awl.sieve
Normal file
|
@ -0,0 +1,32 @@
|
|||
require ["variables", "include", "vnd.stalwart.plugins", "reject"];
|
||||
|
||||
global "score";
|
||||
|
||||
# Create AWL table
|
||||
if not query :use "sql" "CREATE TABLE awl (score FLOAT, count INT, sender TEXT NOT NULL, ip TEXT NOT NULL, PRIMARY KEY (sender, ip))" [] {
|
||||
reject "create table query failed";
|
||||
stop;
|
||||
}
|
||||
|
||||
|
||||
set "score" "1.1";
|
||||
include "awl_include";
|
||||
if eval "score != 1.1" {
|
||||
reject "awl_include #1 set score to ${score}";
|
||||
stop;
|
||||
}
|
||||
|
||||
set "score" "2.2";
|
||||
include "awl_include";
|
||||
if eval "score != 1.6500000000000001" {
|
||||
reject "awl_include #2 set score to ${score}";
|
||||
stop;
|
||||
}
|
||||
|
||||
set "score" "9.3";
|
||||
include "awl_include";
|
||||
if eval "score != 5.4750000000000005" {
|
||||
reject "awl_include #2 set score to ${score}";
|
||||
stop;
|
||||
}
|
||||
|
18
tests/resources/smtp/sieve/awl_include.sieve
Normal file
18
tests/resources/smtp/sieve/awl_include.sieve
Normal file
|
@ -0,0 +1,18 @@
|
|||
require ["variables", "include", "vnd.stalwart.plugins", "reject"];
|
||||
|
||||
global "score";
|
||||
set "awl_factor" "0.5";
|
||||
|
||||
query :use "sql" :set ["awl_score", "awl_count"] "SELECT score, count FROM awl WHERE sender = ? AND ip = ?" ["${env.from}", "%{env.remote_ip}"];
|
||||
if eval "awl_count > 0" {
|
||||
if not query :use "sql" "UPDATE awl SET score = score + ?, count = count + 1 WHERE sender = ? AND ip = ?" ["%{score}", "${env.from}", "%{env.remote_ip}"] {
|
||||
reject "update query failed";
|
||||
stop;
|
||||
}
|
||||
set "score" "%{score + ((awl_score / awl_count) - score) * awl_factor}";
|
||||
} else {
|
||||
if not query :use "sql" "INSERT INTO awl (score, count, sender, ip) VALUES (?, 1, ?, ?)" ["%{score}", "${env.from}", "%{env.remote_ip}"] {
|
||||
reject "insert query failed";
|
||||
stop;
|
||||
}
|
||||
}
|
5
tests/resources/smtp/sieve/stage_connect.sieve
Normal file
5
tests/resources/smtp/sieve/stage_connect.sieve
Normal file
|
@ -0,0 +1,5 @@
|
|||
require ["variables", "reject"];
|
||||
|
||||
if string "${env.remote_ip}" "10.0.0.88" {
|
||||
reject "Your IP '${env.remote_ip}' is not welcomed here.";
|
||||
}
|
30
tests/resources/smtp/sieve/stage_data.sieve
Normal file
30
tests/resources/smtp/sieve/stage_data.sieve
Normal file
|
@ -0,0 +1,30 @@
|
|||
require ["envelope", "reject", "variables", "replace", "mime", "foreverypart", "editheader", "extracttext", "enotify"];
|
||||
|
||||
if envelope :localpart :is "to" "thomas" {
|
||||
deleteheader "from";
|
||||
addheader "From" "no-reply@my.domain";
|
||||
redirect "redirect@here.email";
|
||||
discard;
|
||||
}
|
||||
|
||||
if envelope :localpart :is "to" "bill" {
|
||||
reject "Bill cannot receive messages.";
|
||||
stop;
|
||||
}
|
||||
|
||||
if envelope :localpart :is "to" "jane" {
|
||||
set "counter" "a";
|
||||
foreverypart {
|
||||
if header :mime :contenttype "content-type" "text/html" {
|
||||
extracttext :upper "text_content";
|
||||
replace "${text_content}";
|
||||
}
|
||||
set :length "part_num" "${counter}";
|
||||
addheader :last "X-Part-Number" "${part_num}";
|
||||
set "counter" "${counter}a";
|
||||
}
|
||||
}
|
||||
|
||||
if envelope :domain :is "to" "foobar.net" {
|
||||
notify "mailto:john@example.net?cc=jane@example.org&subject=You%20have%20got%20mail";
|
||||
}
|
5
tests/resources/smtp/sieve/stage_ehlo.sieve
Normal file
5
tests/resources/smtp/sieve/stage_ehlo.sieve
Normal file
|
@ -0,0 +1,5 @@
|
|||
require ["variables", "extlists", "reject"];
|
||||
|
||||
if string :list "${env.helo_domain}" "local/invalid-ehlos" {
|
||||
reject "551 5.1.1 Your domain '${env.helo_domain}' has been blacklisted.";
|
||||
}
|
12
tests/resources/smtp/sieve/stage_mail.sieve
Normal file
12
tests/resources/smtp/sieve/stage_mail.sieve
Normal file
|
@ -0,0 +1,12 @@
|
|||
require ["variables", "vnd.stalwart.plugins", "envelope", "reject"];
|
||||
|
||||
if envelope :localpart :is "from" "spammer" {
|
||||
reject "450 4.1.1 Invalid address";
|
||||
}
|
||||
|
||||
query :use "sql" "CREATE TABLE IF NOT EXISTS blocked_senders (addr TEXT PRIMARY KEY)" [];
|
||||
query :use "sql" "INSERT OR IGNORE INTO blocked_senders (addr) VALUES (?)" "marketing@spam-domain.com";
|
||||
|
||||
if query :use "sql" "SELECT 1 FROM blocked_senders WHERE addr=? LIMIT 1" ["${envelope.from}"] {
|
||||
reject "Your address has been blocked.";
|
||||
}
|
12
tests/resources/smtp/sieve/stage_rcpt.sieve
Normal file
12
tests/resources/smtp/sieve/stage_rcpt.sieve
Normal file
|
@ -0,0 +1,12 @@
|
|||
require ["variables", "vnd.stalwart.plugins", "envelope", "reject"];
|
||||
|
||||
if envelope :domain :is "to" "foobar.org" {
|
||||
query :use "sql" "CREATE TABLE IF NOT EXISTS greylist (addr TEXT PRIMARY KEY)" [];
|
||||
|
||||
set "triplet" "${env.remote_ip}.${envelope.from}.${envelope.to}";
|
||||
|
||||
if not query :use "sql" "SELECT 1 FROM greylist WHERE addr=? LIMIT 1" ["${triplet}"] {
|
||||
query :use "sql" "INSERT INTO greylist (addr) VALUES (?)" ["${triplet}"];
|
||||
reject "422 4.2.2 You have been greylisted '${triplet}'.";
|
||||
}
|
||||
}
|
|
@ -21,7 +21,8 @@
|
|||
* for more details.
|
||||
*/
|
||||
|
||||
use std::path::PathBuf;
|
||||
use core::panic;
|
||||
use std::{fmt::Write, fs, path::PathBuf, sync::Arc};
|
||||
|
||||
use crate::smtp::{
|
||||
inbound::{sign::TextConfigContext, TestMessage, TestQueueEvent},
|
||||
|
@ -32,7 +33,9 @@ use directory::config::ConfigDirectory;
|
|||
use smtp::{
|
||||
config::{scripts::ConfigSieve, session::ConfigSession, ConfigContext, EnvelopeKey, IfBlock},
|
||||
core::{Session, SMTP},
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
use tokio::runtime::Handle;
|
||||
use utils::config::Config;
|
||||
|
||||
const CONFIG: &str = r#"
|
||||
|
@ -63,7 +66,6 @@ from-addr = "sieve@foobar.org"
|
|||
return-path = ""
|
||||
hostname = "mx.foobar.org"
|
||||
sign = ["rsa"]
|
||||
use-directory = "sql"
|
||||
|
||||
[sieve.limits]
|
||||
redirects = 3
|
||||
|
@ -74,86 +76,6 @@ nested-includes = 5
|
|||
duplicate-expiry = "7d"
|
||||
|
||||
[sieve.scripts]
|
||||
connect = '''
|
||||
require ["variables", "reject"];
|
||||
|
||||
if string "${env.remote_ip}" "10.0.0.88" {
|
||||
reject "Your IP '${env.remote_ip}' is not welcomed here.";
|
||||
}
|
||||
'''
|
||||
|
||||
ehlo = '''
|
||||
require ["variables", "extlists", "reject"];
|
||||
|
||||
if string :list "${env.helo_domain}" "local/invalid-ehlos" {
|
||||
reject "551 5.1.1 Your domain '${env.helo_domain}' has been blacklisted.";
|
||||
}
|
||||
'''
|
||||
|
||||
mail = '''
|
||||
require ["variables", "vnd.stalwart.execute", "envelope", "reject"];
|
||||
|
||||
if envelope :localpart :is "from" "spammer" {
|
||||
reject "450 4.1.1 Invalid address";
|
||||
}
|
||||
|
||||
execute :query "CREATE TABLE IF NOT EXISTS blocked_senders (addr TEXT PRIMARY KEY)";
|
||||
execute :query "INSERT OR IGNORE INTO blocked_senders (addr) VALUES (?)" "marketing@spam-domain.com";
|
||||
|
||||
if execute :query "SELECT 1 FROM blocked_senders WHERE addr=? LIMIT 1" ["${envelope.from}"] {
|
||||
reject "Your address has been blocked.";
|
||||
}
|
||||
'''
|
||||
|
||||
rcpt = '''
|
||||
require ["variables", "vnd.stalwart.execute", "envelope", "reject"];
|
||||
|
||||
if envelope :domain :is "to" "foobar.org" {
|
||||
execute :query "CREATE TABLE IF NOT EXISTS greylist (addr TEXT PRIMARY KEY)";
|
||||
|
||||
set "triplet" "${env.remote_ip}.${envelope.from}.${envelope.to}";
|
||||
|
||||
if not execute :query "SELECT 1 FROM greylist WHERE addr=? LIMIT 1" ["${triplet}"] {
|
||||
execute :query "INSERT INTO greylist (addr) VALUES (?)" ["${triplet}"];
|
||||
reject "422 4.2.2 You have been greylisted '${triplet}'.";
|
||||
}
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
data = '''
|
||||
require ["envelope", "reject", "variables", "replace", "mime", "foreverypart", "editheader", "extracttext", "enotify"];
|
||||
|
||||
if envelope :localpart :is "to" "thomas" {
|
||||
deleteheader "from";
|
||||
addheader "From" "no-reply@my.domain";
|
||||
redirect "redirect@here.email";
|
||||
discard;
|
||||
}
|
||||
|
||||
if envelope :localpart :is "to" "bill" {
|
||||
reject "Bill cannot receive messages.";
|
||||
stop;
|
||||
}
|
||||
|
||||
if envelope :localpart :is "to" "jane" {
|
||||
set "counter" "a";
|
||||
foreverypart {
|
||||
if header :mime :contenttype "content-type" "text/html" {
|
||||
extracttext :upper "text_content";
|
||||
replace "${text_content}";
|
||||
}
|
||||
set :length "part_num" "${counter}";
|
||||
addheader :last "X-Part-Number" "${part_num}";
|
||||
set "counter" "${counter}a";
|
||||
}
|
||||
}
|
||||
|
||||
if envelope :domain :is "to" "foobar.net" {
|
||||
notify "mailto:john@example.net?cc=jane@example.org&subject=You%20have%20got%20mail";
|
||||
}
|
||||
'''
|
||||
|
||||
"#;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -165,37 +87,96 @@ async fn sieve_scripts() {
|
|||
)
|
||||
.unwrap();*/
|
||||
|
||||
let mut pipe_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
pipe_path.push("resources");
|
||||
pipe_path.push("smtp");
|
||||
pipe_path.push("pipe");
|
||||
// Add test scripts
|
||||
let mut config = CONFIG.to_string();
|
||||
for entry in fs::read_dir(
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("resources")
|
||||
.join("smtp")
|
||||
.join("sieve"),
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
let entry = entry.unwrap();
|
||||
writeln!(
|
||||
&mut config,
|
||||
"{} = \"file://{}\"",
|
||||
entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.split_once('.')
|
||||
.unwrap()
|
||||
.0,
|
||||
entry.path().to_str().unwrap()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Prepare config
|
||||
let mut core = SMTP::test();
|
||||
let mut qr = core.init_test_queue("smtp_sieve_test");
|
||||
let mut ctx = ConfigContext::new(&[]).parse_signatures();
|
||||
let config = Config::parse(
|
||||
&CONFIG
|
||||
&config
|
||||
.replace("%PATH%", qr._temp_dir.temp_dir.as_path().to_str().unwrap())
|
||||
.replace("%CFG_PATH%", pipe_path.as_path().to_str().unwrap()),
|
||||
.replace(
|
||||
"%CFG_PATH%",
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("resources")
|
||||
.join("smtp")
|
||||
.join("pipe")
|
||||
.as_path()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
ctx.directory = config.parse_directory().unwrap();
|
||||
let pipes = config.parse_pipes(&ctx, &[EnvelopeKey::RemoteIp]).unwrap();
|
||||
core.sieve = config.parse_sieve(&mut ctx).unwrap();
|
||||
let config = &mut core.session.config;
|
||||
config.connect.script = IfBlock::new(ctx.scripts.get("connect").cloned());
|
||||
config.ehlo.script = IfBlock::new(ctx.scripts.get("ehlo").cloned());
|
||||
config.mail.script = IfBlock::new(ctx.scripts.get("mail").cloned());
|
||||
config.rcpt.script = IfBlock::new(ctx.scripts.get("rcpt").cloned());
|
||||
config.data.script = IfBlock::new(ctx.scripts.get("data").cloned());
|
||||
config.connect.script = IfBlock::new(ctx.scripts.get("stage_connect").cloned());
|
||||
config.ehlo.script = IfBlock::new(ctx.scripts.get("stage_ehlo").cloned());
|
||||
config.mail.script = IfBlock::new(ctx.scripts.get("stage_mail").cloned());
|
||||
config.rcpt.script = IfBlock::new(ctx.scripts.get("stage_rcpt").cloned());
|
||||
config.data.script = IfBlock::new(ctx.scripts.get("stage_data").cloned());
|
||||
config.rcpt.relay = IfBlock::new(true);
|
||||
config.data.pipe_commands = pipes;
|
||||
let core = Arc::new(core);
|
||||
|
||||
// Test connect script
|
||||
let mut session = Session::test(core);
|
||||
// Build session
|
||||
let mut session = Session::test(core.clone());
|
||||
session.data.remote_ip = "10.0.0.88".parse().unwrap();
|
||||
assert!(!session.init_conn().await);
|
||||
|
||||
// Run tests
|
||||
let span = tracing::info_span!("sieve_scripts");
|
||||
for (name, script) in &ctx.scripts {
|
||||
if name.starts_with("stage_") || name.ends_with("_include") {
|
||||
continue;
|
||||
}
|
||||
let script = script.clone();
|
||||
let params = session
|
||||
.build_script_parameters()
|
||||
.set_variable("from", "john.doe@example.org");
|
||||
let handle = Handle::current();
|
||||
let span = span.clone();
|
||||
let core_ = core.clone();
|
||||
match core
|
||||
.spawn_worker(move || core_.run_script_blocking(script, params, handle, span))
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
ScriptResult::Accept { .. } => (),
|
||||
ScriptResult::Reject(message) => panic!("{}", message),
|
||||
err => {
|
||||
panic!("Unexpected script result {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test connect script
|
||||
session
|
||||
.response()
|
||||
.assert_contains("503 5.5.3 Your IP '10.0.0.88' is not welcomed here");
|
||||
|
|
|
@ -441,7 +441,7 @@ impl TestConfig for SieveCore {
|
|||
from_name: "Mailer Daemon".to_string(),
|
||||
return_path: "".to_string(),
|
||||
sign: vec![],
|
||||
db: None,
|
||||
directories: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue