mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-12-09 21:05:59 +08:00
DKIM record management API
This commit is contained in:
parent
93a2f691ea
commit
3a5ca70365
16 changed files with 564 additions and 141 deletions
145
Cargo.lock
generated
145
Cargo.lock
generated
|
|
@ -268,7 +268,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"synstructure 0.13.1",
|
||||
]
|
||||
|
||||
|
|
@ -291,14 +291,14 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c"
|
||||
checksum = "86a9249d1447a85f95810c620abea82e001fe58a31713fcce614caf52499f905"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"futures-core",
|
||||
|
|
@ -315,7 +315,7 @@ checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -337,7 +337,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -348,7 +348,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -536,7 +536,7 @@ dependencies = [
|
|||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"which",
|
||||
]
|
||||
|
||||
|
|
@ -679,7 +679,7 @@ dependencies = [
|
|||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"syn_derive",
|
||||
]
|
||||
|
||||
|
|
@ -922,7 +922,7 @@ dependencies = [
|
|||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim 0.11.0",
|
||||
"strsim 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -934,7 +934,7 @@ dependencies = [
|
|||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1267,7 +1267,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1315,7 +1315,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1337,7 +1337,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
|||
dependencies = [
|
||||
"darling_core 0.20.8",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1403,9 +1403,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "decancer"
|
||||
version = "3.1.1"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b6083786d3fc4f0978c6bdd60334338b2b23a512ce897deafa76be8aa917d30"
|
||||
checksum = "544abbf2df1b730422bf18a9d9ac020aaf4bea0467b52829d29f4379e27c8383"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"paste",
|
||||
|
|
@ -1414,9 +1414,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.8"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
|
||||
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
|
|
@ -1588,7 +1588,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1790,7 +1790,7 @@ dependencies = [
|
|||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1991,7 +1991,7 @@ checksum = "f8db6653cbc621a3810d95d55bd342be3e71181d6df21a4eb29ef986202d3f9c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"try_map",
|
||||
]
|
||||
|
||||
|
|
@ -2030,7 +2030,7 @@ checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e"
|
|||
dependencies = [
|
||||
"frunk_proc_macro_helpers",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2042,7 +2042,7 @@ dependencies = [
|
|||
"frunk_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2054,7 +2054,7 @@ dependencies = [
|
|||
"frunk_core",
|
||||
"frunk_proc_macro_helpers",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2119,7 +2119,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3113,13 +3113,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.0.1"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3218,15 +3217,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lz4_flex"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15"
|
||||
checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
|
||||
|
||||
[[package]]
|
||||
name = "mail-auth"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23ec1e6a7ed37e42babe87f67b865c9484e271538a3a9c4664e522836cc3e73a"
|
||||
checksum = "4e9759ecef5c0d048464fee80947ca5ef25faff98add10ea8787a6e195b8dc5f"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"flate2",
|
||||
|
|
@ -3236,7 +3235,9 @@ dependencies = [
|
|||
"mail-parser",
|
||||
"parking_lot",
|
||||
"quick-xml 0.31.0",
|
||||
"rand",
|
||||
"ring 0.17.8",
|
||||
"rsa",
|
||||
"rustls-pemfile 2.1.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -3358,7 +3359,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3465,7 +3466,7 @@ dependencies = [
|
|||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"termcolor",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
@ -3509,9 +3510,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mysql_common"
|
||||
version = "0.32.1"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a60cb978c0a1d654edcc1460f8d6092dacf21346ed6017d81fb76a23ef5a8de"
|
||||
checksum = "0ccdc1fe2bb3ef97e07ba4397327ed45509a1e2e499e2f8265243879cbc7313c"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bigdecimal",
|
||||
|
|
@ -3756,7 +3757,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3776,9 +3777,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.101"
|
||||
version = "0.9.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
|
||||
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
|
@ -3804,9 +3805,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "opentelemetry-http"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cbfa5308166ca861434f0b0913569579b8e587430a3d6bcd7fd671921ec145a"
|
||||
checksum = "7690dc77bf776713848c4faa6501157469017eaf332baccd4eb1cea928743d94"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
|
@ -4063,7 +4064,7 @@ dependencies = [
|
|||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4101,14 +4102,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
|
@ -4221,7 +4222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4319,7 +4320,7 @@ dependencies = [
|
|||
"itertools 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4591,9 +4592,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
|
||||
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
|
|
@ -5237,9 +5238,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.9.2"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
|
||||
checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
|
|
@ -5250,9 +5251,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.9.1"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
|
||||
checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
|
|
@ -5359,7 +5360,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5429,7 +5430,7 @@ checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5830,9 +5831,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subprocess"
|
||||
|
|
@ -5863,9 +5864,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.55"
|
||||
version = "2.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -5881,7 +5882,7 @@ dependencies = [
|
|||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5910,7 +5911,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6050,7 +6051,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6130,9 +6131,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.36.0"
|
||||
version = "1.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
|
||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
|
@ -6165,7 +6166,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6364,7 +6365,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6740,7 +6741,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
|
@ -6774,7 +6775,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
|
@ -7149,9 +7150,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.19"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
|
||||
checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
|
|
@ -7185,7 +7186,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7205,7 +7206,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -248,11 +248,16 @@ impl ConfigManager {
|
|||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn set(&self, keys: impl IntoIterator<Item = ConfigKey>) -> store::Result<()> {
|
||||
pub async fn set<I, T>(&self, keys: I) -> store::Result<()>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<ConfigKey>,
|
||||
{
|
||||
let mut batch = BatchBuilder::new();
|
||||
let mut local_batch = Vec::new();
|
||||
|
||||
for key in keys {
|
||||
let key = key.into();
|
||||
if self.cfg_local_patterns.is_local_key(&key.key) {
|
||||
local_batch.push(key);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ impl Scripting {
|
|||
IfBlock::new::<()>(
|
||||
"sieve.trusted.sign",
|
||||
[],
|
||||
"['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
|
||||
"['rsa-' + key_get('default', 'domain'), 'ed25519-' + key_get('default', 'domain')]",
|
||||
)
|
||||
}),
|
||||
scripts,
|
||||
|
|
@ -349,7 +349,7 @@ impl Default for Scripting {
|
|||
sign: IfBlock::new::<()>(
|
||||
"sieve.trusted.sign",
|
||||
[],
|
||||
"['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
|
||||
"['rsa-' + key_get('default', 'domain'), 'ed25519-' + key_get('default', 'domain')]",
|
||||
),
|
||||
scripts: AHashMap::new(),
|
||||
bayes_cache: BayesTokenCache::new(
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ impl Default for MailAuthConfig {
|
|||
"auth.dkim.sign",
|
||||
[(
|
||||
"is_local_domain('*', sender_domain)",
|
||||
"['rsa_' + sender_domain, 'ed_' + sender_domain]",
|
||||
"['rsa-' + sender_domain, 'ed25519-' + sender_domain]",
|
||||
)],
|
||||
"false",
|
||||
),
|
||||
|
|
@ -101,7 +101,7 @@ impl Default for MailAuthConfig {
|
|||
seal: IfBlock::new::<()>(
|
||||
"auth.arc.seal",
|
||||
[],
|
||||
"['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
|
||||
"['rsa-' + key_get('default', 'domain'), 'ed25519-' + key_get('default', 'domain')]",
|
||||
),
|
||||
},
|
||||
spf: SpfAuthConfig {
|
||||
|
|
@ -226,50 +226,8 @@ fn build_signature(config: &mut Config, id: &str) -> Option<(DkimSigner, ArcSeal
|
|||
(DkimSigner::RsaSha256(signer), ArcSealer::RsaSha256(sealer)).into()
|
||||
}
|
||||
Algorithm::Ed25519Sha256 => {
|
||||
let mut public_key = vec![];
|
||||
let mut private_key = vec![];
|
||||
|
||||
for (key, key_bytes) in [
|
||||
(("signature", id, "public-key"), &mut public_key),
|
||||
(("signature", id, "private-key"), &mut private_key),
|
||||
] {
|
||||
let mut contents = config.value_require(key)?.as_bytes().iter().copied();
|
||||
let mut base64 = vec![];
|
||||
|
||||
'outer: while let Some(ch) = contents.next() {
|
||||
if !ch.is_ascii_whitespace() {
|
||||
if ch == b'-' {
|
||||
for ch in contents.by_ref() {
|
||||
if ch == b'\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
base64.push(ch);
|
||||
}
|
||||
|
||||
for ch in contents.by_ref() {
|
||||
if ch == b'-' {
|
||||
break 'outer;
|
||||
} else if !ch.is_ascii_whitespace() {
|
||||
base64.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*key_bytes = base64_decode(&base64)
|
||||
.ok_or_else(|| {
|
||||
config.new_build_error(
|
||||
("signature", id),
|
||||
format!("Failed to base64 decode key for {}.", key.as_key(),),
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
}
|
||||
|
||||
let private_key = parse_pem(config, ("signature", id, "private-key"))?;
|
||||
let key = Ed25519Key::from_pkcs8_maybe_unchecked_der(&private_key)
|
||||
.or_else(|_| Ed25519Key::from_seed_and_public_key(&private_key, &public_key))
|
||||
.map_err(|err| {
|
||||
config.new_build_error(
|
||||
("signature", id),
|
||||
|
|
@ -278,7 +236,6 @@ fn build_signature(config: &mut Config, id: &str) -> Option<(DkimSigner, ArcSeal
|
|||
})
|
||||
.ok()?;
|
||||
let key_clone = Ed25519Key::from_pkcs8_maybe_unchecked_der(&private_key)
|
||||
.or_else(|_| Ed25519Key::from_seed_and_public_key(&private_key, &public_key))
|
||||
.map_err(|err| {
|
||||
config.new_build_error(
|
||||
("signature", id),
|
||||
|
|
@ -304,6 +261,44 @@ fn build_signature(config: &mut Config, id: &str) -> Option<(DkimSigner, ArcSeal
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_pem(config: &mut Config, key: impl AsKey) -> Option<Vec<u8>> {
|
||||
if let Some(der) = simple_pem_parse(config.value_require(key.clone())?) {
|
||||
Some(der)
|
||||
} else {
|
||||
config.new_build_error(key, "Failed to base64 decode key.");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simple_pem_parse(contents: &str) -> Option<Vec<u8>> {
|
||||
let mut contents = contents.as_bytes().iter().copied();
|
||||
let mut base64 = vec![];
|
||||
|
||||
'outer: while let Some(ch) = contents.next() {
|
||||
if !ch.is_ascii_whitespace() {
|
||||
if ch == b'-' {
|
||||
for ch in contents.by_ref() {
|
||||
if ch == b'\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
base64.push(ch);
|
||||
}
|
||||
|
||||
for ch in contents.by_ref() {
|
||||
if ch == b'-' {
|
||||
break 'outer;
|
||||
} else if !ch.is_ascii_whitespace() {
|
||||
base64.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base64_decode(&base64)
|
||||
}
|
||||
|
||||
fn parse_signature<T: SigningKey, U: SigningKey<Hasher = Sha256>>(
|
||||
config: &mut Config,
|
||||
id: &str,
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ impl Default for QueueConfig {
|
|||
sign: IfBlock::new::<()>(
|
||||
"report.dsn.sign",
|
||||
[],
|
||||
"['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
|
||||
"['rsa-' + key_get('default', 'domain'), 'ed25519-' + key_get('default', 'domain')]",
|
||||
),
|
||||
},
|
||||
timeout: QueueOutboundTimeout {
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ impl Report {
|
|||
sign: IfBlock::new::<()>(
|
||||
format!("report.{id}.sign"),
|
||||
[],
|
||||
"['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
|
||||
"['rsa-' + key_get('default', 'domain'), 'ed25519-' + key_get('default', 'domain')]",
|
||||
),
|
||||
send: IfBlock::new::<()>(format!("report.{id}.send"), [], "[1, 1d]"),
|
||||
};
|
||||
|
|
@ -174,7 +174,7 @@ impl AggregateReport {
|
|||
sign: IfBlock::new::<()>(
|
||||
format!("report.{id}.aggregate.sign"),
|
||||
[],
|
||||
"['rsa_' + key_get('default', 'domain'), 'ed_' + key_get('default', 'domain')]",
|
||||
"['rsa-' + key_get('default', 'domain'), 'ed25519-' + key_get('default', 'domain')]",
|
||||
),
|
||||
max_size: IfBlock::new::<()>(format!("report.{id}.aggregate.max-size"), [], "26214400"),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ impl CharUtils for char {
|
|||
|
||||
pub fn fn_cure_text<'x>(_: &'x Context<'x>, v: Vec<Variable>) -> Variable {
|
||||
decancer::cure(v[0].to_string().as_ref(), decancer::Options::default())
|
||||
.map(|s| s.into_str())
|
||||
.map(String::from)
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -587,11 +587,11 @@ mod test {
|
|||
// Test spec
|
||||
let mut text = String::new();
|
||||
for i in 0..100 {
|
||||
text += &format!("Line{i} test test test\n");
|
||||
text += format!("Line{i} test test test\n").as_str();
|
||||
}
|
||||
let mut expected = String::new();
|
||||
for i in [20, 21, 22, 60, 61, 62] {
|
||||
expected += &format!("Line{i}testtesttest");
|
||||
expected += format!("Line{i}testtesttest").as_str();
|
||||
}
|
||||
assert_eq!(
|
||||
String::from_utf8(pyzor_digest(Vec::new(), text.lines(), &psl)).unwrap(),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ smtp-proto = { version = "0.1" }
|
|||
mail-parser = { version = "0.9", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
|
||||
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }
|
||||
mail-send = { version = "0.4", default-features = false, features = ["cram-md5"] }
|
||||
mail-auth = { version = "0.3" }
|
||||
mail-auth = { version = "0.3", features = ["generate"] }
|
||||
sieve-rs = { version = "0.5" }
|
||||
serde = { version = "1.0", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
|
|
|
|||
266
crates/jmap/src/api/management/dkim.rs
Normal file
266
crates/jmap/src/api/management/dkim.rs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Stalwart Labs Ltd.
|
||||
*
|
||||
* This file is part of Stalwart Mail Server.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
* in the LICENSE file at the top-level directory of this distribution.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* You can be released from the requirements of the AGPLv3 license by
|
||||
* purchasing a commercial license. Please contact licensing@stalw.art
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use common::config::smtp::auth::simple_pem_parse;
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use mail_auth::{
|
||||
common::crypto::{Ed25519Key, RsaKey, Sha256},
|
||||
dkim::generate::DkimKeyPair,
|
||||
};
|
||||
use mail_builder::encoders::base64::base64_encode;
|
||||
use mail_parser::DateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use store::write::now;
|
||||
|
||||
use crate::{
|
||||
api::{
|
||||
http::ToHttpResponse, management::ManagementApiError, HttpRequest, HttpResponse,
|
||||
JsonResponse,
|
||||
},
|
||||
JMAP,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Algorithm {
|
||||
Rsa,
|
||||
Ed25519,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct DkimSignature {
|
||||
id: Option<String>,
|
||||
algorithm: Algorithm,
|
||||
domain: String,
|
||||
selector: Option<String>,
|
||||
}
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_manage_dkim(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
match *req.method() {
|
||||
Method::GET => self.handle_get_public_key(path).await,
|
||||
Method::POST => self.handle_create_signature(body).await,
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_get_public_key(&self, path: Vec<&str>) -> HttpResponse {
|
||||
let signature_id = match path.get(1) {
|
||||
Some(signature_id) => *signature_id,
|
||||
None => {
|
||||
return RequestError::not_found().into_http_response();
|
||||
}
|
||||
};
|
||||
|
||||
let (pk, algo) = match (
|
||||
self.core
|
||||
.storage
|
||||
.config
|
||||
.get(&format!("signature.{signature_id}.private-key"))
|
||||
.await,
|
||||
self.core
|
||||
.storage
|
||||
.config
|
||||
.get(&format!("signature.{signature_id}.algorithm"))
|
||||
.await
|
||||
.map(|algo| algo.and_then(|algo| algo.parse::<Algorithm>().ok())),
|
||||
) {
|
||||
(Ok(Some(pk)), Ok(Some(algorithm))) => (pk, algorithm),
|
||||
(Err(err), _) | (_, Err(err)) => return err.into_http_response(),
|
||||
_ => return RequestError::not_found().into_http_response(),
|
||||
};
|
||||
|
||||
match obtain_dkim_public_key(algo, &pk) {
|
||||
Ok(data) => JsonResponse::new(json!({
|
||||
"data": data,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => ManagementApiError::Other {
|
||||
details: err.into(),
|
||||
}
|
||||
.into_http_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_create_signature(&self, body: Option<Vec<u8>>) -> HttpResponse {
|
||||
let request =
|
||||
match serde_json::from_slice::<DkimSignature>(body.as_deref().unwrap_or_default()) {
|
||||
Ok(request) => request,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
|
||||
let algo_str = match request.algorithm {
|
||||
Algorithm::Rsa => "rsa",
|
||||
Algorithm::Ed25519 => "ed25519",
|
||||
};
|
||||
let id = request
|
||||
.id
|
||||
.unwrap_or_else(|| format!("{algo_str}-{}", request.domain));
|
||||
let selector = request.selector.unwrap_or_else(|| {
|
||||
let dt = DateTime::from_timestamp(now() as i64);
|
||||
format!(
|
||||
"{:04}{:02}{}",
|
||||
dt.year,
|
||||
dt.month,
|
||||
if Algorithm::Rsa == request.algorithm {
|
||||
"r"
|
||||
} else {
|
||||
"e"
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
// Make sure the signature does not exist already
|
||||
match self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.get(&format!("signature.{id}.private-key"))
|
||||
.await
|
||||
{
|
||||
Ok(None) => (),
|
||||
Ok(Some(value)) => {
|
||||
return ManagementApiError::FieldAlreadyExists {
|
||||
field: format!("signature.{id}.private-key").into(),
|
||||
value: value.into(),
|
||||
}
|
||||
.into_http_response();
|
||||
}
|
||||
Err(err) => return err.into_http_response(),
|
||||
}
|
||||
|
||||
// Create signature
|
||||
match self
|
||||
.create_dkim_key(request.algorithm, id, request.domain, selector)
|
||||
.await
|
||||
{
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_dkim_key(
|
||||
&self,
|
||||
algo: Algorithm,
|
||||
id: impl AsRef<str>,
|
||||
domain: impl Into<String>,
|
||||
selector: impl Into<String>,
|
||||
) -> store::Result<()> {
|
||||
let id = id.as_ref();
|
||||
let (algorithm, pk_type) = match algo {
|
||||
Algorithm::Rsa => ("rsa-sha256", "RSA PRIVATE KEY"),
|
||||
Algorithm::Ed25519 => ("ed25519-sha256", "PRIVATE KEY"),
|
||||
};
|
||||
let mut pk = format!("-----BEGIN {pk_type}-----\n").into_bytes();
|
||||
let mut lf_count = 65;
|
||||
for ch in base64_encode(
|
||||
match algo {
|
||||
Algorithm::Rsa => DkimKeyPair::generate_rsa(2048),
|
||||
Algorithm::Ed25519 => DkimKeyPair::generate_ed25519(),
|
||||
}
|
||||
.map_err(|err| store::Error::InternalError(err.to_string()))?
|
||||
.private_key(),
|
||||
)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
pk.push(ch);
|
||||
lf_count -= 1;
|
||||
if lf_count == 0 {
|
||||
pk.push(b'\n');
|
||||
lf_count = 65;
|
||||
}
|
||||
}
|
||||
if lf_count != 65 {
|
||||
pk.push(b'\n');
|
||||
}
|
||||
pk.extend_from_slice(format!("-----END {pk_type}-----\n").as_bytes());
|
||||
|
||||
self.core
|
||||
.storage
|
||||
.config
|
||||
.set([
|
||||
(
|
||||
format!("signature.{id}.private-key"),
|
||||
String::from_utf8(pk).unwrap(),
|
||||
),
|
||||
(format!("signature.{id}.domain"), domain.into()),
|
||||
(format!("signature.{id}.selector"), selector.into()),
|
||||
(format!("signature.{id}.algorithm"), algorithm.to_string()),
|
||||
(
|
||||
format!("signature.{id}.canonicalization"),
|
||||
"relaxed/relaxed".to_string(),
|
||||
),
|
||||
(
|
||||
format!("signature.{id}.headers"),
|
||||
"['From', 'To', 'Date', 'Subject', 'Message-ID']".to_string(),
|
||||
),
|
||||
(format!("signature.{id}.report"), "false".to_string()),
|
||||
])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn obtain_dkim_public_key(algo: Algorithm, pk: &str) -> Result<String, &'static str> {
|
||||
match simple_pem_parse(pk) {
|
||||
Some(der) => match algo {
|
||||
Algorithm::Rsa => match RsaKey::<Sha256>::from_der(&der) {
|
||||
Ok(pk) => Ok(String::from_utf8(
|
||||
base64_encode(&pk.public_key()).unwrap_or_default(),
|
||||
)
|
||||
.unwrap_or_default()),
|
||||
Err(_) => Err("Failed to read RSA DER"),
|
||||
},
|
||||
Algorithm::Ed25519 => match Ed25519Key::from_pkcs8_der(&der) {
|
||||
Ok(pk) => Ok(String::from_utf8(
|
||||
base64_encode(&pk.public_key()).unwrap_or_default(),
|
||||
)
|
||||
.unwrap_or_default()),
|
||||
Err(_) => Err("Failed to read ED25519 PKCS#8 DER"),
|
||||
},
|
||||
},
|
||||
None => Err("Failed to decode private key"),
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Algorithm {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once('-').map(|(algo, _)| algo) {
|
||||
Some("rsa") => Ok(Algorithm::Rsa),
|
||||
Some("ed25519") => Ok(Algorithm::Ed25519),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,14 +25,28 @@ use directory::backend::internal::manage::ManageDirectory;
|
|||
use http_body_util::combinators::BoxBody;
|
||||
use hyper::{body::Bytes, Method};
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use store::ahash::AHashMap;
|
||||
use utils::url_params::UrlParams;
|
||||
|
||||
use crate::{
|
||||
api::{http::ToHttpResponse, HttpRequest, JsonResponse},
|
||||
api::{
|
||||
http::ToHttpResponse,
|
||||
management::dkim::{obtain_dkim_public_key, Algorithm},
|
||||
HttpRequest, JsonResponse,
|
||||
},
|
||||
JMAP,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct DnsRecord {
|
||||
#[serde(rename = "type")]
|
||||
typ: String,
|
||||
name: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_manage_domain(
|
||||
&self,
|
||||
|
|
@ -70,12 +84,17 @@ impl JMAP {
|
|||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
}
|
||||
(Some(domain), &Method::POST) => {
|
||||
// Make sure the current directory supports updates
|
||||
if let Some(response) = self.assert_supported_directory() {
|
||||
return response;
|
||||
(Some(domain), &Method::GET) => {
|
||||
// Obtain DNS records
|
||||
match self.build_dns_records(domain).await {
|
||||
Ok(records) => JsonResponse::new(json!({
|
||||
"data": records,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
|
||||
}
|
||||
(Some(domain), &Method::POST) => {
|
||||
// Create domain
|
||||
match self.core.storage.data.create_domain(domain).await {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
|
|
@ -99,4 +118,98 @@ impl JMAP {
|
|||
_ => RequestError::not_found().into_http_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_dns_records(&self, domain_name: &str) -> store::Result<Vec<DnsRecord>> {
|
||||
// Obtain server name
|
||||
let server_name = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.get("lookup.default.hostname")
|
||||
.await?
|
||||
.unwrap_or_else(|| "localhost".to_string());
|
||||
let mut records = Vec::new();
|
||||
|
||||
// Obtain DKIM keys
|
||||
let mut keys = AHashMap::new();
|
||||
let mut signature_ids = Vec::new();
|
||||
for (key, value) in self.core.storage.config.list("signature.", true).await? {
|
||||
match key.strip_suffix(".domain") {
|
||||
Some(key_id) if value == domain_name => {
|
||||
signature_ids.push(key_id.to_string());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
keys.insert(key, value);
|
||||
}
|
||||
|
||||
// Add MX and CNAME records
|
||||
records.push(DnsRecord {
|
||||
typ: "MX".to_string(),
|
||||
name: format!("{domain_name}."),
|
||||
content: format!("10 {server_name}."),
|
||||
});
|
||||
if server_name
|
||||
.strip_prefix("mail.")
|
||||
.map_or(true, |s| s != domain_name)
|
||||
{
|
||||
records.push(DnsRecord {
|
||||
typ: "CNAME".to_string(),
|
||||
name: format!("mail.{domain_name}."),
|
||||
content: format!("{server_name}."),
|
||||
});
|
||||
}
|
||||
|
||||
// Process DKIM keys
|
||||
for signature_id in signature_ids {
|
||||
if let (Some(algo), Some(pk), Some(selector)) = (
|
||||
keys.get(&format!("{signature_id}.algorithm"))
|
||||
.and_then(|algo| algo.parse::<Algorithm>().ok()),
|
||||
keys.get(&format!("{signature_id}.private-key")),
|
||||
keys.get(&format!("{signature_id}.selector")),
|
||||
) {
|
||||
match obtain_dkim_public_key(algo, pk) {
|
||||
Ok(public) => {
|
||||
records.push(DnsRecord {
|
||||
typ: "TXT".to_string(),
|
||||
name: format!("{selector}._domainkey.{domain_name}.",),
|
||||
content: match algo {
|
||||
Algorithm::Rsa => format!("v=DKIM1; k=rsa; h=sha256; p={public}"),
|
||||
Algorithm::Ed25519 => {
|
||||
format!("v=DKIM1; k=ed25519; h=sha256; p={public}")
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::debug!("Failed to obtain DKIM public key: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add SPF records
|
||||
if server_name.ends_with(&format!(".{domain_name}")) || server_name == domain_name {
|
||||
records.push(DnsRecord {
|
||||
typ: "TXT".to_string(),
|
||||
name: format!("{server_name}."),
|
||||
content: "v=spf1 a -all ra=postmaster".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
records.push(DnsRecord {
|
||||
typ: "TXT".to_string(),
|
||||
name: format!("{domain_name}."),
|
||||
content: "v=spf1 mx -all ra=postmaster".to_string(),
|
||||
});
|
||||
|
||||
// Add DMARC records
|
||||
records.push(DnsRecord {
|
||||
typ: "TXT".to_string(),
|
||||
name: format!("_dmarc.{domain_name}."),
|
||||
content: format!("v=DMARC1; p=reject; rua=mailto:postmaster@{domain_name}; ruf=mailto:postmaster@{domain_name}",),
|
||||
});
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
* for more details.
|
||||
*/
|
||||
|
||||
pub mod dkim;
|
||||
pub mod domain;
|
||||
pub mod principal;
|
||||
pub mod queue;
|
||||
|
|
@ -83,6 +84,7 @@ impl JMAP {
|
|||
"settings" if is_superuser => self.handle_manage_settings(req, path, body).await,
|
||||
"queue" if is_superuser => self.handle_manage_queue(req, path).await,
|
||||
"reports" if is_superuser => self.handle_manage_reports(req, path).await,
|
||||
"dkim" if is_superuser => self.handle_manage_dkim(req, path, body).await,
|
||||
"oauth" => self.handle_oauth_api_request(access_token, body).await,
|
||||
"crypto" => match *req.method() {
|
||||
Method::POST => self.handle_crypto_post(access_token, body).await,
|
||||
|
|
|
|||
|
|
@ -217,3 +217,36 @@ impl Config {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(String, String)> for ConfigKey {
|
||||
fn from((key, value): (String, String)) -> Self {
|
||||
Self { key, value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&str, &str)> for ConfigKey {
|
||||
fn from((key, value): (&str, &str)) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
value: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&str, String)> for ConfigKey {
|
||||
fn from((key, value): (&str, String)) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(String, &str)> for ConfigKey {
|
||||
fn from((key, value): (String, &str)) -> Self {
|
||||
Self {
|
||||
key,
|
||||
value: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
[[queue.quota]]
|
||||
[queue.quota.queue-max-size]
|
||||
messages = 100000
|
||||
size = 10737418240 # 10gb
|
||||
enable = true
|
||||
|
||||
[[queue.throttle]]
|
||||
[queue.throttle.recipient-limit]
|
||||
key = ["rcpt_domain"]
|
||||
concurrency = 5
|
||||
enable = true
|
||||
|
||||
[[session.throttle]]
|
||||
[session.throttle.concurrency-by-remote-ip]
|
||||
key = ["remote_ip"]
|
||||
concurrency = 5
|
||||
enable = true
|
||||
|
||||
[[session.throttle]]
|
||||
[session.throttle.rate-by-sender]
|
||||
key = ["sender_domain", "rcpt"]
|
||||
rate = "25/1h"
|
||||
enable = true
|
||||
|
|
|
|||
|
|
@ -586,7 +586,7 @@ pub fn spawn_mock_smtp_server() -> (mpsc::Receiver<MockMessage>, Arc<Mutex<MockS
|
|||
message.message = message.message.trim().to_string();
|
||||
break;
|
||||
} else {
|
||||
message.message += &buf;
|
||||
message.message += buf.as_str();
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,9 +80,8 @@ set-body-length = true
|
|||
report = true
|
||||
|
||||
[signature.ed]
|
||||
public-key = '11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo='
|
||||
private-key = '-----BEGIN PRIVATE KEY-----
|
||||
nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=
|
||||
MC4CAQAwBQYDK2VwBCIEIAO3hAf144lTAVjTkht3ZwBTK0CMCCd1bI0alggneN3B
|
||||
-----END PRIVATE KEY-----'
|
||||
domain = 'example.com'
|
||||
selector = 'ed'
|
||||
|
|
@ -142,6 +141,15 @@ verify = "relaxed"
|
|||
|
||||
#[tokio::test]
|
||||
async fn sign_and_seal() {
|
||||
// Enable logging
|
||||
/*let disable = "true";
|
||||
tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::TRACE)
|
||||
.finish(),
|
||||
)
|
||||
.unwrap();*/
|
||||
|
||||
let tmp_dir = TempDir::new("smtp_sign_test", true);
|
||||
let mut config = Config::new(tmp_dir.update_config(CONFIG.to_string() + SIGNATURES)).unwrap();
|
||||
let stores = Stores::parse_all(&mut config).await;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue