Add LMTP support
This commit is contained in:
commit
6b5b53916e
21 changed files with 1290 additions and 269 deletions
621
Cargo.lock
generated
621
Cargo.lock
generated
|
@ -8,7 +8,7 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "871a574ed52e84ec15e6266d57d477e3e5c396cd86f9b05f2cb629a2c5af2eec"
|
checksum = "871a574ed52e84ec15e6266d57d477e3e5c396cd86f9b05f2cb629a2c5af2eec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom 6.2.1",
|
"nom 6.1.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.15"
|
version = "0.7.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -58,6 +58,17 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compat"
|
name = "async-compat"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -67,10 +78,139 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-executor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
|
||||||
|
dependencies = [
|
||||||
|
"async-task",
|
||||||
|
"concurrent-queue",
|
||||||
|
"fastrand",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-fs"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2"
|
||||||
|
dependencies = [
|
||||||
|
"async-lock",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-global-executor"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd8b508d585e01084059b60f06ade4cb7415cd2e4084b71dd1cb44e7d3fb9880"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-io"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"futures-lite",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"parking",
|
||||||
|
"polling",
|
||||||
|
"slab",
|
||||||
|
"socket2",
|
||||||
|
"waker-fn",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-lock"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-net"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5373304df79b9b4395068fb080369ec7178608827306ce4d081cba51cac551df"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-process"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"blocking",
|
||||||
|
"cfg-if",
|
||||||
|
"event-listener",
|
||||||
|
"futures-lite",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"signal-hook",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-std"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-global-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"gloo-timers",
|
||||||
|
"kv-log-macro",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite 0.2.9",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-task"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.56"
|
version = "0.1.56"
|
||||||
|
@ -82,6 +222,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-waker"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -93,6 +239,40 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auto_enums"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe0dfe45d75158751e195799f47ea02e81f570aa24bc5ef999cdd9e888c4b5c3"
|
||||||
|
dependencies = [
|
||||||
|
"auto_enums_core",
|
||||||
|
"auto_enums_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auto_enums_core"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da47c46001293a2c4b744d731958be22cff408a2ab76e2279328f9713b1267b4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auto_enums_derive"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41aed1da83ecdc799503b7cb94da1b45a34d72b49caf40a61d9cf5b88ec07cfd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"derive_utils",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -165,6 +345,20 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blocking"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-task",
|
||||||
|
"atomic-waker",
|
||||||
|
"fastrand",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "boitalettres"
|
name = "boitalettres"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -184,6 +378,12 @@ dependencies = [
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.3"
|
version = "1.4.3"
|
||||||
|
@ -196,6 +396,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cache-padded"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.73"
|
version = "1.0.73"
|
||||||
|
@ -221,21 +427,22 @@ dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"time",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.1.18"
|
version = "3.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
|
checksum = "6d20de3739b4fb45a17837824f40aa1769cc7655d7a83e68739a77fe7b30c87a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lazy_static",
|
"once_cell",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
|
@ -243,9 +450,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "3.1.18"
|
version = "3.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
|
checksum = "026baf08b89ffbd332836002ec9378ef0e69648cbfadd68af7cd398ca5bf98f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
|
@ -256,13 +463,22 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.2.0"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
|
checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
|
||||||
|
dependencies = [
|
||||||
|
"cache-padded",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -386,6 +602,27 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_utils"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "532b4c15dccee12c7044f1fcad956e98410860b22231e44a3b827464797ca7bf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -427,6 +664,16 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "duplexify"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1cc346cd6db38ceab2d33f59b26024c3ddb8e75f047c6cafbcbc016ea8065d5"
|
||||||
|
dependencies = [
|
||||||
|
"async-std",
|
||||||
|
"pin-project-lite 0.1.12",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "1.5.2"
|
version = "1.5.2"
|
||||||
|
@ -455,6 +702,12 @@ dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "2.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
|
@ -559,6 +812,21 @@ version = "0.3.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-lite"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"memchr",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite 0.2.9",
|
||||||
|
"waker-fn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
|
@ -595,7 +863,7 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
@ -612,13 +880,25 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-timers"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -693,9 +973,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.7"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
|
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -710,7 +990,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http",
|
"http",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -750,7 +1030,7 @@ dependencies = [
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
@ -805,7 +1085,7 @@ dependencies = [
|
||||||
"abnf-core",
|
"abnf-core",
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"nom 6.2.1",
|
"nom 6.1.2",
|
||||||
"rand",
|
"rand",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -852,10 +1132,19 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "k2v-client"
|
name = "k2v-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/garage.git?branch=main#a1abed0378f14792bfc45f98a6abcf91b31cc3fe"
|
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/garage.git?branch=main#d544a0e0e03c9b69b226fb5bba2ce27a7af270ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"http",
|
"http",
|
||||||
|
@ -869,6 +1158,15 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kv-log-macro"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -958,6 +1256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"value-bag",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -970,12 +1269,14 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"boitalettres",
|
"boitalettres",
|
||||||
"clap",
|
"clap",
|
||||||
|
"duplexify",
|
||||||
"futures",
|
"futures",
|
||||||
"hex",
|
"hex",
|
||||||
"im",
|
"im",
|
||||||
"imap-codec",
|
"imap-codec",
|
||||||
"itertools",
|
"itertools",
|
||||||
"k2v-client",
|
"k2v-client",
|
||||||
|
"lazy_static",
|
||||||
"ldap3",
|
"ldap3",
|
||||||
"log",
|
"log",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
@ -987,8 +1288,11 @@ dependencies = [
|
||||||
"rusoto_s3",
|
"rusoto_s3",
|
||||||
"rusoto_signature",
|
"rusoto_signature",
|
||||||
"serde",
|
"serde",
|
||||||
|
"smtp-message",
|
||||||
|
"smtp-server",
|
||||||
"sodiumoxide",
|
"sodiumoxide",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
"tower",
|
"tower",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1015,9 +1319,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.4"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
|
@ -1104,9 +1408,9 @@ checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "6.2.1"
|
version = "6.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
|
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"funty",
|
"funty",
|
||||||
|
@ -1217,6 +1521,12 @@ version = "6.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
|
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -1283,6 +1593,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -1301,6 +1617,19 @@ version = "0.3.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polling"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wepoll-ffi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
@ -1432,15 +1761,24 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.4.6"
|
version = "1.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
|
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
dependencies = [
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.26"
|
version = "0.6.26"
|
||||||
|
@ -1564,7 +1902,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"md-5",
|
"md-5",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"rusoto_credential",
|
"rusoto_credential",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1637,9 +1975,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.9"
|
version = "1.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
|
checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
|
@ -1700,6 +2038,16 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1737,6 +2085,62 @@ version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smol"
|
||||||
|
version = "1.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-executor",
|
||||||
|
"async-fs",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-net",
|
||||||
|
"async-process",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smtp-message"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+http://github.com/Alexis211/kannader?branch=feature/lmtp#8c01360230f21c20d4c2da462dcf62e8a801ce0f"
|
||||||
|
dependencies = [
|
||||||
|
"auto_enums",
|
||||||
|
"futures",
|
||||||
|
"idna",
|
||||||
|
"lazy_static",
|
||||||
|
"nom 6.1.2",
|
||||||
|
"pin-project",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smtp-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+http://github.com/Alexis211/kannader?branch=feature/lmtp#8c01360230f21c20d4c2da462dcf62e8a801ce0f"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
|
"duplexify",
|
||||||
|
"futures",
|
||||||
|
"smol",
|
||||||
|
"smtp-message",
|
||||||
|
"smtp-server-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smtp-server-types"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+http://github.com/Alexis211/kannader?branch=feature/lmtp#8c01360230f21c20d4c2da462dcf62e8a801ce0f"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"smtp-message",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -1852,6 +2256,17 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1869,9 +2284,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.18.2"
|
version = "1.19.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
|
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1880,7 +2295,7 @@ dependencies = [
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
|
@ -1889,9 +2304,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.7.0"
|
version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1910,12 +2325,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
|
checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1938,14 +2353,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c"
|
checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
@ -1970,7 +2386,7 @@ dependencies = [
|
||||||
"hdrhistogram",
|
"hdrhistogram",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"rand",
|
"rand",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -1994,13 +2410,13 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.34"
|
version = "0.1.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
|
checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite 0.2.9",
|
||||||
"tracing-attributes",
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
@ -2018,11 +2434,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.26"
|
version = "0.1.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
|
checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"once_cell",
|
||||||
"valuable",
|
"valuable",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2081,9 +2497,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
|
@ -2118,6 +2534,16 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "value-bag"
|
||||||
|
version = "1.0.0-alpha.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
|
||||||
|
dependencies = [
|
||||||
|
"ctor",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -2130,6 +2556,12 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "waker-fn"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
@ -2153,9 +2585,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
|
@ -2163,6 +2595,91 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wepoll-ffi"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -12,9 +12,12 @@ argon2 = "0.3"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
clap = { version = "3.1.18", features = ["derive", "env"] }
|
clap = { version = "3.1.18", features = ["derive", "env"] }
|
||||||
|
duplexify = "1.1.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
futures = "0.3"
|
||||||
im = "15"
|
im = "15"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
|
lazy_static = "1.4"
|
||||||
ldap3 = { version = "0.10", default-features = false, features = ["tls"] }
|
ldap3 = { version = "0.10", default-features = false, features = ["tls"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
|
@ -27,25 +30,19 @@ rand = "0.8.5"
|
||||||
rmp-serde = "0.15"
|
rmp-serde = "0.15"
|
||||||
rpassword = "6.0"
|
rpassword = "6.0"
|
||||||
sodiumoxide = "0.2"
|
sodiumoxide = "0.2"
|
||||||
tokio = "1.17.0"
|
tokio = { version = "1.18", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] }
|
||||||
|
tokio-util = { version = "0.7", features = [ "compat" ] }
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
zstd = { version = "0.9", default-features = false }
|
zstd = { version = "0.9", default-features = false }
|
||||||
|
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tower = "0.4"
|
tower = "0.4"
|
||||||
futures = "0.3"
|
|
||||||
imap-codec = "0.5"
|
imap-codec = "0.5"
|
||||||
|
|
||||||
|
|
||||||
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "main" }
|
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "main" }
|
||||||
boitalettres = { git = "https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git", branch = "main" }
|
boitalettres = { git = "https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git", branch = "main" }
|
||||||
|
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
||||||
|
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
||||||
|
|
||||||
#k2v-client = { path = "../garage/src/k2v-client" }
|
#k2v-client = { path = "../garage/src/k2v-client" }
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "test"
|
|
||||||
path = "src/test.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "main"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2022-06-14"
|
||||||
|
components = ["rustc-dev", "rust-src"]
|
BIN
src/.server.rs.swo
Normal file
BIN
src/.server.rs.swo
Normal file
Binary file not shown.
BIN
src/.service.rs.swo
Normal file
BIN
src/.service.rs.swo
Normal file
Binary file not shown.
BIN
src/.session.rs.swo
Normal file
BIN
src/.session.rs.swo
Normal file
Binary file not shown.
38
src/bayou.rs
38
src/bayou.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use std::str::FromStr;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
@ -123,7 +124,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// 3. List all operations starting from checkpoint
|
// 3. List all operations starting from checkpoint
|
||||||
let ts_ser = self.checkpoint.0.serialize();
|
let ts_ser = self.checkpoint.0.to_string();
|
||||||
debug!("(sync) looking up operations starting at {}", ts_ser);
|
debug!("(sync) looking up operations starting at {}", ts_ser);
|
||||||
let ops_map = self
|
let ops_map = self
|
||||||
.k2v
|
.k2v
|
||||||
|
@ -148,8 +149,9 @@ impl<S: BayouState> Bayou<S> {
|
||||||
|
|
||||||
let mut ops = vec![];
|
let mut ops = vec![];
|
||||||
for (tsstr, val) in ops_map {
|
for (tsstr, val) in ops_map {
|
||||||
let ts = Timestamp::parse(&tsstr)
|
let ts = tsstr
|
||||||
.ok_or(anyhow!("Invalid operation timestamp: {}", tsstr))?;
|
.parse::<Timestamp>()
|
||||||
|
.map_err(|_| anyhow!("Invalid operation timestamp: {}", tsstr))?;
|
||||||
if val.value.len() != 1 {
|
if val.value.len() != 1 {
|
||||||
bail!("Invalid operation, has {} values", val.value.len());
|
bail!("Invalid operation, has {} values", val.value.len());
|
||||||
}
|
}
|
||||||
|
@ -251,7 +253,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
self.k2v
|
self.k2v
|
||||||
.insert_item(
|
.insert_item(
|
||||||
&self.path,
|
&self.path,
|
||||||
&ts.serialize(),
|
&ts.to_string(),
|
||||||
seal_serialize(&op, &self.key)?,
|
seal_serialize(&op, &self.key)?,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -316,7 +318,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
let ts_cp = self.history[i_cp].0;
|
let ts_cp = self.history[i_cp].0;
|
||||||
debug!(
|
debug!(
|
||||||
"(cp) we could checkpoint at time {} (index {} in history)",
|
"(cp) we could checkpoint at time {} (index {} in history)",
|
||||||
ts_cp.serialize(),
|
ts_cp.to_string(),
|
||||||
i_cp
|
i_cp
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -330,13 +332,13 @@ impl<S: BayouState> Bayou<S> {
|
||||||
{
|
{
|
||||||
debug!(
|
debug!(
|
||||||
"(cp) last checkpoint is too recent: {}, not checkpointing",
|
"(cp) last checkpoint is too recent: {}, not checkpointing",
|
||||||
last_cp.0.serialize()
|
last_cp.0.to_string()
|
||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("(cp) saving checkpoint at {}", ts_cp.serialize());
|
debug!("(cp) saving checkpoint at {}", ts_cp.to_string());
|
||||||
|
|
||||||
// Calculate state at time of checkpoint
|
// Calculate state at time of checkpoint
|
||||||
let mut last_known_state = (0, &self.checkpoint.1);
|
let mut last_known_state = (0, &self.checkpoint.1);
|
||||||
|
@ -356,7 +358,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
|
|
||||||
let mut por = PutObjectRequest::default();
|
let mut por = PutObjectRequest::default();
|
||||||
por.bucket = self.bucket.clone();
|
por.bucket = self.bucket.clone();
|
||||||
por.key = format!("{}/checkpoint/{}", self.path, ts_cp.serialize());
|
por.key = format!("{}/checkpoint/{}", self.path, ts_cp.to_string());
|
||||||
por.body = Some(cryptoblob.into());
|
por.body = Some(cryptoblob.into());
|
||||||
self.s3.put_object(por).await?;
|
self.s3.put_object(por).await?;
|
||||||
|
|
||||||
|
@ -375,7 +377,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete corresponding range of operations
|
// Delete corresponding range of operations
|
||||||
let ts_ser = existing_checkpoints[last_to_keep].0.serialize();
|
let ts_ser = existing_checkpoints[last_to_keep].0.to_string();
|
||||||
self.k2v
|
self.k2v
|
||||||
.delete_batch(&[BatchDeleteOp {
|
.delete_batch(&[BatchDeleteOp {
|
||||||
partition_key: &self.path,
|
partition_key: &self.path,
|
||||||
|
@ -414,7 +416,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
for object in checkpoints_res.contents.unwrap_or_default() {
|
for object in checkpoints_res.contents.unwrap_or_default() {
|
||||||
if let Some(key) = object.key {
|
if let Some(key) = object.key {
|
||||||
if let Some(ckid) = key.strip_prefix(&prefix) {
|
if let Some(ckid) = key.strip_prefix(&prefix) {
|
||||||
if let Some(ts) = Timestamp::parse(ckid) {
|
if let Ok(ts) = ckid.parse::<Timestamp>() {
|
||||||
checkpoints.push((ts, key));
|
checkpoints.push((ts, key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,20 +453,26 @@ impl Timestamp {
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self { msec: 0, rand: 0 }
|
Self { msec: 0, rand: 0 }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> String {
|
impl ToString for Timestamp {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
let mut bytes = [0u8; 16];
|
let mut bytes = [0u8; 16];
|
||||||
bytes[0..8].copy_from_slice(&u64::to_be_bytes(self.msec));
|
bytes[0..8].copy_from_slice(&u64::to_be_bytes(self.msec));
|
||||||
bytes[8..16].copy_from_slice(&u64::to_be_bytes(self.rand));
|
bytes[8..16].copy_from_slice(&u64::to_be_bytes(self.rand));
|
||||||
hex::encode(&bytes)
|
hex::encode(&bytes)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse(v: &str) -> Option<Self> {
|
impl FromStr for Timestamp {
|
||||||
let bytes = hex::decode(v).ok()?;
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Timestamp, &'static str> {
|
||||||
|
let bytes = hex::decode(s).map_err(|_| "invalid hex")?;
|
||||||
if bytes.len() != 16 {
|
if bytes.len() != 16 {
|
||||||
return None;
|
return Err("bad length");
|
||||||
}
|
}
|
||||||
Some(Self {
|
Ok(Self {
|
||||||
msec: u64::from_be_bytes(bytes[0..8].try_into().unwrap()),
|
msec: u64::from_be_bytes(bytes[0..8].try_into().unwrap()),
|
||||||
rand: u64::from_be_bytes(bytes[8..16].try_into().unwrap()),
|
rand: u64::from_be_bytes(bytes[8..16].try_into().unwrap()),
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,6 @@ use imap_codec::types::response::{Capability, Data};
|
||||||
use imap_codec::types::sequence::SequenceSet;
|
use imap_codec::types::sequence::SequenceSet;
|
||||||
|
|
||||||
use crate::mailbox::Mailbox;
|
use crate::mailbox::Mailbox;
|
||||||
use crate::mailstore::Mailstore;
|
|
||||||
use crate::session;
|
use crate::session;
|
||||||
|
|
||||||
pub struct Command<'a> {
|
pub struct Command<'a> {
|
||||||
|
@ -33,7 +32,7 @@ impl<'a> Command<'a> {
|
||||||
let (u, p) = (String::try_from(username)?, String::try_from(password)?);
|
let (u, p) = (String::try_from(username)?, String::try_from(password)?);
|
||||||
tracing::info!(user = %u, "command.login");
|
tracing::info!(user = %u, "command.login");
|
||||||
|
|
||||||
let creds = match self.session.mailstore.login_provider.login(&u, &p).await {
|
let creds = match self.session.login_provider.login(&u, &p).await {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Ok(Response::no(
|
return Ok(Response::no(
|
||||||
"[AUTHENTICATIONFAILED] Authentication failed.",
|
"[AUTHENTICATIONFAILED] Authentication failed.",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -13,6 +14,8 @@ pub struct Config {
|
||||||
|
|
||||||
pub login_static: Option<LoginStaticConfig>,
|
pub login_static: Option<LoginStaticConfig>,
|
||||||
pub login_ldap: Option<LoginLdapConfig>,
|
pub login_ldap: Option<LoginLdapConfig>,
|
||||||
|
|
||||||
|
pub lmtp: Option<LmtpConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -23,6 +26,8 @@ pub struct LoginStaticConfig {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct LoginStaticUser {
|
pub struct LoginStaticUser {
|
||||||
|
#[serde(default)]
|
||||||
|
pub email_addresses: Vec<String>,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
|
||||||
pub aws_access_key_id: String,
|
pub aws_access_key_id: String,
|
||||||
|
@ -60,6 +65,12 @@ pub struct LoginLdapConfig {
|
||||||
pub bucket_attr: Option<String>,
|
pub bucket_attr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct LmtpConfig {
|
||||||
|
pub bind_addr: SocketAddr,
|
||||||
|
pub hostname: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_config(config_file: PathBuf) -> Result<Config> {
|
pub fn read_config(config_file: PathBuf) -> Result<Config> {
|
||||||
let mut file = std::fs::OpenOptions::new()
|
let mut file = std::fs::OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
|
|
263
src/lmtp.rs
Normal file
263
src/lmtp.rs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::{pin::Pin, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use duplexify::Duplex;
|
||||||
|
use futures::{io, AsyncRead, AsyncReadExt, AsyncWrite};
|
||||||
|
use futures::{stream, stream::FuturesUnordered, StreamExt};
|
||||||
|
use log::*;
|
||||||
|
use rusoto_s3::{PutObjectRequest, S3Client, S3};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
use tokio::select;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
use tokio_util::compat::*;
|
||||||
|
|
||||||
|
use smtp_message::{Email, EscapedDataReader, Reply, ReplyCode};
|
||||||
|
use smtp_server::{reply, Config, ConnectionMetadata, Decision, MailMetadata, Protocol};
|
||||||
|
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::cryptoblob::*;
|
||||||
|
use crate::login::*;
|
||||||
|
use crate::mail_ident::*;
|
||||||
|
|
||||||
|
pub struct LmtpServer {
|
||||||
|
bind_addr: SocketAddr,
|
||||||
|
hostname: String,
|
||||||
|
login_provider: Arc<dyn LoginProvider + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LmtpServer {
|
||||||
|
pub fn new(
|
||||||
|
config: LmtpConfig,
|
||||||
|
login_provider: Arc<dyn LoginProvider + Send + Sync>,
|
||||||
|
) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
bind_addr: config.bind_addr,
|
||||||
|
hostname: config.hostname,
|
||||||
|
login_provider,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(self: &Arc<Self>, mut must_exit: watch::Receiver<bool>) -> Result<()> {
|
||||||
|
let tcp = TcpListener::bind(self.bind_addr).await?;
|
||||||
|
let mut connections = FuturesUnordered::new();
|
||||||
|
|
||||||
|
while !*must_exit.borrow() {
|
||||||
|
let wait_conn_finished = async {
|
||||||
|
if connections.is_empty() {
|
||||||
|
futures::future::pending().await
|
||||||
|
} else {
|
||||||
|
connections.next().await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (socket, remote_addr) = select! {
|
||||||
|
a = tcp.accept() => a?,
|
||||||
|
_ = wait_conn_finished => continue,
|
||||||
|
_ = must_exit.changed() => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn = tokio::spawn(smtp_server::interact(
|
||||||
|
socket.compat(),
|
||||||
|
smtp_server::IsAlreadyTls::No,
|
||||||
|
Conn { remote_addr },
|
||||||
|
self.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
connections.push(conn);
|
||||||
|
}
|
||||||
|
drop(tcp);
|
||||||
|
|
||||||
|
info!("LMTP server shutting down, draining remaining connections...");
|
||||||
|
while connections.next().await.is_some() {}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
|
||||||
|
pub struct Conn {
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Message {
|
||||||
|
to: Vec<PublicCredentials>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Config for LmtpServer {
|
||||||
|
const PROTOCOL: Protocol = Protocol::Lmtp;
|
||||||
|
|
||||||
|
type ConnectionUserMeta = Conn;
|
||||||
|
type MailUserMeta = Message;
|
||||||
|
|
||||||
|
fn hostname(&self, _conn_meta: &ConnectionMetadata<Conn>) -> &str {
|
||||||
|
&self.hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_mail(&self, _conn_meta: &mut ConnectionMetadata<Conn>) -> Message {
|
||||||
|
Message { to: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tls_accept<IO>(
|
||||||
|
&self,
|
||||||
|
_io: IO,
|
||||||
|
_conn_meta: &mut ConnectionMetadata<Conn>,
|
||||||
|
) -> io::Result<Duplex<Pin<Box<dyn Send + AsyncRead>>, Pin<Box<dyn Send + AsyncWrite>>>>
|
||||||
|
where
|
||||||
|
IO: Send + AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"TLS not implemented for LMTP server",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_from(
|
||||||
|
&self,
|
||||||
|
from: Option<Email>,
|
||||||
|
meta: &mut MailMetadata<Message>,
|
||||||
|
_conn_meta: &mut ConnectionMetadata<Conn>,
|
||||||
|
) -> Decision<Option<Email>> {
|
||||||
|
Decision::Accept {
|
||||||
|
reply: reply::okay_from().convert(),
|
||||||
|
res: from,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_to(
|
||||||
|
&self,
|
||||||
|
to: Email,
|
||||||
|
meta: &mut MailMetadata<Message>,
|
||||||
|
_conn_meta: &mut ConnectionMetadata<Conn>,
|
||||||
|
) -> Decision<Email> {
|
||||||
|
let to_str = match to.hostname.as_ref() {
|
||||||
|
Some(h) => format!("{}@{}", to.localpart, h),
|
||||||
|
None => to.localpart.to_string(),
|
||||||
|
};
|
||||||
|
match self.login_provider.public_login(&to_str).await {
|
||||||
|
Ok(creds) => {
|
||||||
|
meta.user.to.push(creds);
|
||||||
|
Decision::Accept {
|
||||||
|
reply: reply::okay_to().convert(),
|
||||||
|
res: to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Decision::Reject {
|
||||||
|
reply: Reply {
|
||||||
|
code: ReplyCode::POLICY_REASON,
|
||||||
|
ecode: None,
|
||||||
|
text: vec![smtp_message::MaybeUtf8::Utf8(e.to_string())],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_mail<'a, R>(
|
||||||
|
&self,
|
||||||
|
reader: &mut EscapedDataReader<'a, R>,
|
||||||
|
_mail: MailMetadata<Message>,
|
||||||
|
_conn_meta: &mut ConnectionMetadata<Conn>,
|
||||||
|
) -> Decision<()>
|
||||||
|
where
|
||||||
|
R: Send + Unpin + AsyncRead,
|
||||||
|
{
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_mail_multi<'a, 'slife0, 'slife1, 'stream, R>(
|
||||||
|
&'slife0 self,
|
||||||
|
reader: &mut EscapedDataReader<'a, R>,
|
||||||
|
meta: MailMetadata<Message>,
|
||||||
|
conn_meta: &'slife1 mut ConnectionMetadata<Conn>,
|
||||||
|
) -> Pin<Box<dyn futures::Stream<Item = Decision<()>> + Send + 'stream>>
|
||||||
|
where
|
||||||
|
R: Send + Unpin + AsyncRead,
|
||||||
|
'slife0: 'stream,
|
||||||
|
'slife1: 'stream,
|
||||||
|
Self: 'stream,
|
||||||
|
{
|
||||||
|
let err_response_stream = |meta: MailMetadata<Message>, msg: String| {
|
||||||
|
Box::pin(
|
||||||
|
stream::iter(meta.user.to.into_iter()).map(move |_| Decision::Reject {
|
||||||
|
reply: Reply {
|
||||||
|
code: ReplyCode::POLICY_REASON,
|
||||||
|
ecode: None,
|
||||||
|
text: vec![smtp_message::MaybeUtf8::Utf8(msg.clone())],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut text = Vec::new();
|
||||||
|
if reader.read_to_end(&mut text).await.is_err() {
|
||||||
|
return err_response_stream(meta, "io error".into());
|
||||||
|
}
|
||||||
|
reader.complete();
|
||||||
|
|
||||||
|
let encrypted_message = match EncryptedMessage::new(text) {
|
||||||
|
Ok(x) => Arc::new(x),
|
||||||
|
Err(e) => return err_response_stream(meta, e.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::pin(stream::iter(meta.user.to.into_iter()).then(move |creds| {
|
||||||
|
let encrypted_message = encrypted_message.clone();
|
||||||
|
async move {
|
||||||
|
match encrypted_message.deliver_to(creds).await {
|
||||||
|
Ok(()) => Decision::Accept {
|
||||||
|
reply: reply::okay_mail().convert(),
|
||||||
|
res: (),
|
||||||
|
},
|
||||||
|
Err(e) => Decision::Reject {
|
||||||
|
reply: Reply {
|
||||||
|
code: ReplyCode::POLICY_REASON,
|
||||||
|
ecode: None,
|
||||||
|
text: vec![smtp_message::MaybeUtf8::Utf8(e.to_string())],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
|
||||||
|
struct EncryptedMessage {
|
||||||
|
key: Key,
|
||||||
|
encrypted_body: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncryptedMessage {
|
||||||
|
fn new(body: Vec<u8>) -> Result<Self> {
|
||||||
|
let key = gen_key();
|
||||||
|
let encrypted_body = seal(&body, &key)?;
|
||||||
|
Ok(Self {
|
||||||
|
key,
|
||||||
|
encrypted_body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn deliver_to(self: Arc<Self>, creds: PublicCredentials) -> Result<()> {
|
||||||
|
let s3_client = creds.storage.s3_client()?;
|
||||||
|
|
||||||
|
let encrypted_key =
|
||||||
|
sodiumoxide::crypto::sealedbox::seal(self.key.as_ref(), &creds.public_key);
|
||||||
|
let key_header = base64::encode(&encrypted_key);
|
||||||
|
|
||||||
|
let mut por = PutObjectRequest::default();
|
||||||
|
por.bucket = creds.storage.bucket.clone();
|
||||||
|
por.key = format!("incoming/{}", gen_ident().to_string());
|
||||||
|
por.metadata = Some(
|
||||||
|
[("Message-Key".to_string(), key_header)]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
);
|
||||||
|
por.body = Some(self.encrypted_body.clone().into());
|
||||||
|
s3_client.put_object(por).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,11 +84,30 @@ impl LdapLoginProvider {
|
||||||
bucket_source,
|
bucket_source,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result<StorageCredentials> {
|
||||||
|
let aws_access_key_id = get_attr(user, &self.aws_access_key_id_attr)?;
|
||||||
|
let aws_secret_access_key = get_attr(user, &self.aws_secret_access_key_attr)?;
|
||||||
|
let bucket = match &self.bucket_source {
|
||||||
|
BucketSource::Constant(b) => b.clone(),
|
||||||
|
BucketSource::Attr(a) => get_attr(user, a)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(StorageCredentials {
|
||||||
|
k2v_region: self.k2v_region.clone(),
|
||||||
|
s3_region: self.s3_region.clone(),
|
||||||
|
aws_access_key_id,
|
||||||
|
aws_secret_access_key,
|
||||||
|
bucket,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl LoginProvider for LdapLoginProvider {
|
impl LoginProvider for LdapLoginProvider {
|
||||||
async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
|
async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
|
||||||
|
check_identifier(username)?;
|
||||||
|
|
||||||
let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
|
let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
|
||||||
ldap3::drive!(conn);
|
ldap3::drive!(conn);
|
||||||
|
|
||||||
|
@ -97,13 +116,6 @@ impl LoginProvider for LdapLoginProvider {
|
||||||
ldap.simple_bind(dn, pw).await?.success()?;
|
ldap.simple_bind(dn, pw).await?.success()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let username_is_ok = username
|
|
||||||
.chars()
|
|
||||||
.all(|c| c.is_alphanumeric() || "-+_.@".contains(c));
|
|
||||||
if !username_is_ok {
|
|
||||||
bail!("Invalid username, must contain only a-z A-Z 0-9 - + _ . @");
|
|
||||||
}
|
|
||||||
|
|
||||||
let (matches, _res) = ldap
|
let (matches, _res) = ldap
|
||||||
.search(
|
.search(
|
||||||
&self.search_base,
|
&self.search_base,
|
||||||
|
@ -137,32 +149,9 @@ impl LoginProvider for LdapLoginProvider {
|
||||||
.context("Invalid password")?;
|
.context("Invalid password")?;
|
||||||
debug!("Ldap login with user name {} successfull", username);
|
debug!("Ldap login with user name {} successfull", username);
|
||||||
|
|
||||||
let get_attr = |attr: &str| -> Result<String> {
|
let storage = self.storage_creds_from_ldap_user(&user)?;
|
||||||
Ok(user
|
|
||||||
.attrs
|
|
||||||
.get(attr)
|
|
||||||
.ok_or(anyhow!("Missing attr: {}", attr))?
|
|
||||||
.iter()
|
|
||||||
.next()
|
|
||||||
.ok_or(anyhow!("No value for attr: {}", attr))?
|
|
||||||
.clone())
|
|
||||||
};
|
|
||||||
let aws_access_key_id = get_attr(&self.aws_access_key_id_attr)?;
|
|
||||||
let aws_secret_access_key = get_attr(&self.aws_secret_access_key_attr)?;
|
|
||||||
let bucket = match &self.bucket_source {
|
|
||||||
BucketSource::Constant(b) => b.clone(),
|
|
||||||
BucketSource::Attr(a) => get_attr(a)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let storage = StorageCredentials {
|
let user_secret = get_attr(&user, &self.user_secret_attr)?;
|
||||||
k2v_region: self.k2v_region.clone(),
|
|
||||||
s3_region: self.s3_region.clone(),
|
|
||||||
aws_access_key_id,
|
|
||||||
aws_secret_access_key,
|
|
||||||
bucket,
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_secret = get_attr(&self.user_secret_attr)?;
|
|
||||||
let alternate_user_secrets = match &self.alternate_user_secrets_attr {
|
let alternate_user_secrets = match &self.alternate_user_secrets_attr {
|
||||||
None => vec![],
|
None => vec![],
|
||||||
Some(a) => user.attrs.get(a).cloned().unwrap_or_default(),
|
Some(a) => user.attrs.get(a).cloned().unwrap_or_default(),
|
||||||
|
@ -178,4 +167,71 @@ impl LoginProvider for LdapLoginProvider {
|
||||||
|
|
||||||
Ok(Credentials { storage, keys })
|
Ok(Credentials { storage, keys })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn public_login(&self, email: &str) -> Result<PublicCredentials> {
|
||||||
|
check_identifier(email)?;
|
||||||
|
|
||||||
|
let (dn, pw) = match self.bind_dn_and_pw.as_ref() {
|
||||||
|
Some(x) => x,
|
||||||
|
None => bail!("Missing bind_dn and bind_password in LDAP login provider config"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
|
||||||
|
ldap3::drive!(conn);
|
||||||
|
ldap.simple_bind(dn, pw).await?.success()?;
|
||||||
|
|
||||||
|
let (matches, _res) = ldap
|
||||||
|
.search(
|
||||||
|
&self.search_base,
|
||||||
|
Scope::Subtree,
|
||||||
|
&format!(
|
||||||
|
"(&(objectClass=inetOrgPerson)({}={}))",
|
||||||
|
self.mail_attr, email
|
||||||
|
),
|
||||||
|
&self.attrs_to_retrieve,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.success()?;
|
||||||
|
|
||||||
|
if matches.is_empty() {
|
||||||
|
bail!("No such user account");
|
||||||
|
}
|
||||||
|
if matches.len() > 1 {
|
||||||
|
bail!("Multiple matching user accounts");
|
||||||
|
}
|
||||||
|
let user = SearchEntry::construct(matches.into_iter().next().unwrap());
|
||||||
|
debug!("Found matching LDAP user for email {}: {}", email, user.dn);
|
||||||
|
|
||||||
|
let storage = self.storage_creds_from_ldap_user(&user)?;
|
||||||
|
drop(ldap);
|
||||||
|
|
||||||
|
let k2v_client = storage.k2v_client()?;
|
||||||
|
let (_, public_key) = CryptoKeys::load_salt_and_public(&k2v_client).await?;
|
||||||
|
|
||||||
|
Ok(PublicCredentials {
|
||||||
|
storage,
|
||||||
|
public_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_attr(user: &SearchEntry, attr: &str) -> Result<String> {
|
||||||
|
Ok(user
|
||||||
|
.attrs
|
||||||
|
.get(attr)
|
||||||
|
.ok_or(anyhow!("Missing attr: {}", attr))?
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.ok_or(anyhow!("No value for attr: {}", attr))?
|
||||||
|
.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_identifier(id: &str) -> Result<()> {
|
||||||
|
let is_ok = id
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_alphanumeric() || "-+_.@".contains(c));
|
||||||
|
if !is_ok {
|
||||||
|
bail!("Invalid username/email address, must contain only a-z A-Z 0-9 - + _ . @");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,9 @@ pub trait LoginProvider {
|
||||||
/// The login method takes an account's password as an input to decypher
|
/// The login method takes an account's password as an input to decypher
|
||||||
/// decryption keys and obtain full access to the user's account.
|
/// decryption keys and obtain full access to the user's account.
|
||||||
async fn login(&self, username: &str, password: &str) -> Result<Credentials>;
|
async fn login(&self, username: &str, password: &str) -> Result<Credentials>;
|
||||||
|
/// The public_login method takes an account's email address and returns
|
||||||
|
/// public credentials for adding mails to the user's inbox.
|
||||||
|
async fn public_login(&self, email: &str) -> Result<PublicCredentials>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The struct Credentials represent all of the necessary information to interact
|
/// The struct Credentials represent all of the necessary information to interact
|
||||||
|
@ -36,6 +39,13 @@ pub struct Credentials {
|
||||||
pub keys: CryptoKeys,
|
pub keys: CryptoKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PublicCredentials {
|
||||||
|
/// The storage credentials are used to authenticate access to the underlying storage (S3, K2V)
|
||||||
|
pub storage: StorageCredentials,
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
/// The struct StorageCredentials contains access key to an S3 and K2V bucket
|
/// The struct StorageCredentials contains access key to an S3 and K2V bucket
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct StorageCredentials {
|
pub struct StorageCredentials {
|
||||||
|
@ -396,7 +406,7 @@ impl CryptoKeys {
|
||||||
Ok((salt_ct, public_ct))
|
Ok((salt_ct, public_ct))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_salt_and_public(k2v: &K2vClient) -> Result<([u8; 32], PublicKey)> {
|
pub async fn load_salt_and_public(k2v: &K2vClient) -> Result<([u8; 32], PublicKey)> {
|
||||||
let mut params = k2v
|
let mut params = k2v
|
||||||
.read_batch(&[
|
.read_batch(&[
|
||||||
k2v_read_single_key("keys", "salt", false),
|
k2v_read_single_key("keys", "salt", false),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -10,16 +11,34 @@ use crate::login::*;
|
||||||
|
|
||||||
pub struct StaticLoginProvider {
|
pub struct StaticLoginProvider {
|
||||||
default_bucket: Option<String>,
|
default_bucket: Option<String>,
|
||||||
users: HashMap<String, LoginStaticUser>,
|
users: HashMap<String, Arc<LoginStaticUser>>,
|
||||||
|
users_by_email: HashMap<String, Arc<LoginStaticUser>>,
|
||||||
|
|
||||||
k2v_region: Region,
|
k2v_region: Region,
|
||||||
s3_region: Region,
|
s3_region: Region,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticLoginProvider {
|
impl StaticLoginProvider {
|
||||||
pub fn new(config: LoginStaticConfig, k2v_region: Region, s3_region: Region) -> Result<Self> {
|
pub fn new(config: LoginStaticConfig, k2v_region: Region, s3_region: Region) -> Result<Self> {
|
||||||
|
let users = config
|
||||||
|
.users
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k, Arc::new(v)))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
let mut users_by_email = HashMap::new();
|
||||||
|
for (_, u) in users.iter() {
|
||||||
|
for m in u.email_addresses.iter() {
|
||||||
|
if users_by_email.contains_key(m) {
|
||||||
|
bail!("Several users have same email address: {}", m);
|
||||||
|
}
|
||||||
|
users_by_email.insert(m.clone(), u.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
default_bucket: config.default_bucket,
|
default_bucket: config.default_bucket,
|
||||||
users: config.users,
|
users,
|
||||||
|
users_by_email,
|
||||||
k2v_region,
|
k2v_region,
|
||||||
s3_region,
|
s3_region,
|
||||||
})
|
})
|
||||||
|
@ -30,54 +49,87 @@ impl StaticLoginProvider {
|
||||||
impl LoginProvider for StaticLoginProvider {
|
impl LoginProvider for StaticLoginProvider {
|
||||||
async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
|
async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
|
||||||
tracing::debug!(user=%username, "login");
|
tracing::debug!(user=%username, "login");
|
||||||
match self.users.get(username) {
|
let user = match self.users.get(username) {
|
||||||
None => bail!("User {} does not exist", username),
|
None => bail!("User {} does not exist", username),
|
||||||
Some(u) => {
|
Some(u) => u,
|
||||||
tracing::debug!(user=%username, "verify password");
|
};
|
||||||
if !verify_password(password, &u.password)? {
|
|
||||||
bail!("Wrong password");
|
|
||||||
}
|
|
||||||
tracing::debug!(user=%username, "fetch bucket");
|
|
||||||
let bucket = u
|
|
||||||
.bucket
|
|
||||||
.clone()
|
|
||||||
.or_else(|| self.default_bucket.clone())
|
|
||||||
.ok_or(anyhow!(
|
|
||||||
"No bucket configured and no default bucket specieid"
|
|
||||||
))?;
|
|
||||||
|
|
||||||
tracing::debug!(user=%username, "fetch configuration");
|
tracing::debug!(user=%username, "verify password");
|
||||||
let storage = StorageCredentials {
|
if !verify_password(password, &user.password)? {
|
||||||
k2v_region: self.k2v_region.clone(),
|
bail!("Wrong password");
|
||||||
s3_region: self.s3_region.clone(),
|
|
||||||
aws_access_key_id: u.aws_access_key_id.clone(),
|
|
||||||
aws_secret_access_key: u.aws_secret_access_key.clone(),
|
|
||||||
bucket,
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!(user=%username, "fetch keys");
|
|
||||||
let keys = match (&u.master_key, &u.secret_key) {
|
|
||||||
(Some(m), Some(s)) => {
|
|
||||||
let master_key = Key::from_slice(&base64::decode(m)?)
|
|
||||||
.ok_or(anyhow!("Invalid master key"))?;
|
|
||||||
let secret_key = SecretKey::from_slice(&base64::decode(s)?)
|
|
||||||
.ok_or(anyhow!("Invalid secret key"))?;
|
|
||||||
CryptoKeys::open_without_password(&storage, &master_key, &secret_key).await?
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
let user_secrets = UserSecrets {
|
|
||||||
user_secret: u.user_secret.clone(),
|
|
||||||
alternate_user_secrets: u.alternate_user_secrets.clone(),
|
|
||||||
};
|
|
||||||
CryptoKeys::open(&storage, &user_secrets, password).await?
|
|
||||||
}
|
|
||||||
_ => bail!("Either both master and secret key or none of them must be specified for user"),
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!(user=%username, "logged");
|
|
||||||
Ok(Credentials { storage, keys })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracing::debug!(user=%username, "fetch bucket");
|
||||||
|
let bucket = user
|
||||||
|
.bucket
|
||||||
|
.clone()
|
||||||
|
.or_else(|| self.default_bucket.clone())
|
||||||
|
.ok_or(anyhow!(
|
||||||
|
"No bucket configured and no default bucket specieid"
|
||||||
|
))?;
|
||||||
|
|
||||||
|
tracing::debug!(user=%username, "fetch keys");
|
||||||
|
let storage = StorageCredentials {
|
||||||
|
k2v_region: self.k2v_region.clone(),
|
||||||
|
s3_region: self.s3_region.clone(),
|
||||||
|
aws_access_key_id: user.aws_access_key_id.clone(),
|
||||||
|
aws_secret_access_key: user.aws_secret_access_key.clone(),
|
||||||
|
bucket,
|
||||||
|
};
|
||||||
|
|
||||||
|
let keys = match (&user.master_key, &user.secret_key) {
|
||||||
|
(Some(m), Some(s)) => {
|
||||||
|
let master_key =
|
||||||
|
Key::from_slice(&base64::decode(m)?).ok_or(anyhow!("Invalid master key"))?;
|
||||||
|
let secret_key = SecretKey::from_slice(&base64::decode(s)?)
|
||||||
|
.ok_or(anyhow!("Invalid secret key"))?;
|
||||||
|
CryptoKeys::open_without_password(&storage, &master_key, &secret_key).await?
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
|
let user_secrets = UserSecrets {
|
||||||
|
user_secret: user.user_secret.clone(),
|
||||||
|
alternate_user_secrets: user.alternate_user_secrets.clone(),
|
||||||
|
};
|
||||||
|
CryptoKeys::open(&storage, &user_secrets, password).await?
|
||||||
|
}
|
||||||
|
_ => bail!(
|
||||||
|
"Either both master and secret key or none of them must be specified for user"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::debug!(user=%username, "logged");
|
||||||
|
Ok(Credentials { storage, keys })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn public_login(&self, email: &str) -> Result<PublicCredentials> {
|
||||||
|
let user = match self.users_by_email.get(email) {
|
||||||
|
None => bail!("No user for email address {}", email),
|
||||||
|
Some(u) => u,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bucket = user
|
||||||
|
.bucket
|
||||||
|
.clone()
|
||||||
|
.or_else(|| self.default_bucket.clone())
|
||||||
|
.ok_or(anyhow!(
|
||||||
|
"No bucket configured and no default bucket specieid"
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let storage = StorageCredentials {
|
||||||
|
k2v_region: self.k2v_region.clone(),
|
||||||
|
s3_region: self.s3_region.clone(),
|
||||||
|
aws_access_key_id: user.aws_access_key_id.clone(),
|
||||||
|
aws_secret_access_key: user.aws_secret_access_key.clone(),
|
||||||
|
bucket,
|
||||||
|
};
|
||||||
|
|
||||||
|
let k2v_client = storage.k2v_client()?;
|
||||||
|
let (_, public_key) = CryptoKeys::load_salt_and_public(&k2v_client).await?;
|
||||||
|
|
||||||
|
Ok(PublicCredentials {
|
||||||
|
storage,
|
||||||
|
public_key,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
95
src/mail_ident.rs
Normal file
95
src/mail_ident.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::time::now_msec;
|
||||||
|
|
||||||
|
/// An internal Mail Identifier is composed of two components:
|
||||||
|
/// - a process identifier, 128 bits, itself composed of:
|
||||||
|
/// - the timestamp of when the process started, 64 bits
|
||||||
|
/// - a 64-bit random number
|
||||||
|
/// - a sequence number, 64 bits
|
||||||
|
/// They are not part of the protocol but an internal representation
|
||||||
|
/// required by Mailrage/Aerogramme.
|
||||||
|
/// Their main property is to be unique without having to rely
|
||||||
|
/// on synchronization between IMAP processes.
|
||||||
|
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub struct MailIdent(pub [u8; 24]);
|
||||||
|
|
||||||
|
struct IdentGenerator {
|
||||||
|
pid: u128,
|
||||||
|
sn: AtomicU64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdentGenerator {
|
||||||
|
fn new() -> Self {
|
||||||
|
let time = now_msec() as u128;
|
||||||
|
let rand = thread_rng().gen::<u64>() as u128;
|
||||||
|
Self {
|
||||||
|
pid: (time << 64) | rand,
|
||||||
|
sn: AtomicU64::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen(&self) -> MailIdent {
|
||||||
|
let sn = self.sn.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let mut res = [0u8; 24];
|
||||||
|
res[0..16].copy_from_slice(&u128::to_be_bytes(self.pid));
|
||||||
|
res[16..24].copy_from_slice(&u64::to_be_bytes(sn));
|
||||||
|
MailIdent(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref GENERATOR: IdentGenerator = IdentGenerator::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_ident() -> MailIdent {
|
||||||
|
GENERATOR.gen()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- serde --
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for MailIdent {
|
||||||
|
fn deserialize<D>(d: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let v = String::deserialize(d)?;
|
||||||
|
MailIdent::from_str(&v).map_err(D::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for MailIdent {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for MailIdent {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
hex::encode(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for MailIdent {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<MailIdent, &'static str> {
|
||||||
|
let bytes = hex::decode(s).map_err(|_| "invalid hex")?;
|
||||||
|
|
||||||
|
if bytes.len() != 24 {
|
||||||
|
return Err("bad length");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tmp = [0u8; 24];
|
||||||
|
tmp[..].copy_from_slice(&bytes);
|
||||||
|
Ok(MailIdent(tmp))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use k2v_client::K2vClient;
|
use k2v_client::K2vClient;
|
||||||
use rand::prelude::*;
|
|
||||||
use rusoto_s3::S3Client;
|
use rusoto_s3::S3Client;
|
||||||
|
|
||||||
use crate::bayou::Bayou;
|
use crate::bayou::Bayou;
|
||||||
use crate::cryptoblob::Key;
|
use crate::cryptoblob::Key;
|
||||||
use crate::login::Credentials;
|
use crate::login::Credentials;
|
||||||
|
use crate::mail_ident::*;
|
||||||
use crate::uidindex::*;
|
use crate::uidindex::*;
|
||||||
|
|
||||||
pub struct Summary {
|
pub struct Summary {
|
||||||
|
@ -64,19 +64,17 @@ impl Mailbox {
|
||||||
|
|
||||||
dump(&self.uid_index);
|
dump(&self.uid_index);
|
||||||
|
|
||||||
let mut rand_id = [0u8; 24];
|
|
||||||
rand_id[..16].copy_from_slice(&u128::to_be_bytes(thread_rng().gen()));
|
|
||||||
let add_mail_op = self
|
let add_mail_op = self
|
||||||
.uid_index
|
.uid_index
|
||||||
.state()
|
.state()
|
||||||
.op_mail_add(MailIdent(rand_id), vec!["\\Unseen".into()]);
|
.op_mail_add(gen_ident(), vec!["\\Unseen".into()]);
|
||||||
self.uid_index.push(add_mail_op).await?;
|
self.uid_index.push(add_mail_op).await?;
|
||||||
|
|
||||||
dump(&self.uid_index);
|
dump(&self.uid_index);
|
||||||
|
|
||||||
if self.uid_index.state().idx_by_uid.len() > 6 {
|
if self.uid_index.state().idx_by_uid.len() > 6 {
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
let (_, uuid) = self
|
let (_, ident) = self
|
||||||
.uid_index
|
.uid_index
|
||||||
.state()
|
.state()
|
||||||
.idx_by_uid
|
.idx_by_uid
|
||||||
|
@ -84,7 +82,7 @@ impl Mailbox {
|
||||||
.skip(3 + i)
|
.skip(3 + i)
|
||||||
.next()
|
.next()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let del_mail_op = self.uid_index.state().op_mail_del(*uuid);
|
let del_mail_op = self.uid_index.state().op_mail_del(*ident);
|
||||||
self.uid_index.push(del_mail_op).await?;
|
self.uid_index.push(del_mail_op).await?;
|
||||||
|
|
||||||
dump(&self.uid_index);
|
dump(&self.uid_index);
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -2,7 +2,9 @@ mod bayou;
|
||||||
mod command;
|
mod command;
|
||||||
mod config;
|
mod config;
|
||||||
mod cryptoblob;
|
mod cryptoblob;
|
||||||
|
mod lmtp;
|
||||||
mod login;
|
mod login;
|
||||||
|
mod mail_ident;
|
||||||
mod mailbox;
|
mod mailbox;
|
||||||
mod mailstore;
|
mod mailstore;
|
||||||
mod server;
|
mod server;
|
||||||
|
@ -38,6 +40,11 @@ enum Command {
|
||||||
#[clap(short, long, env = "CONFIG_FILE", default_value = "mailrage.toml")]
|
#[clap(short, long, env = "CONFIG_FILE", default_value = "mailrage.toml")]
|
||||||
config_file: PathBuf,
|
config_file: PathBuf,
|
||||||
},
|
},
|
||||||
|
/// TEST TEST TEST
|
||||||
|
Test {
|
||||||
|
#[clap(short, long, env = "CONFIG_FILE", default_value = "mailrage.toml")]
|
||||||
|
config_file: PathBuf,
|
||||||
|
},
|
||||||
/// Initializes key pairs for a user and adds a key decryption password
|
/// Initializes key pairs for a user and adds a key decryption password
|
||||||
FirstLogin {
|
FirstLogin {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
|
@ -129,6 +136,12 @@ async fn main() -> Result<()> {
|
||||||
let server = Server::new(config).await?;
|
let server = Server::new(config).await?;
|
||||||
server.run().await?;
|
server.run().await?;
|
||||||
}
|
}
|
||||||
|
Command::Test { config_file } => {
|
||||||
|
let config = read_config(config_file)?;
|
||||||
|
|
||||||
|
let server = Server::new(config).await?;
|
||||||
|
//server.test().await?;
|
||||||
|
}
|
||||||
Command::FirstLogin {
|
Command::FirstLogin {
|
||||||
creds,
|
creds,
|
||||||
user_secrets,
|
user_secrets,
|
||||||
|
|
107
src/server.rs
107
src/server.rs
|
@ -1,40 +1,107 @@
|
||||||
use anyhow::Result;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::config::*;
|
|
||||||
use crate::mailstore;
|
|
||||||
use crate::service;
|
|
||||||
|
|
||||||
use boitalettres::server::accept::addr::AddrIncoming;
|
use boitalettres::server::accept::addr::AddrIncoming;
|
||||||
|
use boitalettres::server::accept::addr::AddrStream;
|
||||||
use boitalettres::server::Server as ImapServer;
|
use boitalettres::server::Server as ImapServer;
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use futures::{try_join, StreamExt};
|
||||||
|
use log::*;
|
||||||
|
use rusoto_signature::Region;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use crate::mailstore;
|
||||||
|
use crate::service;
|
||||||
|
use crate::lmtp::*;
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::login::{ldap_provider::*, static_provider::*, *};
|
||||||
|
use crate::mailbox::Mailbox;
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
pub incoming: AddrIncoming,
|
lmtp_server: Option<Arc<LmtpServer>>,
|
||||||
pub mailstore: Arc<mailstore::Mailstore>,
|
imap_server: ImapServer<AddrIncoming, service::Instance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub async fn new(config: Config) -> Result<Self> {
|
pub async fn new(config: Config) -> Result<Self> {
|
||||||
|
let lmtp_config = config.lmtp.clone(); //@FIXME
|
||||||
|
let login = authenticator(config)?;
|
||||||
|
|
||||||
|
let lmtp = lmtp_config.map(|cfg| LmtpServer::new(cfg, login.clone()));
|
||||||
|
|
||||||
|
let incoming = AddrIncoming::new("127.0.0.1:4567").await?;
|
||||||
|
let imap = ImapServer::new(incoming).serve(service::Instance::new(login.clone()));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
incoming: AddrIncoming::new("127.0.0.1:4567").await?,
|
lmtp_server: lmtp,
|
||||||
mailstore: mailstore::Mailstore::new(config)?,
|
imap_server: imap,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(self: Self) -> Result<()> {
|
|
||||||
tracing::info!("Starting server on {:#}", self.incoming.local_addr);
|
|
||||||
|
|
||||||
/*let creds = self
|
pub async fn run(self) -> Result<()> {
|
||||||
.mailstore
|
//tracing::info!("Starting server on {:#}", self.imap.incoming.local_addr);
|
||||||
.login_provider
|
tracing::info!("Starting Aerogramme...");
|
||||||
.login("quentin", "poupou")
|
|
||||||
.await?;*/
|
let (exit_signal, provoke_exit) = watch_ctrl_c();
|
||||||
//let mut mailbox = Mailbox::new(&creds, "TestMailbox".to_string()).await?;
|
let exit_on_err = move |err: anyhow::Error| {
|
||||||
//mailbox.test().await?;
|
error!("Error: {}", err);
|
||||||
|
let _ = provoke_exit.send(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
try_join!(async {
|
||||||
|
match self.lmtp_server.as_ref() {
|
||||||
|
None => Ok(()),
|
||||||
|
Some(s) => s.run(exit_signal.clone()).await,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//@FIXME handle ctrl + c
|
||||||
|
async {
|
||||||
|
self.imap_server.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
let server =
|
|
||||||
ImapServer::new(self.incoming).serve(service::Instance::new(self.mailstore.clone()));
|
|
||||||
let _ = server.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn authenticator(config: Config) -> Result<Arc<dyn LoginProvider + Send + Sync>> {
|
||||||
|
let s3_region = Region::Custom {
|
||||||
|
name: config.aws_region.clone(),
|
||||||
|
endpoint: config.s3_endpoint,
|
||||||
|
};
|
||||||
|
let k2v_region = Region::Custom {
|
||||||
|
name: config.aws_region,
|
||||||
|
endpoint: config.k2v_endpoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
let lp: Arc<dyn LoginProvider + Send + Sync> = match (config.login_static, config.login_ldap) {
|
||||||
|
(Some(st), None) => Arc::new(StaticLoginProvider::new(st, k2v_region, s3_region)?),
|
||||||
|
(None, Some(ld)) => Arc::new(LdapLoginProvider::new(ld, k2v_region, s3_region)?),
|
||||||
|
(Some(_), Some(_)) => {
|
||||||
|
bail!("A single login provider must be set up in config file")
|
||||||
|
}
|
||||||
|
(None, None) => bail!("No login provider is set up in config file"),
|
||||||
|
};
|
||||||
|
Ok(lp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn watch_ctrl_c() -> (watch::Receiver<bool>, Arc<watch::Sender<bool>>) {
|
||||||
|
let (send_cancel, watch_cancel) = watch::channel(false);
|
||||||
|
let send_cancel = Arc::new(send_cancel);
|
||||||
|
let send_cancel_2 = send_cancel.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tokio::signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install CTRL+C signal handler");
|
||||||
|
info!("Received CTRL+C, shutting down.");
|
||||||
|
send_cancel.send(true).unwrap();
|
||||||
|
});
|
||||||
|
(watch_cancel, send_cancel_2)
|
||||||
|
}
|
||||||
|
|
|
@ -9,15 +9,15 @@ use futures::future::BoxFuture;
|
||||||
use futures::future::FutureExt;
|
use futures::future::FutureExt;
|
||||||
use tower::Service;
|
use tower::Service;
|
||||||
|
|
||||||
use crate::mailstore::Mailstore;
|
|
||||||
use crate::session;
|
use crate::session;
|
||||||
|
use crate::LoginProvider;
|
||||||
|
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
pub mailstore: Arc<Mailstore>,
|
login_provider: Arc<dyn LoginProvider + Send + Sync>,
|
||||||
}
|
}
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn new(mailstore: Arc<Mailstore>) -> Self {
|
pub fn new(login_provider: Arc<dyn LoginProvider + Send + Sync>) -> Self {
|
||||||
Self { mailstore }
|
Self { login_provider }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> Service<&'a AddrStream> for Instance {
|
impl<'a> Service<&'a AddrStream> for Instance {
|
||||||
|
@ -31,8 +31,8 @@ impl<'a> Service<&'a AddrStream> for Instance {
|
||||||
|
|
||||||
fn call(&mut self, addr: &'a AddrStream) -> Self::Future {
|
fn call(&mut self, addr: &'a AddrStream) -> Self::Future {
|
||||||
tracing::info!(remote_addr = %addr.remote_addr, local_addr = %addr.local_addr, "accept");
|
tracing::info!(remote_addr = %addr.remote_addr, local_addr = %addr.local_addr, "accept");
|
||||||
let ms = self.mailstore.clone();
|
let lp = self.login_provider.clone();
|
||||||
async { Ok(Connection::new(ms)) }.boxed()
|
async { Ok(Connection::new(lp)) }.boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +40,9 @@ pub struct Connection {
|
||||||
session: session::Manager,
|
session: session::Manager,
|
||||||
}
|
}
|
||||||
impl Connection {
|
impl Connection {
|
||||||
pub fn new(mailstore: Arc<Mailstore>) -> Self {
|
pub fn new(login_provider: Arc<dyn LoginProvider + Send + Sync>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
session: session::Manager::new(mailstore),
|
session: session::Manager::new(login_provider),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use tokio::sync::{mpsc, oneshot};
|
||||||
use crate::command;
|
use crate::command;
|
||||||
use crate::login::Credentials;
|
use crate::login::Credentials;
|
||||||
use crate::mailbox::Mailbox;
|
use crate::mailbox::Mailbox;
|
||||||
use crate::mailstore::Mailstore;
|
use crate::LoginProvider;
|
||||||
|
|
||||||
/* This constant configures backpressure in the system,
|
/* This constant configures backpressure in the system,
|
||||||
* or more specifically, how many pipelined messages are allowed
|
* or more specifically, how many pipelined messages are allowed
|
||||||
|
@ -30,10 +30,10 @@ pub struct Manager {
|
||||||
|
|
||||||
//@FIXME we should garbage collect the Instance when the Manager is destroyed.
|
//@FIXME we should garbage collect the Instance when the Manager is destroyed.
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn new(mailstore: Arc<Mailstore>) -> Self {
|
pub fn new(login_provider: Arc<dyn LoginProvider + Send + Sync>) -> Self {
|
||||||
let (tx, rx) = mpsc::channel(MAX_PIPELINED_COMMANDS);
|
let (tx, rx) = mpsc::channel(MAX_PIPELINED_COMMANDS);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut instance = Instance::new(mailstore, rx);
|
let mut instance = Instance::new(login_provider, rx);
|
||||||
instance.start().await;
|
instance.start().await;
|
||||||
});
|
});
|
||||||
Self { tx }
|
Self { tx }
|
||||||
|
@ -79,14 +79,14 @@ pub struct User {
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
rx: mpsc::Receiver<Message>,
|
rx: mpsc::Receiver<Message>,
|
||||||
|
|
||||||
pub mailstore: Arc<Mailstore>,
|
pub login_provider: Arc<dyn LoginProvider + Send + Sync>,
|
||||||
pub selected: Option<Mailbox>,
|
pub selected: Option<Mailbox>,
|
||||||
pub user: Option<User>,
|
pub user: Option<User>,
|
||||||
}
|
}
|
||||||
impl Instance {
|
impl Instance {
|
||||||
fn new(mailstore: Arc<Mailstore>, rx: mpsc::Receiver<Message>) -> Self {
|
fn new(login_provider: Arc<dyn LoginProvider + Send + Sync>, rx: mpsc::Receiver<Message>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mailstore,
|
login_provider,
|
||||||
rx,
|
rx,
|
||||||
selected: None,
|
selected: None,
|
||||||
user: None,
|
user: None,
|
||||||
|
|
32
src/test.rs
32
src/test.rs
|
@ -1,32 +0,0 @@
|
||||||
mod config;
|
|
||||||
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let config = config::Config {
|
|
||||||
s3_endpoint: "http://127.0.0.1:3900".to_string(),
|
|
||||||
k2v_endpoint: "http://127.0.0.1:3904".to_string(),
|
|
||||||
aws_region: "garage".to_string(),
|
|
||||||
login_static: Some(config::LoginStaticConfig {
|
|
||||||
default_bucket: Some("mailrage".to_string()),
|
|
||||||
users: HashMap::from([(
|
|
||||||
"quentin".to_string(),
|
|
||||||
config::LoginStaticUser {
|
|
||||||
password: "toto".to_string(),
|
|
||||||
aws_access_key_id: "GKxxx".to_string(),
|
|
||||||
aws_secret_access_key: "ffff".to_string(),
|
|
||||||
bucket: Some("mailrage-quentin".to_string()),
|
|
||||||
user_secret: "xxx".to_string(),
|
|
||||||
alternate_user_secrets: vec![],
|
|
||||||
master_key: None,
|
|
||||||
secret_key: None,
|
|
||||||
},
|
|
||||||
)]),
|
|
||||||
}),
|
|
||||||
login_ldap: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let ser = toml::to_string(&config).unwrap();
|
|
||||||
println!("{}", ser);
|
|
||||||
}
|
|
|
@ -2,21 +2,12 @@ use im::{HashMap, HashSet, OrdMap, OrdSet};
|
||||||
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::bayou::*;
|
use crate::bayou::*;
|
||||||
|
use crate::mail_ident::MailIdent;
|
||||||
|
|
||||||
pub type ImapUid = u32;
|
pub type ImapUid = u32;
|
||||||
pub type ImapUidvalidity = u32;
|
pub type ImapUidvalidity = u32;
|
||||||
pub type Flag = String;
|
pub type Flag = String;
|
||||||
|
|
||||||
/// Mail Identifier (MailIdent) are composed of two components:
|
|
||||||
/// - a process identifier, 128 bits
|
|
||||||
/// - a sequence number, 64 bits
|
|
||||||
/// They are not part of the protocol but an internal representation
|
|
||||||
/// required by Mailrage/Aerogramme.
|
|
||||||
/// Their main property is to be unique without having to rely
|
|
||||||
/// on synchronization between IMAP processes.
|
|
||||||
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
|
|
||||||
pub struct MailIdent(pub [u8; 24]);
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
/// A UidIndex handles the mutable part of a mailbox
|
/// A UidIndex handles the mutable part of a mailbox
|
||||||
/// It is built by running the event log on it
|
/// It is built by running the event log on it
|
||||||
|
@ -244,33 +235,6 @@ impl Serialize for UidIndex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for MailIdent {
|
|
||||||
fn deserialize<D>(d: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let v = String::deserialize(d)?;
|
|
||||||
let bytes = hex::decode(v).map_err(|_| D::Error::custom("invalid hex"))?;
|
|
||||||
|
|
||||||
if bytes.len() != 24 {
|
|
||||||
return Err(D::Error::custom("bad length"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut tmp = [0u8; 24];
|
|
||||||
tmp[..].copy_from_slice(&bytes);
|
|
||||||
Ok(Self(tmp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for MailIdent {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(&hex::encode(self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- TESTS ----
|
// ---- TESTS ----
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in a new issue