diff --git a/Cargo.lock b/Cargo.lock index 2c5796e..93fb039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,15 +78,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -120,18 +120,18 @@ checksum = "ddf3728566eefa873833159754f5732fb0951d3649e6e5b891cc70d56dd41673" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -164,7 +164,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags", + "bitflags 2.13.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -172,8 +172,8 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", - "syn", + "shlex 1.3.0", + "syn 2.0.119", ] [[package]] @@ -208,9 +208,15 @@ checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" [[package]] name = "bitflags" -version = "2.11.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake2" @@ -232,11 +238,11 @@ dependencies = [ [[package]] name = "buffered-reader" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db26bf1f092fd5e05b5ab3be2f290915aeb6f3f20c4e9f86ce0f07f336c2412f" +checksum = "c7531a07caf83a3fa3b47a233b437ee7910cccfb688555d224338295a0a64fcc" dependencies = [ - "bzip2 0.5.2", + "bzip2", "flate2", "libc", ] @@ -249,18 +255,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "bzip2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" -dependencies = [ - "bzip2-sys", -] +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bzip2" @@ -271,21 +268,11 @@ dependencies = [ "libbz2-rs-sys", ] -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "capnp" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfd2485d4b36ac9c5aa6572d7d35daa63a5b34f517627d6c34d068e616e4a73" +checksum = "4777a3bc19b8f8e5fb2d0196c6b5dfcbbcc8b2fe08ae57f873f2f35f15cfc210" dependencies = [ "embedded-io", ] @@ -323,12 +310,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.62" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -337,7 +324,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -348,9 +335,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -372,9 +359,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ "clap_builder", "clap_derive", @@ -382,9 +369,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstream", "anstyle", @@ -394,23 +381,23 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.5" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" +checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -484,18 +471,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -503,27 +490,27 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" @@ -566,6 +553,37 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.19", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -584,7 +602,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -649,13 +667,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -685,6 +703,22 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "email-encoding" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" +dependencies = [ + "base64", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" + [[package]] name = "embedded-io" version = "0.7.1" @@ -724,14 +758,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -739,9 +773,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -780,9 +814,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fd-lock" @@ -871,9 +905,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -886,9 +920,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -896,15 +930,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -913,38 +947,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -969,9 +1003,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.4.1" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab9e9188e97a93276e1fe7b56401b851e2b45a46d045ca658100c1303ada649" +checksum = "ab4e5aa225bc56696909483320f0ff9b600f1a971b52e07a17d70f3d9b43254b" dependencies = [ "rustversion", "typenum", @@ -1014,28 +1048,26 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", ] [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", @@ -1076,9 +1108,9 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ "hashbrown 0.16.1", ] @@ -1108,7 +1140,7 @@ dependencies = [ "hickory-proto", "once_cell", "radix_trie", - "rand 0.8.6", + "rand 0.8.7", "thiserror 1.0.69", "tokio", "tracing", @@ -1131,7 +1163,7 @@ dependencies = [ "ipnet", "once_cell", "openssl", - "rand 0.8.6", + "rand 0.8.7", "thiserror 1.0.69", "tinyvec", "tokio", @@ -1152,7 +1184,7 @@ dependencies = [ "lru-cache", "once_cell", "parking_lot", - "rand 0.8.6", + "rand 0.8.7", "resolv-conf", "smallvec", "thiserror 1.0.69", @@ -1161,10 +1193,21 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.4.1" +name = "hostname" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.2.1", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1172,9 +1215,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1182,9 +1225,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -1200,10 +1243,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "humantime" -version = "2.3.0" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" [[package]] name = "husk" @@ -1212,11 +1261,13 @@ dependencies = [ "aho-corasick", "anyhow", "bytes", + "chrono", "clap", "clap_complete", "daemonix", "fastrand", "indymilter", + "lettre", "log", "log4rs", "sequoia-cert-store", @@ -1229,16 +1280,16 @@ dependencies = [ "serde_derive", "sysinfo", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "toml", ] [[package]] name = "hyper" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -1417,12 +1468,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -1452,8 +1497,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", - "serde", - "serde_core", ] [[package]] @@ -1463,7 +1506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d59de8ca0453c550ba5960c3e646c197f7bbbca553212ab89bf7eeeb0298e40f" dependencies = [ "async-trait", - "bitflags", + "bitflags 2.13.1", "bytes", "tokio", "tracing", @@ -1520,10 +1563,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "e184d09547b80eb7e20d141ba2fb1fbac843ca53f4cf1b31210adc4c1adc6e16" dependencies = [ + "defmt", + "jiff-core", "jiff-static", "log", "portable-atomic", @@ -1532,25 +1577,34 @@ dependencies = [ ] [[package]] -name = "jiff-static" -version = "0.2.28" +name = "jiff-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323da076b7a6faf914dc677cb05a4b907742ff7375c8322c9e7f5061e5e0e9de" +dependencies = [ + "jiff-core", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1625,10 +1679,28 @@ dependencies = [ ] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "lettre" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349" +dependencies = [ + "base64", + "email-encoding", + "email_address", + "fastrand", + "futures-util", + "hostname", + "httpdate", + "idna", + "mime", + "native-tls", + "nom 8.0.0", + "percent-encoding", + "quoted_printable", + "socket2", + "tokio", + "url", +] [[package]] name = "libbz2-rs-sys" @@ -1638,9 +1710,9 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -1654,9 +1726,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "libc", ] @@ -1701,9 +1773,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" dependencies = [ "serde_core", ] @@ -1731,12 +1803,12 @@ dependencies = [ "log-mdc", "mock_instant", "parking_lot", - "rand 0.9.4", + "rand 0.9.5", "serde", "serde-value", "serde_json", "serde_yaml", - "thiserror 2.0.18", + "thiserror 2.0.19", "thread-id", "typemap-ors", "unicode-segmentation", @@ -1754,9 +1826,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memsec" @@ -1788,9 +1860,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -1799,9 +1871,9 @@ dependencies = [ [[package]] name = "mock_instant" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" +checksum = "9bb517913cfcfb9eeda59f36020269075a152701a01606c612f547e4890be399" [[package]] name = "native-tls" @@ -1826,10 +1898,10 @@ version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2578a3627c28fefb60f1680e20e85f38bd8c8bf98c288b370489a573b0640907" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "libc", "nettle-sys", - "thiserror 2.0.18", + "thiserror 2.0.19", "typenum", ] @@ -1872,6 +1944,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -1906,7 +1987,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags", + "bitflags 2.13.1", ] [[package]] @@ -1944,17 +2025,17 @@ dependencies = [ "sha1collisiondetection", "sha2", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", ] [[package]] name = "openssl" -version = "0.10.80" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags", + "bitflags 2.13.1", "cfg-if", "foreign-types", "libc", @@ -1970,7 +2051,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1981,9 +2062,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -2105,9 +2186,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "portable-atomic-util" @@ -2142,34 +2223,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted_printable" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" + [[package]] name = "r-efi" version = "5.3.0" @@ -2194,9 +2271,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2205,9 +2282,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2277,7 +2354,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.1", ] [[package]] @@ -2299,14 +2376,14 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2316,9 +2393,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -2327,9 +2404,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -2398,7 +2475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -2407,7 +2484,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" dependencies = [ - "bitflags", + "bitflags 2.13.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -2418,9 +2495,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -2437,7 +2514,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", @@ -2446,9 +2523,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "once_cell", "rustls-pki-types", @@ -2459,9 +2536,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "zeroize", ] @@ -2479,9 +2556,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -2519,7 +2596,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2545,8 +2622,7 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "sequoia-cert-store" version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e07e420427eb5e0dc3bb61172d181fa3c87afb2a77e8c629e920f54a291a6d" +source = "git+https://gitlab.com/sequoia-pgp/sequoia-cert-store?branch=malte%2Fassert_base_dir#dc8a2fc6258116068d51618e3b6633c54312bd84" dependencies = [ "anyhow", "crossbeam", @@ -2559,7 +2635,7 @@ dependencies = [ "sequoia-net", "sequoia-openpgp", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "url", ] @@ -2593,7 +2669,7 @@ dependencies = [ "sequoia-openpgp", "stfu8", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", ] @@ -2616,7 +2692,7 @@ dependencies = [ "sequoia-openpgp", "socket2", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tokio-util", "winapi", @@ -2625,7 +2701,7 @@ dependencies = [ [[package]] name = "sequoia-keystore" version = "0.7.2" -source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7" +source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#a71066fb8d8a3c09bfcd4acc559666cf40978c26" dependencies = [ "anyhow", "async-generic", @@ -2641,7 +2717,7 @@ dependencies = [ "sequoia-keystore-gpg-agent", "sequoia-keystore-softkeys", "sequoia-openpgp", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tokio-util", ] @@ -2649,7 +2725,7 @@ dependencies = [ [[package]] name = "sequoia-keystore-backend" version = "0.7.0" -source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7" +source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#a71066fb8d8a3c09bfcd4acc559666cf40978c26" dependencies = [ "anyhow", "async-trait", @@ -2658,14 +2734,14 @@ dependencies = [ "log", "sequoia-openpgp", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", ] [[package]] name = "sequoia-keystore-gpg-agent" version = "0.5.0" -source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7" +source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#a71066fb8d8a3c09bfcd4acc559666cf40978c26" dependencies = [ "anyhow", "async-trait", @@ -2682,7 +2758,7 @@ dependencies = [ [[package]] name = "sequoia-keystore-softkeys" version = "0.7.1" -source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7" +source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#a71066fb8d8a3c09bfcd4acc559666cf40978c26" dependencies = [ "anyhow", "async-trait", @@ -2711,7 +2787,7 @@ dependencies = [ "percent-encoding", "reqwest", "sequoia-openpgp", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "url", "z-base-32", @@ -2719,15 +2795,15 @@ dependencies = [ [[package]] name = "sequoia-openpgp" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c847f0f148cf238c3aec88d092fd3c4301c21e906829ea9e415ea7531f7ec094" +checksum = "0fbc8f9818a6fad141d85993777ba298ee52c80a09bd23d45dda461a6b7c83cb" dependencies = [ "anyhow", "argon2", "base64", "buffered-reader", - "bzip2 0.6.1", + "bzip2", "chrono", "dyn-clone", "flate2", @@ -2741,15 +2817,15 @@ dependencies = [ "regex", "regex-syntax", "sha1collisiondetection", - "thiserror 2.0.18", + "thiserror 2.0.19", "xxhash-rust", ] [[package]] name = "sequoia-wot" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a63dfb4727485747f504ec23bfe77e44b02724612f5b90ddf111acabd3c11e49" +checksum = "cc825dbf5b5d5d20b16a01e39319a0e4d6368ea8b6ea7e88d12d2bd30532b011" dependencies = [ "anyhow", "chrono", @@ -2757,14 +2833,14 @@ dependencies = [ "num_cpus", "sequoia-cert-store", "sequoia-openpgp", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2782,29 +2858,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -2854,7 +2930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "digest", - "generic-array 1.4.1", + "generic-array 1.4.4", ] [[package]] @@ -2884,6 +2960,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -2896,9 +2978,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "siphasher" @@ -2914,15 +2996,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2990,9 +3072,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -3016,7 +3109,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3039,7 +3132,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3061,7 +3154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3087,11 +3180,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -3102,18 +3195,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -3138,9 +3231,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -3153,9 +3246,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -3169,13 +3262,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3200,14 +3293,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -3242,14 +3336,14 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tower" @@ -3272,7 +3366,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.1", "bytes", "futures-util", "http", @@ -3315,7 +3409,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3344,9 +3438,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -3356,9 +3450,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -3450,27 +3544,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3481,9 +3566,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3491,9 +3576,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3501,65 +3586,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -3669,7 +3720,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3680,7 +3731,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3926,18 +3977,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "wit-bindgen" @@ -3945,85 +3987,6 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -4032,15 +3995,15 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xxhash-rust" -version = "0.8.15" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4055,34 +4018,34 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "z-base-32" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bf7b4a78668416e1e8a332334e26fb2f377afe707f0c6feaf6ed5f9100133b" +checksum = "44f94003b56f8f812c88dd188d9bafc71d8e3786fd04812c2d8cdb176810ceb4" [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4102,15 +4065,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" @@ -4142,11 +4105,11 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 1838236..09748ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,17 +14,18 @@ rust-version = "1.85" aho-corasick = "1.1.4" anyhow = "1.0.100" bytes = "1.11.1" +chrono = "0.4.45" clap = { version = "4", features = ["derive"] } fastrand = "2.4.1" indymilter = "0.3.0" +lettre = "0.11.22" log = "0.4.30" log4rs = "1.3.0" sequoia-cert-store = "0.7.3" sequoia-directories = "0.1.0" -#sequoia-keystore = "0.7.2" -sequoia-keystore = { git = "https://gitlab.com/sequoia-pgp/sequoia-keystore", branch = "malte/softkey-refresh" } +sequoia-keystore = "0.7.2" sequoia-net = "0.30.1" -sequoia-openpgp = "2.3.0" +sequoia-openpgp = "2.4.0" sequoia-wot = "0.15.0" serde = "1.0.228" serde_derive = "1.0.228" @@ -41,3 +42,11 @@ daemonix = "0.1.0" anyhow = "1.0.100" clap = { version = "4", features = ["derive"] } clap_complete = "4" + +[profile.release] +lto = true +codegen-units = 1 + +[patch.crates-io] +sequoia-cert-store = { git = "https://gitlab.com/sequoia-pgp/sequoia-cert-store", branch = "malte/assert_base_dir" } +sequoia-keystore = { git = "https://gitlab.com/sequoia-pgp/sequoia-keystore", branch = "malte/softkey-refresh" } diff --git a/e2e-tests/common.sh b/e2e-tests/common.sh index e97d84c..04a9d62 100644 --- a/e2e-tests/common.sh +++ b/e2e-tests/common.sh @@ -12,13 +12,14 @@ delete_unix_user() { # name # send a mail and wait for delivery send_test_mail() { # email_address + echo "send test mail to $1" >&2 USER=$(echo $1 | cut -f 1 -d '@') # create an empty mailbox file touch /var/mail/$USER chown $USER:mail /var/mail/$USER chmod 600 /var/mail/$USER - echo -e "Subject: testmail\n\ntest mail" | sendmail $1 + echo -e "To: $1\nSubject: testmail\n\ntest mail" | sendmail -t inotifywait -qq -t 3 -e close_write /var/mail/$USER } @@ -27,10 +28,10 @@ test_header() { # text echo "##############################################" echo $1 echo "Workdir: $WORKDIR" - echo "##############################################" + echo "----------------------------------------------" } -# Generate a key outside the cert store. The key will not be +# Generate a key outside the key store. The key will not be # authenticated. gen_key() { # email FILE=$(echo $1 | cut -f 1 -d '@') @@ -41,31 +42,60 @@ gen_key() { # email echo $FPR } -expect_mail() { # unix_user mode +# Generate a key outside the key store. The key will not be +# authenticated. Import the certificate of the key. +gen_cert() { # email + FILE=$(echo $1 | cut -f 1 -d '@') + FPR=$(sq key generate --home none --shared-key --without-password \ + --email $1 \ + --output $WORKDIR/${FILE}_sk.pgp \ + --rev-cert $WORKDIR/${FILE}.rev |& grep "Fingerprint:" | cut -d ':' -f 2) + sq cert import $WORKDIR/${FILE}_sk.pgp > /dev/null 2>&1 + echo $FPR +} + +authenticate() { # Fingerprint + sq pki link add --cert $1 --all > /dev/null 2>&1 +} + +expect_mail() { # unix_user mode [signature_fpr] if [ -f /var/mail/$1 ] ; then case $2 in encrypted) if grep -q "BEGIN PGP MESSAGE" /var/mail/$1 ; then + echo "GOOD: found expected encrypted mail" >&2 R=0 + if [ "$3" != "" ] ; then + FOUND_FPR=$(sq packet dump /var/mail/$1 | grep "Issuer Fingerprint:" | cut -d ':' -f 2 | tr -d ' ') + + if [ "$3" != "$FOUND_FPR" ] ; then + echo "BAD: Found wrong signature '$FOUND_FPR' - expected '$3'" >&2 + R=1 + else + echo "GOOD: found right signature" >&2 + fi + fi else - echo "expected encrypted mail for $1" >&2 + echo "BAD: encryped mail expected but not found" >&2 R=1 fi ;; unencrypted) if grep -q "BEGIN PGP MESSAGE" /var/mail/$1 ; then - echo "expected unencrypted mail for $1" >&2 + echo "BAD: expected unencrypted mail for $1" >&2 R=1 else + echo "GOOD: found expected unencrypted mail" >&2 R=0 fi ;; delivered) + echo "GOOD: mail delivery worked" >&2 R=0 ;; esac else - echo "no mail for $1" >&2 + echo "BAD: no mail for $1" >&2 R=1 fi echo $R diff --git a/e2e-tests/tests/40_introducer_create.sh b/e2e-tests/tests/40_introducer_create.sh index 77234bf..fc0cf88 100644 --- a/e2e-tests/tests/40_introducer_create.sh +++ b/e2e-tests/tests/40_introducer_create.sh @@ -14,12 +14,11 @@ $HUSK_BIN introducer list --config config/config.toml create_unix_user fred FRED_FPR=$(sq key generate --shared-key --without-password --name "Fred" --email "fred@example.org" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2) sleep 1 -sq pki link retract --cert $FRED_FPR --all +sq pki link retract --cert $FRED_FPR --all > /dev/null 2>&1 -sq pki vouch add --certifier $ELLEN_FPR --cert $FRED_FPR --email "fred@example.org" +sq pki vouch add --certifier $ELLEN_FPR --cert $FRED_FPR --email "fred@example.org" > /dev/null 2>&1 send_test_mail fred@example.org - RESULT_CODE=$(expect_mail fred encrypted) if [ "$RESULT_CODE" == "1" ] ; then diff --git a/e2e-tests/tests/51_add_locals.sh b/e2e-tests/tests/51_add_locals.sh index 738f982..6e36a8a 100644 --- a/e2e-tests/tests/51_add_locals.sh +++ b/e2e-tests/tests/51_add_locals.sh @@ -5,7 +5,7 @@ test_header "Add locals" -sq key generate --shared-key --without-password --email 'hubert@example.com' --output /tmp/hubert.pgp --rev-cert /tmp/hubert_rev.pgp +sq key generate --shared-key --without-password --email 'hubert@example.com' --output /tmp/hubert.pgp --rev-cert /tmp/hubert_rev.pgp > /dev/null 2>&1 cd $HUSK_DIR @@ -16,47 +16,27 @@ if ! $HUSK_BIN locals list --config config/config.toml | grep -q "hubert@example fi # send a mail, husk should encrypt it -echo "send mail to hubert@example.com" create_unix_user hubert send_test_mail hubert@example.com -if [ -f /var/mail/hubert ] ; then - if grep -q "BEGIN PGP MESSAGE" /var/mail/hubert ; then - RESULT_CODE=0 - else - echo "no encryption detected" - delete_unix_user hubert - exit 1 - fi - # remove mail - rm /var/mail/hubert -else - echo "no mail detected" +R=$(expect_mail hubert encrypted) +if [ $R != 0 ] ; then delete_unix_user hubert exit 1 fi sleep 1 -# remove hubert as local + +echo "removing hubert as local" HUBERT_FPR=$(sq cert list --cert-email hubert@example.com 2>/dev/null | head -n 1 | cut -d ' ' -f 3) $HUSK_BIN locals remove --cert $HUBERT_FPR --config config/config.toml -echo "send mail to hubert@example.com" +rm /var/mail/hubert send_test_mail hubert@example.com -if [ -f /var/mail/hubert ] ; then - if grep -q "BEGIN PGP MESSAGE" /var/mail/hubert ; then - echo "encryption detected where there should be none" - RESULT_CODE=1 - else - RESULT_CODE=0 - fi -else - echo "no mail detected" - RESULT_CODE=1 -fi +R=$(expect_mail hubert unencrypted) delete_unix_user hubert -exit $RESULT_CODE +exit $R diff --git a/e2e-tests/tests/60_signing.sh b/e2e-tests/tests/60_signing.sh index 3ca876d..a1d8135 100644 --- a/e2e-tests/tests/60_signing.sh +++ b/e2e-tests/tests/60_signing.sh @@ -5,39 +5,28 @@ test_header "Signing" -# sq key generate --shared-key --without-password --email 'signing@example.com' --output /tmp/signing.pgp --rev-cert /tmp/signing.rev - -SIGN_FPR=$(sq key generate --own-key --without-password --name "Signing Key" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2) +SIGN_FPR_GEN=$(sq key generate --own-key --without-password --name "Signing Key" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2 | tr -d ' ') cd $HUSK_DIR -$HUSK_BIN --config config/config.toml signkey set --cert $SIGN_FPR +$HUSK_BIN --config config/config.toml signkey set --cert $SIGN_FPR_GEN SIGN_FPR=$($HUSK_BIN --config config/config.toml signkey show | cut -d ':' -f 2 | tr -d ' ') echo "found signing key $SIGN_FPR" +if [ "$SIGN_FPR" != "$SIGN_FPR_GEN" ] ; then + echo "setting new siging key failed, found $SIGN_FPR expected $SIGN_FPR_GEN" + exit 1 +fi + create_unix_user juliette JULIETTE_FPR=$(sq key generate --own-key --without-password --name "Juliette" --email "juliette@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2) -sq pki vouch add --certifier-userid "CA" --cert $JULIETTE_FPR --all > /dev/null 2>&1 +# sq pki vouch add --certifier-userid "CA" --cert $JULIETTE_FPR --all > /dev/null 2>&1 # send a mail, husk should encrypt it -echo "send mail to juliette@example.com" send_test_mail juliette@example.com -if [ -f /var/mail/juliette ] ; then - if grep -q "BEGIN PGP MESSAGE" /var/mail/juliette ; then - - FOUND_FPR=$(sq packet dump /var/mail/juliette | grep "Issuer Fingerprint:" | cut -d ':' -f 2 | tr -d ' ') - - if [ "$SIGN_FPR" != "$FOUND_FPR" ] ; then - echo "Found wrong fingerprint '$FOUND_FPR' - expected '$SIGN_FPR'" - exit 1 - fi - else - echo "no encryption detected" - exit 1 - fi -else - echo "no mail detected" +R=$(expect_mail juliette encrypted $SIGN_FPR) +if [ $R != 0 ] ; then exit 1 fi @@ -50,23 +39,6 @@ echo "new signing key $SIGN_FPR" # remove old mail rm /var/mail/juliette -echo "send mail to juliette@example.com" send_test_mail juliette@example.com -if [ -f /var/mail/juliette ] ; then - if grep -q "BEGIN PGP MESSAGE" /var/mail/juliette ; then - - FOUND_FPR=$(sq packet dump /var/mail/juliette | grep "Issuer Fingerprint:" | cut -d ':' -f 2 | tr -d ' ') - - if [ "$SIGN_FPR" != "$FOUND_FPR" ] ; then - echo "Found wrong fingerprint '$FOUND_FPR' - expected '$SIGN_FPR'" - exit 1 - fi - else - echo "no encryption detected" - exit 1 - fi -else - echo "no mail detected" - exit 1 -fi +exit $(expect_mail juliette encrypted $SIGN_FPR) diff --git a/e2e-tests/tests/70_mail_split.sh b/e2e-tests/tests/70_mail_split.sh new file mode 100644 index 0000000..f1efb35 --- /dev/null +++ b/e2e-tests/tests/70_mail_split.sh @@ -0,0 +1,24 @@ +#!/usr/bin/bash + +# source some functions +. common.sh + +test_header "Mail split" + +create_unix_user karl +KARL_FPR=$(gen_cert karl@example.com) +authenticate $KARL_FPR + +create_unix_user laura + +echo -e "To: karl@example.com, Laura \nSubject: Testmail\n\nTestmail." | sendmail -t +sleep 1 + +R=1 +if [ $(expect_mail karl encrypted) == 0 ] ; then + if [ $(expect_mail laura unencrypted) == 0 ] ; then + R=0 + fi +fi + +exit $R diff --git a/src/common/crypto.rs b/src/common/crypto.rs index acfe245..8130b1a 100644 --- a/src/common/crypto.rs +++ b/src/common/crypto.rs @@ -23,7 +23,6 @@ use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse}; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::stream::{Message, Armorer, Encryptor, Signer, LiteralWriter}; use sequoia_openpgp::{Fingerprint, Cert, Packet}; -use sequoia_openpgp::cert::raw::RawCertParser; use sequoia_openpgp::types::SignatureType; use sequoia_cert_store::{Store, CertStore, LazyCert, StoreUpdate}; use sequoia_cert_store::store::MergePublicCollectStats; @@ -45,35 +44,15 @@ const HUSK_SIG_NOTATION: &str = "signing_key@husk-project.org"; /// Returns the local trust root from the cert_store. If the trust root /// cannot be found, it returns an error. pub fn get_local_trust_root(cert_store: &CertStore) -> anyhow::Result { - - let root = cert_store.certd() - .and_then(|certd| { - match certd.certd().get(sequoia_cert_store::store::openpgp_cert_d::TRUST_ROOT) { - Ok(Some((_tag, bytes))) => Some(bytes), - Ok(None) => None, - Err(_) => None - } - }) - .and_then(|bytes| { - match RawCertParser::from_bytes(&bytes[..]) { - Ok(mut parser) => { - match parser.next() { - Some(Ok(cert)) => { - Cert::from_bytes(cert.as_bytes()).ok() - }, - Some(Err(_)) - | None => None - } - }, - Err(_) => None, - } - }); - - match root { - Some(r) => Ok(r), - None => Err(HuskError::NoLocalTrustRoot.into()) + if let Some((root, created)) = cert_store.certd().and_then(|certd| { + certd.trust_root().ok() }) { + if created { + log::info!("Local trust root not found - created one"); + } + Ok(root.to_cert()?.clone()) + } else { + Err(HuskError::NoLocalTrustRoot.into()) } - } /// Returns all local certificates with an authenticated binding to user id 'email'. @@ -255,7 +234,7 @@ pub async fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str) for introducer in &context.introducers { log::debug!("consider introducer {:?}", - introducer.cert.userids().map(|u| u.userid()).collect::>()); + introducer.cert.self_signed_userids().map(|u| u.userid()).collect::>()); if introducer.can_introduce(format!("<{}>", email).as_str()) { log::debug!(" introducer regex matches!"); match lookup_certificates(context, email).await { diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 5d7ec09..154925c 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -1,7 +1,7 @@ //! the actual daemon use anyhow; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use std::ffi::CString; use tokio::signal; use tokio::net::TcpListener; @@ -18,6 +18,7 @@ use indymilter::{ ContextActions, }; +use crate::types::defaults::HELO_REINJECT_ID; use crate::types::husk_context::HuskContext; use crate::config::{HuskConfig, HuskConfigContainer}; use crate::mail; @@ -39,8 +40,8 @@ impl Daemon { let callbacks = Callbacks::new() .on_negotiate(|cx, actions, opts| Box::pin(Self::handle_negotiate(cx, actions, opts))) .on_connect(|cx, hostname, socket_info| Box::pin(Self::handle_connect(cx, hostname, socket_info))) - .on_helo(move |cx, hostname| Box::pin(Self::handle_helo(cx, config_container.clone(), hostname))) - .on_mail(|cx, args| Box::pin(Self::handle_mail(cx, args))) + .on_helo(|cx, hostname| Box::pin(Self::handle_helo(cx, hostname))) + .on_mail(move |cx, args| Box::pin(Self::handle_mail(cx, config_container.clone(), args))) .on_rcpt(|cx, args| Box::pin(Self::handle_rcpt(cx, args))) .on_data(|cx| Box::pin(Self::handle_data(cx))) .on_header(|cx, name, value| Box::pin(Self::handle_header(cx, name, value))) @@ -80,26 +81,19 @@ impl Daemon { Status::Continue } - async fn handle_helo(cx: &mut Context>, config_container: HuskConfigContainer, hostname: CString) -> Status { + async fn handle_helo(_cx: &mut Context>, hostname: CString) -> Status { log::debug!("HELO: hostname: {hostname:?}"); - let config: HuskConfig = config_container.into(); - - // setup Context - match HuskContext::new(&config) { - Ok(context) => { - log::debug!("found introducers: {:?}", context.introducers); - cx.data = Some(context); - Status::Continue - }, - Err(e) => { - log::error!("{}", e); - Status::Tempfail - } + if hostname.to_str() == Ok(HELO_REINJECT_ID) { + // Reinjected mail detected - this mail is unencrypted because + // all recipients lack a valid certificate. Accept mail. + Status::Accept + } else { + Status::Continue } } - async fn handle_mail(cx: &mut Context>, args: Vec) -> Status { + async fn handle_mail(cx: &mut Context>, config_container: HuskConfigContainer, args: Vec) -> Status { log::debug!("MAIL: {args:?}"); let mut sender: Option = None; @@ -117,7 +111,20 @@ impl Daemon { context.set_sender(sender.unwrap()); Status::Continue } else { - Status::Tempfail + let config: HuskConfig = config_container.into(); + + // setup Context + match HuskContext::new(&config) { + Ok(mut context) => { + context.set_sender(sender.unwrap()); + cx.data = Some(context); + Status::Continue + }, + Err(e) => { + log::error!("{}", e); + Status::Tempfail + } + } } } @@ -202,58 +209,149 @@ impl Daemon { return Status::Accept; } - // possible protection - // none -> accept - // partial -> accept (later: split) - // full -> encrypt + let from = match context.mail.sender.clone() { + Some(f) => f, + None => { + log::error!("No sender given"); + return Status::Tempfail; + } + }; - let protection = ProtectionPossibility::from(&context.mail); + match ProtectionPossibility::from(&context.mail) { + ProtectionPossibility::Full => { + log::debug!("completely encryptable"); + let mut body: BytesMut = body.into(); + body.extend_from_slice(mail::generate_footer(&vec![]).as_bytes()); + let inner_mail = MailContext::generate_inner_mail(&context.mail.header, body.into()); - if protection == ProtectionPossibility::Full { + // if an error occures the body is not exchanged + match crypto::encrypt(context, inner_mail.to_string().as_bytes(), &context.mail.recipients) { + Ok(encrypted) => { + // replace the body of the mail with an encrypted, + // mime encoded version. + let outer_mail = MailContext::generate_outer_mail(encrypted); + if cx.actions.replace_body( + outer_mail + .body_to_string() + .as_bytes()).await.is_err() { + log::error!("Cannot exchange body"); + return Status::Continue; + } - let inner_mail = context.mail.generate_inner_mail(body); - - // if an error occures the body is not exchanged - match crypto::encrypt(context, inner_mail.to_string().as_bytes(), &context.mail.recipients) { - Ok(encrypted) => { - // replace the body of the mail with an encrypted, - // mime encoded version. - let outer_mail = MailContext::generate_outer_mail(encrypted); - if cx.actions.replace_body( - outer_mail - .body_to_string() - .as_bytes()).await.is_err() { - log::error!("Cannot exchange body"); - return Status::Continue; - } - - // adjust headers - // This is mostly Content-Type related. - for line in outer_mail.get_header().iter_lines() { - // XXX: handle errors - if let Ok(key) = line.key_as_cstring() { - if let Ok(value) = line.value_as_cstring() { - let _ = cx.actions.change_header( - key, 1, - Some(value) - ).await; + // adjust headers + // This is mostly Content-Type related. + for line in outer_mail.get_header().iter_lines() { + // XXX: handle errors + if let Ok(key) = line.key_as_cstring() { + if let Ok(value) = line.value_as_cstring() { + let _ = cx.actions.change_header( + key, 1, + Some(value) + ).await; + } } } + // Change the subject + let _ = cx.actions.change_header( + "Subject", + 1, + Some(context.subject_replacement.clone())).await; + }, + Err(e) => { + log::error!("{}", e); } - // Change the subject - let _ = cx.actions.change_header( - "Subject", - 1, - Some(context.subject_replacement.clone())).await; - }, - Err(e) => { - log::error!("{}", e); } + }, + ProtectionPossibility::Partial(encryptable, unencryptable) => { + log::debug!("partially encryptable"); + // Generate a new mail for the unencryptable recipients. + + let mut unencryted_body: BytesMut = body.clone().into(); + unencryted_body.extend_from_slice(mail::generate_footer(&encryptable).as_bytes()); + let header = context.mail.header.without_recipients(&encryptable); + if mail::smtp_inject( + from, + &unencryptable, + header, + &unencryted_body.into()).await.is_err() { + // smtp_inject() already logged the error. + return Status::Tempfail; + } + + // Encrypt for remaining recipients. + let mut encrypted_body: BytesMut = body.into(); + encrypted_body.extend_from_slice(mail::generate_footer(&unencryptable).as_bytes()); + let header = context.mail.header.without_recipients(&unencryptable); + let inner_mail = MailContext::generate_inner_mail(&header, encrypted_body.into()); + + // If an error occures, the body is not exchanged. + match crypto::encrypt(context, inner_mail.to_string().as_bytes(), &encryptable) { + Ok(encrypted) => { + // replace the body of the mail with an encrypted, + // mime encoded version. + let outer_mail = MailContext::generate_outer_mail(encrypted); + if cx.actions.replace_body( + outer_mail + .body_to_string() + .as_bytes()).await.is_err() { + log::error!("Cannot exchange body"); + return Status::Continue; + } + + // Adjust headers + // XXX error handling + + // Remove unencryptable recipients from 'to', 'cc' + // and 'bcc'. + for (key, value) in header.iter_lines() { + match key.as_bytes().to_ascii_lowercase().as_slice() { + b"to" + | b"cc" + | b"bcc" => { + let _ = cx.actions.change_header( + key.clone(), + 1, + Some(value.clone()) + ).await; + }, + _ => { } + } + } + // Also remove recipients via the milter protocol. + for rcpt in unencryptable { + let _ = cx.actions.delete_recipient(rcpt.email).await; + } + + // This is Content-Type related. + for line in outer_mail.get_header().iter_lines() { + // XXX: handle errors + if let Ok(key) = line.key_as_cstring() { + if let Ok(value) = line.value_as_cstring() { + let _ = cx.actions.change_header( + key, 1, + Some(value) + ).await; + } + } + } + // Change the subject. + let _ = cx.actions.change_header( + "Subject", + 1, + Some(context.subject_replacement.clone())).await; + }, + Err(e) => { + log::error!("{}", e); + } + } + }, + ProtectionPossibility::NoProtection => { + log::debug!("not encryptable"); + // do nothing } } } - log::logger().flush(); Status::Continue } diff --git a/src/mail.rs b/src/mail.rs index b7b209a..07245f3 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -1,8 +1,24 @@ //! mail functions +use anyhow::Result; +use bytes::Bytes; +use bytes::BytesMut; use std::ffi::CString; use sequoia_openpgp::packet::UserID; +use lettre::transport::smtp::{ + client::SmtpConnection, commands::*, extension::ClientId, SMTP_PORT, +}; +use lettre::Address; +use tokio::task::JoinSet; +use std::time::Duration; +use chrono::Utc; +use crate::types::cheader::CHeader; +use crate::types::defaults; +use crate::types::errors::HuskError; +use crate::types::recipient::Recipient; + +/// Parse the user id fields from a string. pub fn to_userid(rcpt: &CString) -> Option { match rcpt.to_str() { Ok(s) => Some(UserID::from(s)), @@ -10,6 +26,7 @@ pub fn to_userid(rcpt: &CString) -> Option { } } +/// Get the email from the string representation of a user id. pub fn to_email(rcpt: &CString) -> Option { if let Some(u) = to_userid(rcpt) { if let Ok(Some(e)) = u.email() { Some(e.to_string()) } @@ -32,3 +49,136 @@ pub fn is_email(email: &str) -> bool { pub fn is_domain(domain: &str) -> bool { is_email(format!("test@{}", domain).as_str()) } + +pub async fn smtp_inject(from: String, to: &Vec>, header: CHeader, body: &Bytes) -> Result<()> { + + let mut joinset: JoinSet> = JoinSet::new(); + + log::debug!("smtp inject: from {}", from); + + // build message + let mut header_size = 0; + for (key, value) in header.iter_lines() { + header_size += key.count_bytes(); + header_size += value.count_bytes(); + header_size += 4; // 4 is ': ' plus '\r\n' + } + + // The size of the whole message the sum of header and body size + '\r\n' + // as the dividing empty line. + let mut message = BytesMut::with_capacity(header_size + body.len() + 2); + for (key, value) in header.iter_lines() { + message.extend_from_slice(key.as_bytes()); + message.extend_from_slice(": ".as_bytes()); + message.extend_from_slice(value.as_bytes()); + message.extend_from_slice("\r\n".as_bytes()); + } + message.extend_from_slice(b"\r\n"); + message.extend_from_slice(&body[..]); + + // convert addresses + let from_email = CString::new(from.clone())?; + let from_email = to_email(&from_email).unwrap(); // XXX + let from_email: Address = from_email.try_into()?; + + let mut rcpts: Vec
= Vec::new(); + for rcpt in to { + let rcpt = rcpt.email.clone(); + rcpts.push(rcpt.try_into()?); + } + + let my_id = ClientId::Domain(defaults::HELO_REINJECT_ID.to_owned()); // TODO: add secret token? + joinset.spawn(async move { + + // XXX + // We connect to localhost - every other host would require implementing + // TLS. The milter protocol is unencrypted anyways. + match SmtpConnection::connect(("localhost", SMTP_PORT), Some(Duration::from_secs(5)), &my_id, None, None) { + Ok(mut client) => { + log::debug!("connected - start sending"); + + client.command(Mail::new(Some(from_email),vec![]))?; + for rcpt in rcpts { + client.command(Rcpt::new(rcpt, vec![]))?; + } + client.command(Data)?; + client.message(&message)?; + client.command(Quit)?; + + Ok(()) + }, + Err(e) => { + log::error!("cannot connect to localhost:{} - {:?}", SMTP_PORT, e); + Err(e.into()) + } + } + }); + + match joinset.join_all().await.first() { + Some(Ok(_)) => { + log::debug!("mail injection done."); + Ok(()) + }, + Some(Err(e)) => { + log::error!("mail injection failed: {:?}", e); + Err(HuskError::SmtpInjectionFailure(e.to_string()).into()) + }, + _ => { + log::error!("mail injection failed without reason"); + Err(HuskError::SmtpInjectionFailure("no reason".to_string()).into()) + }, + } +} + +pub fn generate_footer(rcpts: &Vec) -> String { + internal_generate_footer(Utc::now().to_rfc2822(), rcpts) +} + +pub(crate) fn internal_generate_footer(now: String, other_rpcts: &Vec) -> String { + let mut footer = defaults::FOOTER.replace("{date}", now.as_str()); + + if ! other_rpcts.is_empty() { + let rcpts = other_rpcts.iter() + .map(|r| r.email.clone()) + .collect::>() + .join(", "); + footer.push_str( + defaults::FOOTER_RECIPIENTS.replace( + "{rcpts}", + &rcpts).as_str() + ); + } + footer +} + + +#[cfg(test)] +pub mod tests { + + use crate::mail::internal_generate_footer; + use crate::types::recipient::Recipient; + + #[test] + fn test_footer() { + + let rcpts = vec![ + Recipient { + email: "alice@example.com".to_string(), + certs: Vec::new() + }, + Recipient { + email: "bob@example.com".to_string(), + certs: Vec::new() + } + ]; + + let footer = internal_generate_footer( + "1.1.1970".to_string(), + &rcpts + ); + + assert!(footer.contains("1.1.1970")); + assert!(footer.contains("alice@example.com")); + assert!(footer.contains("bob@example.com")); + } +} diff --git a/src/types/cheader.rs b/src/types/cheader.rs index bda73d5..9ad1319 100644 --- a/src/types/cheader.rs +++ b/src/types/cheader.rs @@ -5,6 +5,11 @@ use std::ffi::CString; use std::fmt; +use anyhow::Result; + +use crate::types::recipient::Recipient; + +#[derive(Debug, PartialEq, Clone)] pub struct CHeader { lines: Vec<(CString, CString)> } @@ -52,6 +57,47 @@ impl CHeader { pub fn iter_lines(&self) -> std::slice::Iter<'_, (CString, CString)> { self.lines.iter() } + + /// Remove a recipient from a value taken from a to, cc or bcc header + /// line. Only the email address is matched. + pub fn remove_recipient(header_value: &CString, to_remove: &Vec) -> Result { + let content = String::from_utf8_lossy(header_value.as_bytes()); + let parts = content.split(",") + .filter(|part| { + let mut keep = true; + let p = part.to_ascii_lowercase(); + for rcpt in to_remove { + let rcpt = rcpt.email.to_ascii_lowercase(); + if p.contains(&rcpt) { keep = false; break; } + } + keep + }) + .collect::>(); + CString::new(parts.join(",")) + .map_err(|e| e.into()) + } + + /// Returns a new header with `to_remove` removed from to, cc and bcc. + pub fn without_recipients(&self, to_remove: &Vec) -> CHeader { + let mut result = CHeader::new(); + + for (key, value) in self.iter_lines() { + let new_value = match key.as_bytes().to_ascii_lowercase().as_slice() { + b"to" + | b"cc" + | b"bcc" => Self::remove_recipient(value, to_remove), + _ => Ok(value.clone()) + }; + if let Ok(value) = new_value { + if ! value.is_empty() { + result.append(key.clone(), value); + } + } + } + + result + } + } impl fmt::Display for CHeader { @@ -75,10 +121,12 @@ impl fmt::Display for CHeader { #[cfg(test)] pub mod tests { use std::ffi::CString; + use crate::types::recipient::Recipient; + use super::CHeader; -#[test] + #[test] pub fn basic_functionality() { let cheader = CHeader::new() .add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap()) @@ -87,7 +135,7 @@ pub mod tests { assert_eq!("Content-Type: text/plain\r\nContent-Transfer-Encoding: 7bit\r\n", cheader.to_string()); } -#[test] + #[test] pub fn find() { let cheader = CHeader::new() .add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap()) @@ -103,7 +151,7 @@ pub mod tests { assert_eq!("text/plain", value); } -#[test] + #[test] pub fn find_none() { let cheader = CHeader::new() .add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap()) @@ -114,7 +162,7 @@ pub mod tests { assert!(line.is_none()); } -#[test] + #[test] pub fn find_with_default() { let cheader = CHeader::new() .add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap()) @@ -124,5 +172,27 @@ pub mod tests { assert_eq!(value, "baaz"); } + + #[test] + pub fn remove_recipients() { + let header = CHeader::new() + .add(CString::new("To").unwrap(), CString::new("Alice , Bob ").unwrap()) + .add(CString::new("Cc").unwrap(), CString::new("carol@example.com").unwrap()); + + let modified_header = header.without_recipients( + &vec![ Recipient::from("bob@other.org".to_string()) ] + ); + let expected_header = CHeader::new() + .add(CString::new("To").unwrap(), CString::new("Alice ").unwrap()) + .add(CString::new("Cc").unwrap(), CString::new("carol@example.com").unwrap()); + assert_eq!(modified_header, expected_header); + + let modified_header = header.without_recipients( + &vec![ Recipient::from("CaRol@exAmple.Com".to_string()) ] + ); + let expected_header = CHeader::new() + .add(CString::new("To").unwrap(), CString::new("Alice , Bob ").unwrap()); + assert_eq!(modified_header, expected_header); + } } diff --git a/src/types/defaults.rs b/src/types/defaults.rs index 8f5d0d2..774301d 100644 --- a/src/types/defaults.rs +++ b/src/types/defaults.rs @@ -8,6 +8,12 @@ pub const SUBJECT_REPLACEMENT: &str = "..."; pub const PID_FILE_LOCATION: &str = "/run/husk/pid"; +pub const FOOTER: &str = "---\nProcessed by Husk at {date}.\n"; +pub const FOOTER_RECIPIENTS: &str = "This mail was also send to {rcpts}.\n"; + +/// This ID is used when reinjecting an unencrypted mail into the MTA. +pub const HELO_REINJECT_ID: &str = "husk.reinject"; + /// Default logging configuration. pub const CONSOLE_LOGGING: &str = " [appenders.console] diff --git a/src/types/errors.rs b/src/types/errors.rs index 7ccf040..11d269f 100644 --- a/src/types/errors.rs +++ b/src/types/errors.rs @@ -24,4 +24,6 @@ pub enum HuskError { InvalidDomainName(String), #[error("Certificate has no user ids - {0}")] NoUserIDs(String), + #[error("Failing to send mail: {0}")] + SmtpInjectionFailure(String), } diff --git a/src/types/introducer.rs b/src/types/introducer.rs index d44e82c..0b00bb7 100644 --- a/src/types/introducer.rs +++ b/src/types/introducer.rs @@ -110,7 +110,7 @@ use std::fmt::{Display, Formatter}; impl Display for Introducer { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { // XXX: This is unsafe as ANSI control chars are not handled. - let uids = self.cert.userids() + let uids = self.cert.self_signed_userids() .map(|u| String::from_utf8_lossy(u.userid().value()).to_string()) .collect::>() .join(", "); diff --git a/src/types/mail_context.rs b/src/types/mail_context.rs index 117e76a..423acc7 100644 --- a/src/types/mail_context.rs +++ b/src/types/mail_context.rs @@ -15,17 +15,15 @@ use crate::types::mime::header::Header; use crate::types::mime::part::Part; use crate::types::mime::multi_part::MultiPart; -#[derive(PartialEq)] -pub enum ProtectionPossibility { +pub enum ProtectionPossibility<'mc> { // no recipient has a cert NoProtection, // some recipients have a cert (but not all) - Partial, + Partial(Vec>, Vec>), // all recipients have a cert Full } - pub struct MailContext<'mc> { pub sender: Option, pub recipients: Vec>, @@ -84,8 +82,8 @@ impl<'mc> MailContext<'mc> { buffer.into() } - pub fn generate_inner_mail(&self, body: Bytes) -> MultiPart<'mc> { - let header = &self.header; + pub fn generate_inner_mail(header: &CHeader, body: Bytes) -> MultiPart<'mc> { + //let header = &self.header; let inner = Part::with_body(body) .set_header(Header::new() .add( @@ -158,22 +156,27 @@ impl<'mc> Default for MailContext<'mc> { } } -impl From<&MailContext<'_>> for ProtectionPossibility { - fn from(mail_context: &MailContext<'_>) -> ProtectionPossibility { +impl<'mc> From<&MailContext<'mc>> for ProtectionPossibility<'mc> { + fn from(mail_context: &MailContext<'mc>) -> ProtectionPossibility<'mc> { let mut can_encrypt = false; let mut cannot_encrypt = false; + let mut encrypted_rcpt = Vec::new(); + let mut unencrypted_rcpt = Vec::new(); + for rcpt in mail_context.recipients.iter() { if rcpt.can_encrypt() { can_encrypt = true; + encrypted_rcpt.push(rcpt.clone()); } else { cannot_encrypt = true; + unencrypted_rcpt.push(rcpt.clone()); } } match (can_encrypt, cannot_encrypt) { (true, false) => ProtectionPossibility::Full, - (true, true) => ProtectionPossibility::Partial, + (true, true) => ProtectionPossibility::Partial(encrypted_rcpt, unencrypted_rcpt), (false, true) => ProtectionPossibility::NoProtection, // (false, false) occurs if there are no recipients (false, false) => ProtectionPossibility::NoProtection, @@ -185,8 +188,17 @@ impl From<&MailContext<'_>> for ProtectionPossibility { mod tests { use std::ffi::CString; + use std::sync::Arc; + + use anyhow::Result; + use sequoia_cert_store::LazyCert; + use sequoia_openpgp::cert::CertBuilder; + use crate::types::cheader::CHeader; use crate::types::mail_context::MailContext; + use crate::types::recipient::Recipient; + + use super::ProtectionPossibility; #[test] fn test_inner_mail() { @@ -202,8 +214,7 @@ mod tests { let orig_body = "This is the body".as_bytes().to_vec(); - let context = MailContext::from_header(orig_header); - let inner_mail = context.generate_inner_mail(orig_body.into()); + let inner_mail = MailContext::generate_inner_mail(&orig_header, orig_body.into()); let intended_result = [ format!("Content-Type: multipart/mixed; boundary=\"{}\"; protected-headers=\"v1\"", inner_mail.get_boundary()), @@ -220,5 +231,80 @@ mod tests { assert_eq!(intended_result, inner_mail.to_string()); } + + #[test] + fn test_recipient_splitting() -> Result<()> { + let (alice_cert, _) = CertBuilder::general_purpose(["alice@example.com"]) + .generate()?; + + // Mixed recipients: alice can encrypt, bob doesn't. + let mut mail_context = MailContext::new(); + mail_context.recipients = vec![ + Recipient { + email: "alice@example.com".to_string(), + certs: vec![Arc::new(LazyCert::from_cert(alice_cert))], + }, + Recipient { + email: "bob@example.com".to_string(), + certs: Vec::new() + } + ]; + + let protection = ProtectionPossibility::from(&mail_context); + + assert!(matches!(protection, ProtectionPossibility::Partial(_, _))); + + if let ProtectionPossibility::Partial(a, b) = protection { + assert_eq!(a.len(), 1); + assert_eq!(b.len(), 1); + + let alice = a.iter().next().unwrap(); + assert_eq!(alice.email, "alice@example.com"); + + let bob = b.iter().next().unwrap(); + assert_eq!(bob.email, "bob@example.com"); + } else { + return Err(anyhow::anyhow!("this should be unreachable")); + } + + // Both recipients can encrypt. + let (alice_cert, _) = CertBuilder::general_purpose(["alice@example.com"]) + .generate()?; + let (bob_cert, _) = CertBuilder::general_purpose(["bob@example.com"]) + .generate()?; + let mut mail_context = MailContext::new(); + mail_context.recipients = vec![ + Recipient { + email: "alice@example.com".to_string(), + certs: vec![Arc::new(LazyCert::from_cert(alice_cert))], + }, + Recipient { + email: "bob@example.com".to_string(), + certs: vec![Arc::new(LazyCert::from_cert(bob_cert))], + } + ]; + + let protection = ProtectionPossibility::from(&mail_context); + + assert!(matches!(protection, ProtectionPossibility::Full)); + + // No recipient can encrypt. + let mut mail_context = MailContext::new(); + mail_context.recipients = vec![ + Recipient { + email: "alice@example.com".to_string(), + certs: Vec::new() + }, + Recipient { + email: "bob@example.com".to_string(), + certs: Vec::new() + } + ]; + + let protection = ProtectionPossibility::from(&mail_context); + + assert!(matches!(protection, ProtectionPossibility::NoProtection)); + Ok(()) + } }