Branding + fixes

This commit is contained in:
mdecimus 2024-09-19 19:39:35 +02:00
parent e9d12aea44
commit a67f308645
51 changed files with 516 additions and 429 deletions

322
Cargo.lock generated
View file

@ -4,19 +4,13 @@ version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler2"
version = "2.0.0"
@ -180,9 +174,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.86"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "arbitrary"
@ -213,9 +207,9 @@ dependencies = [
[[package]]
name = "arrayref"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
@ -385,7 +379,7 @@ dependencies = [
"serde",
"serde_json",
"url",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@ -469,17 +463,17 @@ dependencies = [
[[package]]
name = "backtrace"
version = "0.3.73"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide 0.7.4",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
@ -788,9 +782,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.17.1"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2"
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
[[package]]
name = "byteorder"
@ -800,9 +794,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]]
name = "bzip2"
@ -855,9 +849,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.1.15"
version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0"
dependencies = [
"jobserver",
"libc",
@ -962,9 +956,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.16"
version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
dependencies = [
"clap_builder",
"clap_derive",
@ -972,9 +966,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.15"
version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
dependencies = [
"anstream",
"anstyle",
@ -1042,7 +1036,7 @@ dependencies = [
[[package]]
name = "common"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"arc-swap",
@ -1075,12 +1069,13 @@ dependencies = [
"privdrop",
"prometheus",
"proxy-header",
"psl",
"pwhash",
"rcgen 0.12.1",
"regex",
"reqwest 0.12.7",
"ring 0.17.8",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@ -1176,9 +1171,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.13"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
dependencies = [
"libc",
]
@ -1437,9 +1432,9 @@ dependencies = [
[[package]]
name = "dashmap"
version = "6.0.1"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
@ -1650,7 +1645,7 @@ dependencies = [
[[package]]
name = "directory"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"argon2",
@ -1670,7 +1665,7 @@ dependencies = [
"proc_macros",
"pwhash",
"regex",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pki-types",
"scrypt",
"serde",
@ -1932,11 +1927,11 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "enum-as-inner"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.77",
@ -2038,7 +2033,7 @@ checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
dependencies = [
"crc32fast",
"libz-sys",
"miniz_oxide 0.8.0",
"miniz_oxide",
]
[[package]]
@ -2354,9 +2349,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.29.0"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "glob"
@ -2739,20 +2734,20 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.27.2"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.4.1",
"hyper-util",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.0",
"tower-service",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@ -2770,9 +2765,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba"
dependencies = [
"bytes",
"futures-channel",
@ -2790,9 +2785,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.60"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -2984,7 +2979,7 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "imap"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"common",
@ -2999,7 +2994,7 @@ dependencies = [
"nlp",
"parking_lot",
"rand",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pemfile 2.1.3",
"store",
"tokio",
@ -3096,9 +3091,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.9.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
[[package]]
name = "is-terminal"
@ -3196,7 +3191,7 @@ dependencies = [
[[package]]
name = "jmap"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"aes",
"aes-gcm",
@ -3549,9 +3544,9 @@ dependencies = [
[[package]]
name = "lz4-sys"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868"
checksum = "fcb44a01837a858d47e5a630d2ccf304c8efcc4b83b8f9f75b7a9ee4fcc6e57d"
dependencies = [
"cc",
"libc",
@ -3575,9 +3570,9 @@ dependencies = [
[[package]]
name = "mail-auth"
version = "0.4.3"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd9d657de66a3d5ac360c3eab8c9f5cac2565f2b97cc032d5de4c900ef470de"
checksum = "aaee4c38f4df428c6732f3d5472a013fa248d2772f48c8932295b32c683a23c4"
dependencies = [
"ahash 0.8.11",
"flate2",
@ -3586,7 +3581,7 @@ dependencies = [
"mail-builder",
"mail-parser",
"parking_lot",
"quick-xml 0.32.0",
"quick-xml 0.36.1",
"rand",
"ring 0.17.8",
"rsa",
@ -3607,9 +3602,9 @@ dependencies = [
[[package]]
name = "mail-parser"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5a1335c3a964788c90cb42ae04a34b5f2628e89566949ce3bd4ada695c0bcd"
checksum = "93c3b9e5d8b17faf573330bbc43b37d6e918c0a3bf8a88e7d0a220ebc84af9fc"
dependencies = [
"encoding_rs",
"serde",
@ -3624,17 +3619,17 @@ dependencies = [
"base64 0.22.1",
"gethostname",
"md5",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pki-types",
"smtp-proto",
"tokio",
"tokio-rustls 0.26.0",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
name = "mail-server"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"common",
"directory",
@ -3653,7 +3648,7 @@ dependencies = [
[[package]]
name = "managesieve"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"bincode",
@ -3667,7 +3662,7 @@ dependencies = [
"mail-send",
"md5",
"parking_lot",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pemfile 2.1.3",
"sieve-rs",
"store",
@ -3786,15 +3781,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@ -3880,7 +3866,7 @@ dependencies = [
"twox-hash",
"url",
"webpki",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@ -3952,7 +3938,7 @@ dependencies = [
[[package]]
name = "nlp"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"bincode",
@ -3962,12 +3948,12 @@ dependencies = [
"nohash",
"parking_lot",
"phf",
"psl",
"rust-stemmers",
"serde",
"siphasher 1.0.1",
"tinysegmenter",
"tokio",
"utils",
"whatlang",
"xxhash-rust",
]
@ -4300,7 +4286,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.3",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
@ -4503,7 +4489,7 @@ dependencies = [
[[package]]
name = "pop3"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"common",
"directory",
@ -4512,7 +4498,7 @@ dependencies = [
"jmap_proto",
"mail-parser",
"mail-send",
"rustls 0.23.12",
"rustls 0.23.13",
"store",
"tokio",
"tokio-rustls 0.26.0",
@ -4546,9 +4532,9 @@ dependencies = [
[[package]]
name = "postgres-types"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9"
checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f"
dependencies = [
"bytes",
"fallible-iterator 0.2.0",
@ -4717,6 +4703,21 @@ dependencies = [
"tokio",
]
[[package]]
name = "psl"
version = "2.1.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce9398ad066421139b2e3afe16ea46772ffda30bd9ba57554dc035df5e26edc8"
dependencies = [
"psl-types",
]
[[package]]
name = "psl-types"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
[[package]]
name = "ptr_meta"
version = "0.1.4"
@ -4779,16 +4780,16 @@ dependencies = [
[[package]]
name = "quinn"
version = "0.11.3"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156"
checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684"
dependencies = [
"bytes",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.0.0",
"rustls 0.23.12",
"rustls 0.23.13",
"socket2",
"thiserror",
"tokio",
@ -4797,15 +4798,15 @@ dependencies = [
[[package]]
name = "quinn-proto"
version = "0.11.6"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd"
checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
dependencies = [
"bytes",
"rand",
"ring 0.17.8",
"rustc-hash 2.0.0",
"rustls 0.23.12",
"rustls 0.23.13",
"slab",
"thiserror",
"tinyvec",
@ -4814,15 +4815,15 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.4"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b"
dependencies = [
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -5011,7 +5012,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"rand",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-native-certs 0.7.3",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
@ -5022,23 +5023,14 @@ dependencies = [
"tokio-rustls 0.26.0",
"tokio-util",
"url",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853"
dependencies = [
"bitflags 2.6.0",
]
@ -5152,7 +5144,7 @@ dependencies = [
"http-body 1.0.1",
"http-body-util",
"hyper 1.4.1",
"hyper-rustls 0.27.2",
"hyper-rustls 0.27.3",
"hyper-util",
"ipnet",
"js-sys",
@ -5163,7 +5155,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@ -5177,7 +5169,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
"windows-registry",
]
@ -5480,9 +5472,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.35"
version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [
"bitflags 2.6.0",
"errno",
@ -5512,21 +5504,21 @@ dependencies = [
"log",
"ring 0.17.8",
"rustls-pki-types",
"rustls-webpki 0.102.7",
"rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
[[package]]
name = "rustls"
version = "0.23.12"
version = "0.23.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
dependencies = [
"once_cell",
"ring 0.17.8",
"rustls-pki-types",
"rustls-webpki 0.102.7",
"rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
@ -5593,9 +5585,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.102.7"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring 0.17.8",
"rustls-pki-types",
@ -5640,20 +5632,20 @@ checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
[[package]]
name = "scc"
version = "2.1.16"
version = "2.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeb7ac86243095b70a7920639507b71d51a63390d1ba26c4f60a552fbb914a37"
checksum = "0c947adb109a8afce5fc9c7bf951f87f146e9147b3a6a58413105628fb1d1e66"
dependencies = [
"sdd",
]
[[package]]
name = "schannel"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -5695,9 +5687,9 @@ dependencies = [
[[package]]
name = "sdd"
version = "3.0.2"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0495e4577c672de8254beb68d01a9b62d0e8a13c099edecdbedccce3223cd29f"
checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc"
[[package]]
name = "seahash"
@ -5821,9 +5813,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.209"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
@ -5839,9 +5831,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.209"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
@ -5850,9 +5842,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.127"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"itoa",
"memchr",
@ -6065,7 +6057,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smtp"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"bincode",
@ -6091,7 +6083,7 @@ dependencies = [
"rayon",
"regex",
"reqwest 0.12.7",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@ -6105,7 +6097,7 @@ dependencies = [
"tokio-rustls 0.26.0",
"trc",
"utils",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
"x509-parser 0.16.0",
]
@ -6181,7 +6173,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "stalwart-cli"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"clap",
"console",
@ -6212,7 +6204,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "store"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"arc-swap",
@ -6244,7 +6236,7 @@ dependencies = [
"rocksdb",
"rusqlite",
"rust-s3",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pki-types",
"serde",
"serde_json",
@ -6462,7 +6454,7 @@ dependencies = [
"rayon",
"reqwest 0.12.7",
"ring 0.17.8",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@ -6604,9 +6596,9 @@ dependencies = [
[[package]]
name = "tokio-postgres"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3"
checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb"
dependencies = [
"async-trait",
"byteorder",
@ -6655,16 +6647,16 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
dependencies = [
"futures-core",
"pin-project-lite",
@ -6684,7 +6676,7 @@ dependencies = [
"tokio",
"tokio-rustls 0.25.0",
"tungstenite 0.21.0",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
]
[[package]]
@ -6701,9 +6693,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
@ -6720,9 +6712,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
[[package]]
name = "toml_edit"
version = "0.22.20"
version = "0.22.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
dependencies = [
"indexmap 2.5.0",
"toml_datetime",
@ -6839,7 +6831,7 @@ dependencies = [
[[package]]
name = "trc"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"base64 0.22.1",
@ -6970,15 +6962,15 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
@ -6997,9 +6989,9 @@ checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd"
[[package]]
name = "unicode-security"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee9e13753df674873f3c4693b240ae5c03245ddc157dfccf7c26db9329af3a11"
checksum = "2e4ddba1535dd35ed8b61c52166b7155d7f4e4b8847cec6f48e71dc66d8b5e50"
dependencies = [
"unicode-normalization",
"unicode-script",
@ -7013,9 +7005,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "unicode-xid"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "universal-hash"
@ -7082,7 +7074,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utils"
version = "0.9.4"
version = "0.10.0"
dependencies = [
"ahash 0.8.11",
"base64 0.22.1",
@ -7103,7 +7095,7 @@ dependencies = [
"regex",
"reqwest 0.12.7",
"ring 0.17.8",
"rustls 0.23.12",
"rustls 0.23.13",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"serde",
@ -7112,7 +7104,7 @@ dependencies = [
"tokio",
"tokio-rustls 0.26.0",
"trc",
"webpki-roots 0.26.5",
"webpki-roots 0.26.6",
"x509-parser 0.16.0",
]
@ -7282,9 +7274,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "webpki-roots"
version = "0.26.5"
version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a"
checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
dependencies = [
"rustls-pki-types",
]
@ -7313,11 +7305,11 @@ dependencies = [
[[package]]
name = "whoami"
version = "1.5.1"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
dependencies = [
"redox_syscall 0.4.1",
"redox_syscall",
"wasite",
"web-sys",
]
@ -7643,9 +7635,9 @@ dependencies = [
[[package]]
name = "xml-rs"
version = "0.8.21"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601"
checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26"
[[package]]
name = "xxhash-rust"

View file

@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <hello@stalw.art>"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
repository = "https://github.com/stalwartlabs/cli"
homepage = "https://github.com/stalwartlabs/cli"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
readme = "README.md"
resolver = "2"
@ -29,4 +29,4 @@ human-size = "0.4.2"
futures = "0.3.28"
pwhash = "1.0.0"
rand = "0.8.5"
mail-auth = { version = "0.4" }
mail-auth = { version = "0.5" }

View file

@ -88,7 +88,7 @@ async fn oauth(url: &str) -> Credentials {
.danger_accept_invalid_certs(is_localhost(url))
.build()
.unwrap_or_default()
.get(&format!("{}/.well-known/oauth-authorization-server", url))
.get(format!("{}/.well-known/oauth-authorization-server", url))
.send()
.await
.unwrap_result("send OAuth GET request")

View file

@ -1,6 +1,6 @@
[package]
name = "common"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"
@ -14,7 +14,7 @@ jmap_proto = { path = "../jmap-proto" }
sieve-rs = { version = "0.5" }
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }
mail-auth = { version = "0.4" }
mail-auth = { version = "0.5" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
smtp-proto = { version = "0.1", features = ["serde_support"] }
dns-update = { version = "0.1" }
@ -58,6 +58,7 @@ hostname = "0.4.0"
zip = "2.1"
pwhash = "1.0.0"
xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
psl = "2"
[target.'cfg(unix)'.dependencies]
privdrop = "0.5.3"

View file

@ -175,6 +175,7 @@ impl Core {
32,
),
permissions_version: Default::default(),
logos: Default::default(),
},
storage: Storage {
data,

View file

@ -22,10 +22,7 @@ use mail_auth::{
Resolver,
};
use parking_lot::Mutex;
use utils::{
config::{utils::ParseValue, Config},
suffixlist::PublicSuffix,
};
use utils::config::{utils::ParseValue, Config};
use crate::Core;
@ -33,7 +30,6 @@ pub struct Resolvers {
pub dns: Resolver,
pub dnssec: DnssecResolver,
pub cache: DnsRecordCache,
pub psl: PublicSuffix,
}
#[derive(Clone)]
@ -237,7 +233,6 @@ impl Resolvers {
.unwrap_or(1024),
),
},
psl: PublicSuffix::parse(config, "resolver.public-suffix").await,
}
}
}
@ -356,7 +351,6 @@ impl Default for Resolvers {
tlsa: LruCache::with_capacity(1024),
mta_sts: LruCache::with_capacity(1024),
},
psl: PublicSuffix::default(),
}
}
}
@ -402,7 +396,6 @@ impl Clone for Resolvers {
dns: self.dns.clone(),
dnssec: self.dnssec.clone(),
cache: self.cache.clone(),
psl: self.psl.clone(),
}
}
}

View file

@ -117,6 +117,7 @@ impl Enterprise {
.property_or_default::<Option<Duration>>("storage.undelete.retention", "false")
.unwrap_or_default()
.map(|retention| Undelete { retention }),
logo_url: config.value("enterprise.logo-url").map(|s| s.to_string()),
trace_store,
metrics_store,
metrics_alerts: parse_metric_alerts(config),

View file

@ -15,17 +15,22 @@ pub mod undelete;
use std::time::Duration;
use directory::{
backend::internal::{lookup::DirectoryStore, PrincipalField},
QueryBy, Type,
};
use license::LicenseKey;
use mail_parser::DateTime;
use store::Store;
use trc::{EventType, MetricType};
use trc::{AddContext, EventType, MetricType};
use utils::config::cron::SimpleCron;
use crate::{expr::Expression, Core};
use crate::{expr::Expression, manager::webadmin::Resource, Core};
#[derive(Clone)]
pub struct Enterprise {
pub license: LicenseKey,
pub logo_url: Option<String>,
pub undelete: Option<Undelete>,
pub trace_store: Option<TraceStore>,
pub metrics_store: Option<MetricStore>,
@ -114,4 +119,95 @@ impl Core {
);
}
}
pub async fn logo_resource(&self, domain: &str) -> trc::Result<Option<Resource<Vec<u8>>>> {
if self.is_enterprise_edition() {
let domain = psl::domain_str(domain).unwrap_or(domain);
let logo = { self.security.logos.lock().get(domain).cloned() };
if let Some(logo) = logo {
Ok(logo)
} else {
// Try fetching the logo for the domain
let logo_url = if let Some(mut principal) = self
.storage
.data
.query(QueryBy::Name(domain), false)
.await
.caused_by(trc::location!())?
.filter(|p| p.typ() == Type::Domain)
{
if let Some(logo) = principal
.take_str(PrincipalField::Picture)
.filter(|l| l.starts_with("http"))
{
logo.into()
} else if let Some(tenant_id) = principal.get_int(PrincipalField::Tenant) {
if let Some(logo) = self
.storage
.data
.query(QueryBy::Id(tenant_id as u32), false)
.await
.caused_by(trc::location!())?
.and_then(|mut p| p.take_str(PrincipalField::Picture))
.filter(|l| l.starts_with("http"))
{
logo.into()
} else {
self.default_logo_url()
}
} else {
self.default_logo_url()
}
} else {
self.default_logo_url()
};
let mut logo = None;
if let Some(logo_url) = logo_url {
let response = reqwest::get(&logo_url).await.map_err(|err| {
trc::ResourceEvent::DownloadExternal
.into_err()
.details("Failed to download logo")
.reason(err)
})?;
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
.unwrap_or("image/svg+xml")
.to_string();
let contents = response
.bytes()
.await
.map_err(|err| {
trc::ResourceEvent::DownloadExternal
.into_err()
.details("Failed to download logo")
.reason(err)
})?
.to_vec();
logo = Resource::new(content_type, contents).into();
}
self.security
.logos
.lock()
.insert(domain.to_string(), logo.clone());
Ok(logo)
}
} else {
Ok(None)
}
}
fn default_logo_url(&self) -> Option<String> {
self.enterprise
.as_ref()
.and_then(|e| e.logo_url.as_ref().map(|l| l.to_string()))
}
}

View file

@ -91,14 +91,14 @@ impl<'x> Tokenizer<'x> {
_ => {
let (prev_token, ch) = if ch == b'(' && self.buf.eq(b"matches") {
// Parse regular expressions
let stop_ch = self.find_char(&[b'\"', b'\''])?;
let stop_ch = self.find_char(b"\"'")?;
let regex_str = self.parse_string(stop_ch)?;
let regex = Regex::new(&regex_str).map_err(|e| {
format!("Invalid regular expression {:?}: {}", regex_str, e)
})?;
self.has_alpha = false;
self.buf.clear();
self.find_char(&[b','])?;
self.find_char(b",")?;
(Token::Regex(regex).into(), b'(')
} else if !self.buf.is_empty() {
self.is_start = false;

View file

@ -10,6 +10,7 @@ use std::{
sync::{atomic::AtomicU8, Arc},
};
use ahash::AHashMap;
use arc_swap::ArcSwap;
use auth::{roles::RolePermissions, AccessToken};
use config::{
@ -35,6 +36,8 @@ use listener::{
};
use mail_send::Credentials;
use manager::webadmin::Resource;
use parking_lot::Mutex;
use sieve::Sieve;
use store::{
write::{QueueClass, ValueClass},
@ -58,6 +61,8 @@ pub mod manager;
pub mod scripts;
pub mod telemetry;
pub use psl;
pub static USER_AGENT: &str = concat!("Stalwart/", env!("CARGO_PKG_VERSION"),);
pub static DAEMON_NAME: &str = concat!("Stalwart Mail Server v", env!("CARGO_PKG_VERSION"),);
@ -83,6 +88,7 @@ pub struct Core {
//TODO: temporary hack until OIDC is implemented
#[derive(Default)]
pub struct Security {
pub logos: Mutex<AHashMap<String, Option<Resource<Vec<u8>>>>>,
pub access_tokens: TtlDashMap<u32, Arc<AccessToken>>,
pub permissions: ADashMap<u32, Arc<RolePermissions>>,
pub permissions_version: AtomicU8,
@ -405,6 +411,7 @@ impl Clone for Security {
self.permissions_version
.load(std::sync::atomic::Ordering::Relaxed),
),
logos: Mutex::new(self.logos.lock().clone()),
}
}
}

View file

@ -9,7 +9,6 @@ use rustls::sign::CertifiedKey;
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use std::sync::Arc;
use std::time::{Duration, Instant};
use utils::suffixlist::DomainPart;
use x509_parser::parse_x509_certificate;
use crate::listener::acme::directory::Identifier;
@ -241,11 +240,10 @@ impl Core {
let domain = domain.strip_prefix("*.").unwrap_or(&domain);
let name = format!("_acme-challenge.{}", domain);
let origin = origin
.clone()
.or_else(|| {
self.smtp.resolvers.psl.domain_part(domain, DomainPart::Sld)
})
.unwrap_or_else(|| domain.to_string());
.as_deref()
.or_else(|| psl::domain_str(domain))
.unwrap_or(domain)
.to_string();
// First try deleting the record
if let Err(err) = updater.delete(&name, &origin).await {

View file

@ -5,6 +5,7 @@
*/
use std::{
borrow::Cow,
io::{self, Cursor, Read},
path::PathBuf,
};
@ -22,12 +23,21 @@ pub struct WebAdminManager {
routes: ArcSwap<AHashMap<String, Resource<PathBuf>>>,
}
#[derive(Default)]
#[derive(Default, Clone)]
pub struct Resource<T> {
pub content_type: &'static str,
pub content_type: Cow<'static, str>,
pub contents: T,
}
impl<T> Resource<T> {
pub fn new(content_type: impl Into<Cow<'static, str>>, contents: T) -> Self {
Self {
content_type: content_type.into(),
contents,
}
}
}
impl WebAdminManager {
pub fn new() -> Self {
Self {
@ -42,7 +52,7 @@ impl WebAdminManager {
tokio::fs::read(&resource.contents)
.await
.map(|contents| Resource {
content_type: resource.content_type,
content_type: resource.content_type.clone(),
contents,
})
.map_err(|err| {
@ -114,7 +124,8 @@ impl WebAdminManager {
"svg" => "image/svg+xml",
"ico" => "image/x-icon",
_ => "application/octet-stream",
},
}
.into(),
contents: path,
};

View file

@ -9,10 +9,10 @@ use unicode_security::MixedScript;
pub fn fn_is_ascii<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
match &v[0] {
Variable::String(s) => s.chars().all(|c| c.is_ascii()),
Variable::String(s) => s.is_ascii(),
Variable::Integer(_) | Variable::Float(_) => true,
Variable::Array(a) => a.iter().all(|v| match v {
Variable::String(s) => s.chars().all(|c| c.is_ascii()),
Variable::String(s) => s.is_ascii(),
_ => true,
}),
}

View file

@ -63,10 +63,7 @@ async fn train(ctx: PluginContext<'_>, is_train: bool) -> trc::Result<Variable>
// Train the model
let mut model = BayesModel::default();
model.train(
OsbTokenizer::new(
BayesTokenizer::new(text.as_ref(), &ctx.core.smtp.resolvers.psl),
5,
),
OsbTokenizer::new(BayesTokenizer::new(text.as_ref()), 5),
is_spam,
);
if model.weights.is_empty() {
@ -187,10 +184,7 @@ pub async fn exec_classify(ctx: PluginContext<'_>) -> trc::Result<Variable> {
// Classify the text
let mut tokens = Vec::new();
for token in OsbTokenizer::<_, TokenHash>::new(
BayesTokenizer::new(text.as_ref(), &ctx.core.smtp.resolvers.psl),
5,
) {
for token in OsbTokenizer::<_, TokenHash>::new(BayesTokenizer::new(text.as_ref()), 5) {
let weights = bayes_cache.get_or_update(token.inner, store).await?;
tokens.push(OsbToken {
inner: weights,

View file

@ -18,7 +18,6 @@ use mail_parser::{decoders::html::add_html_token, Message, PartType};
use nlp::tokenizers::types::{TokenType, TypesTokenizer};
use sha1::{Digest, Sha1};
use tokio::net::UdpSocket;
use utils::suffixlist::PublicSuffix;
const MIN_LINE_LENGTH: usize = 8;
const ATOMIC_NUM_LINES: usize = 4;
@ -47,9 +46,7 @@ pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
}
// Hash message
let request = ctx
.message
.pyzor_check_message(&ctx.core.smtp.resolvers.psl);
let request = ctx.message.pyzor_check_message();
#[cfg(feature = "test_mode")]
{
@ -161,15 +158,15 @@ async fn pyzor_send_message(
}
trait PyzorDigest<W: Write> {
fn pyzor_digest(&self, writer: W, psl: &PublicSuffix) -> W;
fn pyzor_digest(&self, writer: W) -> W;
}
pub trait PyzorCheck {
fn pyzor_check_message(&self, psl: &PublicSuffix) -> String;
fn pyzor_check_message(&self) -> String;
}
impl<'x, W: Write> PyzorDigest<W> for Message<'x> {
fn pyzor_digest(&self, writer: W, psl: &PublicSuffix) -> W {
fn pyzor_digest(&self, writer: W) -> W {
let parts = self
.parts
.iter()
@ -180,33 +177,27 @@ impl<'x, W: Write> PyzorDigest<W> for Message<'x> {
})
.collect::<Vec<Cow<str>>>();
pyzor_digest(writer, parts.iter().flat_map(|text| text.lines()), psl)
pyzor_digest(writer, parts.iter().flat_map(|text| text.lines()))
}
}
impl<'x> PyzorCheck for Message<'x> {
fn pyzor_check_message(&self, psl: &PublicSuffix) -> String {
fn pyzor_check_message(&self) -> String {
let time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
pyzor_create_message(
self,
psl,
time,
(time & 0xFFFF) as u16 ^ ((time >> 16) & 0xFFFF) as u16,
)
}
}
fn pyzor_create_message(
message: &Message<'_>,
psl: &PublicSuffix,
time: u64,
thread: u16,
) -> String {
fn pyzor_create_message(message: &Message<'_>, time: u64, thread: u16) -> String {
// Hash message
let hash = message.pyzor_digest(Sha1::new(), psl).finalize();
let hash = message.pyzor_digest(Sha1::new()).finalize();
// Hash key
let mut hash_key = Sha1::new();
hash_key.update("anonymous:".as_bytes());
@ -223,13 +214,13 @@ fn pyzor_create_message(
// Sign
let mut sig = Sha1::new();
sig.update(msg_hash);
sig.update(&format!(":{time}:{hash_key:x}"));
sig.update(format!(":{time}:{hash_key:x}"));
let sig = sig.finalize();
format!("{message}\nSig: {sig:x}\n")
}
fn pyzor_digest<'x, I, W>(mut writer: W, lines: I, psl: &PublicSuffix) -> W
fn pyzor_digest<'x, I, W>(mut writer: W, lines: I) -> W
where
I: Iterator<Item = &'x str>,
W: Write,
@ -254,7 +245,7 @@ where
}
};
for token in TypesTokenizer::new(line, psl) {
for token in TypesTokenizer::new(line) {
match token.word {
TokenType::Alphabetic(_)
| TokenType::Alphanumeric(_)
@ -448,7 +439,6 @@ mod test {
use mail_parser::MessageParser;
use sha1::Digest;
use sha1::Sha1;
use utils::suffixlist::PublicSuffix;
use super::pyzor_create_message;
use super::pyzor_send_message;
@ -485,11 +475,8 @@ mod test {
#[test]
fn message_pyzor() {
let mut psl = PublicSuffix::default();
psl.suffixes.insert("com".to_string());
let message = pyzor_create_message(
&MessageParser::new().parse(HTML_TEXT_STYLE_SCRIPT).unwrap(),
&psl,
1697468672,
49005,
);
@ -510,9 +497,6 @@ mod test {
#[test]
fn digest_pyzor() {
let mut psl = PublicSuffix::default();
psl.suffixes.insert("com".to_string());
// HTML stripping
assert_eq!(html_to_text(HTML_RAW), HTML_RAW_STRIPED);
@ -531,7 +515,6 @@ mod test {
String::from_utf8(pyzor_digest(
Vec::new(),
format!("Test {strip_me} Test2").lines(),
&psl
))
.unwrap(),
"TestTest2"
@ -543,7 +526,6 @@ mod test {
String::from_utf8(pyzor_digest(
Vec::new(),
concat!("This line is included\n", "not this\n", "This also").lines(),
&psl
))
.unwrap(),
"ThislineisincludedThisalso"
@ -554,7 +536,6 @@ mod test {
String::from_utf8(pyzor_digest(
Vec::new(),
"All this message\nShould be included\nIn the digest".lines(),
&psl
))
.unwrap(),
"AllthismessageShouldbeincludedInthedigest"
@ -570,7 +551,7 @@ mod test {
expected += format!("Line{i}testtesttest").as_str();
}
assert_eq!(
String::from_utf8(pyzor_digest(Vec::new(), text.lines(), &psl)).unwrap(),
String::from_utf8(pyzor_digest(Vec::new(), text.lines(),)).unwrap(),
expected
);
@ -602,7 +583,7 @@ mod test {
MessageParser::new()
.parse(input)
.unwrap()
.pyzor_digest(Vec::new(), &psl)
.pyzor_digest(Vec::new(),)
)
.unwrap(),
expected,
@ -617,7 +598,7 @@ mod test {
MessageParser::new()
.parse(HTML_TEXT_STYLE_SCRIPT)
.unwrap()
.pyzor_digest(Sha1::new(), &psl)
.pyzor_digest(Sha1::new(),)
.finalize()
),
"b2c27325a034c581df0c9ef37e4a0d63208a3e7e",

View file

@ -6,7 +6,6 @@
use nlp::tokenizers::types::{TokenType, TypesTokenizer};
use sieve::{runtime::Variable, FunctionMap};
use utils::suffixlist::DomainPart;
use crate::scripts::functions::{html::html_to_tokens, text::tokenize_words, ApplyString};
@ -33,7 +32,7 @@ pub fn exec_tokenize(ctx: PluginContext<'_>) -> trc::Result<Variable> {
Ok(match v.remove(0) {
v @ (Variable::String(_) | Variable::Array(_)) => {
TypesTokenizer::new(v.to_string().as_ref(), &ctx.core.smtp.resolvers.psl)
TypesTokenizer::new(v.to_string().as_ref())
.tokenize_numbers(false)
.tokenize_urls(urls)
.tokenize_urls_without_scheme(urls_without_scheme)
@ -53,6 +52,12 @@ pub fn exec_tokenize(ctx: PluginContext<'_>) -> trc::Result<Variable> {
})
}
enum DomainPart {
Sld,
Tld,
Host,
}
pub fn exec_domain_part(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let v = ctx.arguments;
let part = match v[1].to_string().as_ref() {
@ -63,11 +68,11 @@ pub fn exec_domain_part(ctx: PluginContext<'_>) -> trc::Result<Variable> {
};
Ok(v[0].transform(|domain| {
ctx.core
.smtp
.resolvers
.psl
.domain_part(domain, part)
match part {
DomainPart::Sld => psl::domain_str(domain),
DomainPart::Tld => domain.rsplit_once('.').map(|(_, tld)| tld),
DomainPart::Host => domain.split_once('.').map(|(host, _)| host),
}
.map(Variable::from)
.unwrap_or_default()
}))

View file

@ -1,6 +1,6 @@
[package]
name = "directory"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"

View file

@ -405,7 +405,7 @@ impl ManageDirectory for Store {
principal_id: MaybeDynamicId::Static(member.id),
member_of: MaybeDynamicId::Dynamic(0),
}),
vec![member.typ as u8],
vec![principal.typ as u8],
);
batch.set(
ValueClass::Directory(DirectoryClass::Members {
@ -460,7 +460,15 @@ impl ManageDirectory for Store {
.list_principals(
None,
principal.id().into(),
&[],
&[
Type::Individual,
Type::Group,
Type::Role,
Type::List,
Type::Resource,
Type::Other,
Type::Location,
],
&[PrincipalField::Name],
0,
0,
@ -742,13 +750,16 @@ impl ManageDirectory for Store {
));
}
match principal.inner.tenant() {
Some(old_tenant_id) if old_tenant_id != tenant_info.id => {
if principal.inner.tenant() == Some(tenant_info.id) {
continue;
}
// Update quota
if let Some(used_quota) = used_quota {
batch
.add(DirectoryClass::UsedQuota(old_tenant_id), -used_quota)
.add(DirectoryClass::UsedQuota(tenant_info.id), used_quota);
if let Some(old_tenant_id) = principal.inner.tenant() {
batch.add(DirectoryClass::UsedQuota(old_tenant_id), -used_quota);
}
batch.add(DirectoryClass::UsedQuota(tenant_info.id), used_quota);
}
principal.inner.set(PrincipalField::Tenant, tenant_info.id);
@ -758,9 +769,6 @@ impl ManageDirectory for Store {
tenant_info.id.into(),
)
.serialize();
}
_ => continue,
}
} else if let Some(tenant_id) = principal.inner.tenant() {
// Update quota
if let Some(used_quota) = used_quota {
@ -826,15 +834,13 @@ impl ManageDirectory for Store {
}
(
PrincipalAction::Set,
PrincipalField::Description,
PrincipalValue::String(description),
PrincipalField::Description | PrincipalField::Picture,
PrincipalValue::String(value),
) => {
if !description.is_empty() {
principal
.inner
.set(PrincipalField::Description, description);
if !value.is_empty() {
principal.inner.set(change.field, value);
} else {
principal.inner.remove(PrincipalField::Description);
principal.inner.remove(change.field);
}
}
(PrincipalAction::Set, PrincipalField::Quota, PrincipalValue::Integer(quota))
@ -1118,7 +1124,7 @@ impl ManageDirectory for Store {
principal_id: MaybeDynamicId::Static(member_info.id),
member_of: MaybeDynamicId::Static(principal_id),
}),
vec![member_info.typ as u8],
vec![principal.inner.typ as u8],
);
batch.set(
ValueClass::Directory(DirectoryClass::Members {
@ -1180,7 +1186,7 @@ impl ManageDirectory for Store {
principal_id: MaybeDynamicId::Static(member_info.id),
member_of: MaybeDynamicId::Static(principal_id),
}),
vec![member_info.typ as u8],
vec![principal.inner.typ as u8],
);
batch.set(
ValueClass::Directory(DirectoryClass::Members {
@ -1561,25 +1567,24 @@ impl ManageDirectory for Store {
let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::EmailToId(
vec![u8::MAX; 10],
)));
let mut results = Vec::new();
let domain_name = principal.name();
let mut total: u64 = 0;
self.iterate(
IterateParams::new(from_key, to_key).no_values(),
|key, _| {
let email = std::str::from_utf8(key.get(1..).unwrap_or_default())
.unwrap_or_default();
if email
if std::str::from_utf8(key.get(1..).unwrap_or_default())
.unwrap_or_default()
.rsplit_once('@')
.map_or(false, |(_, domain)| domain == domain_name)
{
results.push(email.to_string());
total += 1;
}
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
principal.set(PrincipalField::Members, results);
principal.set(PrincipalField::Members, total);
}
Type::Tenant => {
let from_key =
@ -1587,25 +1592,23 @@ impl ManageDirectory for Store {
let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(
vec![u8::MAX; 10],
)));
let mut results = Vec::new();
self.iterate(IterateParams::new(from_key, to_key), |key, value| {
let mut total: u64 = 0;
self.iterate(IterateParams::new(from_key, to_key), |_, value| {
let pinfo =
PrincipalInfo::deserialize(value).caused_by(trc::location!())?;
if pinfo.typ == Type::Individual
&& pinfo.has_tenant_access(Some(principal.id))
{
results.push(
std::str::from_utf8(key.get(1..).unwrap_or_default())
.unwrap_or_default()
.to_string(),
);
total += 1;
}
Ok(true)
})
.await
.caused_by(trc::location!())?;
principal.set(PrincipalField::Members, results);
principal.set(PrincipalField::Members, total);
}
_ => {}
}

View file

@ -95,6 +95,7 @@ impl PrincipalInfo {
pub fn has_tenant_access(&self, tenant_id: Option<u32>) -> bool {
tenant_id.map_or(true, |tenant_id| {
self.tenant.map_or(false, |t| tenant_id == t)
|| (self.typ == Type::Tenant && self.id == tenant_id)
})
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "imap"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"

View file

@ -1,6 +1,6 @@
[package]
name = "jmap"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"
@ -17,7 +17,7 @@ smtp-proto = { version = "0.1" }
mail-parser = { version = "0.9", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
mail-auth = { version = "0.4", features = ["generate"] }
mail-auth = { version = "0.5", features = ["generate"] }
sieve-rs = { version = "0.5" }
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"

View file

@ -67,11 +67,10 @@ impl JMAP {
);
config.push_str("</clientConfig>\n");
Ok(Resource {
content_type: "application/xml; charset=utf-8",
contents: config.into_bytes(),
}
.into_http_response())
Ok(
Resource::new("application/xml; charset=utf-8", config.into_bytes())
.into_http_response(),
)
}
pub async fn handle_autodiscover_request(
@ -147,11 +146,10 @@ impl JMAP {
let _ = writeln!(&mut config, "\t</Response>");
let _ = writeln!(&mut config, "</Autodiscover>");
Ok(Resource {
content_type: "application/xml; charset=utf-8",
contents: config.into_bytes(),
}
.into_http_response())
Ok(
Resource::new("application/xml; charset=utf-8", config.into_bytes())
.into_http_response(),
)
}
async fn autoconfig_parameters<'x>(

View file

@ -223,10 +223,7 @@ impl JMAP {
.key_get::<String>(format!("acme:{token}").into_bytes())
.await?
{
Some(proof) => Ok(Resource {
content_type: "text/plain",
contents: proof.into_bytes(),
}
Some(proof) => Ok(Resource::new("text/plain", proof.into_bytes())
.into_http_response()),
None => Err(trc::ResourceEvent::NotFound.into_err()),
};
@ -234,10 +231,7 @@ impl JMAP {
}
("mta-sts.txt", &Method::GET) => {
if let Some(policy) = self.core.build_mta_sts_policy() {
return Ok(Resource {
content_type: "text/plain",
contents: policy.to_string().into_bytes(),
}
return Ok(Resource::new("text/plain", policy.to_string().into_bytes())
.into_http_response());
} else {
return Err(trc::ResourceEvent::NotFound.into_err());
@ -357,11 +351,10 @@ impl JMAP {
}
}
"robots.txt" => {
return Ok(Resource {
content_type: "text/plain",
contents: b"User-agent: *\nDisallow: /\n".to_vec(),
}
.into_http_response());
return Ok(
Resource::new("text/plain", b"User-agent: *\nDisallow: /\n".to_vec())
.into_http_response(),
);
}
"healthz" => match path.next().unwrap_or_default() {
"live" => {
@ -394,10 +387,10 @@ impl JMAP {
}
}
return Ok(Resource {
content_type: "text/plain; version=0.0.4",
contents: self.core.export_prometheus_metrics().await?.into_bytes(),
}
return Ok(Resource::new(
"text/plain; version=0.0.4",
self.core.export_prometheus_metrics().await?.into_bytes(),
)
.into_http_response());
}
}
@ -406,6 +399,42 @@ impl JMAP {
}
_ => (),
},
#[cfg(feature = "enterprise")]
"logo.svg" if self.core.is_enterprise_edition() => {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
match self
.core
.logo_resource(
req.headers()
.get(header::HOST)
.and_then(|h| h.to_str().ok())
.map(|h| h.rsplit_once(':').map_or(h, |(h, _)| h))
.unwrap_or_default(),
)
.await
{
Ok(Some(resource)) => {
return Ok(resource.into_http_response());
}
Ok(None) => (),
Err(err) => {
trc::error!(err.span_id(session.session_id));
}
}
let resource = self.inner.webadmin.get("logo.svg").await?;
return if !resource.is_empty() {
Ok(resource.into_http_response())
} else {
Err(trc::ResourceEvent::NotFound.into_err())
};
// SPDX-SnippetEnd
}
_ => {
let path = req.uri().path();
let resource = self
@ -895,7 +924,7 @@ impl ToRequestError for trc::Error {
},
trc::EventType::Auth(cause) => match cause {
trc::AuthEvent::MissingTotp => {
RequestError::blank(403, "TOTP code required", cause.message())
RequestError::blank(402, "TOTP code required", cause.message())
}
trc::AuthEvent::TooManyAttempts => RequestError::too_many_auth_attempts(),
_ => RequestError::unauthorized(),

View file

@ -115,10 +115,10 @@ impl JMAP {
}
for signature_id in signature_ids {
if let (Some(algo), Some(pk), Some(selector)) = (
keys.value(&format!("{signature_id}.algorithm"))
keys.value(format!("{signature_id}.algorithm"))
.and_then(|algo| algo.parse::<Algorithm>().ok()),
keys.value(&format!("{signature_id}.private-key")),
keys.value(&format!("{signature_id}.selector")),
keys.value(format!("{signature_id}.private-key")),
keys.value(format!("{signature_id}.selector")),
) {
match obtain_dkim_public_key(algo, pk) {
Ok(public) => {

View file

@ -108,6 +108,7 @@ impl JMAP {
let filter = params.get("filter");
let page: usize = params.parse("page").unwrap_or(0);
let limit: usize = params.parse("limit").unwrap_or(0);
let count = params.get("count").is_some();
// Parse types
let mut types = Vec::new();
@ -181,13 +182,17 @@ impl JMAP {
return Err(manage::enterprise());
}
let principals = self
let mut principals = self
.core
.storage
.data
.list_principals(filter, tenant, &types, &fields, page, limit)
.await?;
if count {
principals.items.clear();
}
Ok(JsonResponse::new(json!({
"data": principals,
}))
@ -356,9 +361,6 @@ impl JMAP {
} else {
expire_token = true;
}
if change.field == PrincipalField::Roles {
needs_assert = true;
}
}
}
}

View file

@ -69,11 +69,7 @@ impl JMAP {
.to_vec()
};
Ok(Resource {
content_type: "application/octet-stream",
contents,
}
.into_http_response())
Ok(Resource::new("application/octet-stream", contents).into_http_response())
}
(Some("purge"), Some("blob"), _, &Method::GET) => {
// Validate the access token

View file

@ -200,10 +200,9 @@ impl JMAP {
Property::Value,
)
.await?
.map(|value| {
.inspect(|value| {
was_active = value.inner.properties.get(&Property::IsActive)
== Some(&Value::Bool(true));
value
})
.ok_or_else(|| {
trc::StoreEvent::NotFound

View file

@ -7,7 +7,7 @@ homepage = "https://stalw.art"
keywords = ["imap", "jmap", "smtp", "email", "mail", "server"]
categories = ["email"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"

View file

@ -1,6 +1,6 @@
[package]
name = "managesieve"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"

View file

@ -1,11 +1,10 @@
[package]
name = "nlp"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"
[dependencies]
utils = { path = "../utils" }
xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
farmhash = "1.1.5"
siphasher = "1.0"
@ -20,6 +19,7 @@ jieba-rs = "0.7" # Chinese stemmer
phf = { version = "0.11", features = ["macros"] }
lru-cache = "0.1.2"
parking_lot = "0.12.1"
psl = "2"
[features]
test_mode = []

View file

@ -6,8 +6,6 @@
use std::borrow::Cow;
use utils::suffixlist::PublicSuffix;
use crate::{
language::{
detect::{LanguageDetector, MIN_LANGUAGE_SCORE},
@ -21,9 +19,9 @@ use crate::{
},
};
pub struct BayesTokenizer<'x, 'y> {
pub struct BayesTokenizer<'x> {
text: &'x str,
tokenizer: TypesTokenizer<'x, 'y>,
tokenizer: TypesTokenizer<'x>,
stemmer: Stemmer,
stop_words: Option<&'static phf::Set<&'static str>>,
tokens: Vec<Cow<'x, str>>,
@ -36,8 +34,8 @@ enum Stemmer {
None,
}
impl<'x, 'y> BayesTokenizer<'x, 'y> {
pub fn new(text: &'x str, suffixes: &'y PublicSuffix) -> Self {
impl<'x> BayesTokenizer<'x> {
pub fn new(text: &'x str) -> Self {
// Detect language
let (mut language, score) =
LanguageDetector::detect_single(text).unwrap_or((Language::English, 1.0));
@ -47,7 +45,7 @@ impl<'x, 'y> BayesTokenizer<'x, 'y> {
Self {
text,
tokenizer: TypesTokenizer::new(text, suffixes),
tokenizer: TypesTokenizer::new(text),
stemmer: match language {
Language::Mandarin => Stemmer::Mandarin,
Language::Japanese => Stemmer::Japanese,
@ -61,7 +59,7 @@ impl<'x, 'y> BayesTokenizer<'x, 'y> {
}
}
impl<'x, 'y> Iterator for BayesTokenizer<'x, 'y> {
impl<'x> Iterator for BayesTokenizer<'x> {
type Item = Cow<'x, str>;
fn next(&mut self) -> Option<Self::Item> {
@ -1149,8 +1147,6 @@ pub static SYMBOLS: phf::Set<char> = phf::phf_set! {
mod tests {
use std::borrow::Cow;
use utils::suffixlist::PublicSuffix;
use crate::bayes::tokenize::BayesTokenizer;
#[test]
@ -1236,10 +1232,8 @@ mod tests {
("시작이 반이다", vec!["시작이", "반이다"]),
];
let suffixes = PublicSuffix::default();
for (input, expect) in inputs.iter() {
let input = BayesTokenizer::new(input, &suffixes).collect::<Vec<_>>();
let input = BayesTokenizer::new(input).collect::<Vec<_>>();
let expect = expect.iter().copied().map(Cow::from).collect::<Vec<_>>();
assert_eq!(input, expect,);

View file

@ -6,8 +6,6 @@ pub mod tokenizers;
mod test {
use std::fs;
use utils::suffixlist::PublicSuffix;
use crate::{
bayes::{tokenize::BayesTokenizer, BayesClassifier, BayesModel},
tokenizers::osb::{OsbToken, OsbTokenizer},
@ -19,16 +17,12 @@ mod test {
let db =
fs::read_to_string("/Users/me/code/mail-server/_ignore/spam_or_not_spam.csv").unwrap();
let mut bayes = BayesModel::default();
let suffixes = PublicSuffix::default();
for line in db.lines() {
let (text, is_spam) = line.rsplit_once(',').unwrap();
let is_spam = is_spam == "1";
bayes.train(
OsbTokenizer::new(BayesTokenizer::new(text, &suffixes), 5),
is_spam,
);
bayes.train(OsbTokenizer::new(BayesTokenizer::new(text), 5), is_spam);
}
println!("Ham: {} Spam: {}", bayes.ham_learns, bayes.spam_learns,);
fs::write(
@ -46,7 +40,6 @@ mod test {
)
.unwrap();
let bayes = BayesClassifier::new();
let suffixes = PublicSuffix::default();
for text in [
"i am attaching to this email a presentation to integrate the spreadsheet into our server",
@ -58,7 +51,7 @@ mod test {
"{:?} -> {}",
text,
bayes
.classify(OsbTokenizer::new(BayesTokenizer::new(text, &suffixes), 5).filter_map(|x| model.weights.get(&x.inner).map(|w| {
.classify(OsbTokenizer::new(BayesTokenizer::new(text), 5).filter_map(|x| model.weights.get(&x.inner).map(|w| {
OsbToken {
idx: x.idx,
inner: *w,

View file

@ -37,7 +37,7 @@ impl<'x> InnerToken<'x> for Cow<'x, str> {
}
fn is_alphabetic_8bit(&self) -> bool {
!self.chars().all(|c| c.is_ascii())
!self.is_ascii()
}
fn unwrap_alphabetic(self) -> Cow<'x, str> {

View file

@ -6,13 +6,10 @@
use std::str::CharIndices;
use utils::suffixlist::PublicSuffix;
use super::Token;
pub struct TypesTokenizer<'x, 'y> {
pub struct TypesTokenizer<'x> {
text: &'x str,
suffixes: &'y PublicSuffix,
iter: CharIndices<'x>,
tokens: Vec<Token<TokenType<&'x str>>>,
peek_pos: usize,
@ -45,7 +42,7 @@ pub enum TokenType<T> {
impl Copy for Token<TokenType<&'_ str>> {}
impl<'x, 'y> Iterator for TypesTokenizer<'x, 'y> {
impl<'x> Iterator for TypesTokenizer<'x> {
type Item = Token<TokenType<&'x str>>;
fn next(&mut self) -> Option<Self::Item> {
@ -58,7 +55,7 @@ impl<'x, 'y> Iterator for TypesTokenizer<'x, 'y> {
&& matches!(
token.word,
TokenType::Alphabetic(t) | TokenType::Alphanumeric(t)
if t.len() <= 8 && t.chars().all(|c| c.is_ascii()))
if t.len() <= 8 && t.is_ascii())
&& self.try_skip_url_scheme()
{
if let Some(url) = self.try_parse_url(token.into()) {
@ -111,15 +108,14 @@ impl<'x, 'y> Iterator for TypesTokenizer<'x, 'y> {
}
}
impl<'x, 'y> TypesTokenizer<'x, 'y> {
pub fn new(text: &'x str, suffixes: &'y PublicSuffix) -> Self {
impl<'x> TypesTokenizer<'x> {
pub fn new(text: &'x str) -> Self {
Self {
text,
iter: text.char_indices(),
tokens: Vec::new(),
eof: false,
peek_pos: 0,
suffixes,
last_ch_is_space: false,
last_token_is_dot: false,
tokenize_urls: true,
@ -327,8 +323,13 @@ impl<'x, 'y> TypesTokenizer<'x, 'y> {
while let Some(token) = self.peek() {
match token.word {
TokenType::Alphabetic(text) | TokenType::Alphanumeric(text) => {
last_label_is_tld =
text.len() >= 2 && self.suffixes.contains(&text.to_ascii_lowercase());
last_label_is_tld = text.len() >= 2
&& psl::Psl::find(
&psl::List,
[text.to_ascii_lowercase().as_bytes()].into_iter(),
)
.typ
.is_some();
text_count += 1;
}
TokenType::Integer(text) => {
@ -552,8 +553,13 @@ impl<'x, 'y> TypesTokenizer<'x, 'y> {
.map(|(from, to)| (from, to, true));
}
TokenType::Alphabetic(text) | TokenType::Alphanumeric(text) if text.len() <= 63 => {
last_label_is_tld =
text.len() >= 2 && self.suffixes.contains(&text.to_ascii_lowercase());
last_label_is_tld = text.len() >= 2
&& psl::Psl::find(
&psl::List,
[text.to_ascii_lowercase().as_bytes()].into_iter(),
)
.typ
.is_some();
has_alpha = true;
last_ch = 0;
}
@ -691,7 +697,7 @@ impl<'x, 'y> TypesTokenizer<'x, 'y> {
(TokenType::Punctuation('/'), State::Slash2) => return true,
(TokenType::Punctuation('+'), State::None) => State::PlusAlpha,
(TokenType::Alphabetic(t) | TokenType::Alphanumeric(t), State::PlusAlpha)
if t.chars().all(|c| c.is_ascii()) =>
if t.is_ascii() =>
{
State::Colon
}
@ -748,17 +754,10 @@ impl<T> TokenType<T> {
#[cfg(test)]
mod test {
use utils::suffixlist::PublicSuffix;
use super::{TokenType, TypesTokenizer};
#[test]
fn type_tokenizer() {
let mut suffixes = PublicSuffix::default();
suffixes.suffixes.insert("com".to_string());
suffixes.suffixes.insert("co".to_string());
suffixes.suffixes.insert("org".to_string());
// Credits: test suite from linkify crate
for (text, expected) in [
("", vec![]),
@ -2862,7 +2861,7 @@ mod test {
],
),
] {
let result = TypesTokenizer::new(text, &suffixes)
let result = TypesTokenizer::new(text)
.map(|t| t.word)
.collect::<Vec<_>>();

View file

@ -1,6 +1,6 @@
[package]
name = "pop3"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"

View file

@ -7,7 +7,7 @@ homepage = "https://stalw.art/smtp"
keywords = ["smtp", "email", "mail", "server"]
categories = ["email"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"
@ -18,7 +18,7 @@ nlp = { path = "../nlp" }
directory = { path = "../directory" }
common = { path = "../common" }
trc = { path = "../trc" }
mail-auth = { version = "0.4" }
mail-auth = { version = "0.5" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }

View file

@ -14,6 +14,7 @@ use std::{
use common::{
config::smtp::{auth::VerifyStrategy, session::Stage},
listener::SessionStream,
psl,
scripts::ScriptModification,
};
use mail_auth::{
@ -249,6 +250,7 @@ impl<T: SessionStream> Session<T> {
&self.data.helo_domain
},
spf_output,
|domain| psl::domain_str(domain).unwrap_or(domain),
)
.await;

View file

@ -1,6 +1,6 @@
[package]
name = "store"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"

View file

@ -110,7 +110,7 @@ impl MysqlStore {
SUBSPACE_TELEMETRY_INDEX,
] {
let table = char::from(table);
conn.query_drop(&format!(
conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {table} (
k TINYBLOB,
v MEDIUMBLOB NOT NULL,
@ -121,7 +121,7 @@ impl MysqlStore {
.map_err(into_error)?;
}
conn.query_drop(&format!(
conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {} (
k TINYBLOB,
v LONGBLOB NOT NULL,
@ -139,7 +139,7 @@ impl MysqlStore {
SUBSPACE_BITMAP_TEXT,
] {
let table = char::from(table);
conn.query_drop(&format!(
conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {table} (
k BLOB,
PRIMARY KEY (k(400))
@ -150,7 +150,7 @@ impl MysqlStore {
}
for table in [SUBSPACE_COUNTER, SUBSPACE_QUOTA] {
conn.query_drop(&format!(
conn.query_drop(format!(
"CREATE TABLE IF NOT EXISTS {} (
k TINYBLOB,
v BIGINT NOT NULL DEFAULT 0,

View file

@ -22,7 +22,7 @@ impl MysqlStore {
{
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
let s = conn
.prep(&format!(
.prep(format!(
"SELECT v FROM {} WHERE k = ?",
char::from(key.subspace())
))
@ -54,7 +54,7 @@ impl MysqlStore {
let mut bm = RoaringBitmap::new();
let s = conn
.prep(&format!("SELECT k FROM {table} WHERE k >= ? AND k <= ?"))
.prep(format!("SELECT k FROM {table} WHERE k >= ? AND k <= ?"))
.await
.map_err(into_error)?;
let mut rows = conn
@ -147,7 +147,7 @@ impl MysqlStore {
let key = key.serialize(0);
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
let s = conn
.prep(&format!("SELECT v FROM {table} WHERE k = ?"))
.prep(format!("SELECT v FROM {table} WHERE k = ?"))
.await
.map_err(into_error)?;
match conn.exec_first::<i64, _, _>(&s, (key,)).await {

View file

@ -108,10 +108,10 @@ impl MysqlStore {
let exists = asserted_values.get(&key);
let s = if let Some(exists) = exists {
if *exists {
trx.prep(&format!("UPDATE {} SET v = :v WHERE k = :k", table))
trx.prep(format!("UPDATE {} SET v = :v WHERE k = :k", table))
.await?
} else {
trx.prep(&format!(
trx.prep(format!(
"INSERT INTO {} (k, v) VALUES (:k, :v)",
table
))
@ -120,7 +120,7 @@ impl MysqlStore {
} else {
trx
.prep(
&format!("INSERT INTO {} (k, v) VALUES (:k, :v) ON DUPLICATE KEY UPDATE v = VALUES(v)", table),
format!("INSERT INTO {} (k, v) VALUES (:k, :v) ON DUPLICATE KEY UPDATE v = VALUES(v)", table),
)
.await?
};
@ -149,7 +149,7 @@ impl MysqlStore {
ValueOp::AtomicAdd(by) => {
if *by >= 0 {
let s = trx
.prep(&format!(
.prep(format!(
concat!(
"INSERT INTO {} (k, v) VALUES (?, ?) ",
"ON DUPLICATE KEY UPDATE v = v + VALUES(v)"
@ -160,14 +160,14 @@ impl MysqlStore {
trx.exec_drop(&s, (key, by)).await?;
} else {
let s = trx
.prep(&format!("UPDATE {table} SET v = v + ? WHERE k = ?"))
.prep(format!("UPDATE {table} SET v = v + ? WHERE k = ?"))
.await?;
trx.exec_drop(&s, (by, key)).await?;
}
}
ValueOp::AddAndGet(by) => {
let s = trx
.prep(&format!(
.prep(format!(
concat!(
"INSERT INTO {} (k, v) VALUES (:k, LAST_INSERT_ID(:v)) ",
"ON DUPLICATE KEY UPDATE v = LAST_INSERT_ID(v + :v)"
@ -190,7 +190,7 @@ impl MysqlStore {
}
ValueOp::Clear => {
let s = trx
.prep(&format!("DELETE FROM {} WHERE k = ?", table))
.prep(format!("DELETE FROM {} WHERE k = ?", table))
.await?;
trx.exec_drop(&s, (key,)).await?;
}
@ -256,11 +256,11 @@ impl MysqlStore {
if is_document_id {
trx.prep("INSERT INTO b (k) VALUES (?)").await?
} else {
trx.prep(&format!("INSERT IGNORE INTO {} (k) VALUES (?)", table))
trx.prep(format!("INSERT IGNORE INTO {} (k) VALUES (?)", table))
.await?
}
} else {
trx.prep(&format!("DELETE FROM {} WHERE k = ?", table))
trx.prep(format!("DELETE FROM {} WHERE k = ?", table))
.await?
};
@ -301,7 +301,7 @@ impl MysqlStore {
let table = char::from(class.subspace(collection));
let s = trx
.prep(&format!("SELECT v FROM {} WHERE k = ? FOR UPDATE", table))
.prep(format!("SELECT v FROM {} WHERE k = ? FOR UPDATE", table))
.await?;
let (exists, matches) = trx
.exec_first::<Vec<u8>, _, _>(&s, (&key,))
@ -324,7 +324,7 @@ impl MysqlStore {
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
for subspace in [SUBSPACE_QUOTA, SUBSPACE_COUNTER] {
let s = conn
.prep(&format!("DELETE FROM {} WHERE v = 0", char::from(subspace),))
.prep(format!("DELETE FROM {} WHERE v = 0", char::from(subspace),))
.await
.map_err(into_error)?;
conn.exec_drop(&s, ()).await.map_err(into_error)?;
@ -337,7 +337,7 @@ impl MysqlStore {
let mut conn = self.conn_pool.get_conn().await.map_err(into_error)?;
let s = conn
.prep(&format!(
.prep(format!(
"DELETE FROM {} WHERE k >= ? AND k < ?",
char::from(from.subspace()),
))

View file

@ -1,12 +1,12 @@
[package]
name = "trc"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"
[dependencies]
event_macro = { path = "./event-macro" }
mail-auth = { version = "0.4" }
mail-auth = { version = "0.5" }
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
base64 = "0.22.1"
serde = "1.0"

View file

@ -1,6 +1,6 @@
[package]
name = "utils"
version = "0.9.4"
version = "0.10.0"
edition = "2021"
resolver = "2"
@ -12,7 +12,7 @@ rustls-pki-types = { version = "1" }
tokio = { version = "1.23", features = ["net", "macros"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] }
serde = { version = "1.0", features = ["derive"]}
mail-auth = { version = "0.4" }
mail-auth = { version = "0.5" }
smtp-proto = { version = "0.1" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
dashmap = "6.0"

View file

@ -12,7 +12,6 @@ pub mod glob;
pub mod lru_cache;
pub mod map;
pub mod snowflake;
pub mod suffixlist;
pub mod url_params;
use rustls::{

View file

@ -31,7 +31,7 @@ trc = { path = "../crates/trc" }
managesieve = { path = "../crates/managesieve", features = ["test_mode"] }
smtp-proto = { version = "0.1" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
mail-auth = { version = "0.4", features = ["test"] }
mail-auth = { version = "0.5", features = ["test"] }
sieve-rs = { version = "0.5" }
utils = { path = "../crates/utils", features = ["test_mode"] }
jmap-client = { version = "0.3", features = ["websockets", "debug", "async"] }

View file

@ -104,6 +104,7 @@ pub async fn test(params: &mut JMAPTest) {
}
.into(),
metrics_alerts: parse_metric_alerts(&mut config),
logo_url: None,
}
.into();
config.assert_no_errors();
@ -160,6 +161,7 @@ impl EnterpriseCore for Core {
trace_store: None,
metrics_store: None,
metrics_alerts: vec![],
logo_url: None,
}
.into();
self

View file

@ -13,8 +13,6 @@ use directory::{
backend::internal::{PrincipalField, PrincipalUpdate, PrincipalValue},
Permission, Principal, Type,
};
use hyper::header::TE;
use rayon::vec;
use utils::BlobHash;
use crate::jmap::assert_is_empty;
@ -360,12 +358,12 @@ pub async fn test(params: &JMAPTest) {
)
.await
.unwrap()
.expect_error("notFound");
.expect_request_error("Forbidden");
tenant_api
.get::<()>("/api/principal/foobar")
.await
.unwrap()
.expect_error("notFound");
.expect_request_error("Forbidden");
tenant_api
.get::<()>("/api/principal?type=tenant")
.await

View file

@ -131,10 +131,7 @@ pub fn spawn_mock_webhook_endpoint() -> Arc<MockWebhookEndpoint> {
endpoint.events.lock().extend(request.events);
Ok::<_, hyper::Error>(
Resource {
content_type: "application/json",
contents: "[]".to_string().into_bytes(),
}
Resource::new("application/json", "[]".to_string().into_bytes())
.into_http_response().build(),
)
} else {

View file

@ -78,8 +78,8 @@ path = "{PATH}/test_antispam.db"
#url = "redis://127.0.0.1"
[lookup]
"spam-free" = {"gmail.com", "googlemail.com", "yahoomail.com", "*.freemail.org"}
"spam-disposable" = {"guerrillamail.com", "*.disposable.org"}
"spam-free" = {"gmail.com", "googlemail.com", "yahoomail.com", "*freemail.org"}
"spam-disposable" = {"guerrillamail.com", "*disposable.org"}
"spam-redirect" = {"bit.ly", "redirect.io", "redirect.me", "redirect.org", "redirect.com", "redirect.net", "t.ly", "tinyurl.com"}
"spam-dmarc" = {"dmarc-allow.org"}
"spam-spdk" = {"spf-dkim-allow.org"}

View file

@ -809,10 +809,7 @@ pub fn spawn_mock_mta_hook_server() -> watch::Sender<bool> {
let response = handle_mta_hook(request, tests);
Ok::<_, hyper::Error>(
Resource {
content_type: "application/json",
contents: serde_json::to_string(&response).unwrap().into_bytes(),
}
Resource::new("application/json", serde_json::to_string(&response).unwrap().into_bytes())
.into_http_response().build(),
)
}

View file

@ -35,7 +35,6 @@ use mail_auth::{
Resolver, MX,
};
use rustls_pki_types::CertificateDer;
use utils::suffixlist::PublicSuffix;
use crate::smtp::{
inbound::{TestMessage, TestQueueEvent, TestReportingEvent},
@ -260,7 +259,6 @@ async fn dane_test() {
tlsa: LruCache::with_capacity(10),
mta_sts: LruCache::with_capacity(10),
},
psl: PublicSuffix::default(),
};
let r = SMTP {
core: core.into(),