From cb4d2f15ae2cb871da9a6ec6160a1845fa52e238 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Thu, 28 Mar 2024 17:21:32 +0100 Subject: [PATCH] Config hot reloading API --- Cargo.lock | 403 +++++++++--------- crates/common/Cargo.toml | 2 +- crates/common/src/config/imap.rs | 2 +- crates/common/src/config/jmap/settings.rs | 4 +- crates/common/src/config/manager.rs | 291 ++++++++++--- crates/common/src/config/mod.rs | 5 +- crates/common/src/config/scripts.rs | 24 +- crates/common/src/config/server/listener.rs | 8 +- crates/common/src/config/server/tls.rs | 202 ++++----- crates/common/src/config/smtp/auth.rs | 23 +- crates/common/src/config/smtp/mod.rs | 4 +- crates/common/src/config/smtp/queue.rs | 29 +- crates/common/src/config/smtp/report.rs | 26 +- crates/common/src/config/smtp/resolver.rs | 22 + crates/common/src/config/smtp/session.rs | 31 +- crates/common/src/config/smtp/throttle.rs | 9 + crates/common/src/config/storage.rs | 8 +- crates/common/src/config/tracers.rs | 10 + crates/common/src/expr/mod.rs | 2 +- crates/common/src/expr/tokenizer.rs | 2 +- crates/common/src/lib.rs | 3 +- crates/common/src/listener/acme/mod.rs | 13 + crates/common/src/listener/blocked.rs | 20 +- crates/common/src/listener/tls.rs | 16 + crates/common/src/scripts/functions/array.rs | 14 +- crates/common/src/scripts/functions/email.rs | 4 +- crates/common/src/scripts/functions/header.rs | 16 +- crates/common/src/scripts/functions/html.rs | 10 +- crates/common/src/scripts/functions/image.rs | 2 +- crates/common/src/scripts/functions/misc.rs | 18 +- crates/common/src/scripts/functions/mod.rs | 2 +- crates/common/src/scripts/functions/text.rs | 56 +-- .../common/src/scripts/functions/unicode.rs | 12 +- crates/common/src/scripts/functions/url.rs | 4 +- crates/common/src/scripts/plugins/bayes.rs | 8 +- crates/common/src/scripts/plugins/dns.rs | 4 +- crates/common/src/scripts/plugins/exec.rs | 2 +- crates/common/src/scripts/plugins/headers.rs | 2 +- crates/common/src/scripts/plugins/http.rs | 2 +- crates/common/src/scripts/plugins/lookup.rs | 10 +- crates/common/src/scripts/plugins/mod.rs | 4 +- crates/common/src/scripts/plugins/pyzor.rs | 2 +- crates/common/src/scripts/plugins/query.rs | 2 +- crates/common/src/scripts/plugins/text.rs | 4 +- crates/directory/src/core/config.rs | 13 +- crates/install/Cargo.toml | 4 +- crates/install/src/main.rs | 17 +- crates/jmap-proto/src/request/capability.rs | 2 +- crates/jmap/Cargo.toml | 2 +- crates/jmap/src/api/admin.rs | 88 +++- crates/jmap/src/lib.rs | 3 + crates/managesieve/Cargo.toml | 2 +- crates/nlp/src/bayes/cache.rs | 11 + crates/smtp/Cargo.toml | 2 +- crates/store/src/backend/rocksdb/main.rs | 7 +- crates/store/src/backend/sqlite/main.rs | 5 +- crates/store/src/config.rs | 101 +++-- crates/store/src/dispatch/blob.rs | 11 +- crates/utils/Cargo.toml | 2 +- crates/utils/src/config/cron.rs | 35 +- crates/utils/src/config/ipmask.rs | 42 +- crates/utils/src/config/mod.rs | 26 +- crates/utils/src/config/utils.rs | 252 ++++------- tests/Cargo.toml | 2 +- 64 files changed, 1118 insertions(+), 846 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a56c374..3c952eb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -179,9 +179,9 @@ checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arc-swap" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "argon2" @@ -268,7 +268,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "synstructure 0.13.1", ] @@ -291,7 +291,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -309,13 +309,13 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -337,18 +337,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -369,9 +369,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "aws-creds" @@ -446,9 +446,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -523,7 +523,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -536,7 +536,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.52", + "syn 2.0.55", "which", ] @@ -563,9 +563,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitvec" @@ -661,9 +661,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" dependencies = [ "borsh-derive", "cfg_aliases", @@ -671,15 +671,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "syn_derive", ] @@ -744,9 +744,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bzip2" @@ -859,9 +859,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -905,9 +905,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -927,14 +927,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1013,11 +1013,11 @@ dependencies = [ "pem", "privdrop", "proxy-header", - "rcgen", + "rcgen 0.12.1", "regex", - "reqwest 0.12.0", + "reqwest 0.12.2", "ring 0.17.8", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pemfile 2.1.1", "rustls-pki-types", "serde", @@ -1267,7 +1267,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1315,7 +1315,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1337,7 +1337,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core 0.20.8", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1403,11 +1403,13 @@ dependencies = [ [[package]] name = "decancer" -version = "3.0.2" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103e221d1e9e9b00b35aee177797a4f92d6aeab5752f256812639136b13557a" +checksum = "1b6083786d3fc4f0978c6bdd60334338b2b23a512ce897deafa76be8aa917d30" dependencies = [ + "lazy_static", "paste", + "regex", ] [[package]] @@ -1523,7 +1525,7 @@ dependencies = [ "pbkdf2 0.12.2", "pwhash", "regex", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "scrypt", "serde", @@ -1586,7 +1588,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1719,7 +1721,7 @@ dependencies = [ "dyn-clone", "lazy_static", "percent-encoding", - "reqwest 0.11.26", + "reqwest 0.11.27", "rustc_version 0.2.3", "serde", "serde_json", @@ -1785,10 +1787,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1833,7 +1835,7 @@ checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" dependencies = [ "bit-set", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1850,9 +1852,9 @@ checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "ff" @@ -1866,9 +1868,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] name = "filetime" @@ -1989,7 +1991,7 @@ checksum = "f8db6653cbc621a3810d95d55bd342be3e71181d6df21a4eb29ef986202d3f9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "try_map", ] @@ -2028,7 +2030,7 @@ checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2040,7 +2042,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2052,7 +2054,7 @@ dependencies = [ "frunk_core", "frunk_proc_macro_helpers", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2117,7 +2119,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2237,9 +2239,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", @@ -2247,7 +2249,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2256,9 +2258,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" dependencies = [ "bytes", "fnv", @@ -2266,7 +2268,7 @@ dependencies = [ "futures-sink", "futures-util", "http 1.1.0", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2307,6 +2309,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -2502,7 +2510,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.24", + "h2 0.3.25", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -2525,7 +2533,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.2", + "h2 0.4.3", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -2561,7 +2569,7 @@ dependencies = [ "http 1.1.0", "hyper 1.2.0", "hyper-util", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", @@ -2681,7 +2689,7 @@ dependencies = [ "nlp", "parking_lot", "rand", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pemfile 2.1.1", "store", "tokio", @@ -2714,9 +2722,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -2821,9 +2829,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jemalloc-sys" @@ -2896,7 +2904,7 @@ dependencies = [ "rasn", "rasn-cms", "rasn-pkix", - "reqwest 0.12.0", + "reqwest 0.12.2", "rsa", "sequoia-openpgp", "serde", @@ -2927,8 +2935,8 @@ dependencies = [ "futures-util", "maybe-async", "parking_lot", - "reqwest 0.11.26", - "rustls 0.22.2", + "reqwest 0.11.27", + "rustls 0.22.3", "rustls-pki-types", "serde", "serde_json", @@ -2975,7 +2983,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", ] [[package]] @@ -3011,7 +3019,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "string_cache", "term", "tiny-keccak", @@ -3108,7 +3116,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall", ] @@ -3142,9 +3150,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.15" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "pkg-config", @@ -3215,9 +3223,9 @@ checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15" [[package]] name = "mail-auth" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84725ed2de724f96bfe85cb4a0c06ac9a77fe612915675220525682ebdfdd7fa" +checksum = "23ec1e6a7ed37e42babe87f67b865c9484e271538a3a9c4664e522836cc3e73a" dependencies = [ "ahash 0.8.11", "flate2", @@ -3245,9 +3253,9 @@ dependencies = [ [[package]] name = "mail-parser" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af1b812c3c16317ccadacb0db823f04c2872dfc5a1125f171f4a22d1705e9a7" +checksum = "ed5a1335c3a964788c90cb42ae04a34b5f2628e89566949ce3bd4ada695c0bcd" dependencies = [ "encoding_rs", "serde", @@ -3262,7 +3270,7 @@ dependencies = [ "base64 0.21.7", "gethostname", "md5", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "smtp-proto", "tokio", @@ -3304,7 +3312,7 @@ dependencies = [ "mail-send", "md5", "parking_lot", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pemfile 2.1.1", "sieve-rs", "store", @@ -3349,7 +3357,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -3381,9 +3389,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" @@ -3450,13 +3458,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c60492b5eb751e55b42d716b6b26dceb66767996cd7a5560a842fbf613ca2e92" dependencies = [ "darling 0.20.8", - "heck", + "heck 0.4.1", "num-bigint", "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "termcolor", "thiserror", ] @@ -3483,7 +3491,7 @@ dependencies = [ "percent-encoding", "pin-project", "rand", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pemfile 2.1.1", "serde", "serde_json", @@ -3507,7 +3515,7 @@ dependencies = [ "base64 0.21.7", "bigdecimal", "bindgen", - "bitflags 2.4.2", + "bitflags 2.5.0", "bitvec", "btoi", "byteorder", @@ -3534,14 +3542,14 @@ dependencies = [ "thiserror", "time", "uuid", - "zstd 0.13.0", + "zstd 0.13.1", ] [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" @@ -3571,7 +3579,7 @@ dependencies = [ "phf", "rust-stemmers", "serde", - "siphasher 1.0.0", + "siphasher 1.0.1", "tinysegmenter", "tokio", "utils", @@ -3730,7 +3738,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -3747,7 +3755,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -3803,7 +3811,7 @@ dependencies = [ "bytes", "http 0.2.12", "opentelemetry", - "reqwest 0.11.26", + "reqwest 0.11.27", ] [[package]] @@ -3821,7 +3829,7 @@ dependencies = [ "opentelemetry-semantic-conventions", "opentelemetry_sdk", "prost", - "reqwest 0.11.26", + "reqwest 0.11.27", "thiserror", "tokio", "tonic", @@ -4011,7 +4019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.5", + "indexmap 2.2.6", ] [[package]] @@ -4054,7 +4062,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4092,7 +4100,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4136,9 +4144,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "polyval" @@ -4207,12 +4215,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4310,7 +4318,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4497,9 +4505,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -4527,6 +4535,19 @@ dependencies = [ "yasna", ] +[[package]] +name = "rcgen" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa96feb4d337a43eae1b39b6d4cafc2860a46cf9cec6f1e65294244ece65e348" +dependencies = [ + "pem", + "ring 0.17.8", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "redis" version = "0.25.2" @@ -4544,7 +4565,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rand", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-native-certs 0.7.0", "rustls-pemfile 2.1.1", "rustls-pki-types", @@ -4580,14 +4601,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4607,7 +4628,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4618,9 +4639,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rend" @@ -4633,9 +4654,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.26" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "async-compression", "base64 0.21.7", @@ -4643,7 +4664,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.24", + "h2 0.3.25", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", @@ -4677,16 +4698,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b48d98d932f4ee75e541614d32a7f44c889b72bd9c2e04d95edd135989df88" +checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" dependencies = [ "base64 0.21.7", "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.4.2", + "h2 0.4.3", "http 1.1.0", "http-body 1.0.0", "http-body-util", @@ -4701,7 +4722,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pemfile 1.0.4", "rustls-pki-types", "serde", @@ -4874,7 +4895,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "fallible-iterator 0.3.0", "fallible-streaming-iterator", "hashlink", @@ -4913,7 +4934,7 @@ dependencies = [ "md5", "percent-encoding", "quick-xml 0.26.0", - "reqwest 0.11.26", + "reqwest 0.11.27", "serde", "serde_derive", "sha2 0.10.8", @@ -4936,9 +4957,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.34.3" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" dependencies = [ "arrayvec", "borsh", @@ -4991,11 +5012,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -5028,9 +5049,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", "ring 0.17.8", @@ -5086,9 +5107,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" @@ -5299,7 +5320,7 @@ dependencies = [ "rand", "rand_core", "regex", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "ripemd", "rsa", "sha1collisiondetection", @@ -5337,14 +5358,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -5407,7 +5428,7 @@ checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5442,9 +5463,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha1collisiondetection" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d5c4be690002e8a5d7638b0b7323f03c268c7a919bd8af69ce963a4dc83220" +checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "const-oid", "digest 0.10.7", @@ -5498,9 +5519,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "sieve-rs" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25494c13da6c336430906aa783e4bb2ae251c84158d6e5a4fdf0449a779c2521" +checksum = "e21701f587c62a8f18d7f4eeb705a7043163630b684ed8e51955ffc5cefe379f" dependencies = [ "ahash 0.8.11", "bincode", @@ -5544,9 +5565,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "siphasher" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -5559,9 +5580,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smtp" @@ -5594,8 +5615,8 @@ dependencies = [ "rand", "rayon", "regex", - "reqwest 0.12.0", - "rustls 0.22.2", + "reqwest 0.12.2", + "rustls 0.22.3", "rustls-pemfile 2.1.1", "rustls-pki-types", "serde", @@ -5641,7 +5662,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -5697,7 +5718,7 @@ dependencies = [ "prettytable-rs", "pwhash", "rand", - "reqwest 0.12.0", + "reqwest 0.12.2", "rpassword", "serde", "serde_json", @@ -5708,7 +5729,7 @@ dependencies = [ name = "stalwart-install" version = "0.6.0" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "clap", "dialoguer", "flate2", @@ -5717,8 +5738,8 @@ dependencies = [ "openssl", "pwhash", "rand", - "rcgen", - "reqwest 0.12.0", + "rcgen 0.13.0", + "reqwest 0.12.2", "rpassword", "tar", "zip-extract", @@ -5764,7 +5785,7 @@ dependencies = [ "rocksdb", "rusqlite", "rust-s3", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "serde", "serde_json", @@ -5841,9 +5862,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -5859,7 +5880,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5888,7 +5909,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5993,8 +6014,8 @@ dependencies = [ "nlp", "num_cpus", "rayon", - "reqwest 0.12.0", - "rustls 0.22.2", + "reqwest 0.12.2", + "rustls 0.22.3", "rustls-pemfile 2.1.1", "rustls-pki-types", "serde", @@ -6028,7 +6049,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -6143,7 +6164,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -6188,16 +6209,16 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -6212,7 +6233,7 @@ checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", @@ -6246,7 +6267,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -6262,7 +6283,7 @@ dependencies = [ "axum", "base64 0.21.7", "bytes", - "h2 0.3.24", + "h2 0.3.25", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", @@ -6342,7 +6363,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -6438,7 +6459,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "sha1", "thiserror", @@ -6615,11 +6636,11 @@ dependencies = [ "pem", "privdrop", "rand", - "rcgen", + "rcgen 0.13.0", "regex", - "reqwest 0.12.0", + "reqwest 0.12.2", "ring 0.17.8", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pemfile 2.1.1", "rustls-pki-types", "serde", @@ -6635,9 +6656,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] @@ -6718,7 +6739,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "wasm-bindgen-shared", ] @@ -6752,7 +6773,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7163,7 +7184,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -7183,7 +7204,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -7228,11 +7249,11 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ - "zstd-safe 7.0.0", + "zstd-safe 7.1.0", ] [[package]] @@ -7247,18 +7268,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 887852ce..e8c74b34 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -10,7 +10,7 @@ nlp = { path = "../nlp" } store = { path = "../store" } directory = { path = "../directory" } jmap_proto = { path = "../jmap-proto" } -sieve-rs = { version = "0.4" } +sieve-rs = { version = "0.5" } mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] } mail-auth = { version = "0.3" } mail-send = { version = "0.4", default-features = false, features = ["cram-md5"] } diff --git a/crates/common/src/config/imap.rs b/crates/common/src/config/imap.rs index f24c20fb..a94307cc 100644 --- a/crates/common/src/config/imap.rs +++ b/crates/common/src/config/imap.rs @@ -2,7 +2,7 @@ use std::time::Duration; use utils::config::{Config, Rate}; -#[derive(Default)] +#[derive(Default, Clone)] pub struct ImapConfig { pub max_request_size: usize, pub max_auth_failures: u32, diff --git a/crates/common/src/config/jmap/settings.rs b/crates/common/src/config/jmap/settings.rs index 9ba9cbe6..4f0c9338 100644 --- a/crates/common/src/config/jmap/settings.rs +++ b/crates/common/src/config/jmap/settings.rs @@ -6,7 +6,7 @@ use nlp::language::Language; use store::rand::{distributions::Alphanumeric, thread_rng, Rng}; use utils::config::{cron::SimpleCron, utils::ParseValue, Config, Rate}; -#[derive(Default)] +#[derive(Default, Clone)] pub struct JmapConfig { pub default_language: Language, pub query_max_results: usize, @@ -263,7 +263,7 @@ impl JmapConfig { .unwrap_or_else(|| Duration::from_secs(1)), session_purge_frequency: config .property_or_default::("jmap.session.purge.frequency", "15 * *") - .unwrap_or_else(|| SimpleCron::parse_value("15 * *", "").unwrap()), + .unwrap_or_else(|| SimpleCron::parse_value("15 * *").unwrap()), }; // Add capabilities diff --git a/crates/common/src/config/manager.rs b/crates/common/src/config/manager.rs index d53970c7..2e568894 100644 --- a/crates/common/src/config/manager.rs +++ b/crates/common/src/config/manager.rs @@ -4,22 +4,27 @@ use std::{ sync::Arc, }; +use ahash::AHashSet; use arc_swap::ArcSwap; +use parking_lot::RwLock; use store::{ write::{BatchBuilder, ValueClass}, Deserialize, IterateParams, Store, Stores, ValueKey, }; use tracing_appender::non_blocking::WorkerGuard; use utils::{ - config::{Config, ConfigKey}, + config::{ipmask::IpAddrOrMask, utils::ParseValue, Config, ConfigKey}, failed, glob::GlobPattern, UnwrapFailure, }; -use crate::{Core, SharedCore}; +use crate::{listener::blocked::BLOCKED_IP_KEY, Core, SharedCore}; -use super::{server::Servers, tracers::Tracers}; +use super::{ + server::{tls::parse_certificates, Servers}, + tracers::Tracers, +}; #[derive(Default)] pub struct ConfigManager { @@ -39,6 +44,11 @@ enum Pattern { Exclude(MatchType), } +pub struct ReloadResult { + pub config: Config, + pub new_core: Option, +} + enum MatchType { Equal(String), StartsWith(String), @@ -96,60 +106,6 @@ impl BootManager { // Resolve macros config.resolve_macros().await; - // Parse include/exclude patterns - let mut cfg_local_patterns = Vec::new(); - for (key, value) in &config.keys { - if !key.starts_with("config.local-keys") { - if cfg_local_patterns.is_empty() { - continue; - } else { - break; - } - }; - let value = value.trim(); - let (value, is_include) = value - .strip_prefix('!') - .map_or((value, true), |value| (value, false)); - let value = value.trim().to_ascii_lowercase(); - if value.is_empty() { - continue; - } - let match_type = if value == "*" { - MatchType::All - } else if let Some(value) = value.strip_prefix('*') { - MatchType::StartsWith(value.to_string()) - } else if let Some(value) = value.strip_suffix('*') { - MatchType::EndsWith(value.to_string()) - } else if value.contains('*') { - MatchType::Matches(GlobPattern::compile(&value, false)) - } else { - MatchType::Equal(value.to_string()) - }; - - cfg_local_patterns.push(if is_include { - Pattern::Include(match_type) - } else { - Pattern::Exclude(match_type) - }); - } - if cfg_local_patterns.is_empty() { - cfg_local_patterns = vec![ - Pattern::Include(MatchType::StartsWith("store.".to_string())), - Pattern::Include(MatchType::StartsWith("server.listener.".to_string())), - Pattern::Include(MatchType::StartsWith("cluster.".to_string())), - Pattern::Include(MatchType::Equal("storage.data".to_string())), - Pattern::Include(MatchType::Equal("storage.blob".to_string())), - Pattern::Include(MatchType::Equal("storage.lookup".to_string())), - Pattern::Include(MatchType::Equal("storage.fts".to_string())), - Pattern::Include(MatchType::Equal("server.run-as.user".to_string())), - Pattern::Include(MatchType::Equal("server.run-as.group".to_string())), - Pattern::Exclude(MatchType::Matches(GlobPattern::compile( - "store.*.query.*", - false, - ))), - ]; - } - // Parser servers let mut servers = Servers::parse(&mut config); @@ -163,10 +119,7 @@ impl BootManager { let manager = ConfigManager { cfg_local: ArcSwap::from_pointee(cfg_local), cfg_local_path, - cfg_local_patterns: Patterns { - patterns: cfg_local_patterns, - } - .into(), + cfg_local_patterns: Patterns::parse(&mut config).into(), cfg_store: config .value("storage.data") .and_then(|id| stores.stores.get(id)) @@ -177,7 +130,7 @@ impl BootManager { // Extend configuration with settings stored in the db if !manager.cfg_store.is_none() { manager - .extend_config(&mut config) + .extend_config(&mut config, "") .await .failed("Failed to read configuration"); } @@ -203,8 +156,19 @@ impl BootManager { } impl ConfigManager { - async fn extend_config(&self, config: &mut Config) -> store::Result<()> { - for (key, value) in self.db_list("", false).await? { + pub async fn build_config(&self, prefix: &str) -> store::Result { + let mut config = Config { + keys: self.cfg_local.load().as_ref().clone(), + ..Default::default() + }; + config.resolve_macros().await; + self.extend_config(&mut config, prefix) + .await + .map(|_| config) + } + + async fn extend_config(&self, config: &mut Config, prefix: &str) -> store::Result<()> { + for (key, value) in self.db_list(prefix, false).await? { config.keys.entry(key).or_insert(value); } @@ -400,7 +364,186 @@ impl ConfigManager { } } +impl Core { + pub async fn reload_blocked_ips(&self) -> store::Result { + let mut ip_addresses = AHashSet::new(); + let mut config = self.storage.config.build_config(BLOCKED_IP_KEY).await?; + + for ip in config + .set_values(BLOCKED_IP_KEY) + .map(IpAddrOrMask::parse_value) + .collect::>() + { + match ip { + Ok(IpAddrOrMask::Ip(ip)) => { + ip_addresses.insert(ip); + } + Ok(IpAddrOrMask::Mask(_)) => {} + Err(err) => { + config.new_parse_error(BLOCKED_IP_KEY, err); + } + } + } + + *self.network.blocked_ips.ip_addresses.write() = ip_addresses; + + Ok(config.into()) + } + + pub async fn reload_certificates(&self) -> store::Result { + let mut config = self.storage.config.build_config("certificate").await?; + let mut certificates = self.tls.certificates.load().as_ref().clone(); + + parse_certificates(&mut config, &mut certificates, &mut Default::default()); + + self.tls.certificates.store(certificates.into()); + + Ok(config.into()) + } + + pub async fn reload_lookups(&self) -> store::Result { + let mut config = self.storage.config.build_config("certificate").await?; + let mut stores = Stores::default(); + stores.parse_memory_stores(&mut config); + + let mut core = self.clone(); + for (id, store) in stores.lookup_stores { + core.storage.lookups.insert(id, store); + } + + Ok(ReloadResult { + config, + new_core: core.into(), + }) + } + + pub async fn reload(&self) -> store::Result { + let mut config = self.storage.config.build_config("").await?; + + // Parse tracers + Tracers::parse(&mut config); + + // Load stores + let mut stores = Stores { + stores: self.storage.stores.clone(), + blob_stores: self.storage.blobs.clone(), + fts_stores: self.storage.ftss.clone(), + lookup_stores: self.storage.lookups.clone(), + purge_schedules: Default::default(), + }; + stores.parse_stores(&mut config).await; + stores.parse_lookups(&mut config).await; + if !config.errors.is_empty() { + return Ok(config.into()); + } + + // Build manager + let manager = ConfigManager { + cfg_local: ArcSwap::from_pointee(self.storage.config.cfg_local.load().as_ref().clone()), + cfg_local_path: self.storage.config.cfg_local_path.clone(), + cfg_local_patterns: Patterns::parse(&mut config).into(), + cfg_store: config + .value("storage.data") + .and_then(|id| stores.stores.get(id)) + .cloned() + .unwrap_or_default(), + }; + + // Parse settings and build shared core + let mut core = Core::parse(&mut config, stores, manager).await; + if !config.errors.is_empty() { + return Ok(config.into()); + } + // Transfer Sieve cache + core.sieve.bayes_cache = self.sieve.bayes_cache.clone(); + core.sieve.remote_lists = RwLock::new(self.sieve.remote_lists.read().clone()); + + // Copy ACME certificates + let mut certificates = core.tls.certificates.load().as_ref().clone(); + for (cert_id, cert) in self.tls.certificates.load().iter() { + certificates + .entry(cert_id.to_string()) + .or_insert(cert.clone()); + } + core.tls.certificates.store(certificates.into()); + + // Parser servers + let mut servers = Servers::parse(&mut config); + servers.parse_tcp_acceptors(&mut config, core.clone().into_shared()); + + Ok(if config.errors.is_empty() { + ReloadResult { + config, + new_core: core.into(), + } + } else { + config.into() + }) + } +} + impl Patterns { + pub fn parse(config: &mut Config) -> Self { + let mut cfg_local_patterns = Vec::new(); + for (key, value) in &config.keys { + if !key.starts_with("config.local-keys") { + if cfg_local_patterns.is_empty() { + continue; + } else { + break; + } + }; + let value = value.trim(); + let (value, is_include) = value + .strip_prefix('!') + .map_or((value, true), |value| (value, false)); + let value = value.trim().to_ascii_lowercase(); + if value.is_empty() { + continue; + } + let match_type = if value == "*" { + MatchType::All + } else if let Some(value) = value.strip_prefix('*') { + MatchType::StartsWith(value.to_string()) + } else if let Some(value) = value.strip_suffix('*') { + MatchType::EndsWith(value.to_string()) + } else if value.contains('*') { + MatchType::Matches(GlobPattern::compile(&value, false)) + } else { + MatchType::Equal(value.to_string()) + }; + + cfg_local_patterns.push(if is_include { + Pattern::Include(match_type) + } else { + Pattern::Exclude(match_type) + }); + } + if cfg_local_patterns.is_empty() { + cfg_local_patterns = vec![ + Pattern::Include(MatchType::StartsWith("store.".to_string())), + Pattern::Include(MatchType::StartsWith("server.listener.".to_string())), + Pattern::Include(MatchType::StartsWith("server.socket.".to_string())), + Pattern::Include(MatchType::StartsWith("server.tls.".to_string())), + Pattern::Include(MatchType::Equal("cluster.node-id".to_string())), + Pattern::Include(MatchType::Equal("storage.data".to_string())), + Pattern::Include(MatchType::Equal("storage.blob".to_string())), + Pattern::Include(MatchType::Equal("storage.lookup".to_string())), + Pattern::Include(MatchType::Equal("storage.fts".to_string())), + Pattern::Include(MatchType::Equal("server.run-as.user".to_string())), + Pattern::Include(MatchType::Equal("server.run-as.group".to_string())), + Pattern::Exclude(MatchType::Matches(GlobPattern::compile( + "store.*.query.*", + false, + ))), + ]; + } + + Patterns { + patterns: cfg_local_patterns, + } + } + pub fn is_local_key(&self, key: &str) -> bool { let mut is_local = false; @@ -434,3 +577,23 @@ impl MatchType { } } } + +impl Clone for ConfigManager { + fn clone(&self) -> Self { + Self { + cfg_local: ArcSwap::from_pointee(self.cfg_local.load().as_ref().clone()), + cfg_local_path: self.cfg_local_path.clone(), + cfg_local_patterns: self.cfg_local_patterns.clone(), + cfg_store: self.cfg_store.clone(), + } + } +} + +impl From for ReloadResult { + fn from(config: Config) -> Self { + Self { + config, + new_core: None, + } + } +} diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs index 0e66bc49..c1920207 100644 --- a/crates/common/src/config/mod.rs +++ b/crates/common/src/config/mod.rs @@ -123,11 +123,14 @@ impl Core { blob, fts, lookup, - lookups: stores.lookup_stores, directory, directories: directories.directories, purge_schedules: stores.purge_schedules, config: config_manager, + stores: stores.stores, + lookups: stores.lookup_stores, + blobs: stores.blob_stores, + ftss: stores.fts_stores, }, } } diff --git a/crates/common/src/config/scripts.rs b/crates/common/src/config/scripts.rs index 13cacb2e..f993d364 100644 --- a/crates/common/src/config/scripts.rs +++ b/crates/common/src/config/scripts.rs @@ -17,8 +17,8 @@ use super::smtp::parse_server_hostname; pub struct Scripting { pub untrusted_compiler: Compiler, - pub untrusted_runtime: Runtime<()>, - pub trusted_runtime: Runtime<()>, + pub untrusted_runtime: Runtime, + pub trusted_runtime: Runtime, pub from_addr: String, pub from_name: String, pub return_path: String, @@ -28,6 +28,7 @@ pub struct Scripting { pub remote_lists: RwLock>, } +#[derive(Clone)] pub struct RemoteList { pub entries: HashSet, pub expires: Instant, @@ -206,7 +207,7 @@ impl Scripting { ) .register_functions(&mut fnc_map); - let mut trusted_runtime = Runtime::new_with_context(()) + let mut trusted_runtime = Runtime::new() .without_capabilities([ Capability::FileInto, Capability::Vacation, @@ -345,3 +346,20 @@ impl Default for Scripting { } } } + +impl Clone for Scripting { + fn clone(&self) -> Self { + Self { + untrusted_compiler: self.untrusted_compiler.clone(), + untrusted_runtime: self.untrusted_runtime.clone(), + trusted_runtime: self.trusted_runtime.clone(), + from_addr: self.from_addr.clone(), + from_name: self.from_name.clone(), + return_path: self.return_path.clone(), + sign: self.sign.clone(), + scripts: self.scripts.clone(), + bayes_cache: self.bayes_cache.clone(), + remote_lists: RwLock::new(self.remote_lists.read().clone()), + } + } +} diff --git a/crates/common/src/config/server/listener.rs b/crates/common/src/config/server/listener.rs index bda1bd77..1eb755d8 100644 --- a/crates/common/src/config/server/listener.rs +++ b/crates/common/src/config/server/listener.rs @@ -318,7 +318,7 @@ impl Servers { } impl ParseValue for ServerProtocol { - fn parse_value(key: impl AsKey, value: &str) -> utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { if value.eq_ignore_ascii_case("smtp") { Ok(Self::Smtp) } else if value.eq_ignore_ascii_case("lmtp") { @@ -330,11 +330,7 @@ impl ParseValue for ServerProtocol { } else if value.eq_ignore_ascii_case("managesieve") { Ok(Self::ManageSieve) } else { - Err(format!( - "Invalid server protocol type {:?} for property {:?}.", - value, - key.as_key() - )) + Err(format!("Invalid server protocol type {:?}.", value,)) } } } diff --git a/crates/common/src/config/server/tls.rs b/crates/common/src/config/server/tls.rs index 89707f3c..9648fb71 100644 --- a/crates/common/src/config/server/tls.rs +++ b/crates/common/src/config/server/tls.rs @@ -61,104 +61,7 @@ impl TlsManager { let mut subject_names = AHashSet::new(); // Parse certificates - for cert_id in config - .sub_keys("certificate", ".cert") - .map(|s| s.to_string()) - .collect::>() - { - let cert_id = cert_id.as_str(); - let key_cert = ("certificate", cert_id, "cert"); - let key_pk = ("certificate", cert_id, "private-key"); - - let cert = config - .value_require(key_cert) - .map(|s| s.as_bytes().to_vec()); - let pk = config.value_require(key_pk).map(|s| s.as_bytes().to_vec()); - - if let (Some(cert), Some(pk)) = (cert, pk) { - match build_certified_key(cert, pk) { - Ok(cert) => { - match cert - .end_entity_cert() - .map_err(|err| format!("Failed to obtain end entity cert: {err}")) - .and_then(|cert| { - X509Certificate::from_der(cert.as_ref()).map_err(|err| { - format!("Failed to parse end entity cert: {err}") - }) - }) { - Ok((_, parsed)) => { - // Add CNs and SANs to the list of names - let mut names = AHashSet::new(); - for name in parsed.subject().iter_common_name() { - if let Ok(name) = name.as_str() { - names.insert(name.to_string()); - } - } - for ext in parsed.extensions() { - if let ParsedExtension::SubjectAlternativeName(san) = - ext.parsed_extension() - { - for name in &san.general_names { - let name = match name { - GeneralName::DNSName(name) => name.to_string(), - GeneralName::IPAddress(ip) => match ip.len() { - 4 => Ipv4Addr::from( - <[u8; 4]>::try_from(*ip).unwrap(), - ) - .to_string(), - 16 => Ipv6Addr::from( - <[u8; 16]>::try_from(*ip).unwrap(), - ) - .to_string(), - _ => continue, - }, - _ => { - continue; - } - }; - names.insert(name); - } - } - } - - // Add custom SNIs - names.extend( - config - .values(("certificate", cert_id, "subjects")) - .map(|(_, v)| v.trim().to_string()), - ); - - // Add domain names - subject_names.extend(names.iter().cloned()); - - // Add certificates - let cert = Arc::new(cert); - for name in names { - certificates.insert( - name.strip_prefix("*.") - .map(|name| name.to_string()) - .unwrap_or(name), - cert.clone(), - ); - } - - // Add default certificate - if config - .property::(("certificate", cert_id, "default")) - .unwrap_or_default() - { - certificates.insert("*".to_string(), cert.clone()); - } - } - Err(err) => { - config.new_build_error(format!("certificate.{cert_id}"), err) - } - } - } - Err(err) => config.new_build_error(format!("certificate.{cert_id}"), err), - } - } - } + parse_certificates(config, &mut certificates, &mut subject_names); // Parse ACME providers for acme_id in config @@ -243,6 +146,109 @@ impl TlsManager { } } +pub(crate) fn parse_certificates( + config: &mut Config, + certificates: &mut AHashMap>, + subject_names: &mut AHashSet, +) { + // Parse certificates + for cert_id in config + .sub_keys("certificate", ".cert") + .map(|s| s.to_string()) + .collect::>() + { + let cert_id = cert_id.as_str(); + let key_cert = ("certificate", cert_id, "cert"); + let key_pk = ("certificate", cert_id, "private-key"); + + let cert = config + .value_require(key_cert) + .map(|s| s.as_bytes().to_vec()); + let pk = config.value_require(key_pk).map(|s| s.as_bytes().to_vec()); + + if let (Some(cert), Some(pk)) = (cert, pk) { + match build_certified_key(cert, pk) { + Ok(cert) => { + match cert + .end_entity_cert() + .map_err(|err| format!("Failed to obtain end entity cert: {err}")) + .and_then(|cert| { + X509Certificate::from_der(cert.as_ref()) + .map_err(|err| format!("Failed to parse end entity cert: {err}")) + }) { + Ok((_, parsed)) => { + // Add CNs and SANs to the list of names + let mut names = AHashSet::new(); + for name in parsed.subject().iter_common_name() { + if let Ok(name) = name.as_str() { + names.insert(name.to_string()); + } + } + for ext in parsed.extensions() { + if let ParsedExtension::SubjectAlternativeName(san) = + ext.parsed_extension() + { + for name in &san.general_names { + let name = match name { + GeneralName::DNSName(name) => name.to_string(), + GeneralName::IPAddress(ip) => match ip.len() { + 4 => Ipv4Addr::from( + <[u8; 4]>::try_from(*ip).unwrap(), + ) + .to_string(), + 16 => Ipv6Addr::from( + <[u8; 16]>::try_from(*ip).unwrap(), + ) + .to_string(), + _ => continue, + }, + _ => { + continue; + } + }; + names.insert(name); + } + } + } + + // Add custom SNIs + names.extend( + config + .values(("certificate", cert_id, "subjects")) + .map(|(_, v)| v.trim().to_string()), + ); + + // Add domain names + subject_names.extend(names.iter().cloned()); + + // Add certificates + let cert = Arc::new(cert); + for name in names { + certificates.insert( + name.strip_prefix("*.") + .map(|name| name.to_string()) + .unwrap_or(name), + cert.clone(), + ); + } + + // Add default certificate + if config + .property::(("certificate", cert_id, "default")) + .unwrap_or_default() + { + certificates.insert("*".to_string(), cert.clone()); + } + } + Err(err) => config.new_build_error(format!("certificate.{cert_id}"), err), + } + } + Err(err) => config.new_build_error(format!("certificate.{cert_id}"), err), + } + } + } +} + pub(crate) fn build_certified_key( cert: Vec, pk: Vec, diff --git a/crates/common/src/config/smtp/auth.rs b/crates/common/src/config/smtp/auth.rs index ac008c10..dd842a01 100644 --- a/crates/common/src/config/smtp/auth.rs +++ b/crates/common/src/config/smtp/auth.rs @@ -15,6 +15,7 @@ use crate::expr::{self, if_block::IfBlock, tokenizer::TokenMap, Constant, Consta use super::*; +#[derive(Clone)] pub struct MailAuthConfig { pub dkim: DkimAuthConfig, pub arc: ArcAuthConfig, @@ -26,24 +27,30 @@ pub struct MailAuthConfig { pub sealers: AHashMap>, } +#[derive(Clone)] pub struct DkimAuthConfig { pub verify: IfBlock, pub sign: IfBlock, } +#[derive(Clone)] pub struct ArcAuthConfig { pub verify: IfBlock, pub seal: IfBlock, } +#[derive(Clone)] pub struct SpfAuthConfig { pub verify_ehlo: IfBlock, pub verify_mail_from: IfBlock, } + +#[derive(Clone)] pub struct DmarcAuthConfig { pub verify: IfBlock, } +#[derive(Clone)] pub struct IpRevAuthConfig { pub verify: IfBlock, } @@ -399,16 +406,12 @@ impl VerifyStrategy { } impl ParseValue for VerifyStrategy { - fn parse_value(key: impl AsKey, value: &str) -> Result { + fn parse_value(value: &str) -> Result { match value { "relaxed" => Ok(VerifyStrategy::Relaxed), "strict" => Ok(VerifyStrategy::Strict), "disable" | "disabled" | "never" | "none" => Ok(VerifyStrategy::Disable), - _ => Err(format!( - "Invalid value {:?} for key {:?}.", - value, - key.as_key() - )), + _ => Err(format!("Invalid value {:?}.", value)), } } } @@ -426,14 +429,14 @@ impl ConstantValue for VerifyStrategy { } impl ParseValue for DkimCanonicalization { - fn parse_value(key: impl AsKey, value: &str) -> utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { if let Some((headers, body)) = value.split_once('/') { Ok(DkimCanonicalization { - headers: Canonicalization::parse_value(key.clone(), headers.trim())?, - body: Canonicalization::parse_value(key, body.trim())?, + headers: Canonicalization::parse_value(headers.trim())?, + body: Canonicalization::parse_value(body.trim())?, }) } else { - let c = Canonicalization::parse_value(key, value)?; + let c = Canonicalization::parse_value(value)?; Ok(DkimCanonicalization { headers: c, body: c, diff --git a/crates/common/src/config/smtp/mod.rs b/crates/common/src/config/smtp/mod.rs index ce280e77..b173d717 100644 --- a/crates/common/src/config/smtp/mod.rs +++ b/crates/common/src/config/smtp/mod.rs @@ -14,7 +14,7 @@ use self::{ session::SessionConfig, }; -#[derive(Default)] +#[derive(Default, Clone)] pub struct SmtpConfig { pub session: SessionConfig, pub queue: QueueConfig, @@ -23,7 +23,7 @@ pub struct SmtpConfig { pub report: ReportConfig, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] #[cfg_attr(feature = "test_mode", derive(PartialEq, Eq))] pub struct Throttle { pub expr: Expression, diff --git a/crates/common/src/config/smtp/queue.rs b/crates/common/src/config/smtp/queue.rs index 0eedf402..e93c5c66 100644 --- a/crates/common/src/config/smtp/queue.rs +++ b/crates/common/src/config/smtp/queue.rs @@ -17,6 +17,7 @@ use self::throttle::{parse_throttle, parse_throttle_key}; use super::*; +#[derive(Clone)] pub struct QueueConfig { // Schedule pub retry: IfBlock, @@ -44,17 +45,20 @@ pub struct QueueConfig { pub relay_hosts: AHashMap, } +#[derive(Clone)] pub struct QueueOutboundSourceIp { pub ipv4: IfBlock, pub ipv6: IfBlock, } +#[derive(Clone)] pub struct Dsn { pub name: IfBlock, pub address: IfBlock, pub sign: IfBlock, } +#[derive(Clone)] pub struct QueueOutboundTls { pub dane: IfBlock, pub mta_sts: IfBlock, @@ -62,6 +66,7 @@ pub struct QueueOutboundTls { pub invalid_certs: IfBlock, } +#[derive(Clone)] pub struct QueueOutboundTimeout { pub connect: IfBlock, pub greeting: IfBlock, @@ -73,19 +78,21 @@ pub struct QueueOutboundTimeout { pub mta_sts: IfBlock, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct QueueThrottle { pub sender: Vec, pub rcpt: Vec, pub host: Vec, } +#[derive(Clone)] pub struct QueueQuotas { pub sender: Vec, pub rcpt: Vec, pub rcpt_domain: Vec, } +#[derive(Clone)] pub struct QueueQuota { pub expr: Expression, pub keys: u16, @@ -93,6 +100,7 @@ pub struct QueueQuota { pub messages: Option, } +#[derive(Clone)] pub struct RelayHost { pub address: String, pub port: u16, @@ -439,6 +447,15 @@ fn parse_queue_quota(config: &mut Config) -> QueueQuotas { fn parse_queue_quota_item(config: &mut Config, prefix: impl AsKey) -> Option { let prefix = prefix.as_key(); + + // Skip disabled throttles + if !config + .property::((prefix.as_str(), "enable")) + .unwrap_or(true) + { + return None; + } + let mut keys = 0; for (key_, value) in config .values((&prefix, "key")) @@ -505,16 +522,12 @@ fn parse_queue_quota_item(config: &mut Config, prefix: impl AsKey) -> Option utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { match value { "optional" => Ok(RequireOptional::Optional), "require" | "required" => Ok(RequireOptional::Require), "disable" | "disabled" | "none" | "false" => Ok(RequireOptional::Disable), - _ => Err(format!( - "Invalid TLS option value {:?} for key {:?}.", - value, - key.as_key() - )), + _ => Err(format!("Invalid TLS option value {:?}.", value,)), } } } @@ -567,7 +580,7 @@ impl<'x> TryFrom> for IpLookupStrategy { 5 => Ok(IpLookupStrategy::Ipv4thenIpv6), _ => Err(()), }, - Variable::String(value) => IpLookupStrategy::parse_value("", &value).map_err(|_| ()), + Variable::String(value) => IpLookupStrategy::parse_value(&value).map_err(|_| ()), _ => Err(()), } } diff --git a/crates/common/src/config/smtp/report.rs b/crates/common/src/config/smtp/report.rs index cb927bdc..f95c2516 100644 --- a/crates/common/src/config/smtp/report.rs +++ b/crates/common/src/config/smtp/report.rs @@ -1,14 +1,12 @@ use std::time::Duration; -use utils::config::{ - utils::{AsKey, ParseValue}, - Config, -}; +use utils::config::{utils::ParseValue, Config}; use crate::expr::{if_block::IfBlock, tokenizer::TokenMap, Constant, ConstantValue, Variable}; use super::*; +#[derive(Clone)] pub struct ReportConfig { pub submitter: IfBlock, pub analysis: ReportAnalysis, @@ -20,18 +18,21 @@ pub struct ReportConfig { pub tls: AggregateReport, } +#[derive(Clone)] pub struct ReportAnalysis { pub addresses: Vec, pub forward: bool, pub store: Option, } +#[derive(Clone)] pub enum AddressMatch { StartsWith(String), EndsWith(String), Equals(String), } +#[derive(Clone)] pub struct AggregateReport { pub name: IfBlock, pub address: IfBlock, @@ -42,6 +43,7 @@ pub struct AggregateReport { pub max_size: IfBlock, } +#[derive(Clone)] pub struct Report { pub name: IfBlock, pub address: IfBlock, @@ -241,17 +243,13 @@ impl Default for AggregateReport { } impl ParseValue for AggregateFrequency { - fn parse_value(key: impl AsKey, value: &str) -> utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { match value { "daily" | "day" => Ok(AggregateFrequency::Daily), "hourly" | "hour" => Ok(AggregateFrequency::Hourly), "weekly" | "week" => Ok(AggregateFrequency::Weekly), "never" | "disable" | "false" => Ok(AggregateFrequency::Never), - _ => Err(format!( - "Invalid aggregate frequency value {:?} for key {:?}.", - value, - key.as_key() - )), + _ => Err(format!("Invalid aggregate frequency value {:?}.", value,)), } } } @@ -298,7 +296,7 @@ impl ConstantValue for AggregateFrequency { } impl ParseValue for AddressMatch { - fn parse_value(key: impl AsKey, value: &str) -> utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { if let Some(value) = value.strip_prefix('*').map(|v| v.trim()) { if !value.is_empty() { return Ok(AddressMatch::EndsWith(value.to_lowercase())); @@ -310,10 +308,6 @@ impl ParseValue for AddressMatch { } else if value.contains('@') { return Ok(AddressMatch::Equals(value.trim().to_lowercase())); } - Err(format!( - "Invalid address match value {:?} for key {:?}.", - value, - key.as_key() - )) + Err(format!("Invalid address match value {:?}.", value,)) } } diff --git a/crates/common/src/config/smtp/resolver.rs b/crates/common/src/config/smtp/resolver.rs index ac289afb..2638c9c4 100644 --- a/crates/common/src/config/smtp/resolver.rs +++ b/crates/common/src/config/smtp/resolver.rs @@ -12,6 +12,7 @@ use mail_auth::{ }, Resolver, }; +use parking_lot::Mutex; use utils::{config::Config, suffixlist::PublicSuffix}; pub struct Resolvers { @@ -21,6 +22,7 @@ pub struct Resolvers { pub psl: PublicSuffix, } +#[derive(Clone)] pub struct DnssecResolver { pub resolver: TokioAsyncResolver, } @@ -239,3 +241,23 @@ impl Default for Resolvers { } } } + +impl Clone for Resolvers { + fn clone(&self) -> Self { + Self { + dns: self.dns.clone(), + dnssec: self.dnssec.clone(), + cache: self.cache.clone(), + psl: self.psl.clone(), + } + } +} + +impl Clone for DnsRecordCache { + fn clone(&self) -> Self { + Self { + tlsa: Mutex::new(self.tlsa.lock().clone()), + mta_sts: Mutex::new(self.mta_sts.lock().clone()), + } + } +} diff --git a/crates/common/src/config/smtp/session.rs b/crates/common/src/config/smtp/session.rs index 67e26653..4cf4f152 100644 --- a/crates/common/src/config/smtp/session.rs +++ b/crates/common/src/config/smtp/session.rs @@ -4,10 +4,7 @@ use std::{ }; use smtp_proto::*; -use utils::config::{ - utils::{AsKey, ParseValue}, - Config, -}; +use utils::config::{utils::ParseValue, Config}; use crate::expr::{if_block::IfBlock, tokenizer::TokenMap, Constant, ConstantValue, Variable}; @@ -15,6 +12,7 @@ use self::throttle::parse_throttle; use super::*; +#[derive(Clone)] pub struct SessionConfig { pub timeout: IfBlock, pub duration: IfBlock, @@ -30,24 +28,27 @@ pub struct SessionConfig { pub extensions: Extensions, } -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct SessionThrottle { pub connect: Vec, pub mail_from: Vec, pub rcpt_to: Vec, } +#[derive(Clone)] pub struct Connect { pub script: IfBlock, pub greeting: IfBlock, } +#[derive(Clone)] pub struct Ehlo { pub script: IfBlock, pub require: IfBlock, pub reject_non_fqdn: IfBlock, } +#[derive(Clone)] pub struct Extensions { pub pipelining: IfBlock, pub chunking: IfBlock, @@ -61,6 +62,7 @@ pub struct Extensions { pub mt_priority: IfBlock, } +#[derive(Clone)] pub struct Auth { pub directory: IfBlock, pub mechanisms: IfBlock, @@ -71,11 +73,13 @@ pub struct Auth { pub errors_wait: IfBlock, } +#[derive(Clone)] pub struct Mail { pub script: IfBlock, pub rewrite: IfBlock, } +#[derive(Clone)] pub struct Rcpt { pub script: IfBlock, pub relay: IfBlock, @@ -94,7 +98,7 @@ pub struct Rcpt { pub subaddressing: AddressMapping, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub enum AddressMapping { Enable, Custom(IfBlock), @@ -102,6 +106,7 @@ pub enum AddressMapping { Disable, } +#[derive(Clone)] pub struct Data { pub script: IfBlock, pub pipe_commands: Vec, @@ -122,12 +127,14 @@ pub struct Data { } // Ceci n'est pas une pipe +#[derive(Clone)] pub struct Pipe { pub command: IfBlock, pub arguments: IfBlock, pub timeout: IfBlock, } +#[derive(Clone)] pub struct Milter { pub enable: IfBlock, pub addrs: Vec, @@ -641,7 +648,7 @@ impl Default for SessionConfig { pub struct Mechanism(u64); impl ParseValue for Mechanism { - fn parse_value(key: impl AsKey, value: &str) -> utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { Ok(Mechanism(match value.to_ascii_uppercase().as_str() { "LOGIN" => AUTH_LOGIN, "PLAIN" => AUTH_PLAIN, @@ -685,13 +692,7 @@ impl ParseValue for Mechanism { "CRAM-MD5" => AUTH_CRAM_MD5, "DIGEST-MD5" => AUTH_DIGEST_MD5, "ANONYMOUS" => AUTH_ANONYMOUS,*/ - _ => { - return Err(format!( - "Unsupported mechanism {:?} for property {:?}.", - value, - key.as_key() - )) - } + _ => return Err(format!("Unsupported mechanism {:?}.", value)), })) } } @@ -758,7 +759,7 @@ impl<'x> TryFrom> for MtPriority { 4 => Ok(MtPriority::Nsep), _ => Err(()), }, - Variable::String(value) => MtPriority::parse_value("", &value).map_err(|_| ()), + Variable::String(value) => MtPriority::parse_value(&value).map_err(|_| ()), _ => Err(()), } } diff --git a/crates/common/src/config/smtp/throttle.rs b/crates/common/src/config/smtp/throttle.rs index c650595a..0c48ad48 100644 --- a/crates/common/src/config/smtp/throttle.rs +++ b/crates/common/src/config/smtp/throttle.rs @@ -61,6 +61,15 @@ fn parse_throttle_item( available_throttle_keys: u16, ) -> Option { let prefix = prefix.as_key(); + + // Skip disabled throttles + if !config + .property::((prefix.as_str(), "enable")) + .unwrap_or(true) + { + return None; + } + let mut keys = 0; for (key_, value) in config .values((&prefix, "key")) diff --git a/crates/common/src/config/storage.rs b/crates/common/src/config/storage.rs index 96034f6c..30d9ebfb 100644 --- a/crates/common/src/config/storage.rs +++ b/crates/common/src/config/storage.rs @@ -6,15 +6,19 @@ use store::{write::purge::PurgeSchedule, BlobStore, FtsStore, LookupStore, Store use super::manager::ConfigManager; -#[derive(Default)] +#[derive(Default, Clone)] pub struct Storage { pub data: Store, pub blob: BlobStore, pub fts: FtsStore, pub lookup: LookupStore, - pub lookups: AHashMap, pub directory: Arc, pub directories: AHashMap>, pub purge_schedules: Vec, pub config: ConfigManager, + + pub stores: AHashMap, + pub blobs: AHashMap, + pub lookups: AHashMap, + pub ftss: AHashMap, } diff --git a/crates/common/src/config/tracers.rs b/crates/common/src/config/tracers.rs index 04245f17..019f2009 100644 --- a/crates/common/src/config/tracers.rs +++ b/crates/common/src/config/tracers.rs @@ -43,6 +43,16 @@ impl Tracers { .collect::>() { let id = tracer_id.as_str(); + + // Skip disabled tracers + if !config + .property::(("tracer", id, "enable")) + .unwrap_or(true) + { + continue; + } + + // Parse level let level = Level::from_str(config.value(("tracer", id, "level")).unwrap_or("info")) .map_err(|err| { config.new_parse_error( diff --git a/crates/common/src/expr/mod.rs b/crates/common/src/expr/mod.rs index 56020b6e..0f16d8b1 100644 --- a/crates/common/src/expr/mod.rs +++ b/crates/common/src/expr/mod.rs @@ -310,7 +310,7 @@ impl<'x> TryFrom> for Duration { Variable::Integer(value) if value > 0 => Ok(Duration::from_millis(value as u64)), Variable::Float(value) if value > 0.0 => Ok(Duration::from_millis(value as u64)), Variable::String(value) if !value.is_empty() => { - Duration::parse_value("", &value).map_err(|_| ()) + Duration::parse_value(&value).map_err(|_| ()) } _ => Err(()), } diff --git a/crates/common/src/expr/tokenizer.rs b/crates/common/src/expr/tokenizer.rs index e62b27e7..d60f4c89 100644 --- a/crates/common/src/expr/tokenizer.rs +++ b/crates/common/src/expr/tokenizer.rs @@ -336,7 +336,7 @@ impl<'x> Tokenizer<'x> { }) } else if let Some(token) = self.token_map.tokens.get(buf.as_str()) { Ok(token.clone()) - } else if let Ok(duration) = Duration::parse_value("", &buf) { + } else if let Ok(duration) = Duration::parse_value(&buf) { Ok(Token::Constant(Constant::Integer( duration.as_millis() as i64 ))) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index d55d186d..aed93bfa 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -42,7 +42,7 @@ pub static DAEMON_NAME: &str = concat!("Stalwart Mail Server v", env!("CARGO_PKG pub type SharedCore = Arc>; -#[derive(Default)] +#[derive(Default, Clone)] pub struct Core { pub storage: Storage, pub sieve: Scripting, @@ -53,6 +53,7 @@ pub struct Core { pub imap: ImapConfig, } +#[derive(Clone)] pub struct Network { pub blocked_ips: BlockedIps, pub hostname: IfBlock, diff --git a/crates/common/src/listener/acme/mod.rs b/crates/common/src/listener/acme/mod.rs index 91d6dadb..321f3554 100644 --- a/crates/common/src/listener/acme/mod.rs +++ b/crates/common/src/listener/acme/mod.rs @@ -129,3 +129,16 @@ impl Debug for AcmeResolver { f.debug_struct("AcmeResolver").finish() } } + +impl Clone for AcmeProvider { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + directory_url: self.directory_url.clone(), + domains: self.domains.clone(), + contact: self.contact.clone(), + renew_before: self.renew_before, + account_key: ArcSwap::from_pointee(self.account_key.load().as_ref().clone()), + } + } +} diff --git a/crates/common/src/listener/blocked.rs b/crates/common/src/listener/blocked.rs index b2db51d9..82e649f1 100644 --- a/crates/common/src/listener/blocked.rs +++ b/crates/common/src/listener/blocked.rs @@ -48,12 +48,11 @@ impl BlockedIps { let mut ip_addresses = AHashSet::new(); let mut ip_networks = Vec::new(); - let ips = config + for ip in config .set_values(BLOCKED_IP_KEY) - .map(|value| IpAddrOrMask::parse_value(BLOCKED_IP_KEY, value)) - .collect::>(); - - for ip in ips { + .map(IpAddrOrMask::parse_value) + .collect::>() + { match ip { Ok(IpAddrOrMask::Ip(ip)) => { ip_addresses.insert(ip); @@ -138,6 +137,17 @@ impl Default for BlockedIps { } } +impl Clone for BlockedIps { + fn clone(&self) -> Self { + Self { + ip_addresses: RwLock::new(self.ip_addresses.read().clone()), + ip_networks: self.ip_networks.clone(), + has_networks: self.has_networks, + limiter_rate: self.limiter_rate.clone(), + } + } +} + impl Debug for BlockedIps { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BlockedIps") diff --git a/crates/common/src/listener/tls.rs b/crates/common/src/listener/tls.rs index 10ed665a..0c54523e 100644 --- a/crates/common/src/listener/tls.rs +++ b/crates/common/src/listener/tls.rs @@ -58,6 +58,7 @@ pub struct TlsManager { pub self_signed_cert: Option>, } +#[derive(Clone)] pub(crate) struct AcmeAuthKey { pub provider_id: String, pub key: Arc, @@ -218,3 +219,18 @@ impl std::fmt::Debug for CertificateResolver { f.debug_struct("CertificateResolver").finish() } } + +impl Clone for TlsManager { + fn clone(&self) -> Self { + Self { + certificates: ArcSwap::from_pointee(self.certificates.load().as_ref().clone()), + acme_providers: self.acme_providers.clone(), + acme_auth_keys: Mutex::new(self.acme_auth_keys.lock().clone()), + acme_in_progress: self + .acme_in_progress + .load(std::sync::atomic::Ordering::Relaxed) + .into(), + self_signed_cert: self.self_signed_cert.clone(), + } + } +} diff --git a/crates/common/src/scripts/functions/array.rs b/crates/common/src/scripts/functions/array.rs index 46a6e9b7..0d3c49ca 100644 --- a/crates/common/src/scripts/functions/array.rs +++ b/crates/common/src/scripts/functions/array.rs @@ -25,7 +25,7 @@ use std::collections::{HashMap, HashSet}; use sieve::{runtime::Variable, Context}; -pub fn fn_count<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_count<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match &v[0] { Variable::Array(a) => a.len(), v => { @@ -39,7 +39,7 @@ pub fn fn_count<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_sort<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_sort<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let is_asc = v[1].to_bool(); let mut arr = (*v[0].to_array()).clone(); if is_asc { @@ -50,7 +50,7 @@ pub fn fn_sort<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { arr.into() } -pub fn fn_dedup<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_dedup<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let arr = v[0].to_array(); let mut result = Vec::with_capacity(arr.len()); @@ -63,7 +63,7 @@ pub fn fn_dedup<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { result.into() } -pub fn fn_cosine_similarity<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_cosine_similarity<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let mut word_freq: HashMap = HashMap::new(); for (idx, var) in v.into_iter().enumerate() { @@ -99,7 +99,7 @@ pub fn fn_cosine_similarity<'x>(_: &'x Context<'x, ()>, v: Vec) -> Var .into() } -pub fn fn_jaccard_similarity<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_jaccard_similarity<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let mut word_freq = [HashSet::new(), HashSet::new()]; for (idx, var) in v.into_iter().enumerate() { @@ -128,7 +128,7 @@ pub fn fn_jaccard_similarity<'x>(_: &'x Context<'x, ()>, v: Vec) -> Va .into() } -pub fn fn_is_intersect<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_intersect<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match (&v[0], &v[1]) { (Variable::Array(a), Variable::Array(b)) => a.iter().any(|x| b.contains(x)), (Variable::Array(a), item) | (item, Variable::Array(a)) => a.contains(item), @@ -137,7 +137,7 @@ pub fn fn_is_intersect<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable .into() } -pub fn fn_winnow<'x>(_: &'x Context<'x, ()>, mut v: Vec) -> Variable { +pub fn fn_winnow<'x>(_: &'x Context<'x>, mut v: Vec) -> Variable { match v.remove(0) { Variable::Array(a) => a .iter() diff --git a/crates/common/src/scripts/functions/email.rs b/crates/common/src/scripts/functions/email.rs index d522e339..87e54be8 100644 --- a/crates/common/src/scripts/functions/email.rs +++ b/crates/common/src/scripts/functions/email.rs @@ -25,7 +25,7 @@ use sieve::{runtime::Variable, Context}; use super::ApplyString; -pub fn fn_is_email<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_email<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let mut last_ch = 0; let mut in_quote = false; let mut at_count = 0; @@ -95,7 +95,7 @@ pub fn fn_is_email<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { (at_count == 1 && dot_count > 0 && lp_len > 0 && value > 0).into() } -pub fn fn_email_part<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_email_part<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| { s.rsplit_once('@') .map(|(u, d)| match v[1].to_string().as_ref() { diff --git a/crates/common/src/scripts/functions/header.rs b/crates/common/src/scripts/functions/header.rs index fd08e4af..8038fc11 100644 --- a/crates/common/src/scripts/functions/header.rs +++ b/crates/common/src/scripts/functions/header.rs @@ -26,7 +26,7 @@ use sieve::{compiler::ReceivedPart, runtime::Variable, Context}; use super::ApplyString; -pub fn fn_received_part<'x>(ctx: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_received_part<'x>(ctx: &'x Context<'x>, v: Vec) -> Variable { if let (Ok(part), Some(HeaderValue::Received(rcvd))) = ( ReceivedPart::try_from(v[1].to_string().as_ref()), ctx.message() @@ -45,7 +45,7 @@ pub fn fn_received_part<'x>(ctx: &'x Context<'x, ()>, v: Vec) -> Varia } } -pub fn fn_is_encoding_problem<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Variable { +pub fn fn_is_encoding_problem<'x>(ctx: &'x Context<'x>, _: Vec) -> Variable { ctx.message() .part(ctx.part()) .map(|p| p.is_encoding_problem) @@ -53,16 +53,16 @@ pub fn fn_is_encoding_problem<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> .into() } -pub fn fn_is_attachment<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Variable { +pub fn fn_is_attachment<'x>(ctx: &'x Context<'x>, _: Vec) -> Variable { ctx.message().attachments.contains(&ctx.part()).into() } -pub fn fn_is_body<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Variable { +pub fn fn_is_body<'x>(ctx: &'x Context<'x>, _: Vec) -> Variable { (ctx.message().text_body.contains(&ctx.part()) || ctx.message().html_body.contains(&ctx.part())) .into() } -pub fn fn_attachment_name<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Variable { +pub fn fn_attachment_name<'x>(ctx: &'x Context<'x>, _: Vec) -> Variable { ctx.message() .part(ctx.part()) .and_then(|p| p.attachment_name()) @@ -70,7 +70,7 @@ pub fn fn_attachment_name<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Var .into() } -pub fn fn_mime_part_len<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Variable { +pub fn fn_mime_part_len<'x>(ctx: &'x Context<'x>, _: Vec) -> Variable { ctx.message() .part(ctx.part()) .map(|p| p.len()) @@ -78,11 +78,11 @@ pub fn fn_mime_part_len<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Varia .into() } -pub fn fn_thread_name<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_thread_name<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| thread_name(s).into()) } -pub fn fn_is_header_utf8_valid<'x>(ctx: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_header_utf8_valid<'x>(ctx: &'x Context<'x>, v: Vec) -> Variable { ctx.message() .part(ctx.part()) .map(|p| { diff --git a/crates/common/src/scripts/functions/html.rs b/crates/common/src/scripts/functions/html.rs index f0c8cb20..114a0768 100644 --- a/crates/common/src/scripts/functions/html.rs +++ b/crates/common/src/scripts/functions/html.rs @@ -26,11 +26,11 @@ use std::borrow::Cow; use mail_parser::decoders::html::{add_html_token, html_to_text}; use sieve::{runtime::Variable, Context}; -pub fn fn_html_to_text<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_html_to_text<'x>(_: &'x Context<'x>, v: Vec) -> Variable { html_to_text(v[0].to_string().as_ref()).into() } -pub fn fn_html_has_tag<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_html_has_tag<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].as_array() .map(|arr| { let token = v[1].to_string(); @@ -45,7 +45,7 @@ pub fn fn_html_has_tag<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable .into() } -pub fn fn_html_attr_size<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_html_attr_size<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let t = v[0].to_string(); let mut dimension = None; @@ -63,7 +63,7 @@ pub fn fn_html_attr_size<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variab dimension.map(Variable::Integer).unwrap_or_default() } -pub fn fn_html_attrs<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_html_attrs<'x>(_: &'x Context<'x>, v: Vec) -> Variable { html_attr_tokens( v[0].to_string().as_ref(), v[1].to_string().as_ref(), @@ -72,7 +72,7 @@ pub fn fn_html_attrs<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_html_attr<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_html_attr<'x>(_: &'x Context<'x>, v: Vec) -> Variable { get_attribute(v[0].to_string().as_ref(), v[1].to_string().as_ref()) .map(Variable::from) .unwrap_or_default() diff --git a/crates/common/src/scripts/functions/image.rs b/crates/common/src/scripts/functions/image.rs index ea8d4bc6..034ca259 100644 --- a/crates/common/src/scripts/functions/image.rs +++ b/crates/common/src/scripts/functions/image.rs @@ -23,7 +23,7 @@ use sieve::{runtime::Variable, Context}; -pub fn fn_img_metadata<'x>(ctx: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_img_metadata<'x>(ctx: &'x Context<'x>, v: Vec) -> Variable { ctx.message() .part(ctx.part()) .map(|p| p.contents()) diff --git a/crates/common/src/scripts/functions/misc.rs b/crates/common/src/scripts/functions/misc.rs index fc7690ab..a1a1dd65 100644 --- a/crates/common/src/scripts/functions/misc.rs +++ b/crates/common/src/scripts/functions/misc.rs @@ -30,7 +30,7 @@ use sieve::{runtime::Variable, Context}; use super::ApplyString; -pub fn fn_is_empty<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_empty<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match &v[0] { Variable::String(s) => s.is_empty(), Variable::Integer(_) | Variable::Float(_) => false, @@ -39,29 +39,29 @@ pub fn fn_is_empty<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_is_number<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_number<'x>(_: &'x Context<'x>, v: Vec) -> Variable { matches!(&v[0], Variable::Integer(_) | Variable::Float(_)).into() } -pub fn fn_is_ip_addr<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_ip_addr<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string().parse::().is_ok().into() } -pub fn fn_is_ipv4_addr<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_ipv4_addr<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .parse::() .map_or(false, |ip| matches!(ip, IpAddr::V4(_))) .into() } -pub fn fn_is_ipv6_addr<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_ipv6_addr<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .parse::() .map_or(false, |ip| matches!(ip, IpAddr::V6(_))) .into() } -pub fn fn_ip_reverse_name<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_ip_reverse_name<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .parse::() .map(|ip| ip.to_reverse_name()) @@ -69,7 +69,7 @@ pub fn fn_ip_reverse_name<'x>(_: &'x Context<'x, ()>, v: Vec) -> Varia .into() } -pub fn fn_detect_file_type<'x>(ctx: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_detect_file_type<'x>(ctx: &'x Context<'x>, v: Vec) -> Variable { ctx.message() .part(ctx.part()) .and_then(|p| infer::get(p.contents())) @@ -86,7 +86,7 @@ pub fn fn_detect_file_type<'x>(ctx: &'x Context<'x, ()>, v: Vec) -> Va .unwrap_or_default() } -pub fn fn_hash<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_hash<'x>(_: &'x Context<'x>, v: Vec) -> Variable { use sha1::Digest; let hash = v[1].to_string(); @@ -111,7 +111,7 @@ pub fn fn_hash<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { }) } -pub fn fn_is_var_names<'x>(ctx: &'x Context<'x, ()>, _: Vec) -> Variable { +pub fn fn_is_var_names<'x>(ctx: &'x Context<'x>, _: Vec) -> Variable { Variable::Array( ctx.global_variable_names() .map(|v| Variable::from(v.to_uppercase())) diff --git a/crates/common/src/scripts/functions/mod.rs b/crates/common/src/scripts/functions/mod.rs index 02d8dcad..38d371ab 100644 --- a/crates/common/src/scripts/functions/mod.rs +++ b/crates/common/src/scripts/functions/mod.rs @@ -37,7 +37,7 @@ use self::{ array::*, email::*, header::*, html::*, image::*, misc::*, text::*, unicode::*, url::*, }; -pub fn register_functions() -> FunctionMap<()> { +pub fn register_functions() -> FunctionMap { FunctionMap::new() .with_function("trim", fn_trim) .with_function("trim_start", fn_trim_start) diff --git a/crates/common/src/scripts/functions/text.rs b/crates/common/src/scripts/functions/text.rs index 2af43f56..58767656 100644 --- a/crates/common/src/scripts/functions/text.rs +++ b/crates/common/src/scripts/functions/text.rs @@ -25,19 +25,19 @@ use sieve::{runtime::Variable, Context}; use super::ApplyString; -pub fn fn_trim<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_trim<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| Variable::from(s.trim())) } -pub fn fn_trim_end<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_trim_end<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| Variable::from(s.trim_end())) } -pub fn fn_trim_start<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_trim_start<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| Variable::from(s.trim_start())) } -pub fn fn_len<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_len<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match &v[0] { Variable::String(s) => s.len(), Variable::Array(a) => a.len(), @@ -46,15 +46,15 @@ pub fn fn_len<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_to_lowercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_to_lowercase<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| Variable::from(s.to_lowercase())) } -pub fn fn_to_uppercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_to_uppercase<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| Variable::from(s.to_uppercase())) } -pub fn fn_is_uppercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_uppercase<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| { s.chars() .filter(|c| c.is_alphabetic()) @@ -63,7 +63,7 @@ pub fn fn_is_uppercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable }) } -pub fn fn_is_lowercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_lowercase<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| { s.chars() .filter(|c| c.is_alphabetic()) @@ -72,7 +72,7 @@ pub fn fn_is_lowercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable }) } -pub fn fn_has_digits<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_has_digits<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|s| s.chars().any(|c| c.is_ascii_digit()).into()) } @@ -85,7 +85,7 @@ pub fn tokenize_words(v: &Variable) -> Variable { .into() } -pub fn fn_count_spaces<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_count_spaces<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .as_ref() .chars() @@ -94,7 +94,7 @@ pub fn fn_count_spaces<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable .into() } -pub fn fn_count_uppercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_count_uppercase<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .as_ref() .chars() @@ -103,7 +103,7 @@ pub fn fn_count_uppercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Varia .into() } -pub fn fn_count_lowercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_count_lowercase<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .as_ref() .chars() @@ -112,17 +112,17 @@ pub fn fn_count_lowercase<'x>(_: &'x Context<'x, ()>, v: Vec) -> Varia .into() } -pub fn fn_count_chars<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_count_chars<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string().as_ref().chars().count().into() } -pub fn fn_eq_ignore_case<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_eq_ignore_case<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .eq_ignore_ascii_case(v[1].to_string().as_ref()) .into() } -pub fn fn_contains<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_contains<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match &v[0] { Variable::String(s) => s.contains(v[1].to_string().as_ref()), Variable::Array(arr) => arr.contains(&v[1]), @@ -131,7 +131,7 @@ pub fn fn_contains<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_contains_ignore_case<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_contains_ignore_case<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let needle = v[1].to_string(); match &v[0] { Variable::String(s) => s.to_lowercase().contains(&needle.to_lowercase()), @@ -144,17 +144,17 @@ pub fn fn_contains_ignore_case<'x>(_: &'x Context<'x, ()>, v: Vec) -> .into() } -pub fn fn_starts_with<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_starts_with<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .starts_with(v[1].to_string().as_ref()) .into() } -pub fn fn_ends_with<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_ends_with<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string().ends_with(v[1].to_string().as_ref()).into() } -pub fn fn_lines<'x>(_: &'x Context<'x, ()>, mut v: Vec) -> Variable { +pub fn fn_lines<'x>(_: &'x Context<'x>, mut v: Vec) -> Variable { match v.remove(0) { Variable::String(s) => s .lines() @@ -165,7 +165,7 @@ pub fn fn_lines<'x>(_: &'x Context<'x, ()>, mut v: Vec) -> Variable { } } -pub fn fn_substring<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_substring<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .chars() .skip(v[1].to_usize()) @@ -174,7 +174,7 @@ pub fn fn_substring<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_strip_prefix<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_strip_prefix<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let prefix = v[1].to_string(); v[0].transform(|s| { s.strip_prefix(prefix.as_ref()) @@ -183,7 +183,7 @@ pub fn fn_strip_prefix<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable }) } -pub fn fn_strip_suffix<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_strip_suffix<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let suffix = v[1].to_string(); v[0].transform(|s| { s.strip_suffix(suffix.as_ref()) @@ -192,7 +192,7 @@ pub fn fn_strip_suffix<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable }) } -pub fn fn_split<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_split<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .split(v[1].to_string().as_ref()) .map(|s| Variable::from(s.to_string())) @@ -200,7 +200,7 @@ pub fn fn_split<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_rsplit<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_rsplit<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .rsplit(v[1].to_string().as_ref()) .map(|s| Variable::from(s.to_string())) @@ -208,7 +208,7 @@ pub fn fn_rsplit<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_split_once<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_split_once<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .split_once(v[1].to_string().as_ref()) .map(|(a, b)| { @@ -219,7 +219,7 @@ pub fn fn_split_once<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .unwrap_or_default() } -pub fn fn_rsplit_once<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_rsplit_once<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].to_string() .rsplit_once(v[1].to_string().as_ref()) .map(|(a, b)| { @@ -237,7 +237,7 @@ pub fn fn_rsplit_once<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable * * Copyright (c) 2016 Titus Wormer */ -pub fn fn_levenshtein_distance<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_levenshtein_distance<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let a = v[0].to_string(); let b = v[1].to_string(); @@ -298,7 +298,7 @@ pub fn fn_levenshtein_distance<'x>(_: &'x Context<'x, ()>, v: Vec) -> result.into() } -pub fn fn_detect_language<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_detect_language<'x>(_: &'x Context<'x>, v: Vec) -> Variable { whatlang::detect_lang(v[0].to_string().as_ref()) .map(|l| l.code()) .unwrap_or("unknown") diff --git a/crates/common/src/scripts/functions/unicode.rs b/crates/common/src/scripts/functions/unicode.rs index 41097ae3..2d085c8f 100644 --- a/crates/common/src/scripts/functions/unicode.rs +++ b/crates/common/src/scripts/functions/unicode.rs @@ -24,7 +24,7 @@ use sieve::{runtime::Variable, Context}; use unicode_security::MixedScript; -pub fn fn_is_ascii<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_ascii<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match &v[0] { Variable::String(s) => s.chars().all(|c| c.is_ascii()), Variable::Integer(_) | Variable::Float(_) => true, @@ -36,7 +36,7 @@ pub fn fn_is_ascii<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_has_zwsp<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_has_zwsp<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match &v[0] { Variable::String(s) => s.chars().any(|c| c.is_zwsp()), Variable::Array(a) => a.iter().any(|v| match v { @@ -48,7 +48,7 @@ pub fn fn_has_zwsp<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { .into() } -pub fn fn_has_obscured<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_has_obscured<'x>(_: &'x Context<'x>, v: Vec) -> Variable { match &v[0] { Variable::String(s) => s.chars().any(|c| c.is_obscured()), Variable::Array(a) => a.iter().any(|v| match v { @@ -84,20 +84,20 @@ impl CharUtils for char { } } -pub fn fn_cure_text<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_cure_text<'x>(_: &'x Context<'x>, v: Vec) -> Variable { decancer::cure(v[0].to_string().as_ref(), decancer::Options::default()) .map(|s| s.into_str()) .unwrap_or_default() .into() } -pub fn fn_unicode_skeleton<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_unicode_skeleton<'x>(_: &'x Context<'x>, v: Vec) -> Variable { unicode_security::skeleton(v[0].to_string().as_ref()) .collect::() .into() } -pub fn fn_is_single_script<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_is_single_script<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let text = v[0].to_string(); if !text.is_empty() { text.as_ref().is_single_script() diff --git a/crates/common/src/scripts/functions/url.rs b/crates/common/src/scripts/functions/url.rs index bcb70d8a..d2cc2f43 100644 --- a/crates/common/src/scripts/functions/url.rs +++ b/crates/common/src/scripts/functions/url.rs @@ -26,7 +26,7 @@ use sieve::{runtime::Variable, Context}; use super::ApplyString; -pub fn fn_uri_part<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_uri_part<'x>(_: &'x Context<'x>, v: Vec) -> Variable { let part = v[1].to_string(); v[0].transform(|uri| { uri.parse::() @@ -49,7 +49,7 @@ pub fn fn_uri_part<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { }) } -pub fn fn_puny_decode<'x>(_: &'x Context<'x, ()>, v: Vec) -> Variable { +pub fn fn_puny_decode<'x>(_: &'x Context<'x>, v: Vec) -> Variable { v[0].transform(|domain| { if domain.contains("xn--") { let mut decoded = String::with_capacity(domain.len()); diff --git a/crates/common/src/scripts/plugins/bayes.rs b/crates/common/src/scripts/plugins/bayes.rs index 9e8d5c89..3173a428 100644 --- a/crates/common/src/scripts/plugins/bayes.rs +++ b/crates/common/src/scripts/plugins/bayes.rs @@ -34,19 +34,19 @@ use tokio::runtime::Handle; use super::PluginContext; -pub fn register_train(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_train(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("bayes_train", plugin_id, 3); } -pub fn register_untrain(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_untrain(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("bayes_untrain", plugin_id, 3); } -pub fn register_classify(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_classify(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("bayes_classify", plugin_id, 3); } -pub fn register_is_balanced(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_is_balanced(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("bayes_is_balanced", plugin_id, 3); } diff --git a/crates/common/src/scripts/plugins/dns.rs b/crates/common/src/scripts/plugins/dns.rs index c88b1b8f..0e588fda 100644 --- a/crates/common/src/scripts/plugins/dns.rs +++ b/crates/common/src/scripts/plugins/dns.rs @@ -28,11 +28,11 @@ use sieve::{runtime::Variable, FunctionMap}; use super::PluginContext; -pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("dns_query", plugin_id, 2); } -pub fn register_exists(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_exists(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("dns_exists", plugin_id, 2); } diff --git a/crates/common/src/scripts/plugins/exec.rs b/crates/common/src/scripts/plugins/exec.rs index 400b7b14..d15ba260 100644 --- a/crates/common/src/scripts/plugins/exec.rs +++ b/crates/common/src/scripts/plugins/exec.rs @@ -27,7 +27,7 @@ use sieve::{runtime::Variable, FunctionMap}; use super::PluginContext; -pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("exec", plugin_id, 2); } diff --git a/crates/common/src/scripts/plugins/headers.rs b/crates/common/src/scripts/plugins/headers.rs index e6325d76..fb1594a3 100644 --- a/crates/common/src/scripts/plugins/headers.rs +++ b/crates/common/src/scripts/plugins/headers.rs @@ -27,7 +27,7 @@ use crate::scripts::ScriptModification; use super::PluginContext; -pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("add_header", plugin_id, 2); } diff --git a/crates/common/src/scripts/plugins/http.rs b/crates/common/src/scripts/plugins/http.rs index 421a1dad..c4bdf1ea 100644 --- a/crates/common/src/scripts/plugins/http.rs +++ b/crates/common/src/scripts/plugins/http.rs @@ -28,7 +28,7 @@ use sieve::{runtime::Variable, FunctionMap}; use super::PluginContext; -pub fn register_header(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_header(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("http_header", plugin_id, 4); } diff --git a/crates/common/src/scripts/plugins/lookup.rs b/crates/common/src/scripts/plugins/lookup.rs index 4e38b830..46299b5b 100644 --- a/crates/common/src/scripts/plugins/lookup.rs +++ b/crates/common/src/scripts/plugins/lookup.rs @@ -35,23 +35,23 @@ use crate::{config::scripts::RemoteList, scripts::into_sieve_value, USER_AGENT}; use super::PluginContext; -pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("key_exists", plugin_id, 2); } -pub fn register_get(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_get(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("key_get", plugin_id, 2); } -pub fn register_set(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_set(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("key_set", plugin_id, 4); } -pub fn register_remote(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_remote(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("key_exists_http", plugin_id, 3); } -pub fn register_local_domain(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_local_domain(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("is_local_domain", plugin_id, 2); } diff --git a/crates/common/src/scripts/plugins/mod.rs b/crates/common/src/scripts/plugins/mod.rs index 321a7f4a..a3ad8225 100644 --- a/crates/common/src/scripts/plugins/mod.rs +++ b/crates/common/src/scripts/plugins/mod.rs @@ -39,7 +39,7 @@ use crate::Core; use super::ScriptModification; -type RegisterPluginFnc = fn(u32, &mut FunctionMap<()>) -> (); +type RegisterPluginFnc = fn(u32, &mut FunctionMap) -> (); type ExecPluginFnc = fn(PluginContext<'_>) -> Variable; pub struct PluginContext<'x> { @@ -96,7 +96,7 @@ pub trait RegisterSievePlugins { fn register_plugins(self) -> Self; } -impl RegisterSievePlugins for FunctionMap<()> { +impl RegisterSievePlugins for FunctionMap { fn register_plugins(mut self) -> Self { #[cfg(feature = "test_mode")] { diff --git a/crates/common/src/scripts/plugins/pyzor.rs b/crates/common/src/scripts/plugins/pyzor.rs index d7af8c9a..f6810383 100644 --- a/crates/common/src/scripts/plugins/pyzor.rs +++ b/crates/common/src/scripts/plugins/pyzor.rs @@ -48,7 +48,7 @@ struct PyzorResponse { wl_count: u64, } -pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("pyzor_check", plugin_id, 2); } diff --git a/crates/common/src/scripts/plugins/query.rs b/crates/common/src/scripts/plugins/query.rs index 71cc9478..6102c9df 100644 --- a/crates/common/src/scripts/plugins/query.rs +++ b/crates/common/src/scripts/plugins/query.rs @@ -29,7 +29,7 @@ use store::{Rows, Value}; use super::PluginContext; -pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("query", plugin_id, 3); } diff --git a/crates/common/src/scripts/plugins/text.rs b/crates/common/src/scripts/plugins/text.rs index 363c6f0f..afe001a0 100644 --- a/crates/common/src/scripts/plugins/text.rs +++ b/crates/common/src/scripts/plugins/text.rs @@ -35,11 +35,11 @@ enum MatchPart { Host, } -pub fn register_tokenize(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_tokenize(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("tokenize", plugin_id, 2); } -pub fn register_domain_part(plugin_id: u32, fnc_map: &mut FunctionMap<()>) { +pub fn register_domain_part(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("domain_part", plugin_id, 2); } diff --git a/crates/directory/src/core/config.rs b/crates/directory/src/core/config.rs index 413106f4..ce4542b3 100644 --- a/crates/directory/src/core/config.rs +++ b/crates/directory/src/core/config.rs @@ -27,10 +27,7 @@ use deadpool::{ }; use std::{sync::Arc, time::Duration}; use store::{Store, Stores}; -use utils::config::{ - utils::{AsKey, ParseValue}, - Config, -}; +use utils::config::{utils::ParseValue, Config}; use ahash::AHashMap; @@ -184,17 +181,13 @@ impl Default for LookupFormat { } impl ParseValue for LookupType { - fn parse_value(key: impl AsKey, value: &str) -> utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { match value { "list" => Ok(LookupType::List), "glob" => Ok(LookupType::Glob), "regex" => Ok(LookupType::Regex), "map" => Ok(LookupType::Map), - _ => Err(format!( - "Invalid value for lookup type {key:?}: {value:?}", - key = key.as_key(), - value = value - )), + _ => Err(format!("Invalid value for lookup type {value:?}",)), } } } diff --git a/crates/install/Cargo.toml b/crates/install/Cargo.toml index ea0bc864..07c653d9 100644 --- a/crates/install/Cargo.toml +++ b/crates/install/Cargo.toml @@ -16,12 +16,12 @@ rpassword = "7.0" indicatif = "0.17.0" dialoguer = "0.11" openssl = { version = "0.10.55", features = ["vendored"] } -base64 = "0.21.2" +base64 = "0.22" pwhash = "1.0.0" rand = "0.8.5" clap = { version = "4.1.6", features = ["derive"] } zip-extract = "0.1.2" -rcgen = "0.12" +rcgen = "0.13" [target.'cfg(not(target_env = "msvc"))'.dependencies] libc = "0.2.147" diff --git a/crates/install/src/main.rs b/crates/install/src/main.rs index a71f2adf..4d9def3a 100644 --- a/crates/install/src/main.rs +++ b/crates/install/src/main.rs @@ -35,7 +35,7 @@ use clap::{Parser, ValueEnum}; use dialoguer::{console::Term, theme::ColorfulTheme, Confirm, Input, Select}; use openssl::rsa::Rsa; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use rcgen::generate_simple_self_signed; +use rcgen::{generate_simple_self_signed, CertifiedKey}; const CONFIG_URL: &str = "https://get.stalw.art/resources/config.zip"; @@ -482,15 +482,12 @@ fn main() -> std::io::Result<()> { let pk_path = base_path.join("privkey.pem"); // Build self-signed cert - let cert = generate_simple_self_signed(vec![hostname.to_string()]).unwrap_or_else(|err| { - panic!("Failed to generate self-signed certificate for {hostname}: {err}",) - }); - std::fs::write( - &cert_path, - cert.serialize_pem() - .unwrap_or_else(|err| panic!("Failed to write certificate for {hostname}: {err}",)), - )?; - std::fs::write(&pk_path, cert.serialize_private_key_pem())?; + let CertifiedKey { cert, key_pair } = + generate_simple_self_signed(vec![hostname.to_string()]).unwrap_or_else(|err| { + panic!("Failed to generate self-signed certificate for {hostname}: {err}",) + }); + std::fs::write(&cert_path, cert.pem())?; + std::fs::write(&pk_path, key_pair.serialize_pem())?; ( cert_path.to_str().unwrap().to_string(), diff --git a/crates/jmap-proto/src/request/capability.rs b/crates/jmap-proto/src/request/capability.rs index d8506df7..48bbe9cb 100644 --- a/crates/jmap-proto/src/request/capability.rs +++ b/crates/jmap-proto/src/request/capability.rs @@ -196,7 +196,7 @@ pub struct BlobCapabilities { #[derive(Debug, Clone, Default, serde::Serialize)] pub struct EmptyCapabilities {} -#[derive(Default)] +#[derive(Default, Clone)] pub struct BaseCapabilities { pub session: VecMap, pub account: VecMap, diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml index 2087439e..e51eda8e 100644 --- a/crates/jmap/Cargo.toml +++ b/crates/jmap/Cargo.toml @@ -16,7 +16,7 @@ smtp-proto = { version = "0.1" } mail-parser = { version = "0.9", features = ["full_encoding", "serde_support", "ludicrous_mode"] } mail-builder = { version = "0.3", features = ["ludicrous_mode"] } mail-send = { version = "0.4", default-features = false, features = ["cram-md5"] } -sieve-rs = { version = "0.4" } +sieve-rs = { version = "0.5" } serde = { version = "1.0", features = ["derive"]} serde_json = "1.0" hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } diff --git a/crates/jmap/src/api/admin.rs b/crates/jmap/src/api/admin.rs index 665d6302..4b7104fa 100644 --- a/crates/jmap/src/api/admin.rs +++ b/crates/jmap/src/api/admin.rs @@ -380,29 +380,77 @@ impl JMAP { .into_http_response(), } } - ("reload", Some("settings"), &Method::GET) => { - /*let _ = self - .inner - .housekeeper_tx - .send(housekeeper::Event::ReloadConfig) - .await;*/ + ("reload", Some("lookup"), &Method::GET) => { + match self.core.reload_lookups().await { + Ok(result) => { + // Update core + if let Some(core) = result.new_core { + self.shared_core.store(core.into()); + } - JsonResponse::new(json!({ - "data": (), - })) - .into_http_response() + JsonResponse::new(json!({ + "data": result.config, + })) + .into_http_response() + } + Err(err) => RequestError::blank( + StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + "Database error", + err.to_string(), + ) + .into_http_response(), + } } - ("reload", Some("certificates"), &Method::GET) => { - /*let _ = self - .inner - .housekeeper_tx - .send(housekeeper::Event::ReloadCertificates) - .await;*/ + ("reload", Some("certificate"), &Method::GET) => { + match self.core.reload_certificates().await { + Ok(result) => JsonResponse::new(json!({ + "data": result.config, + })) + .into_http_response(), + Err(err) => RequestError::blank( + StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + "Database error", + err.to_string(), + ) + .into_http_response(), + } + } + ("reload", Some("server.blocked-ip"), &Method::GET) => { + match self.core.reload_blocked_ips().await { + Ok(result) => JsonResponse::new(json!({ + "data": result.config, + })) + .into_http_response(), + Err(err) => RequestError::blank( + StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + "Database error", + err.to_string(), + ) + .into_http_response(), + } + } + ("reload", _, &Method::GET) => { + match self.core.reload().await { + Ok(result) => { + if !UrlParams::new(req.uri().query()).has_key("dry-run") { + // Update core + if let Some(core) = result.new_core { + self.shared_core.store(core.into()); + } + } - JsonResponse::new(json!({ - "data": (), - })) - .into_http_response() + JsonResponse::new(json!({ + "data": result.config, + })) + .into_http_response() + } + Err(err) => RequestError::blank( + StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + "Database error", + err.to_string(), + ) + .into_http_response(), + } } ("settings", Some("group"), &Method::GET) => { // List settings diff --git a/crates/jmap/src/lib.rs b/crates/jmap/src/lib.rs index 2d672224..b164dcf1 100644 --- a/crates/jmap/src/lib.rs +++ b/crates/jmap/src/lib.rs @@ -82,6 +82,7 @@ pub const LONG_SLUMBER: Duration = Duration::from_secs(60 * 60 * 24); #[derive(Clone)] pub struct JMAP { pub core: Arc, + pub shared_core: SharedCore, pub inner: Arc, pub smtp: SMTP, } @@ -571,6 +572,7 @@ impl JMAP { impl From for JMAP { fn from(value: JmapInstance) -> Self { + let shared_core = value.core.clone(); let core = value.core.load().clone(); JMAP { smtp: SMTP { @@ -578,6 +580,7 @@ impl From for JMAP { inner: value.smtp_inner, }, core, + shared_core, inner: value.jmap_inner, } } diff --git a/crates/managesieve/Cargo.toml b/crates/managesieve/Cargo.toml index 7d1ae127..ef840ff9 100644 --- a/crates/managesieve/Cargo.toml +++ b/crates/managesieve/Cargo.toml @@ -15,7 +15,7 @@ store = { path = "../store" } utils = { path = "../utils" } mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] } mail-send = { version = "0.4", default-features = false, features = ["cram-md5"] } -sieve-rs = { version = "0.4" } +sieve-rs = { version = "0.5" } rustls = "0.22" rustls-pemfile = "2.0" tokio = { version = "1.23", features = ["full"] } diff --git a/crates/nlp/src/bayes/cache.rs b/crates/nlp/src/bayes/cache.rs index 52cfcda4..798b5c62 100644 --- a/crates/nlp/src/bayes/cache.rs +++ b/crates/nlp/src/bayes/cache.rs @@ -116,3 +116,14 @@ impl Default for BayesTokenCache { } } } + +impl Clone for BayesTokenCache { + fn clone(&self) -> Self { + Self { + positive: Mutex::new(self.positive.lock().clone()), + negative: Mutex::new(self.negative.lock().clone()), + ttl_negative: self.ttl_negative, + ttl_positive: self.ttl_positive, + } + } +} diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index ad615e00..3fc4f663 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -22,7 +22,7 @@ mail-send = { version = "0.4", default-features = false, features = ["cram-md5"] mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] } mail-builder = { version = "0.3", features = ["ludicrous_mode"] } smtp-proto = { version = "0.1", features = ["serde_support"] } -sieve-rs = { version = "0.4" } +sieve-rs = { version = "0.5" } ahash = { version = "0.8" } rustls = "0.22" rustls-pemfile = "2.0" diff --git a/crates/store/src/backend/rocksdb/main.rs b/crates/store/src/backend/rocksdb/main.rs index 158affad..6900e69d 100644 --- a/crates/store/src/backend/rocksdb/main.rs +++ b/crates/store/src/backend/rocksdb/main.rs @@ -110,12 +110,13 @@ impl RocksDbStore { .ok()? .into(), worker_pool: rayon::ThreadPoolBuilder::new() - .num_threads( + .num_threads(std::cmp::max( config .property::((&prefix, "pool.workers")) .filter(|v| *v > 0) - .unwrap_or_else(|| num_cpus::get() * 4), - ) + .unwrap_or_else(num_cpus::get), + 4, + )) .build() .map_err(|err| { config.new_build_error( diff --git a/crates/store/src/backend/sqlite/main.rs b/crates/store/src/backend/sqlite/main.rs index a19366ee..02a462d0 100644 --- a/crates/store/src/backend/sqlite/main.rs +++ b/crates/store/src/backend/sqlite/main.rs @@ -61,12 +61,13 @@ impl SqliteStore { }) .ok()?, worker_pool: rayon::ThreadPoolBuilder::new() - .num_threads( + .num_threads(std::cmp::max( config .property::((&prefix, "pool.workers")) .filter(|v| *v > 0) .unwrap_or_else(num_cpus::get), - ) + 4, + )) .build() .map_err(|err| { config.new_build_error( diff --git a/crates/store/src/config.rs b/crates/store/src/config.rs index 7ff20b5a..e2a10b59 100644 --- a/crates/store/src/config.rs +++ b/crates/store/src/config.rs @@ -63,7 +63,14 @@ impl Stores { } pub async fn parse(config: &mut Config) -> Self { - let mut stores = Stores::default(); + let mut stores = Self::default(); + stores.parse_stores(config).await; + stores + } + + pub async fn parse_stores(&mut self, config: &mut Config) { + let is_reload = !self.stores.is_empty(); + for id in config .sub_keys("store", ".type") .map(|id| id.to_string()) @@ -95,86 +102,104 @@ impl Stores { match protocol.as_str() { #[cfg(feature = "rocks")] "rocksdb" => { + // Avoid opening the same store twice + if is_reload + && self + .stores + .values() + .any(|store| matches!(store, Store::RocksDb(_))) + { + continue; + } + if let Some(db) = RocksDbStore::open(config, prefix).await.map(Store::from) { - stores.stores.insert(store_id.clone(), db.clone()); - stores - .fts_stores - .insert(store_id.clone(), db.clone().into()); - stores.blob_stores.insert( + self.stores.insert(store_id.clone(), db.clone()); + self.fts_stores.insert(store_id.clone(), db.clone().into()); + self.blob_stores.insert( store_id.clone(), BlobStore::from(db.clone()).with_compression(compression_algo), ); - stores.lookup_stores.insert(store_id, db.into()); + self.lookup_stores.insert(store_id, db.into()); } } #[cfg(feature = "foundation")] "foundationdb" => { + // Avoid opening the same store twice + if is_reload + && self + .stores + .values() + .any(|store| matches!(store, Store::FoundationDb(_))) + { + continue; + } + if let Some(db) = FdbStore::open(config, prefix).await.map(Store::from) { - stores.stores.insert(store_id.clone(), db.clone()); - stores - .fts_stores - .insert(store_id.clone(), db.clone().into()); - stores.blob_stores.insert( + self.stores.insert(store_id.clone(), db.clone()); + self.fts_stores.insert(store_id.clone(), db.clone().into()); + self.blob_stores.insert( store_id.clone(), BlobStore::from(db.clone()).with_compression(compression_algo), ); - stores.lookup_stores.insert(store_id, db.into()); + self.lookup_stores.insert(store_id, db.into()); } } #[cfg(feature = "postgres")] "postgresql" => { if let Some(db) = PostgresStore::open(config, prefix).await.map(Store::from) { - stores.stores.insert(store_id.clone(), db.clone()); - stores - .fts_stores - .insert(store_id.clone(), db.clone().into()); - stores.blob_stores.insert( + self.stores.insert(store_id.clone(), db.clone()); + self.fts_stores.insert(store_id.clone(), db.clone().into()); + self.blob_stores.insert( store_id.clone(), BlobStore::from(db.clone()).with_compression(compression_algo), ); - stores.lookup_stores.insert(store_id.clone(), db.into()); + self.lookup_stores.insert(store_id.clone(), db.into()); } } #[cfg(feature = "mysql")] "mysql" => { if let Some(db) = MysqlStore::open(config, prefix).await.map(Store::from) { - stores.stores.insert(store_id.clone(), db.clone()); - stores - .fts_stores - .insert(store_id.clone(), db.clone().into()); - stores.blob_stores.insert( + self.stores.insert(store_id.clone(), db.clone()); + self.fts_stores.insert(store_id.clone(), db.clone().into()); + self.blob_stores.insert( store_id.clone(), BlobStore::from(db.clone()).with_compression(compression_algo), ); - stores.lookup_stores.insert(store_id.clone(), db.into()); + self.lookup_stores.insert(store_id.clone(), db.into()); } } #[cfg(feature = "sqlite")] "sqlite" => { + // Avoid opening the same store twice + if is_reload + && self + .stores + .values() + .any(|store| matches!(store, Store::SQLite(_))) + { + continue; + } + if let Some(db) = SqliteStore::open(config, prefix).map(Store::from) { - stores.stores.insert(store_id.clone(), db.clone()); - stores - .fts_stores - .insert(store_id.clone(), db.clone().into()); - stores.blob_stores.insert( + self.stores.insert(store_id.clone(), db.clone()); + self.fts_stores.insert(store_id.clone(), db.clone().into()); + self.blob_stores.insert( store_id.clone(), BlobStore::from(db.clone()).with_compression(compression_algo), ); - stores.lookup_stores.insert(store_id.clone(), db.into()); + self.lookup_stores.insert(store_id.clone(), db.into()); } } "fs" => { if let Some(db) = FsStore::open(config, prefix).await.map(BlobStore::from) { - stores - .blob_stores + self.blob_stores .insert(store_id, db.with_compression(compression_algo)); } } #[cfg(feature = "s3")] "s3" => { if let Some(db) = S3Store::open(config, prefix).await.map(BlobStore::from) { - stores - .blob_stores + self.blob_stores .insert(store_id, db.with_compression(compression_algo)); } } @@ -184,7 +209,7 @@ impl Stores { .await .map(FtsStore::from) { - stores.fts_stores.insert(store_id, db); + self.fts_stores.insert(store_id, db); } } #[cfg(feature = "redis")] @@ -193,7 +218,7 @@ impl Stores { .await .map(LookupStore::from) { - stores.lookup_stores.insert(store_id, db); + self.lookup_stores.insert(store_id, db); } } unknown => { @@ -201,8 +226,6 @@ impl Stores { } } } - - stores } pub async fn parse_lookups(&mut self, config: &mut Config) { diff --git a/crates/store/src/dispatch/blob.rs b/crates/store/src/dispatch/blob.rs index b17ea754..06d94121 100644 --- a/crates/store/src/dispatch/blob.rs +++ b/crates/store/src/dispatch/blob.rs @@ -166,19 +166,12 @@ impl CompressionAlgo { } impl ParseValue for CompressionAlgo { - fn parse_value( - key: impl utils::config::utils::AsKey, - value: &str, - ) -> utils::config::Result { + fn parse_value(value: &str) -> utils::config::Result { match value { "lz4" => Ok(CompressionAlgo::Lz4), //"zstd" => Ok(CompressionAlgo::Zstd), "none" | "false" | "disable" | "disabled" => Ok(CompressionAlgo::None), - algo => Err(format!( - "Invalid compression algorithm: {} for key {}", - algo, - key.as_key() - )), + algo => Err(format!("Invalid compression algorithm: {algo}",)), } } } diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index db0c9abb..aa98efaa 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -23,7 +23,7 @@ webpki-roots = { version = "0.26"} ring = { version = "0.17" } base64 = "0.21" serde_json = "1.0" -rcgen = "0.12" +rcgen = "0.13" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} x509-parser = "0.16.0" pem = "3.0" diff --git a/crates/utils/src/config/cron.rs b/crates/utils/src/config/cron.rs index abd09d96..48f2df63 100644 --- a/crates/utils/src/config/cron.rs +++ b/crates/utils/src/config/cron.rs @@ -81,52 +81,53 @@ impl SimpleCron { } impl ParseValue for SimpleCron { - fn parse_value(key: impl super::utils::AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { let mut hour = 0; let mut minute = 0; - let key = key.as_key(); for (pos, value) in value.split(' ').enumerate() { if pos == 0 { - minute = value.parse::().map_err(|_| { - format!("Invalid cron key {key:?}: failed to parse cron minute") - })?; + minute = value + .parse::() + .map_err(|_| "Invalid cron key: failed to parse cron minute".to_string())?; if !(0..=59).contains(&minute) { return Err(format!( - "Invalid cron key {key:?}: failed to parse minute, invalid value: {minute}" + "Invalid cron key: failed to parse minute, invalid value: {minute}" )); } } else if pos == 1 { if value .as_bytes() .first() - .ok_or_else(|| format!("Invalid cron key {key:?}: failed to parse cron hour"))? + .ok_or_else(|| "Invalid cron key: failed to parse cron hour".to_string())? == &b'*' { return Ok(SimpleCron::Hour { minute }); } else { - hour = value.parse::().map_err(|_| { - format!("Invalid cron key {key:?}: failed to parse cron hour") - })?; + hour = value + .parse::() + .map_err(|_| "Invalid cron key: failed to parse cron hour".to_string())?; if !(0..=23).contains(&hour) { return Err(format!( - "Invalid cron key {key:?}: failed to parse hour, invalid value: {hour}" + "Invalid cron key: failed to parse hour, invalid value: {hour}" )); } } } else if pos == 2 { - if value.as_bytes().first().ok_or_else(|| { - format!("Invalid cron key {key:?}: failed to parse cron weekday") - })? == &b'*' + if value + .as_bytes() + .first() + .ok_or_else(|| "Invalid cron key: failed to parse cron weekday".to_string())? + == &b'*' { return Ok(SimpleCron::Day { hour, minute }); } else { let day = value.parse::().map_err(|_| { - format!("Invalid cron key {key:?}: failed to parse cron weekday") + "Invalid cron key: failed to parse cron weekday".to_string() })?; if !(1..=7).contains(&hour) { return Err(format!( - "Invalid cron key {key:?}: failed to parse weekday, invalid value: {}, range is 1 (Monday) to 7 (Sunday).", + "Invalid cron key: failed to parse weekday, invalid value: {}, range is 1 (Monday) to 7 (Sunday).", hour, )); } @@ -136,7 +137,7 @@ impl ParseValue for SimpleCron { } } - Err(format!("Invalid cron key {key:?}: parse cron expression.")) + Err("Invalid cron key: parse cron expression.".to_string()) } } diff --git a/crates/utils/src/config/ipmask.rs b/crates/utils/src/config/ipmask.rs index 4afaa97f..fc524598 100644 --- a/crates/utils/src/config/ipmask.rs +++ b/crates/utils/src/config/ipmask.rs @@ -25,7 +25,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use rustls::{crypto::ring::cipher_suite::*, SupportedCipherSuite}; -use super::utils::{AsKey, ParseKey, ParseValue}; +use super::utils::ParseValue; #[derive(Debug, Clone, PartialEq, Eq)] pub enum IpAddrMask { @@ -91,7 +91,7 @@ impl IpAddrMask { } impl ParseValue for IpAddrMask { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { if let Some((addr, mask)) = value.rsplit_once('/') { if let (Ok(addr), Ok(mask)) = (addr.trim().parse::(), mask.trim().parse::()) @@ -130,38 +130,30 @@ impl ParseValue for IpAddrMask { } } - Err(format!( - "Invalid IP address {:?} for property {:?}.", - value, - key.as_key() - )) + Err(format!("Invalid IP address {:?}", value,)) } } impl ParseValue for IpAddrOrMask { - fn parse_value(key: impl AsKey, ip: &str) -> super::Result { + fn parse_value(ip: &str) -> super::Result { if ip.contains('/') { - ip.parse_key(key).map(IpAddrOrMask::Mask) + IpAddrMask::parse_value(ip).map(IpAddrOrMask::Mask) } else { - ip.parse_key(key).map(IpAddrOrMask::Ip) + IpAddr::parse_value(ip).map(IpAddrOrMask::Ip) } } } impl ParseValue for SocketAddr { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid socket address {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid socket address {:?}.", value,)) } } impl ParseValue for SupportedCipherSuite { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { Ok(match value { // TLS1.3 suites "TLS13_AES_256_GCM_SHA384" => TLS13_AES_256_GCM_SHA384, @@ -178,13 +170,7 @@ impl ParseValue for SupportedCipherSuite { "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => { TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 } - cipher => { - return Err(format!( - "Unsupported TLS cipher suite {:?} found in key {:?}", - cipher, - key.as_key() - )) - } + cipher => return Err(format!("Unsupported TLS cipher suite {:?}", cipher,)), }) } } @@ -200,7 +186,7 @@ mod tests { ("10.0.0.0/8", "10.0.13.73"), ("192.168.1.1", "192.168.1.1"), ] { - let mask = IpAddrMask::parse_value("test", mask).unwrap(); + let mask = IpAddrMask::parse_value(mask).unwrap(); let ip = ip.parse::().unwrap(); assert!(mask.matches(&ip)); } @@ -209,7 +195,7 @@ mod tests { ("10.0.0.0/8", "11.30.20.11"), ("192.168.1.1", "193.168.1.1"), ] { - let mask = IpAddrMask::parse_value("test", mask).unwrap(); + let mask = IpAddrMask::parse_value(mask).unwrap(); let ip = ip.parse::().unwrap(); assert!(!mask.matches(&ip)); } diff --git a/crates/utils/src/config/mod.rs b/crates/utils/src/config/mod.rs index db687e17..283e8154 100644 --- a/crates/utils/src/config/mod.rs +++ b/crates/utils/src/config/mod.rs @@ -29,15 +29,25 @@ pub mod utils; use std::{collections::BTreeMap, time::Duration}; use ahash::AHashMap; +use serde::Serialize; -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] pub struct Config { + #[serde(skip)] pub keys: BTreeMap, - pub missing: AHashMap>, + pub warnings: AHashMap, pub errors: AHashMap, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(tag = "type")] +pub enum ConfigWarning { + Missing, + AppliedDefault(String), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(tag = "type")] pub enum ConfigError { Parse(String), Build(String), @@ -184,12 +194,12 @@ impl Config { } pub fn log_warnings(&self, use_stderr: bool) { - for (key, value) in &self.missing { - let message = match value { - Some(replaced) => { - format!("WARNING: Missing setting {key:?}, applied default {replaced:?}") + for (key, warn) in &self.warnings { + let message = match warn { + ConfigWarning::AppliedDefault(default) => { + format!("WARNING: Missing setting {key:?}, applied default {default:?}") } - None => { + ConfigWarning::Missing => { format!("WARNING: Missing setting {key:?}") } }; diff --git a/crates/utils/src/config/utils.rs b/crates/utils/src/config/utils.rs index 3048ad35..bd850f2a 100644 --- a/crates/utils/src/config/utils.rs +++ b/crates/utils/src/config/utils.rs @@ -34,13 +34,13 @@ use mail_auth::{ }; use smtp_proto::MtPriority; -use super::{Config, ConfigError, Rate}; +use super::{Config, ConfigError, ConfigWarning, Rate}; impl Config { pub fn property(&mut self, key: impl AsKey) -> Option { let key = key.as_key(); if let Some(value) = self.keys.get(&key) { - match T::parse_value(key.as_str(), value) { + match T::parse_value(value) { Ok(value) => Some(value), Err(err) => { self.new_parse_error(key, err); @@ -61,11 +61,14 @@ impl Config { let value = match self.keys.get(&key) { Some(value) => value.as_str(), None => { - self.missing.insert(key.clone(), default.to_string().into()); + self.warnings.insert( + key.clone(), + ConfigWarning::AppliedDefault(default.to_string()), + ); default } }; - match T::parse_value(key.as_str(), value) { + match T::parse_value(value) { Ok(value) => Some(value), Err(err) => { self.new_parse_error(key, err); @@ -83,12 +86,13 @@ impl Config { let value = match self.value_or_else(key.as_str(), default.clone()) { Some(value) => value, None => { - self.missing.insert(default.as_key(), None); + self.warnings + .insert(default.as_key(), ConfigWarning::Missing); return None; } }; - match T::parse_value(key.as_str(), value) { + match T::parse_value(value) { Ok(value) => Some(value), Err(err) => { self.new_parse_error(key, err); @@ -100,7 +104,7 @@ impl Config { pub fn property_require(&mut self, key: impl AsKey) -> Option { let key = key.as_key(); if let Some(value) = self.keys.get(&key) { - match T::parse_value(key.as_str(), value) { + match T::parse_value(value) { Ok(value) => Some(value), Err(err) => { self.new_parse_error(key, err); @@ -169,7 +173,7 @@ impl Config { for (key, value) in &self.keys { if key.starts_with(&prefix) || key == &full_prefix { - match T::parse_value(key.as_str(), value) { + match T::parse_value(value) { Ok(value) => { results.push((key.to_string(), value)); } @@ -203,7 +207,7 @@ impl Config { } pub fn try_parse_value(&mut self, key: impl AsKey, value: &str) -> Option { - match T::parse_value(key.clone(), value) { + match T::parse_value(value) { Ok(value) => Some(value), Err(err) => { self.errors.insert(key.as_key(), ConfigError::Parse(err)); @@ -261,7 +265,7 @@ impl Config { match self.keys.get(&key) { Some(value) => Some(value.as_str()), None => { - self.missing.insert(key, None); + self.warnings.insert(key, ConfigWarning::Missing); None } } @@ -278,38 +282,16 @@ impl Config { } pub fn new_missing_property(&mut self, key: impl AsKey) { - self.missing.insert(key.as_key(), None); + self.warnings.insert(key.as_key(), ConfigWarning::Missing); } } pub trait ParseValue: Sized { - fn parse_value(key: impl AsKey, value: &str) -> super::Result; -} - -pub trait ParseKey { - fn parse_key(&self, key: impl AsKey) -> super::Result; -} - -impl ParseKey for &str { - fn parse_key(&self, key: impl AsKey) -> super::Result { - T::parse_value(key, self) - } -} - -impl ParseKey for String { - fn parse_key(&self, key: impl AsKey) -> super::Result { - T::parse_value(key, self.as_str()) - } -} - -impl ParseKey for &String { - fn parse_key(&self, key: impl AsKey) -> super::Result { - T::parse_value(key, self.as_str()) - } + fn parse_value(value: &str) -> super::Result; } impl ParseValue for Option { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { if !value.is_empty() && !value.eq_ignore_ascii_case("false") && !value.eq_ignore_ascii_case("disable") @@ -317,7 +299,7 @@ impl ParseValue for Option { && !value.eq_ignore_ascii_case("never") && !value.eq("0") { - T::parse_value(key, value).map(Some) + T::parse_value(value).map(Some) } else { Ok(None) } @@ -325,137 +307,101 @@ impl ParseValue for Option { } impl ParseValue for String { - fn parse_value(_key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { Ok(value.to_string()) } } impl ParseValue for u64 { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid integer value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid integer value {:?}.", value,)) } } impl ParseValue for f64 { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid floating point value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid floating point value {:?}.", value)) } } impl ParseValue for u16 { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid integer value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid integer value {:?}.", value)) } } impl ParseValue for i16 { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid integer value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid integer value {:?}.", value)) } } impl ParseValue for u32 { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid integer value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid integer value {:?}.", value)) } } impl ParseValue for i32 { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid integer value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid integer value {:?}.", value)) } } impl ParseValue for IpAddr { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid IP address value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid IP address value {:?}.", value)) } } impl ParseValue for usize { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid integer value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid integer value {:?}.", value)) } } impl ParseValue for bool { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { - value.parse().map_err(|_| { - format!( - "Invalid boolean value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + fn parse_value(value: &str) -> super::Result { + value + .parse() + .map_err(|_| format!("Invalid boolean value {:?}.", value)) } } impl ParseValue for Ipv4Addr { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { value .parse() - .map_err(|_| format!("Invalid IPv4 value {:?} for key {:?}.", value, key.as_key())) + .map_err(|_| format!("Invalid IPv4 value {:?}.", value)) } } impl ParseValue for Ipv6Addr { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { value .parse() - .map_err(|_| format!("Invalid IPv6 value {:?} for key {:?}.", value, key.as_key())) + .map_err(|_| format!("Invalid IPv6 value {:?}.", value)) } } impl ParseValue for PathBuf { - fn parse_value(_key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { let path = PathBuf::from(value); if path.exists() { @@ -467,84 +413,62 @@ impl ParseValue for PathBuf { } impl ParseValue for MtPriority { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { match value.to_ascii_lowercase().as_str() { "mixer" => Ok(MtPriority::Mixer), "stanag4406" => Ok(MtPriority::Stanag4406), "nsep" => Ok(MtPriority::Nsep), - _ => Err(format!( - "Invalid priority value {:?} for property {:?}.", - value, - key.as_key() - )), + _ => Err(format!("Invalid priority value {:?}.", value)), } } } impl ParseValue for Canonicalization { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { match value { "relaxed" => Ok(Canonicalization::Relaxed), "simple" => Ok(Canonicalization::Simple), - _ => Err(format!( - "Invalid canonicalization value {:?} for key {:?}.", - value, - key.as_key() - )), + _ => Err(format!("Invalid canonicalization value {:?}.", value)), } } } impl ParseValue for IpLookupStrategy { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { Ok(match value.to_lowercase().as_str() { "ipv4_only" => IpLookupStrategy::Ipv4Only, "ipv6_only" => IpLookupStrategy::Ipv6Only, //"ipv4_and_ipv6" => IpLookupStrategy::Ipv4AndIpv6, "ipv6_then_ipv4" => IpLookupStrategy::Ipv6thenIpv4, "ipv4_then_ipv6" => IpLookupStrategy::Ipv4thenIpv6, - _ => { - return Err(format!( - "Invalid IP lookup strategy {:?} for property {:?}.", - value, - key.as_key() - )) - } + _ => return Err(format!("Invalid IP lookup strategy {:?}.", value)), }) } } impl ParseValue for Algorithm { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { match value { "ed25519-sha256" | "ed25519-sha-256" => Ok(Algorithm::Ed25519Sha256), "rsa-sha-256" | "rsa-sha256" => Ok(Algorithm::RsaSha256), "rsa-sha-1" | "rsa-sha1" => Ok(Algorithm::RsaSha1), - _ => Err(format!( - "Invalid algorithm {:?} for key {:?}.", - value, - key.as_key() - )), + _ => Err(format!("Invalid algorithm {:?}.", value)), } } } impl ParseValue for HashAlgorithm { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { match value { "sha256" | "sha-256" => Ok(HashAlgorithm::Sha256), "sha-1" | "sha1" => Ok(HashAlgorithm::Sha1), - _ => Err(format!( - "Invalid hash algorithm {:?} for key {:?}.", - value, - key.as_key() - )), + _ => Err(format!("Invalid hash algorithm {:?}.", value)), } } } impl ParseValue for Duration { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { let mut digits = String::new(); let mut multiplier = String::new(); @@ -562,13 +486,7 @@ impl ParseValue for Duration { "m" => 60 * 1000, "s" => 1000, "ms" | "" => 1, - _ => { - return Err(format!( - "Invalid duration value {:?} for property {:?}.", - value, - key.as_key() - )) - } + _ => return Err(format!("Invalid duration value {:?}.", value)), }; digits @@ -581,18 +499,12 @@ impl ParseValue for Duration { None } }) - .ok_or_else(|| { - format!( - "Invalid duration value {:?} for property {:?}.", - value, - key.as_key() - ) - }) + .ok_or_else(|| format!("Invalid duration value {:?}.", value)) } } impl ParseValue for Rate { - fn parse_value(key: impl AsKey, value: &str) -> super::Result { + fn parse_value(value: &str) -> super::Result { if let Some((requests, period)) = value.split_once('/') { Ok(Rate { requests: requests @@ -600,23 +512,13 @@ impl ParseValue for Rate { .parse::() .ok() .and_then(|r| if r > 0 { Some(r) } else { None }) - .ok_or_else(|| { - format!( - "Invalid rate value {:?} for property {:?}.", - value, - key.as_key() - ) - })?, - period: std::cmp::max(period.parse_key(key)?, Duration::from_secs(1)), + .ok_or_else(|| format!("Invalid rate value {:?}.", value))?, + period: std::cmp::max(Duration::parse_value(period)?, Duration::from_secs(1)), }) } else if ["false", "none", "unlimited"].contains(&value) { Ok(Rate::default()) } else { - Err(format!( - "Invalid rate value {:?} for property {:?}.", - value, - key.as_key() - )) + Err(format!("Invalid rate value {:?}.", value)) } } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index b76bbb58..4fe524cf 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -31,7 +31,7 @@ managesieve = { path = "../crates/managesieve", features = ["test_mode"] } smtp-proto = { version = "0.1" } mail-send = { version = "0.4", default-features = false, features = ["cram-md5"] } mail-auth = { version = "0.3", features = ["test"] } -sieve-rs = { version = "0.4" } +sieve-rs = { version = "0.5" } utils = { path = "../crates/utils", features = ["test_mode"] } jmap-client = { version = "0.3", features = ["websockets", "debug", "async"] } mail-parser = { version = "0.9", features = ["full_encoding", "serde_support", "ludicrous_mode"] }