Migrate to new Sieve version

This commit is contained in:
mdecimus 2023-08-28 19:44:23 +02:00
parent 758280837d
commit cc582b05f9
44 changed files with 1379 additions and 666 deletions

View file

@ -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
View file

@ -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",
]

View file

@ -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"

View file

@ -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,
}
}
}*/
}
}

View file

@ -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")

View file

@ -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"],
),
] {

View file

@ -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
}

View file

@ -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"))
}

View file

@ -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

View file

@ -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(),
}
}

View file

@ -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"))
}

View file

@ -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"))
}

View file

@ -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();

View file

@ -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()

View file

@ -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,

View file

@ -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"

View file

@ -382,7 +382,7 @@ impl JMAP {
}
}
Event::ListContains { .. }
| Event::Execute { .. }
| Event::Plugin { .. }
| Event::Notify { .. }
| Event::SetEnvelope { .. } => {
// Not allowed

View file

@ -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(),
},
})
}

View file

@ -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 {

View file

@ -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)

View file

@ -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);

View file

@ -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,

View file

@ -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",
}
}
}

View file

@ -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,

View file

@ -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",

View file

@ -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"),);

View 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 => (),
}
}
}
}

View file

@ -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 => (),
}
}
}
}

View 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![],
})
}
}

View 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()
}
}

View 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()
}
}
}

View 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())
}
}

View 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,
&parameters.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,
&parameters.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()
}
}
}

View file

@ -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)
}
}

View file

@ -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.";
}
'''

View 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;
}

View 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;
}
}

View 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.";
}

View 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";
}

View 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.";
}

View 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.";
}

View 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}'.";
}
}

View file

@ -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");

View file

@ -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(),
},
}
}