DKIM2 tests

This commit is contained in:
Maurus Decimus
2026-07-03 20:07:37 +02:00
parent c5796b4e7a
commit 94b33187a0
16 changed files with 1124 additions and 148 deletions

401
Cargo.lock generated
View File

@@ -93,7 +93,7 @@ dependencies = [
"once_cell",
"serde",
"version_check",
"zerocopy",
"zerocopy 0.8.52",
]
[[package]]
@@ -122,15 +122,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[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",
]
@@ -167,9 +167,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.7"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "ascii-canvas"
@@ -221,15 +221,15 @@ dependencies = [
[[package]]
name = "astral-tokio-tar"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb50a7aae84a03bf55b067832bc376f4961b790c97e64d3eacee97d389b90277"
checksum = "08648fef353ab39a9d26f909ad53fc4f071be4c91853b78523f5cc3d9e5ebffd"
dependencies = [
"filetime",
"futures-core",
"libc",
"portable-atomic",
"rustc-hash",
"rustix 0.38.44",
"tokio",
"tokio-stream",
"xattr",
@@ -281,7 +281,7 @@ dependencies = [
"memchr",
"pin-project",
"portable-atomic",
"rand 0.10.1",
"rand 0.10.2",
"regex",
"rustls-native-certs",
"rustls-pki-types",
@@ -390,9 +390,9 @@ dependencies = [
[[package]]
name = "aws-lc-rs"
version = "1.17.0"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
dependencies = [
"aws-lc-sys",
"untrusted 0.7.1",
@@ -401,14 +401,15 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.41.0"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
"pkg-config",
]
[[package]]
@@ -770,7 +771,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
"hybrid-array 0.4.13",
"zeroize",
]
@@ -789,7 +790,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
dependencies = [
"hybrid-array",
"hybrid-array 0.4.13",
]
[[package]]
@@ -917,9 +918,9 @@ 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 = [
"libc",
]
@@ -1319,7 +1320,7 @@ dependencies = [
"proxy-header",
"psl",
"pwhash",
"quick-xml 0.40.1",
"quick-xml 0.41.0",
"quick_cache",
"rasn",
"rasn-cms",
@@ -1641,7 +1642,8 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
"hybrid-array 0.4.13",
"rand_core 0.10.1",
]
[[package]]
@@ -1729,8 +1731,8 @@ dependencies = [
"crypto-bigint",
"elliptic-curve",
"rand_core 0.6.4",
"sha3",
"signature",
"sha3 0.10.9",
"signature 2.2.0",
"subtle",
"zeroize",
]
@@ -1849,7 +1851,7 @@ dependencies = [
"hashify",
"hyper",
"mail-parser",
"quick-xml 0.40.1",
"quick-xml 0.41.0",
"rkyv",
"serde",
"serde_json",
@@ -2170,7 +2172,7 @@ dependencies = [
"pkcs8",
"rfc6979",
"sha2 0.10.9",
"signature",
"signature 2.2.0",
"zeroize",
]
@@ -2218,7 +2220,7 @@ dependencies = [
"digest 0.10.7",
"elliptic-curve",
"rfc6979",
"signature",
"signature 2.2.0",
"spki",
]
@@ -2247,7 +2249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
"signature 2.2.0",
]
[[package]]
@@ -2488,7 +2490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee93edf3c501f0035bbeffeccfed0b79e14c311f12195ec0e661e114a0f60da4"
dependencies = [
"portable-atomic",
"rand 0.10.1",
"rand 0.10.2",
"web-time",
]
@@ -2509,16 +2511,6 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@@ -2626,7 +2618,7 @@ dependencies = [
"foundationdb-sys",
"foundationdb-tuple",
"futures",
"rand 0.10.1",
"rand 0.10.2",
"serde",
"serde_bytes",
"serde_json",
@@ -2817,7 +2809,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix",
"rustix 1.1.4",
"windows-link 0.2.1",
]
@@ -3072,7 +3064,7 @@ dependencies = [
"jni 0.22.4",
"lru-cache",
"parking_lot",
"rand 0.10.1",
"rand 0.10.2",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier 0.7.0",
@@ -3099,7 +3091,7 @@ dependencies = [
"jni 0.22.4",
"once_cell",
"prefix-trie",
"rand 0.10.1",
"rand 0.10.2",
"ring",
"rustls-pki-types",
"thiserror 2.0.18",
@@ -3126,7 +3118,7 @@ dependencies = [
"ndk-context",
"once_cell",
"parking_lot",
"rand 0.10.1",
"rand 0.10.2",
"resolv-conf",
"rustls",
"smallvec",
@@ -3230,7 +3222,7 @@ dependencies = [
"mime",
"percent-encoding",
"pkcs8",
"quick-xml 0.40.1",
"quick-xml 0.41.0",
"registry",
"rkyv",
"rsa",
@@ -3334,19 +3326,30 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15"
[[package]]
name = "hybrid-array"
version = "0.4.12"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
checksum = "891d15931895091dea5c47afa5b3c9a01ba634b311919fd4d41388fa0e3d76af"
dependencies = [
"typenum",
]
[[package]]
name = "hybrid-array"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
dependencies = [
"ctutils",
"typenum",
"zeroize",
]
[[package]]
name = "hyper"
version = "1.10.1"
@@ -3730,7 +3733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
"block-padding 0.4.2",
"hybrid-array",
"hybrid-array 0.4.13",
]
[[package]]
@@ -4034,9 +4037,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.102"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
@@ -4073,7 +4076,7 @@ dependencies = [
"serde",
"serde_json",
"sha2 0.10.9",
"signature",
"signature 2.2.0",
"simple_asn1",
"zeroize",
]
@@ -4087,6 +4090,26 @@ dependencies = [
"cpufeatures 0.2.17",
]
[[package]]
name = "keccak"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
]
[[package]]
name = "kem"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd"
dependencies = [
"crypto-common 0.2.2",
"rand_core 0.10.1",
]
[[package]]
name = "keyed-set"
version = "1.1.0"
@@ -4134,7 +4157,7 @@ dependencies = [
"petgraph",
"regex",
"regex-syntax",
"sha3",
"sha3 0.10.9",
"string_cache",
"term",
"unicode-xid",
@@ -4248,9 +4271,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libredox"
version = "0.1.17"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
dependencies = [
"libc",
]
@@ -4299,6 +4322,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@@ -4390,6 +4419,8 @@ dependencies = [
[[package]]
name = "mail-auth"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2a3c0ef3c8f70fc3a2ce043453605b4d5e9501f5ff2318aac92ab8d59fb78b"
dependencies = [
"aws-lc-rs",
"flate2",
@@ -4643,6 +4674,47 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "ml-dsa"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d"
dependencies = [
"crypto-common 0.2.2",
"ctutils",
"hybrid-array 0.4.13",
"module-lattice",
"shake",
"signature 3.0.0",
"zeroize",
]
[[package]]
name = "ml-kem"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66"
dependencies = [
"hybrid-array 0.4.13",
"kem",
"module-lattice",
"rand_core 0.10.1",
"sha3 0.11.0",
"zeroize",
]
[[package]]
name = "module-lattice"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
dependencies = [
"ctutils",
"hybrid-array 0.4.13",
"num-traits",
"zeroize",
]
[[package]]
name = "moka"
version = "0.12.15"
@@ -4893,9 +4965,9 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6"
dependencies = [
"num-integer",
"num-traits",
@@ -5632,7 +5704,7 @@ dependencies = [
"hmac 0.13.0",
"md-5 0.11.0",
"memchr",
"rand 0.10.1",
"rand 0.10.2",
"sha2 0.11.0",
"stringprep",
]
@@ -5677,7 +5749,7 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
"zerocopy 0.8.52",
]
[[package]]
@@ -5844,9 +5916,9 @@ dependencies = [
[[package]]
name = "psl"
version = "2.1.214"
version = "2.1.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9124f6ba3f3aea75e4918fc86c76adfba8c9d289ab36f926f0b6ce4987a9e6a9"
checksum = "9826c2fe4dade07e9da1af2e18427257877bc8bc27aa810f6fbb52d907a480cc"
dependencies = [
"psl-types",
]
@@ -5939,13 +6011,13 @@ dependencies = [
[[package]]
name = "quick_cache"
version = "0.6.23"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3db184a8b66cfe87f0263a1de147a6b554c864d1767c6f7fa4eb0e5497b565"
checksum = "403c1a912fec895cafb223201e368234842acb9220aaf08ab042ae89ba5f135c"
dependencies = [
"ahash",
"equivalent",
"hashbrown 0.16.1",
"foldhash 0.2.0",
"hashbrown 0.17.1",
"parking_lot",
]
@@ -6053,9 +6125,9 @@ checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22"
[[package]]
name = "rancor"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee"
checksum = "daff8b7b3ccf5f7ba270b3e7a0a4d4c701c5797e38dec27c7e2c3dbb830fed1c"
dependencies = [
"ptr_meta",
]
@@ -6096,9 +6168,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"chacha20 0.10.1",
"getrandom 0.4.3",
@@ -6311,9 +6383,9 @@ dependencies = [
[[package]]
name = "redis"
version = "1.2.4"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae41a63fd0b8a5372f82b21e810e09a316f5dd7efd96bf08e678fb240fc1918"
checksum = "2fa6f8e4b491d7a8ef3a9550a4d71969bd0064f46e32b8dbbcc7fc60dad94fed"
dependencies = [
"arcstr",
"async-lock",
@@ -6327,7 +6399,7 @@ dependencies = [
"num-bigint",
"percent-encoding",
"pin-project-lite",
"rand 0.10.1",
"rand 0.10.2",
"rustls",
"rustls-native-certs",
"ryu",
@@ -6429,9 +6501,9 @@ dependencies = [
[[package]]
name = "rend"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6"
checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240"
dependencies = [
"bytecheck",
]
@@ -6572,9 +6644,9 @@ dependencies = [
[[package]]
name = "rkyv"
version = "0.8.16"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73389e0c99e664f919275ab5b5b0471391fe9a8de61e1dff9b1eaf56a90f16e3"
checksum = "815cc8a37159a463064825246cadb07961e25cd9885908606f6d08a98d8f8874"
dependencies = [
"bytecheck",
"bytes",
@@ -6591,9 +6663,9 @@ dependencies = [
[[package]]
name = "rkyv_derive"
version = "0.8.16"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6"
checksum = "c0ed1a78a1b19d184b0daa629dd9a024573173ec7d485b287cb369fb3607cc1c"
dependencies = [
"proc-macro2",
"quote",
@@ -6655,7 +6727,7 @@ dependencies = [
"pkcs8",
"rand_core 0.6.4",
"sha2 0.10.9",
"signature",
"signature 2.2.0",
"spki",
"subtle",
"zeroize",
@@ -6748,9 +6820,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"
@@ -6770,6 +6842,19 @@ dependencies = [
"nom",
]
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.1.4"
@@ -6779,7 +6864,7 @@ dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
]
@@ -7056,9 +7141,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "sequoia-openpgp"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c847f0f148cf238c3aec88d092fd3c4301c21e906829ea9e415ea7531f7ec094"
checksum = "e421679c8175ef4674d913af7fdb8ad0078c8a2599db633ad0b4d02a06b2cb3c"
dependencies = [
"aes 0.8.4",
"aes-gcm",
@@ -7093,6 +7178,8 @@ dependencies = [
"libc",
"md-5 0.10.6",
"memsec",
"ml-dsa",
"ml-kem",
"num-bigint-dig",
"num-integer",
"ocb3",
@@ -7107,7 +7194,8 @@ dependencies = [
"rsa",
"sha1collisiondetection",
"sha2 0.10.9",
"sha3",
"sha3 0.10.9",
"slh-dsa",
"thiserror 2.0.18",
"twofish",
"typenum",
@@ -7431,7 +7519,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
dependencies = [
"digest 0.10.7",
"keccak",
"keccak 0.1.6",
]
[[package]]
name = "sha3"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
dependencies = [
"digest 0.11.3",
"keccak 0.2.0",
]
[[package]]
name = "shake"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
dependencies = [
"digest 0.11.3",
"keccak 0.2.0",
"sponge-cursor",
]
[[package]]
@@ -7500,6 +7609,15 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "signature"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
dependencies = [
"digest 0.11.3",
]
[[package]]
name = "simd-adler32"
version = "0.3.9"
@@ -7555,6 +7673,25 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "slh-dsa"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd2f20f4049197e03db1104a6452f4d9e96665d79f880198dce4a7026ba5f267"
dependencies = [
"const-oid 0.9.6",
"digest 0.10.7",
"hmac 0.12.1",
"hybrid-array 0.3.1",
"pkcs8",
"rand_core 0.6.4",
"sha2 0.10.9",
"sha3 0.10.9",
"signature 2.2.0",
"typenum",
"zerocopy 0.7.35",
]
[[package]]
name = "smallvec"
version = "1.15.2"
@@ -7720,6 +7857,12 @@ dependencies = [
"der",
]
[[package]]
name = "sponge-cursor"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
[[package]]
name = "sqlite-wasm-rs"
version = "0.5.5"
@@ -8095,7 +8238,7 @@ dependencies = [
"nlp",
"num_cpus",
"pop3",
"quick-xml 0.40.1",
"quick-xml 0.41.0",
"rayon",
"rcgen",
"registry",
@@ -8176,9 +8319,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.51"
version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327"
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
dependencies = [
"deranged",
"js-sys",
@@ -8197,9 +8340,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.30"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935"
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
dependencies = [
"num-conv",
"time-core",
@@ -8310,7 +8453,7 @@ dependencies = [
"pin-project-lite",
"postgres-protocol",
"postgres-types",
"rand 0.10.1",
"rand 0.10.2",
"socket2 0.6.4",
"tokio",
"tokio-util",
@@ -8952,9 +9095,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.23.3"
version = "1.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53"
dependencies = [
"getrandom 0.4.3",
"js-sys",
@@ -9083,9 +9226,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.125"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
@@ -9096,9 +9239,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.75"
version = "0.4.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -9106,9 +9249,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.125"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -9116,9 +9259,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.125"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -9129,9 +9272,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.125"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
@@ -9164,9 +9307,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.102"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -9436,6 +9579,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
@@ -9716,7 +9868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
"rustix 1.1.4",
]
[[package]]
@@ -9742,9 +9894,9 @@ dependencies = [
[[package]]
name = "xxhash-rust"
version = "0.8.15"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576"
[[package]]
name = "yasna"
@@ -9907,7 +10059,7 @@ dependencies = [
"hmac 0.12.1",
"rand 0.8.6",
"rand_chacha 0.3.1",
"sha3",
"sha3 0.10.9",
"zenoh-result",
]
@@ -10200,7 +10352,7 @@ dependencies = [
"ringbuffer-spsc",
"rsa",
"serde",
"sha3",
"sha3 0.10.9",
"tokio",
"tokio-util",
"tracing",
@@ -10246,13 +10398,34 @@ dependencies = [
"zenoh-result",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
dependencies = [
"zerocopy-derive",
"zerocopy-derive 0.8.52",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -10369,9 +10542,9 @@ dependencies = [
[[package]]
name = "zlib-rs"
version = "0.6.4"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3"
checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5"
[[package]]
name = "zmij"

View File

@@ -18,7 +18,7 @@ imap_proto = { path = "../imap-proto" }
sieve-rs = { version = "0.7", features = ["rkyv", "serde"] }
mail-parser = { version = "0.11", features = ["full_encoding"] }
mail-builder = { version = "0.4" }
mail-auth = { path = "/Users/me/code/mail-auth", features = ["generate", "arc"] }
mail-auth = { version = "0.11", features = ["generate", "arc"] }
smtp-proto = { version = "0.2", features = ["rkyv"] }
dns-update = { version = "0.5" }
calcard = { version = "0.3", features = ["rkyv"] }
@@ -78,14 +78,14 @@ compact_str = { version = "0.9.0", features = ["rkyv", "serde"] }
lz4_flex = { version = "0.13", features = ["frame"], default-features = false }
hickory-proto = "0.26"
nohash-hasher = "0.2.0"
quick_cache = "0.6.9"
quick_cache = "0.7"
rasn = "0.28"
rasn-cms = "0.28"
rasn-pkix = "0.28"
sequoia-openpgp = { version = "2.0", default-features = false, features = ["crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"] }
zxcvbn = "3.1.0"
pkcs8 = { version = "0.10.2", features = ["alloc", "std"] }
quick-xml = "0.40"
quick-xml = "0.41"
[target.'cfg(unix)'.dependencies]
privdrop = "0.5.3"

View File

@@ -7,7 +7,7 @@ edition = "2024"
trc = { path = "../trc" }
types = { path = "../types" }
hashify = "0.2.6"
quick-xml = "0.40"
quick-xml = "0.41"
calcard = { version = "0.3", features = ["rkyv"] }
mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] }
hyper = "1.6.0"

View File

@@ -23,13 +23,13 @@ registry = { path = "../registry" }
smtp-proto = { version = "0.2" }
mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] }
mail-builder = { version = "0.4" }
mail-auth = { path = "/Users/me/code/mail-auth", features = ["generate", "arc"] }
mail-auth = { version = "0.11", features = ["generate", "arc"] }
tokio = { version = "1.47", features = ["rt"] }
hyper = { version = "1.0.1", features = ["server", "http1", "http2"] }
hyper-util = { version = "0.1.1", features = ["tokio"] }
http-body-util = "0.1.0"
async-stream = "0.3.5"
quick-xml = "0.40"
quick-xml = "0.41"
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
x509-parser = "0.18"

View File

@@ -23,7 +23,7 @@ calcard = { version = "0.3" }
smtp-proto = { version = "0.2" }
mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] }
mail-builder = { version = "0.4" }
mail-auth = { path = "/Users/me/code/mail-auth", features = ["generate", "arc"] }
mail-auth = { version = "0.11", features = ["generate", "arc"] }
sieve-rs = { version = "0.7", features = ["rkyv"] }
jmap-tools = { version = "0.1", features = ["rkyv"] }
serde = { version = "1.0", features = ["derive"]}

View File

@@ -13,7 +13,7 @@ hashify = "0.2.7"
ahash = { version = "0.8" }
jmap-tools = { version = "0.1" }
xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
mail-auth = { path = "/Users/me/code/mail-auth" }
mail-auth = { version = "0.11" }
tokio = { version = "1.47", features = ["fs"] }
lz4_flex = { version = "0.13", default-features = false }

View File

@@ -21,7 +21,7 @@ email = { path = "../email" }
registry = { path = "../registry" }
spam-filter = { path = "../spam-filter" }
trc = { path = "../trc" }
mail-auth = { path = "/Users/me/code/mail-auth", features = ["rkyv"] }
mail-auth = { version = "0.11", features = ["rkyv"] }
mail-parser = { version = "0.11", features = ["full_encoding"] }
mail-builder = { version = "0.4" }
smtp-proto = { version = "0.2", features = ["rkyv", "serde"] }

View File

@@ -85,7 +85,7 @@ impl DkimSign for Server {
});
let instance = MessageInstance::from_message(&modified, original.as_ref());
if let Some(instance) = &instance {
instance.write(&mut headers);
instance.write_header(&mut headers);
}
// Obtain disclosed and undisclosed recipients
@@ -104,11 +104,11 @@ impl DkimSign for Server {
Ok(signature) => {
if envelopes.undisclosed_recipients.is_empty() {
// Happy path: no undisclosed recipients, serialize signature straight to blob
signature.write(&mut headers);
signature.write_header(&mut headers);
} else {
// Undisclosed recipients present, serialize signature to metadata
let mut header = Vec::with_capacity(64);
signature.write(&mut header);
signature.write_header(&mut header);
params.metadata.push(Metadata::Headers {
value: header.into_boxed_slice(),
id: u64::MAX,
@@ -135,7 +135,7 @@ impl DkimSign for Server {
Ok(signature) => {
// Serialize signature to metadata
let mut header = Vec::with_capacity(64);
signature.write(&mut header);
signature.write_header(&mut header);
params.metadata.push(Metadata::Headers {
value: header.into_boxed_slice(),
id: pos as u64,
@@ -251,3 +251,168 @@ impl MessageWrapper {
fn sanitize_or_lower(rcpt: &str) -> String {
sanitize_email(rcpt).unwrap_or_else(|| rcpt.to_lowercase())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::queue::{Message, QueueId, Recipient};
use common::config::smtp::queue::QueueName;
use std::net::{IpAddr, Ipv4Addr};
fn wrapper(recipients: &[&str]) -> MessageWrapper {
MessageWrapper {
queue_id: 0 as QueueId,
queue_name: QueueName::default(),
is_multi_queue: false,
span_id: 0,
message: Message {
created: 0,
blob_hash: Default::default(),
return_path: "sender@example.com".into(),
recipients: recipients.iter().map(Recipient::new).collect(),
received_from_ip: IpAddr::V4(Ipv4Addr::LOCALHOST),
received_via_port: 0,
flags: 0,
env_id: None,
priority: 0,
size: 0,
metadata: Default::default(),
},
}
}
fn split(headers: &str, recipients: &[&str]) -> (Vec<String>, Vec<String>) {
let raw = format!("{headers}\r\nSubject: test\r\n\r\nbody\r\n");
let auth = AuthenticatedMessage::parse(raw.as_bytes()).expect("parse message");
let message = wrapper(recipients);
let envelopes = message.undisclosed_recipients(&auth);
let mut disclosed = envelopes
.disclosed_recipients
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
let mut undisclosed = envelopes
.undisclosed_recipients
.iter()
.map(|(_, s)| s.to_string())
.collect::<Vec<_>>();
disclosed.sort();
undisclosed.sort();
(disclosed, undisclosed)
}
#[test]
fn single_recipient_is_always_disclosed() {
let (disclosed, undisclosed) = split("To: someone-else@example.com", &["bcc@example.org"]);
assert_eq!(disclosed, vec!["bcc@example.org".to_string()]);
assert!(undisclosed.is_empty());
}
#[test]
fn all_recipients_disclosed() {
let (disclosed, undisclosed) = split(
"To: alice@example.com, bob@example.com\r\nCc: carol@example.com",
&["alice@example.com", "bob@example.com", "carol@example.com"],
);
assert_eq!(
disclosed,
vec![
"alice@example.com".to_string(),
"bob@example.com".to_string(),
"carol@example.com".to_string(),
]
);
assert!(undisclosed.is_empty());
}
#[test]
fn mixed_disclosed_and_undisclosed() {
let (disclosed, undisclosed) = split(
"To: alice@example.com\r\nCc: bob@example.com",
&[
"alice@example.com",
"bob@example.com",
"eve@secret.example.org",
"mallory@secret.example.org",
],
);
assert_eq!(
disclosed,
vec![
"alice@example.com".to_string(),
"bob@example.com".to_string()
]
);
assert_eq!(
undisclosed,
vec![
"eve@secret.example.org".to_string(),
"mallory@secret.example.org".to_string(),
]
);
}
#[test]
fn no_to_or_cc_header_all_undisclosed() {
let (disclosed, undisclosed) = split(
"From: sender@example.com",
&["eve@example.org", "mallory@example.org"],
);
assert!(disclosed.is_empty());
assert_eq!(
undisclosed,
vec![
"eve@example.org".to_string(),
"mallory@example.org".to_string()
]
);
}
#[test]
fn group_addresses_are_disclosed() {
let (disclosed, undisclosed) = split(
"To: Team:alice@example.com,bob@example.com;",
&["alice@example.com", "bob@example.com", "eve@example.org"],
);
assert_eq!(
disclosed,
vec![
"alice@example.com".to_string(),
"bob@example.com".to_string()
]
);
assert_eq!(undisclosed, vec!["eve@example.org".to_string()]);
}
#[test]
fn header_address_casing_is_ignored() {
let (disclosed, undisclosed) = split(
"To: Alice@Example.COM, BOB@EXAMPLE.com",
&["alice@example.com", "bob@example.com", "eve@example.org"],
);
assert_eq!(
disclosed,
vec![
"alice@example.com".to_string(),
"bob@example.com".to_string()
]
);
assert_eq!(undisclosed, vec!["eve@example.org".to_string()]);
}
#[test]
fn display_names_and_brackets_are_ignored() {
let (disclosed, undisclosed) = split(
"To: \"Alice Doe\" <alice@example.com>\r\nCc: Bob <bob@example.com>",
&["alice@example.com", "bob@example.com", "eve@example.org"],
);
assert_eq!(
disclosed,
vec![
"alice@example.com".to_string(),
"bob@example.com".to_string()
]
);
assert_eq!(undisclosed, vec!["eve@example.org".to_string()]);
}
}

View File

@@ -50,7 +50,7 @@ pub const SMFIC_UNKNOWN: u8 = b'U'; /* Any unknown command */
impl Command<'_> {
fn build(command: u8, len: u32) -> Vec<u8> {
let mut buf = Vec::with_capacity(len as usize + 1 + std::mem::size_of::<u32>());
buf.extend_from_slice((len + 1).to_be_bytes().as_ref());
buf.extend_from_slice((len + 1).to_be_bytes().as_slice());
buf.push(command);
buf
}
@@ -95,7 +95,7 @@ impl Command<'_> {
buf.extend(hostname);
buf.push(0x00);
buf.push(family);
buf.extend(port.to_be_bytes().as_ref());
buf.extend(port.to_be_bytes().as_slice());
buf.extend(address.as_bytes());
buf.push(0x00);
buf
@@ -168,9 +168,9 @@ impl Command<'_> {
}
Command::OptionNegotiation(opt) => {
let mut buf = Command::build(SMFIC_OPTNEG, 3 * std::mem::size_of::<u32>() as u32);
buf.extend(opt.version.to_be_bytes().as_ref());
buf.extend(opt.actions.to_be_bytes().as_ref());
buf.extend(opt.protocol.to_be_bytes().as_ref());
buf.extend(opt.version.to_be_bytes().as_slice());
buf.extend(opt.actions.to_be_bytes().as_slice());
buf.extend(opt.protocol.to_be_bytes().as_slice());
buf
}
Command::Quit => Command::build(SMFIC_QUIT, 0),
@@ -417,7 +417,7 @@ impl Response {
+ std::mem::size_of::<u32>() as u32
+ 2,
);
buf.extend(index.to_be_bytes().as_ref());
buf.extend(index.to_be_bytes().as_slice());
buf.extend(name.as_bytes());
buf.push(0x00);
buf.extend(value.as_bytes());
@@ -432,7 +432,7 @@ impl Response {
+ std::mem::size_of::<u32>() as u32
+ 2,
);
buf.extend(index.to_be_bytes().as_ref());
buf.extend(index.to_be_bytes().as_slice());
buf.extend(name.as_bytes());
buf.push(0x00);
buf.extend(value.as_bytes());
@@ -451,9 +451,9 @@ impl Response {
Response::SetSymbols => Command::build(SMFIR_SETSYMLIST, 0),
Response::OptionNegotiation(opt) => {
let mut buf = Command::build(SMFIC_OPTNEG, 3 * std::mem::size_of::<u32>() as u32);
buf.extend(opt.version.to_be_bytes().as_ref());
buf.extend(opt.actions.to_be_bytes().as_ref());
buf.extend(opt.protocol.to_be_bytes().as_ref());
buf.extend(opt.version.to_be_bytes().as_slice());
buf.extend(opt.actions.to_be_bytes().as_slice());
buf.extend(opt.protocol.to_be_bytes().as_slice());
buf
}
}

View File

@@ -14,7 +14,7 @@ registry = { path = "../registry" }
smtp-proto = { version = "0.2", features = ["rkyv"] }
mail-parser = { version = "0.11", features = ["full_encoding"] }
mail-builder = { version = "0.4" }
mail-auth = { path = "/Users/me/code/mail-auth" }
mail-auth = { version = "0.11" }
tokio = { version = "1.47", features = ["net", "macros"] }
psl = "2"
hyper = { version = "1.0.1", features = ["server", "http1", "http2"] }

View File

@@ -5,7 +5,7 @@ edition = "2024"
[dependencies]
event_macro = { path = "./event-macro" }
mail-auth = { path = "/Users/me/code/mail-auth", features = ["arc"] }
mail-auth = { version = "0.11", features = ["arc"] }
mail-parser = { version = "0.11", features = ["full_encoding"] }
base64 = "0.22.1"
serde = "1.0"

View File

@@ -12,7 +12,7 @@ rustls-platform-verifier = "0.7"
tokio = { version = "1.47", features = ["net", "macros", "signal"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "tls12"] }
serde = { version = "1.0", features = ["derive"]}
mail-auth = { path = "/Users/me/code/mail-auth" }
mail-auth = { version = "0.11" }
smtp-proto = { version = "0.2" }
ahash = { version = "0.8", features = ["serde"] }
chrono = "0.4"
@@ -32,7 +32,7 @@ http-body-util = "0.1.0"
form_urlencoded = "1.1.0"
psl = "2"
idna = "1.0"
quick_cache = "0.6.9"
quick_cache = "0.7"
fast-float = "0.2.0"
rkyv = { version = "0.8.10", features = ["little_endian"] }
compact_str = "0.9.0"

View File

@@ -19,11 +19,11 @@ use std::{
};
pub struct Cache<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight>(
quick_cache::sync::Cache<K, V, CacheItemWeighter>,
quick_cache::sync::Cache<K, V, CacheItemWeighter, ahash::RandomState>,
);
pub struct CacheWithTtl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight>(
quick_cache::sync::Cache<K, TtlEntry<V>, CacheItemWeighter>,
quick_cache::sync::Cache<K, TtlEntry<V>, CacheItemWeighter, ahash::RandomState>,
);
#[derive(Clone)]
@@ -38,10 +38,12 @@ impl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight> Cache<K, V> {
}
pub fn new_estimated(estimated_items_capacity: usize, weight_capacity: u64) -> Self {
Self(quick_cache::sync::Cache::with_weighter(
Self(quick_cache::sync::Cache::with(
estimated_items_capacity,
weight_capacity,
CacheItemWeighter,
ahash::RandomState::default(),
DefaultLifecycle::default(),
))
}
@@ -101,7 +103,7 @@ impl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight> Cache<K, V> {
}
#[inline(always)]
pub fn inner(&self) -> &quick_cache::sync::Cache<K, V, CacheItemWeighter> {
pub fn inner(&self) -> &quick_cache::sync::Cache<K, V, CacheItemWeighter, ahash::RandomState> {
&self.0
}
}
@@ -112,10 +114,12 @@ impl<K: Eq + Hash + CacheItemWeight, V: Clone + CacheItemWeight> CacheWithTtl<K,
}
pub fn new_estimated(estimated_items_capacity: usize, weight_capacity: u64) -> Self {
Self(quick_cache::sync::Cache::with_weighter(
Self(quick_cache::sync::Cache::with(
estimated_items_capacity,
weight_capacity,
CacheItemWeighter,
ahash::RandomState::default(),
DefaultLifecycle::default(),
))
}

View File

@@ -45,7 +45,7 @@ migration = { path = "../crates/migration", features = ["test_mode", "enterprise
trc = { path = "../crates/trc", features = ["enterprise"] }
managesieve = { path = "../crates/managesieve", features = ["test_mode", "enterprise"] }
smtp-proto = { version = "0.2" }
mail-auth = { path = "/Users/me/code/mail-auth", features = ["test"] }
mail-auth = { version = "0.11", features = ["test"] }
mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] }
mail-builder = "0.4.4"
sieve-rs = { version = "0.7", features = ["rkyv"] }
@@ -79,7 +79,7 @@ biscuit = "0.8.0"
form_urlencoded = "1.1.0"
rkyv = { version = "0.8.10", features = ["little_endian"] }
compact_str = "0.9.0"
quick-xml = "0.40"
quick-xml = "0.41"
jmap-tools = { version = "0.1" }
dns-update = { version = "0.5", features = ["test_provider"] }
x509-parser = "0.18"

View File

@@ -0,0 +1,633 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
smtp::{inbound::TestMessage, session::TestSession},
utils::{account::Account, dns::DnsCache, server::TestServer, server::TestServerBuilder},
};
use base64::{Engine, engine::general_purpose::STANDARD};
use mail_auth::{
DnssecStatus, MX,
common::{crypto::Ed25519Key, parse::TxtRecordParser, verify::DomainKey},
dkim2::{Dkim2Signer, Hop},
};
use registry::schema::{
enums::{DkimCanonicalization, DkimRotationStage},
structs::{
CertificateManagement, Dkim1Signature, Dkim2Signature, DkimManagement, DkimSignature,
DnsManagement, Domain, DsnReportSettings, Expression, SecretText, SecretTextValue,
SenderAuth,
},
};
use std::time::{Duration, Instant};
use types::id::Id;
const ED_PRIVATE: &str = concat!(
"-----BEGIN PRIVATE KEY-----\n",
"MC4CAQAwBQYDK2VwBCIEIIOQVf8MDGvvmIkpUbgoqtyUIxjlzRqaBR6aP12tcGGE\n",
"-----END PRIVATE KEY-----\n"
);
const ED_PUBLIC: &str = "hwjviTXyzUXSCWayBqE17s/4NSynQKxw58jayHudRAI=";
#[tokio::test]
#[serial_test::serial]
async fn dkim2_all_disclosed() {
let (mut local, remote) = build_signer_and_verifier(19040, 19041, false).await;
let delivered = deliver_and_collect(
&mut local,
&remote,
"john@example.com",
&["alice@foobar.org", "bob@foobar.org"],
&message("To: Alice <alice@foobar.org>, Bob <bob@foobar.org>\r\n"),
1,
)
.await;
assert_eq!(delivered.len(), 1);
let msg = &delivered[0];
assert_eq!(msg.recipients, vec!["alice@foobar.org", "bob@foobar.org"]);
assert!(
msg.body.contains("DKIM2-Signature"),
"missing DKIM2 signature: {}",
msg.body
);
assert!(
without_whitespace(&msg.body).contains("dkim2=pass"),
"verifier did not report dkim2=pass: {}",
msg.body
);
// Both disclosed recipients belong to the same shared signature
let stripped = without_whitespace(&msg.body);
assert!(stripped.contains(&rt_token("alice@foobar.org")));
assert!(stripped.contains(&rt_token("bob@foobar.org")));
}
#[tokio::test]
#[serial_test::serial]
async fn dkim2_mixed_recipients_do_not_leak_bcc() {
let (mut local, remote) = build_signer_and_verifier(19042, 19043, false).await;
let delivered = deliver_and_collect(
&mut local,
&remote,
"john@example.com",
&[
"alice@foobar.org",
"bob@foobar.org",
"eve@foobar.org",
"mallory@foobar.org",
],
&message("To: Alice <alice@foobar.org>\r\nCc: Bob <bob@foobar.org>\r\n"),
3,
)
.await;
assert_eq!(delivered.len(), 3);
let eve = rt_token("eve@foobar.org");
let mallory = rt_token("mallory@foobar.org");
for msg in &delivered {
let stripped = without_whitespace(&msg.body);
assert!(
stripped.contains("dkim2=pass"),
"verifier did not report dkim2=pass for {:?}: {}",
msg.recipients,
msg.body
);
if msg.recipients == vec!["alice@foobar.org", "bob@foobar.org"] {
// Disclosed copy: the two Bcc recipients must not appear anywhere
assert!(
!stripped.contains(&eve) && !stripped.contains(&mallory),
"Bcc recipient leaked into the disclosed signature: {}",
msg.body
);
assert!(!msg.body.contains("eve@foobar.org"));
assert!(!msg.body.contains("mallory@foobar.org"));
assert!(stripped.contains(&rt_token("alice@foobar.org")));
assert!(stripped.contains(&rt_token("bob@foobar.org")));
} else if msg.recipients == vec!["eve@foobar.org"] {
// Bcc copy: only Eve's address is in this signature
assert!(stripped.contains(&eve));
assert!(!stripped.contains(&mallory));
assert!(!msg.body.contains("mallory@foobar.org"));
} else if msg.recipients == vec!["mallory@foobar.org"] {
assert!(stripped.contains(&mallory));
assert!(!stripped.contains(&eve));
assert!(!msg.body.contains("eve@foobar.org"));
} else {
panic!("unexpected recipient grouping: {:?}", msg.recipients);
}
}
}
#[tokio::test]
#[serial_test::serial]
async fn dkim2_all_undisclosed_do_not_leak() {
let (mut local, remote) = build_signer_and_verifier(19044, 19045, false).await;
let rcpts = ["carol@foobar.org", "dave@foobar.org", "frank@foobar.org"];
let delivered = deliver_and_collect(
&mut local,
&remote,
"john@example.com",
&rcpts,
&message("To: undisclosed-recipients:;\r\n"),
3,
)
.await;
assert_eq!(delivered.len(), 3);
for msg in &delivered {
assert_eq!(msg.recipients.len(), 1, "expected one recipient per copy");
let own = msg.recipients[0].as_str();
let stripped = without_whitespace(&msg.body);
assert!(
stripped.contains("dkim2=pass"),
"verifier did not report dkim2=pass for {own}: {}",
msg.body
);
assert!(stripped.contains(&rt_token(own)));
// No other recipient may appear in this copy
for other in rcpts.iter().filter(|r| **r != own) {
assert!(
!stripped.contains(&rt_token(other)),
"recipient {other} leaked into the copy for {own}: {}",
msg.body
);
assert!(
!msg.body.contains(other),
"recipient {other} leaked into the copy for {own}: {}",
msg.body
);
}
}
}
#[tokio::test]
#[serial_test::serial]
async fn dkim1_and_dkim2_signed_together() {
let (mut local, remote) = build_signer_and_verifier(19046, 19047, true).await;
let delivered = deliver_and_collect(
&mut local,
&remote,
"john@example.com",
&["alice@foobar.org", "bob@foobar.org"],
&message("To: Alice <alice@foobar.org>, Bob <bob@foobar.org>\r\n"),
1,
)
.await;
assert_eq!(delivered.len(), 1);
let stripped = without_whitespace(&delivered[0].body);
assert!(
delivered[0].body.contains("DKIM-Signature"),
"missing DKIM1 signature: {}",
delivered[0].body
);
assert!(
delivered[0].body.contains("DKIM2-Signature"),
"missing DKIM2 signature: {}",
delivered[0].body
);
assert!(
stripped.contains("dkim=pass"),
"DKIM1 did not pass: {}",
delivered[0].body
);
assert!(
stripped.contains("dkim2=pass"),
"DKIM2 did not pass: {}",
delivered[0].body
);
}
#[tokio::test]
#[serial_test::serial]
async fn dkim2_dsn_is_signed() {
let mut local = TestServerBuilder::new("dkim2_dsn_signer")
.await
.with_http_listener(19048)
.await
.disable_services()
.capture_queue()
.build()
.await;
let admin = local.account("admin");
admin.mta_allow_relaying().await;
admin.mta_no_auth().await;
admin.mta_all_extensions().await;
admin.mta_disable_spam_filter().await;
admin.mta_add_all_headers().await;
let domain_id = admin.create_signing_domain(false).await;
admin
.registry_create_object(DsnReportSettings {
dkim_sign_domain: expr("'example.com'"),
..Default::default()
})
.await;
let _ = domain_id;
admin.reload_settings().await;
local.reload_core();
local.expect_reload_settings().await;
// Deliver to a domain with no DNS records: the lookup fails permanently and
// a DSN is generated for the sender.
let mut session = local.new_mta_session();
session.data.remote_ip_str = "10.0.0.1".into();
session.eval_session_params().await;
session.ehlo("mx.example.com").await;
session
.send_message(
"john@example.com",
&["bob@does-not-resolve.invalid"],
&message("To: Bob <bob@does-not-resolve.invalid>\r\n"),
"250",
)
.await;
local
.expect_message_then_deliver()
.await
.try_deliver(local.server.clone());
let dsn = local.expect_message().await;
assert!(
dsn.message.return_path.is_empty(),
"expected a DSN (null return path)"
);
let body = dsn.read_message(&local).await;
assert!(
body.contains("Content-Type: multipart/report"),
"not a DSN: {body}"
);
assert!(
body.contains("DKIM2-Signature"),
"the generated DSN was not DKIM2 signed: {body}"
);
}
#[tokio::test]
#[serial_test::serial]
async fn dkim2_inbound_dsn_validation() {
let mut server = TestServerBuilder::new("dkim2_dsn_receiver")
.await
.with_http_listener(19049)
.await
.disable_services()
.capture_queue()
.build()
.await;
let admin = server.account("admin");
admin.mta_allow_relaying().await;
admin.mta_no_auth().await;
admin.mta_all_extensions().await;
admin.mta_disable_spam_filter().await;
admin.mta_add_all_headers().await;
admin.configure_sender_auth("''").await;
admin.reload_settings().await;
server.reload_core();
server.expect_reload_settings().await;
// The returned message is signed by us (example.com); the DSN is signed by
// the bouncing domain (foobar.org). Publish both keys.
server
.server
.txt_add("ed._domainkey.example.com", dkim_dns_record(), valid());
server
.server
.txt_add("ed._domainkey.foobar.org", dkim_dns_record(), valid());
let returned = dkim2_sign(
"example.com",
"ed",
RETURNED_PLAIN.as_bytes(),
"john@example.com",
&["bob@foobar.org"],
);
// A well-formed, aligned DSN is accepted
let dsn_ok = build_dsn(&returned, true);
let mut session = server.new_mta_session();
session.data.remote_ip_str = "10.0.0.1".into();
session.eval_session_params().await;
session.ehlo("mx.foobar.org").await;
session
.send_message(
"<>",
&["john@example.com"],
&String::from_utf8(dsn_ok).unwrap(),
"250",
)
.await;
// Tampering the returned message body breaks its signature chain, so the DSN
// is rejected.
let returned_tampered = String::from_utf8(returned)
.unwrap()
.replace("DKIM2-ORIGINAL-BODY-CONTENT", "DKIM2-TAMPERED-BODY-CONTENT")
.into_bytes();
let dsn_bad = build_dsn(&returned_tampered, true);
session
.send_message(
"<>",
&["john@example.com"],
&String::from_utf8(dsn_bad).unwrap(),
"550",
)
.await;
}
impl Account {
async fn create_signing_domain(&self, with_dkim1: bool) -> Id {
let domain_id = self
.registry_create_object(Domain {
name: "example.com".into(),
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
allow_relaying: true,
..Default::default()
})
.await;
self.registry_create_object(DkimSignature::Dkim2Ed25519Sha256(Dkim2Signature {
stage: DkimRotationStage::Active,
selector: "ed2".to_string(),
domain_id,
private_key: SecretText::Text(SecretTextValue {
secret: ED_PRIVATE.to_string(),
}),
..Default::default()
}))
.await;
if with_dkim1 {
self.registry_create_object(DkimSignature::Dkim1Ed25519Sha256(Dkim1Signature {
stage: DkimRotationStage::Active,
selector: "ed1".to_string(),
canonicalization: DkimCanonicalization::RelaxedRelaxed,
domain_id,
private_key: SecretText::Text(SecretTextValue {
secret: ED_PRIVATE.to_string(),
}),
..Default::default()
}))
.await;
}
domain_id
}
async fn configure_sender_auth(&self, dkim_sign_domain: &str) {
self.registry_create_object(SenderAuth {
dmarc_verify: expr("relaxed"),
reverse_ip_verify: expr("relaxed"),
spf_ehlo_verify: expr("relaxed"),
spf_from_verify: expr("relaxed"),
arc_verify: expr("relaxed"),
dkim_sign_domain: expr(dkim_sign_domain),
dkim_verify: expr("relaxed"),
dkim_strict: false,
})
.await;
}
}
async fn build_signer_and_verifier(
http_local: u16,
http_remote: u16,
with_dkim1: bool,
) -> (TestServer, TestServer) {
let mut local = TestServerBuilder::new("dkim2_signer")
.await
.with_http_listener(http_local)
.await
.disable_services()
.capture_queue()
.build()
.await;
let mut remote = TestServerBuilder::new("dkim2_verifier")
.await
.with_http_listener(http_remote)
.await
.with_smtp_listener(9925)
.await
.disable_services()
.capture_queue()
.build()
.await;
// Signer (originating MTA)
let admin = local.account("admin");
admin.mta_allow_relaying().await;
admin.mta_no_auth().await;
admin.mta_all_extensions().await;
admin.mta_disable_spam_filter().await;
admin.mta_add_all_headers().await;
admin.create_signing_domain(with_dkim1).await;
admin.configure_sender_auth("'example.com'").await;
admin.reload_settings().await;
local.reload_core();
local.expect_reload_settings().await;
// Verifier (receiving MTA)
let remote_admin = remote.account("admin");
remote_admin.mta_allow_relaying().await;
remote_admin.mta_no_auth().await;
remote_admin.mta_all_extensions().await;
remote_admin.mta_disable_spam_filter().await;
remote_admin.mta_add_all_headers().await;
remote_admin.configure_sender_auth("''").await;
remote_admin.reload_settings().await;
remote.reload_core();
remote.expect_reload_settings().await;
// Publish the signer's public keys in the verifier's DNS
remote
.server
.txt_add("ed2._domainkey.example.com", dkim_dns_record(), valid());
if with_dkim1 {
remote
.server
.txt_add("ed1._domainkey.example.com", dkim_dns_record(), valid());
}
// Route foobar.org deliveries back to the local (in-process) receiver
local.server.mx_add(
"foobar.org",
vec![MX {
exchanges: vec!["mx.foobar.org".into()].into_boxed_slice(),
preference: 10,
}],
DnssecStatus::Secure,
valid(),
);
local
.server
.ipv4_add("mx.foobar.org", vec!["127.0.0.1".parse().unwrap()], valid());
(local, remote)
}
fn message(to_header: &str) -> String {
format!(
concat!(
"From: John Doe <john@example.com>\r\n",
"{}",
"Subject: DKIM2 privacy test\r\n",
"\r\n",
"This is a DKIM2 test message.\r\n",
),
to_header
)
}
fn build_dsn(returned: &[u8], sign_dsn: bool) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(b"--BOUNDARY\r\nContent-Type: text/plain\r\n\r\n");
body.extend_from_slice(b"Delivery to bob@foobar.org failed.\r\n");
body.extend_from_slice(b"--BOUNDARY\r\nContent-Type: message/delivery-status\r\n\r\n");
body.extend_from_slice(b"Reporting-MTA: dns; foobar.org\r\n\r\n");
body.extend_from_slice(b"Final-Recipient: rfc822; bob@foobar.org\r\n");
body.extend_from_slice(b"Action: failed\r\nStatus: 5.1.1\r\n");
body.extend_from_slice(b"--BOUNDARY\r\nContent-Type: message/rfc822\r\n\r\n");
body.extend_from_slice(returned);
body.extend_from_slice(b"\r\n--BOUNDARY--\r\n");
let mut dsn = Vec::new();
dsn.extend_from_slice(b"From: postmaster@foobar.org\r\n");
dsn.extend_from_slice(b"To: john@example.com\r\n");
dsn.extend_from_slice(b"Subject: Delivery Status Notification (Failure)\r\n");
dsn.extend_from_slice(b"Date: Sat, 01 Mar 2026 12:05:00 +0000\r\n");
dsn.extend_from_slice(b"Message-ID: <dsn@foobar.org>\r\n");
dsn.extend_from_slice(
b"Content-Type: multipart/report; report-type=delivery-status; boundary=\"BOUNDARY\"\r\n\r\n",
);
dsn.extend_from_slice(&body);
if sign_dsn {
dkim2_sign("foobar.org", "ed", &dsn, "<>", &["john@example.com"])
} else {
dsn
}
}
fn ed25519_key() -> Ed25519Key {
let der = STANDARD
.decode("MC4CAQAwBQYDK2VwBCIEIIOQVf8MDGvvmIkpUbgoqtyUIxjlzRqaBR6aP12tcGGE")
.unwrap();
Ed25519Key::from_pkcs8_maybe_unchecked_der(&der).unwrap()
}
fn dkim2_sign(
domain: &str,
selector: &str,
message: &[u8],
mail_from: &str,
rcpt_to: &[&str],
) -> Vec<u8> {
let signed = Dkim2Signer::from_key(ed25519_key())
.domain(domain)
.selector(selector)
.sign(message, Hop::real(mail_from, rcpt_to))
.expect("dkim2 sign");
let mut out = signed.to_header().into_bytes();
out.extend_from_slice(message);
out
}
const RETURNED_PLAIN: &str = concat!(
"From: John Doe <john@example.com>\r\n",
"To: Bob <bob@foobar.org>\r\n",
"Subject: Original message\r\n",
"Date: Sat, 01 Mar 2026 12:00:00 +0000\r\n",
"Message-ID: <original@example.com>\r\n",
"\r\n",
"DKIM2-ORIGINAL-BODY-CONTENT\r\n",
);
struct Delivered {
recipients: Vec<String>,
body: String,
}
async fn deliver_and_collect(
local: &mut TestServer,
remote: &TestServer,
from: &str,
rcpts: &[&str],
raw: &str,
expected: usize,
) -> Vec<Delivered> {
let mut session = local.new_mta_session();
session.data.remote_ip_str = "10.0.0.1".into();
session.eval_session_params().await;
session.ehlo("mx.example.com").await;
session.send_message(from, rcpts, raw, "250").await;
local
.expect_message_then_deliver()
.await
.try_deliver(local.server.clone());
let mut delivered = Vec::new();
for _ in 0..expected {
let mut waited = 0;
let msg = loop {
if let Some(msg) = remote.read_queued_messages().await.into_iter().next() {
break msg;
}
assert!(waited < 100, "timed out waiting for a delivered message");
tokio::time::sleep(Duration::from_millis(50)).await;
waited += 1;
};
let body = msg.read_message(remote).await;
let mut recipients = msg
.message
.recipients
.iter()
.map(|r| r.address().to_string())
.collect::<Vec<_>>();
recipients.sort();
let due = remote.message_due(msg.queue_id).await;
msg.clone().remove(&remote.server, due.into()).await;
delivered.push(Delivered { recipients, body });
}
delivered
}
fn rt_token(address: &str) -> String {
STANDARD.encode(format!("<{address}>"))
}
fn without_whitespace(value: &str) -> String {
value.chars().filter(|c| !c.is_whitespace()).collect()
}
fn dkim_dns_record() -> DomainKey {
DomainKey::parse(format!("v=DKIM1; k=ed25519; p={ED_PUBLIC}").as_bytes()).unwrap()
}
fn valid() -> Instant {
Instant::now() + Duration::from_secs(300)
}
fn expr(value: &str) -> Expression {
Expression {
else_: value.into(),
..Default::default()
}
}

View File

@@ -24,6 +24,7 @@ pub mod asn;
pub mod auth;
pub mod basic;
pub mod data;
pub mod dkim2;
pub mod dmarc;
pub mod ehlo;
pub mod limits;