From 268ca2d1a55e5a097c7d220e7eb4f8d47a6b0a2c Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Sun, 12 Jan 2025 14:10:23 -0800 Subject: [PATCH] This builds, I guess. --- Cargo.lock | 911 ++++++++++++++++++++++---------- Cargo.toml | 8 +- src/bin/hush.rs | 21 +- src/bin/hushd.rs | 67 ++- src/client.rs | 58 +- src/config.rs | 351 +++--------- src/config/client.rs | 60 +++ src/config/command_line.rs | 28 +- src/config/config_file.rs | 143 ++++- src/config/connection.rs | 77 +++ src/config/error.rs | 11 + src/config/resolver.rs | 242 ++++----- src/config/runtime.rs | 19 + src/config/server.rs | 67 +++ src/lib.rs | 1 + src/network.rs | 1 + src/network/host.rs | 40 +- src/network/resolver.rs | 261 +++++++++ src/operational_error.rs | 2 + src/server.rs | 28 + src/server/socket.rs | 75 +++ src/server/state.rs | 8 + src/ssh.rs | 2 +- src/ssh/packets.rs | 2 +- src/ssh/packets/key_exchange.rs | 2 +- 25 files changed, 1750 insertions(+), 735 deletions(-) create mode 100644 src/config/client.rs create mode 100644 src/config/connection.rs create mode 100644 src/config/runtime.rs create mode 100644 src/config/server.rs create mode 100644 src/network/resolver.rs create mode 100644 src/server.rs create mode 100644 src/server/socket.rs create mode 100644 src/server/state.rs diff --git a/Cargo.lock b/Cargo.lock index 76308b2..4bda438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -55,49 +55,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arrayvec" @@ -105,6 +105,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -201,7 +212,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -302,9 +313,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cfg-if" @@ -312,6 +323,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cipher" version = "0.4.4" @@ -325,9 +342,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.19" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -335,9 +352,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -359,15 +376,24 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] [[package]] name = "console-api" @@ -415,9 +441,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -440,6 +466,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -534,6 +569,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -601,6 +647,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -621,12 +673,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -640,10 +692,31 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "2.1.1" +name = "event-listener" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "ff" @@ -663,9 +736,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -775,6 +848,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -841,9 +927,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hdrhistogram" @@ -891,6 +977,25 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hickory-client" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab9683b08d8f8957a857b0236455d80e1886eaa8c6178af556aa7871fb61b55" +dependencies = [ + "cfg-if", + "data-encoding", + "futures-channel", + "futures-util", + "hickory-proto", + "once_cell", + "radix_trie", + "rand", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "hickory-proto" version = "0.24.1" @@ -908,34 +1013,14 @@ dependencies = [ "ipnet", "once_cell", "rand", - "thiserror", + "socket2", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", "url", ] -[[package]] -name = "hickory-resolver" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", -] - [[package]] name = "hkdf" version = "0.12.4" @@ -954,23 +1039,6 @@ dependencies = [ "digest", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "hostname-validator" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" - [[package]] name = "http" version = "0.2.12" @@ -1029,10 +1097,11 @@ dependencies = [ "futures", "generic-array", "hexdump", + "hickory-client", "hickory-proto", - "hickory-resolver", - "hostname-validator", "itertools 0.13.0", + "moka", + "nix", "num-bigint-dig", "num-integer", "num-traits", @@ -1043,10 +1112,11 @@ dependencies = [ "proptest", "rand", "rand_chacha", + "rustix", "sec1", "serde", "tempfile", - "thiserror", + "thiserror 2.0.3", "tokio", "toml", "tracing", @@ -1059,9 +1129,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -1093,6 +1163,124 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "idna" version = "0.4.0" @@ -1105,12 +1293,23 @@ dependencies = [ [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1130,7 +1329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -1143,18 +1342,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "ipconfig" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" -dependencies = [ - "socket2", - "widestring", - "windows-sys 0.48.0", - "winreg", -] - [[package]] name = "ipnet" version = "2.10.1" @@ -1187,9 +1374,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "lazy_static" @@ -1202,21 +1389,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linux-raw-sys" @@ -1224,6 +1405,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -1241,20 +1428,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "lru-cache" -version = "0.1.2" +name = "loom" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ - "linked-hash-map", + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.1.0" @@ -1309,6 +1494,49 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "moka" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener", + "futures-util", + "loom", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1464,6 +1692,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1484,7 +1718,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1513,18 +1747,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", @@ -1533,9 +1767,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1553,6 +1787,12 @@ dependencies = [ "spki", ] +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1583,9 +1823,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1657,6 +1897,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -1707,13 +1957,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -1728,9 +1978,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1749,16 +1999,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -1786,9 +2026,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -1799,9 +2039,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rusty-fork" @@ -1821,6 +2061,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1850,18 +2096,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -1870,9 +2116,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -1955,9 +2201,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1979,6 +2225,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.11.1" @@ -1993,9 +2245,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -2009,10 +2261,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "tempfile" -version = "3.13.0" +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -2023,18 +2292,38 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", @@ -2051,6 +2340,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2068,9 +2367,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -2225,9 +2524,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2236,9 +2535,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -2247,9 +2546,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2323,9 +2622,9 @@ checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" @@ -2338,21 +2637,42 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.3", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" @@ -2405,12 +2725,6 @@ dependencies = [ "wasite", ] -[[package]] -name = "widestring" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" - [[package]] name = "winapi" version = "0.3.9" @@ -2434,12 +2748,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-targets 0.48.5", + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", ] [[package]] @@ -2448,7 +2817,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2457,22 +2826,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -2481,46 +2835,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2533,48 +2869,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2591,14 +2903,16 @@ dependencies = [ ] [[package]] -name = "winreg" -version = "0.50.0" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xdg" @@ -2606,6 +2920,30 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2627,8 +2965,51 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 3efa3ce..5a53d62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,10 +26,11 @@ error-stack = "0.5.0" futures = "0.3.31" generic-array = "0.14.7" hexdump = "0.1.2" +hickory-client = { version = "0.24.1", features = ["mdns"] } hickory-proto = "0.24.1" -hickory-resolver = "0.24.1" -hostname-validator = "1.1.1" itertools = "0.13.0" +moka = { version = "0.12.10", features = ["future"] } +nix = { version = "0.28.0", features = ["user"] } num-bigint-dig = { version = "0.8.4", features = ["arbitrary", "i128", "zeroize", "prime", "rand"] } num-integer = { version = "0.1.46", features = ["i128"] } num-traits = { version = "0.2.19", features = ["i128"] } @@ -40,10 +41,11 @@ p521 = { version = "0.13.3", features = ["ecdh", "ecdsa-core", "hash2curve", "se proptest = "1.5.0" rand = "0.8.5" rand_chacha = "0.3.1" +rustix = "0.38.41" sec1 = "0.7.3" serde = { version = "1.0.203", features = ["derive"] } tempfile = "3.12.0" -thiserror = "1.0.61" +thiserror = "2.0.3" tokio = { version = "1.38.0", features = ["full", "tracing"] } toml = "0.8.14" tracing = "0.1.40" diff --git a/src/bin/hush.rs b/src/bin/hush.rs index ae0964b..ac670c6 100644 --- a/src/bin/hush.rs +++ b/src/bin/hush.rs @@ -1,9 +1,9 @@ -use error_stack::ResultExt; -use hush::config::{BasicClientConfiguration, ClientConfiguration}; +use hush::config::client::ClientConfiguration; +use std::process; #[cfg(not(tarpaulin_include))] fn main() { - let mut base_config = match BasicClientConfiguration::new(std::env::args()) { + let mut config = match ClientConfiguration::new(std::env::args()) { Ok(config) => config, Err(e) => { eprintln!("ERROR: {}", e); @@ -11,29 +11,26 @@ fn main() { } }; - if let Err(e) = base_config.establish_subscribers() { + if let Err(e) = hush::config::establish_subscribers(&config.logging, &mut config.runtime) { eprintln!("ERROR: could not set up logging infrastructure: {}", e); - std::process::exit(2); + process::exit(2); } - let runtime = match base_config.configured_runtime() { + let runtime = match hush::config::configured_runtime(&config.runtime) { Ok(runtime) => runtime, Err(e) => { tracing::error!(%e, "could not start system runtime"); - std::process::exit(3); + process::exit(3); } }; let result = runtime.block_on(async move { tracing::info!("Starting Hush"); - match ClientConfiguration::try_from(base_config).await { - Ok(config) => hush::client::hush(config).await, - Err(e) => Err(e).change_context(hush::OperationalError::ConfigurationError), - } + hush::client::hush(config).await }); if let Err(e) = result { tracing::error!("{}", e); - std::process::exit(1); + process::exit(1); } } diff --git a/src/bin/hushd.rs b/src/bin/hushd.rs index e385c00..3c7b03e 100644 --- a/src/bin/hushd.rs +++ b/src/bin/hushd.rs @@ -1,4 +1,69 @@ +use futures::stream::StreamExt; +use hickory_proto::udp::UdpStream; +use hush::config::server::ServerConfiguration; +use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr}; +use std::process; +use thiserror::Error; +use tokio::net::UdpSocket; + +async fn run_dns() -> tokio::io::Result<()> { + let socket = UdpSocket::bind("127.0.0.1:3553").await?; + let mut buffer = [0; 65536]; + + tracing::info!("Bound socket at {}, starting main DNS handler loop.", socket.local_addr()?); + let (first_len, first_addr) = socket.recv_from(&mut buffer).await?; + println!("Received {} bytes from {}", first_len, first_addr); + + let remote = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 3553)); + let (mut stream, _handle) = UdpStream::with_bound(socket, remote); + + while let Some(item) = stream.next().await { + match item { + Err(e) => eprintln!("Got an error: {:?}", e), + Ok(msg) => { + println!("Got a message from {}", msg.addr()); + match msg.to_message() { + Err(e) => println!(" ... but it was malformed ({e})."), + Ok(v) => println!(" ... and it was {v}"), + } + } + } + } + + Ok(()) +} + #[cfg(not(tarpaulin_include))] fn main() { - unimplemented!(); + let mut config = match ServerConfiguration::new(std::env::args()) { + Ok(config) => config, + Err(e) => { + eprintln!("ERROR: {}", e); + process::exit(1); + } + }; + + if let Err(e) = hush::config::establish_subscribers(&config.logging, &mut config.runtime) { + eprintln!("ERROR: could not set up logging infrastruxture: {}", e); + process::exit(2); + }; + + let runtime = match hush::config::configured_runtime(&config.runtime) { + Ok(runtime) => runtime, + Err(error) => { + tracing::error!(%error, "could not start system runtime"); + process::exit(3); + } + }; + + let result = runtime.block_on(async move { + let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "".to_string()); + tracing::info!(%version, "Starting Hush server (hushd)"); + hush::server::run(config).await + }); + + if let Err(e) = result { + tracing::error!("{}", e); + process::exit(5); + } } diff --git a/src/client.rs b/src/client.rs index d46296d..3c4c8f5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,12 +1,13 @@ -use crate::config::{ClientCommand, ClientConfiguration}; +use crate::config::client::{ClientCommand, ClientConfiguration}; use crate::crypto::{ CompressionAlgorithm, EncryptionAlgorithm, HostKeyAlgorithm, KeyExchangeAlgorithm, MacAlgorithm, }; use crate::network::host::Host; -use crate::ssh; +use crate::network::resolver::Resolver; +use crate::ssh::{self, SshKeyExchange}; use crate::OperationalError; use error_stack::{report, Report, ResultExt}; -use std::fmt::Display; +use std::fmt; use std::str::FromStr; use thiserror::Error; use tracing::Instrument; @@ -27,12 +28,19 @@ pub async fn hush(base_config: ClientConfiguration) -> error_stack::Result<(), O } } +#[derive(Debug)] struct Target { username: String, host: Host, port: u16, } +impl fmt::Display for Target { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}@{}:{}", self.username, self.host, self.port) + } +} + #[derive(Debug, Error)] pub enum TargetParseError { #[error("Invalid port number '{port_string}': {error}")] @@ -87,35 +95,54 @@ impl FromStr for Target { } } +#[allow(unreachable_code,unused_variables)] async fn connect( base_config: &ClientConfiguration, target: &str, ) -> error_stack::Result<(), OperationalError> { - let resolver = base_config.resolver(); + let resolver: Resolver = unimplemented!(); +// let mut resolver = Resolver::new(&base_config.resolver) +// .await +// .change_context(OperationalError::DnsConfig)?; let target = Target::from_str(target) .change_context(OperationalError::UnableToParseHostAddress) .attach_printable_lazy(|| format!("target address '{}'", target))?; + tracing::trace!(%target, "determined SSH target"); + let mut stream = target .host - .connect(resolver, 22) + .connect(&mut resolver, 22) .await .change_context(OperationalError::Connection)?; + let server_addr_str = stream + .peer_addr() + .map(|x| x.to_string()) + .unwrap_or_else(|_| "".to_string()); + let client_addr_str = stream + .local_addr() + .map(|x| x.to_string()) + .unwrap_or_else(|_| "".to_string()); let stream_span = tracing::debug_span!( "client connection", - server = %stream.peer_addr().map(|x| x.to_string()).unwrap_or_else(|_| "".to_string()), - client = %stream.local_addr().map(|x| x.to_string()).unwrap_or_else(|_| "".to_string()), + server = %server_addr_str, + client = %client_addr_str, ); + tracing::trace!(%target, "connected to target server"); + + let stream_error_info = || server_addr_str.clone(); let their_preamble = ssh::Preamble::read(&mut stream) .instrument(stream_span.clone()) .await .change_context(OperationalError::Connection)?; + tracing::trace!("received their preamble"); - let our_preamble = ssh::Preamble::default() + let _our_preamble = ssh::Preamble::default() .write(&mut stream) .instrument(stream_span) .await .change_context(OperationalError::Connection)?; + tracing::trace!("wrote our preamble"); if !their_preamble.preamble.is_empty() { for line in their_preamble.preamble.lines() { @@ -130,13 +157,22 @@ async fn connect( "received server preamble" ); - let mut stream = ssh::SshChannel::new(stream); + let stream = ssh::SshChannel::new(stream); let their_initial = stream .read() .await .attach_printable_lazy(stream_error_info) .change_context(OperationalError::KeyExchange)? - .ok_or_else(); + .ok_or_else(|| report!(OperationalError::KeyExchange)) + .attach_printable_lazy(stream_error_info) + .attach_printable_lazy(|| "No initial key exchange message found")?; + + let their_kex: SshKeyExchange = SshKeyExchange::try_from(their_initial) + .attach_printable_lazy(stream_error_info) + .change_context(OperationalError::KeyExchange)?; + + println!("their_key: {:?}", their_kex); + // let mut rng = rand::thread_rng(); // // let packet = stream @@ -165,7 +201,7 @@ async fn connect( Ok(()) } -fn print_options(items: &[T]) -> error_stack::Result<(), OperationalError> { +fn print_options(items: &[T]) -> error_stack::Result<(), OperationalError> { for item in items.iter() { println!("{}", item); } diff --git a/src/config.rs b/src/config.rs index e1d9969..43437c2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,298 +1,87 @@ +pub mod client; mod command_line; mod config_file; +pub mod connection; mod console; -mod error; +pub mod error; mod logging; -mod resolver; +pub mod resolver; +mod runtime; +pub mod server; -use crate::config::console::ConsoleConfiguration; -use crate::config::logging::LoggingConfiguration; -use crate::crypto::known_algorithms::{ - ALLOWED_COMPRESSION_ALGORITHMS, ALLOWED_ENCRYPTION_ALGORITHMS, ALLOWED_HOST_KEY_ALGORITHMS, - ALLOWED_KEY_EXCHANGE_ALGORITHMS, ALLOWED_MAC_ALGORITHMS, -}; -use crate::crypto::{ - CompressionAlgorithm, EncryptionAlgorithm, HostKeyAlgorithm, KeyExchangeAlgorithm, MacAlgorithm, -}; -use crate::encodings::ssh::{load_openssh_file_keys, PublicKey}; -use clap::Parser; -use config_file::ConfigFile; -use error_stack::ResultExt; -use hickory_resolver::TokioAsyncResolver; -use proptest::arbitrary::Arbitrary; -use proptest::strategy::{BoxedStrategy, Strategy}; -use std::collections::{HashMap, HashSet}; -use std::ffi::OsString; -use std::str::FromStr; -use tokio::runtime::Runtime; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::prelude::*; +use tokio::runtime::Runtime; -pub use self::command_line::ClientCommand; -use self::command_line::CommandLineArguments; -pub use self::error::ConfigurationError; +//impl ClientConfiguration { +// pub async fn try_from( +// mut basic: BasicClientConfiguration, +// ) -> error_stack::Result { +// let mut _ssh_keys = HashMap::new(); +// let mut _defaults = ClientConnectionOpts::default(); +// let resolver = basic +// .config_file +// .as_mut() +// .and_then(|x| x.resolver.take()) +// .unwrap_or_default(); +// let user_ssh_keys = basic.config_file.map(|cf| cf.keys).unwrap_or_default(); +// +// tracing::info!( +// provided_ssh_keys = user_ssh_keys.len(), +// "loading user-provided SSH keys" +// ); +// for (key_name, key_info) in user_ssh_keys.into_iter() { +// let _public_keys = PublicKey::load(key_info.public).await.unwrap(); +// tracing::info!(?key_name, "public keys loaded"); +// let _private_key = load_openssh_file_keys(key_info.private, &key_info.password) +// .await +// .change_context(ConfigurationError::PrivateKey)?; +// tracing::info!(?key_name, "private keys loaded"); +// } +// +// Ok(ClientConfiguration { +// _runtime: basic.runtime, +// _logging: basic.logging, +// resolver, +// _ssh_keys, +// _defaults, +// command: basic.command, +// }) +// } +//} +// -pub struct BasicClientConfiguration { - runtime: RuntimeConfiguration, - logging: LoggingConfiguration, - config_file: Option, - command: ClientCommand, -} +/// Set up the tracing subscribers based on the config file and command line options. +/// +/// This will definitely set up our logging substrate, but may also create a subscriber +/// for the console. +#[cfg(not(tarpaulin_include))] +pub fn establish_subscribers( + logging: &logging::LoggingConfiguration, + runtime: &mut runtime::RuntimeConfiguration, +) -> Result<(), std::io::Error> { + let tracing_layer = logging.layer()?; + let mut layers = vec![tracing_layer]; -impl BasicClientConfiguration { - /// Load a basic client configuration for this run. - /// - /// This will parse the process's command line arguments, and parse - /// a config file if given, but will not interpret this information - /// beyond that required to understand the user's goals for the - /// runtime system and logging. For this reason, it is not async, - /// as it is responsible for determining all the information we - /// will use to generate the runtime. - pub fn new(args: I) -> Result - where - I: IntoIterator, - T: Into + Clone, - { - let mut command_line_arguments = CommandLineArguments::try_parse_from(args)?; - let mut config_file = ConfigFile::new(command_line_arguments.config_file.take())?; - let mut runtime = RuntimeConfiguration::default(); - let mut logging = LoggingConfiguration::default(); - - // we prefer the command line to the config file, so first merge - // in the config file so that when we later merge the command line, - // it overwrites any config file options. - if let Some(config_file) = config_file.as_mut() { - config_file.merge_standard_options_into(&mut runtime, &mut logging); - } - command_line_arguments.merge_standard_options_into(&mut runtime, &mut logging); - - Ok(BasicClientConfiguration { - runtime, - logging, - config_file, - command: command_line_arguments.command, - }) + if let Some(console_config) = runtime.console.take() { + layers.push(console_config.layer()); } - /// Set up the tracing subscribers based on the config file and command line options. - /// - /// This will definitely set up our logging substrate, but may also create a subscriber - /// for the console. - #[cfg(not(tarpaulin_include))] - pub fn establish_subscribers(&mut self) -> Result<(), std::io::Error> { - let tracing_layer = self.logging.layer()?; - let mut layers = vec![tracing_layer]; + tracing_subscriber::registry().with(layers).init(); - if let Some(console_config) = self.runtime.console.take() { - layers.push(console_config.layer()); - } - - tracing_subscriber::registry().with(layers).init(); - - Ok(()) - } - - /// Generate a new tokio runtime based on the configuration / command line options - /// provided. - #[cfg(not(tarpaulin_include))] - pub fn configured_runtime(&mut self) -> Result { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .max_blocking_threads(self.runtime.tokio_blocking_threads) - .worker_threads(self.runtime.tokio_worker_threads) - .build() - } + Ok(()) } -#[derive(Debug)] -pub struct ClientConnectionOpts { - pub key_exchange_algorithms: Vec, - pub server_host_key_algorithms: Vec, - pub encryption_algorithms: Vec, - pub mac_algorithms: Vec, - pub compression_algorithms: Vec, - pub languages: Vec, - pub predict: Option, +/// Generate a new tokio runtime based on the configuration / command line options +/// provided. +#[cfg(not(tarpaulin_include))] +pub fn configured_runtime( + runtime: &runtime::RuntimeConfiguration, +) -> Result { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .max_blocking_threads(runtime.tokio_blocking_threads) + .worker_threads(runtime.tokio_worker_threads) + .build() } -impl Default for ClientConnectionOpts { - fn default() -> Self { - ClientConnectionOpts { - key_exchange_algorithms: vec![KeyExchangeAlgorithm::Curve25519Sha256], - server_host_key_algorithms: vec![HostKeyAlgorithm::Ed25519], - encryption_algorithms: vec![EncryptionAlgorithm::Aes256Ctr], - mac_algorithms: vec![MacAlgorithm::HmacSha256], - compression_algorithms: vec![], - languages: vec![], - predict: None, - } - } -} - -impl Arbitrary for ClientConnectionOpts { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - let keyx = proptest::sample::select(ALLOWED_KEY_EXCHANGE_ALGORITHMS); - let hostkey = proptest::sample::select(ALLOWED_HOST_KEY_ALGORITHMS); - let enc = proptest::sample::select(ALLOWED_ENCRYPTION_ALGORITHMS); - let mac = proptest::sample::select(ALLOWED_MAC_ALGORITHMS); - let comp = proptest::sample::select(ALLOWED_COMPRESSION_ALGORITHMS); - - ( - proptest::collection::hash_set(keyx.clone(), 1..ALLOWED_KEY_EXCHANGE_ALGORITHMS.len()), - proptest::collection::hash_set(hostkey, 1..ALLOWED_HOST_KEY_ALGORITHMS.len()), - proptest::collection::hash_set(enc, 1..ALLOWED_ENCRYPTION_ALGORITHMS.len()), - proptest::collection::hash_set(mac, 1..ALLOWED_MAC_ALGORITHMS.len()), - proptest::collection::hash_set(comp, 1..ALLOWED_COMPRESSION_ALGORITHMS.len()), - proptest::option::of(keyx), - ) - .prop_map(|(kex, host, enc, mac, comp, pred)| ClientConnectionOpts { - key_exchange_algorithms: finalize_options(kex).unwrap(), - server_host_key_algorithms: finalize_options(host).unwrap(), - encryption_algorithms: finalize_options(enc).unwrap(), - mac_algorithms: finalize_options(mac).unwrap(), - compression_algorithms: finalize_options(comp).unwrap(), - languages: vec![], - predict: pred.and_then(|x| KeyExchangeAlgorithm::from_str(x).ok()), - }) - .boxed() - } -} - -fn finalize_options(inputs: HashSet<&str>) -> Result, E> -where - T: FromStr, -{ - let mut result = vec![]; - - for item in inputs.into_iter() { - let item = T::from_str(item)?; - result.push(item) - } - - Ok(result) -} - -#[derive(Default)] -pub struct ServerConfiguration { - runtime: RuntimeConfiguration, - logging: LoggingConfiguration, -} - -pub struct RuntimeConfiguration { - tokio_worker_threads: usize, - tokio_blocking_threads: usize, - console: Option, -} - -impl Default for RuntimeConfiguration { - fn default() -> Self { - RuntimeConfiguration { - tokio_worker_threads: 4, - tokio_blocking_threads: 16, - console: None, - } - } -} - -impl ServerConfiguration { - /// Load a server configuration for this run. - /// - /// This will parse the process's command line arguments, and parse - /// a config file if given, so it can take awhile. Even though this - /// function does a bunch of IO, it is not async, because it is expected - /// ro run before we have a tokio runtime fully established. (This - /// function will determine a bunch of things related to the runtime, - /// like how many threads to run, what tracing subscribers to include, - /// etc.) - pub fn new() -> Result { - let mut new_configuration = Self::default(); - let mut command_line = CommandLineArguments::parse(); - let mut config_file = ConfigFile::new(command_line.config_file.take())?; - - // we prefer the command line to the config file, so first merge - // in the config file so that when we later merge the command line, - // it overwrites any config file options. - if let Some(config_file) = config_file.as_mut() { - config_file.merge_standard_options_into( - &mut new_configuration.runtime, - &mut new_configuration.logging, - ); - } - command_line.merge_standard_options_into( - &mut new_configuration.runtime, - &mut new_configuration.logging, - ); - - Ok(new_configuration) - } -} - -impl BasicClientConfiguration {} - -pub struct ClientConfiguration { - _runtime: RuntimeConfiguration, - _logging: LoggingConfiguration, - resolver: TokioAsyncResolver, - _ssh_keys: HashMap, - _defaults: ClientConnectionOpts, - command: ClientCommand, -} - -impl ClientConfiguration { - pub async fn try_from( - mut basic: BasicClientConfiguration, - ) -> error_stack::Result { - let mut _ssh_keys = HashMap::new(); - let mut _defaults = ClientConnectionOpts::default(); - let resolver = basic - .config_file - .as_mut() - .and_then(|x| x.resolver.take()) - .unwrap_or_default() - .resolver() - .change_context(ConfigurationError::Resolver)?; - let user_ssh_keys = basic - .config_file - .map(|cf| cf.keys) - .unwrap_or_default(); - - tracing::info!( - provided_ssh_keys = user_ssh_keys.len(), - "loading user-provided SSH keys" - ); - for (key_name, key_info) in user_ssh_keys.into_iter() { - let _public_keys = PublicKey::load(key_info.public).await.unwrap(); - tracing::info!(?key_name, "public keys loaded"); - let _private_key = load_openssh_file_keys(key_info.private, &key_info.password) - .await - .change_context(ConfigurationError::PrivateKey)?; - tracing::info!(?key_name, "private keys loaded"); - } - - Ok(ClientConfiguration { - _runtime: basic.runtime, - _logging: basic.logging, - resolver, - _ssh_keys, - _defaults, - command: basic.command, - }) - } - - pub fn command(&self) -> &ClientCommand { - &self.command - } - - pub fn resolver(&self) -> &TokioAsyncResolver { - &self.resolver - } -} - -pub enum SshKey { - Ed25519 {}, - Ecdsa {}, - Rsa {}, -} diff --git a/src/config/client.rs b/src/config/client.rs new file mode 100644 index 0000000..e0a0ff5 --- /dev/null +++ b/src/config/client.rs @@ -0,0 +1,60 @@ +use clap::Parser; +use crate::config::config_file::ConfigFile; +use crate::config::error::ConfigurationError; +use crate::config::logging::LoggingConfiguration; +use crate::config::runtime::RuntimeConfiguration; +use crate::config::command_line::ClientArguments; +pub use crate::config::command_line::ClientCommand; +use std::ffi::OsString; +use tracing_core::LevelFilter; + +pub struct ClientConfiguration { + pub runtime: RuntimeConfiguration, + pub logging: LoggingConfiguration, + config_file: Option, + command: ClientCommand, +} + +impl ClientConfiguration { + /// Load a basic client configuration for this run. + /// + /// This will parse the process's command line arguments, and parse + /// a config file if given, but will not interpret this information + /// beyond that required to understand the user's goals for the + /// runtime system and logging. For this reason, it is not async, + /// as it is responsible for determining all the information we + /// will use to generate the runtime. + pub fn new(args: I) -> Result + where + I: IntoIterator, + T: Into + Clone, + { + let mut command_line_arguments = ClientArguments::try_parse_from(args)?; + let mut config_file = ConfigFile::new(command_line_arguments.arguments.config_file.take())?; + let mut runtime = RuntimeConfiguration::default(); + let mut logging = LoggingConfiguration::default(); + + // we prefer the command line to the config file, so first merge + // in the config file so that when we later merge the command line, + // it overwrites any config file options. + if let Some(config_file) = config_file.as_mut() { + config_file.merge_standard_options_into(&mut runtime, &mut logging); + } + command_line_arguments.arguments.merge_standard_options_into(&mut runtime, &mut logging); + if command_line_arguments.command.is_list_command() { + logging.filter = LevelFilter::ERROR; + } + + Ok(ClientConfiguration { + runtime, + logging, + config_file, + command: command_line_arguments.command, + }) + } + + // Returns the command we received from the user + pub fn command(&self) -> &ClientCommand { + &self.command + } +} diff --git a/src/config/command_line.rs b/src/config/command_line.rs index 973463b..acb394c 100644 --- a/src/config/command_line.rs +++ b/src/config/command_line.rs @@ -1,6 +1,7 @@ use crate::config::logging::{LogTarget, LoggingConfiguration}; -use crate::config::{ConsoleConfiguration, RuntimeConfiguration}; -use clap::{Parser, Subcommand}; +use crate::config::console::ConsoleConfiguration; +use crate::config::runtime::RuntimeConfiguration; +use clap::{Args, Parser, Subcommand}; use std::net::SocketAddr; use std::path::PathBuf; #[cfg(test)] @@ -9,6 +10,23 @@ use tracing_core::LevelFilter; #[derive(Parser, Debug, Default)] #[command(version, about, long_about = None)] +pub struct ServerArguments { + #[command(flatten)] + pub arguments: CommandLineArguments, +} + +#[derive(Parser, Debug, Default)] +#[command(version, about, long_about = None)] +pub struct ClientArguments { + #[command(flatten)] + pub arguments: CommandLineArguments, + + /// The command we're running as the client + #[command(subcommand)] + pub command: ClientCommand, +} + +#[derive(Args, Debug, Default)] pub struct CommandLineArguments { /// The config file to use for this command. #[arg(short, long)] @@ -45,10 +63,6 @@ pub struct CommandLineArguments { /// A unix domain socket address to use for tokio-console inspection. #[arg(short = 'u', long, group = "console")] console_unix_socket: Option, - - /// The command we're running as the client - #[command(subcommand)] - pub command: ClientCommand, } #[derive(Debug, Default, Subcommand)] @@ -102,8 +116,6 @@ impl CommandLineArguments { if let Some(log_level) = self.log_level.take() { logging_config.filter = log_level; - } else if self.command.is_list_command() { - logging_config.filter = LevelFilter::ERROR; } if (self.console_network_server.is_some() || self.console_unix_socket.is_some()) diff --git a/src/config/config_file.rs b/src/config/config_file.rs index e65dd41..d1bff99 100644 --- a/src/config/config_file.rs +++ b/src/config/config_file.rs @@ -1,6 +1,7 @@ +use crate::config::error::ConfigurationError; use crate::config::logging::{LogMode, LogTarget, LoggingConfiguration}; use crate::config::resolver::DnsConfig; -use crate::config::RuntimeConfiguration; +use crate::config::runtime::RuntimeConfiguration; use crate::crypto::known_algorithms::{ ALLOWED_COMPRESSION_ALGORITHMS, ALLOWED_ENCRYPTION_ALGORITHMS, ALLOWED_HOST_KEY_ALGORITHMS, ALLOWED_KEY_EXCHANGE_ALGORITHMS, ALLOWED_MAC_ALGORITHMS, @@ -21,6 +22,7 @@ pub struct ConfigFile { runtime: Option, logging: Option, pub resolver: Option, + pub sockets: Option>, pub keys: HashMap, defaults: Option, servers: HashMap, @@ -35,15 +37,17 @@ impl Arbitrary for ConfigFile { any::>(), any::>(), any::>(), + proptest::option::of(keyed_section(SocketConfig::arbitrary())), keyed_section(KeyConfig::arbitrary()), any::>(), keyed_section(ServerConfig::arbitrary()), ) .prop_map( - |(runtime, logging, resolver, keys, defaults, servers)| ConfigFile { + |(runtime, logging, resolver,sockets, keys, defaults, servers)| ConfigFile { runtime, logging, resolver, + sockets, keys, defaults, servers, @@ -65,6 +69,141 @@ where .boxed() } +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct SocketConfig { + path: PathBuf, + user: Option, + group: Option, + permissions: Option, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum Permissions { + User, + Group, + UserGroup, + Everyone, +} + +impl From for rustix::fs::Mode { + fn from(value: Permissions) -> Self { + match value { + Permissions::User => + rustix::fs::Mode::RUSR | + rustix::fs::Mode::WUSR, + + Permissions::Group => + rustix::fs::Mode::RUSR | + rustix::fs::Mode::WUSR, + + Permissions::UserGroup => + rustix::fs::Mode::RUSR | + rustix::fs::Mode::WUSR | + rustix::fs::Mode::RGRP | + rustix::fs::Mode::WGRP, + + Permissions::Everyone => + rustix::fs::Mode::RUSR | + rustix::fs::Mode::WUSR | + rustix::fs::Mode::RGRP | + rustix::fs::Mode::WGRP | + rustix::fs::Mode::ROTH | + rustix::fs::Mode::WOTH, + } + } +} + +impl SocketConfig { + pub async fn into_listener(self) -> Result { + let base = tokio::net::UnixListener::bind(&self.path) + .map_err(|error| ConfigurationError::CouldNotMakeSocket { + path: self.path.clone(), + error, + })?; + + let user = self.user.map(|x| { + nix::unistd::User::from_name(&x) + .map_err(|error| ConfigurationError::CouldNotSetPerms { + thing: "find user".to_string(), + path: self.path.clone(), + error: error.into(), + }) + .transpose() + .unwrap_or_else(|| Err(ConfigurationError::CouldNotSetPerms { + thing: "find user".to_string(), + path: self.path.clone(), + error: std::io::Error::new(std::io::ErrorKind::NotFound, "could not find user"), + })) + }).transpose()?; + + let group = self.group.map(|x| { + nix::unistd::Group::from_name(&x) + .map_err(|error| ConfigurationError::CouldNotSetPerms { + thing: "find user".to_string(), + path: self.path.clone(), + error: error.into(), + }) + .transpose() + .unwrap_or_else(|| Err(ConfigurationError::CouldNotSetPerms { + thing: "find user".to_string(), + path: self.path.clone(), + error: std::io::Error::new(std::io::ErrorKind::NotFound, "could not find user"), + })) + }).transpose()?; + + if user.is_some() || group.is_some() { + std::os::unix::fs::chown(&self.path, user.map(|x| x.uid.as_raw()), group.map(|x| x.gid.as_raw())) + .map_err(|error| + ConfigurationError::CouldNotSetPerms { + thing: "set user/group ownership".to_string(), + path: self.path.clone(), + error, + } + )?; + } + + let perms = self.permissions.unwrap_or(Permissions::UserGroup); + rustix::fs::chmod(&self.path, perms.into()) + .map_err(|error| ConfigurationError::CouldNotSetPerms { + thing: "set permissions".to_string(), + path: self.path.clone(), + error: error.into(), + })?; + + + Ok(base) + } +} + +impl Arbitrary for SocketConfig { + type Strategy = BoxedStrategy; + type Parameters = (); + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + (PathBuf::arbitrary(), + proptest::option::of(proptest::string::string_regex("[a-zA-Z0-9]{1,30}").unwrap()), + proptest::option::of(proptest::string::string_regex("[a-zA-Z0-9]{1,30}").unwrap()), + proptest::option::of(Permissions::arbitrary())) + .prop_map(|(path, user, group, permissions)| + SocketConfig{ path, user, group, permissions }) + .boxed() + } +} + +impl Arbitrary for Permissions { + type Strategy = BoxedStrategy; + type Parameters = (); + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + proptest::prop_oneof![ + proptest::strategy::Just(Permissions::User), + proptest::strategy::Just(Permissions::Group), + proptest::strategy::Just(Permissions::UserGroup), + proptest::strategy::Just(Permissions::Everyone), + ].boxed() + } +} + #[derive(Debug, Deserialize, PartialEq, Serialize)] pub struct RuntimeConfig { worker_threads: Option, diff --git a/src/config/connection.rs b/src/config/connection.rs new file mode 100644 index 0000000..4f678f6 --- /dev/null +++ b/src/config/connection.rs @@ -0,0 +1,77 @@ +use crate::crypto::known_algorithms::{ + ALLOWED_COMPRESSION_ALGORITHMS, ALLOWED_ENCRYPTION_ALGORITHMS, ALLOWED_HOST_KEY_ALGORITHMS, + ALLOWED_KEY_EXCHANGE_ALGORITHMS, ALLOWED_MAC_ALGORITHMS, +}; +use crate::crypto::{ + CompressionAlgorithm, EncryptionAlgorithm, HostKeyAlgorithm, KeyExchangeAlgorithm, MacAlgorithm, +}; +use proptest::arbitrary::Arbitrary; +use proptest::strategy::{BoxedStrategy, Strategy}; +use std::collections::HashSet; +use std::str::FromStr; + + +#[derive(Debug)] +pub struct ClientConnectionOpts { + pub key_exchange_algorithms: Vec, + pub server_host_key_algorithms: Vec, + pub encryption_algorithms: Vec, + pub mac_algorithms: Vec, + pub compression_algorithms: Vec, + pub languages: Vec, + pub predict: Option, +} + +impl Default for ClientConnectionOpts { + fn default() -> Self { + ClientConnectionOpts { + key_exchange_algorithms: vec![KeyExchangeAlgorithm::Curve25519Sha256], + server_host_key_algorithms: vec![HostKeyAlgorithm::Ed25519], + encryption_algorithms: vec![EncryptionAlgorithm::Aes256Ctr], + mac_algorithms: vec![MacAlgorithm::HmacSha256], + compression_algorithms: vec![], + languages: vec![], + predict: None, + } + } +} + +impl Arbitrary for ClientConnectionOpts { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + let keyx = proptest::sample::select(ALLOWED_KEY_EXCHANGE_ALGORITHMS); + let hostkey = proptest::sample::select(ALLOWED_HOST_KEY_ALGORITHMS); + let enc = proptest::sample::select(ALLOWED_ENCRYPTION_ALGORITHMS); + let mac = proptest::sample::select(ALLOWED_MAC_ALGORITHMS); + let comp = proptest::sample::select(ALLOWED_COMPRESSION_ALGORITHMS); + + ( + proptest::collection::hash_set(keyx.clone(), 1..ALLOWED_KEY_EXCHANGE_ALGORITHMS.len()), + proptest::collection::hash_set(hostkey, 1..ALLOWED_HOST_KEY_ALGORITHMS.len()), + proptest::collection::hash_set(enc, 1..ALLOWED_ENCRYPTION_ALGORITHMS.len()), + proptest::collection::hash_set(mac, 1..ALLOWED_MAC_ALGORITHMS.len()), + proptest::collection::hash_set(comp, 1..ALLOWED_COMPRESSION_ALGORITHMS.len()), + proptest::option::of(keyx), + ) + .prop_map(|(kex, host, enc, mac, comp, pred)| ClientConnectionOpts { + key_exchange_algorithms: finalize_options(kex).unwrap(), + server_host_key_algorithms: finalize_options(host).unwrap(), + encryption_algorithms: finalize_options(enc).unwrap(), + mac_algorithms: finalize_options(mac).unwrap(), + compression_algorithms: finalize_options(comp).unwrap(), + languages: vec![], + predict: pred.and_then(|x| KeyExchangeAlgorithm::from_str(x).ok()), + }) + .boxed() + } +} + +fn finalize_options(values: HashSet<&str>) -> Result, E> + where T: FromStr +{ + Ok(values.into_iter().map(T::from_str).collect::,E>>()?) +} + + diff --git a/src/config/error.rs b/src/config/error.rs index 51b04ce..7b47a5f 100644 --- a/src/config/error.rs +++ b/src/config/error.rs @@ -19,4 +19,15 @@ pub enum ConfigurationError { PrivateKey, #[error("Error configuring DNS resolver")] Resolver, + #[error("Could not create UNIX listener socket at {path}: {error}")] + CouldNotMakeSocket { + path: PathBuf, + error: std::io::Error, + }, + #[error("Could not {thing} for {path}: {error}")] + CouldNotSetPerms { + thing: String, + path: PathBuf, + error: std::io::Error, + }, } diff --git a/src/config/resolver.rs b/src/config/resolver.rs index 3d23c4e..f56f882 100644 --- a/src/config/resolver.rs +++ b/src/config/resolver.rs @@ -1,35 +1,28 @@ -use error_stack::report; -use hickory_proto::error::ProtoError; -use hickory_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}; -use hickory_resolver::{Name, TokioAsyncResolver}; use proptest::arbitrary::Arbitrary; use proptest::strategy::{BoxedStrategy, Just, Strategy}; use serde::{Deserialize, Serialize}; -use std::net::SocketAddr; -use thiserror::Error; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; #[derive(Debug, PartialEq, Deserialize, Serialize)] pub struct DnsConfig { built_in: Option, - local_domain: Option, + pub local_domain: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] - search_domains: Vec, + pub search_domains: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] - name_servers: Vec, + pub name_servers: Vec, #[serde(default)] - timeout_in_seconds: Option, + pub retry_attempts: Option, #[serde(default)] - retry_attempts: Option, + pub cache_size: Option, #[serde(default)] - cache_size: Option, + pub max_concurrent_requests_for_query: Option, #[serde(default)] - use_hosts_file: Option, + pub preserve_intermediates: Option, #[serde(default)] - max_concurrent_requests_for_query: Option, + pub shuffle_dns_servers: Option, #[serde(default)] - preserve_intermediates: Option, - #[serde(default)] - shuffle_dns_servers: Option, + pub allow_mdns: Option, } impl Default for DnsConfig { @@ -39,13 +32,12 @@ impl Default for DnsConfig { local_domain: None, search_domains: vec![], name_servers: vec![], - timeout_in_seconds: None, retry_attempts: None, cache_size: None, - use_hosts_file: None, max_concurrent_requests_for_query: None, preserve_intermediates: None, shuffle_dns_servers: None, + allow_mdns: None, } } } @@ -62,13 +54,12 @@ impl Arbitrary for DnsConfig { local_domain: None, search_domains: vec![], name_servers: vec![], - timeout_in_seconds: None, retry_attempts: None, cache_size: None, - use_hosts_file: None, max_concurrent_requests_for_query: None, preserve_intermediates: None, shuffle_dns_servers: None, + allow_mdns: None, }) .boxed() } else { @@ -79,51 +70,47 @@ impl Arbitrary for DnsConfig { let search_domains = proptest::collection::vec(domain_name_strat(), 0..10); let min_servers = if built_in.is_some() { 0 } else { 1 }; let name_servers = - proptest::collection::vec(ServerConfig::arbitrary(), min_servers..6); - let timeout_in_seconds = proptest::option::of(u16::arbitrary()); + proptest::collection::vec(NameServerConfig::arbitrary(), min_servers..6); let retry_attempts = proptest::option::of(u16::arbitrary()); let cache_size = proptest::option::of(u32::arbitrary()); - let use_hosts_file = proptest::option::of(bool::arbitrary()); let max_concurrent_requests_for_query = proptest::option::of(u16::arbitrary()); let preserve_intermediates = proptest::option::of(bool::arbitrary()); let shuffle_dns_servers = proptest::option::of(bool::arbitrary()); + let allow_mdns = proptest::option::of(bool::arbitrary()); ( local_domain, search_domains, name_servers, - timeout_in_seconds, retry_attempts, cache_size, - use_hosts_file, max_concurrent_requests_for_query, preserve_intermediates, shuffle_dns_servers, + allow_mdns, ) .prop_map( move |( local_domain, search_domains, name_servers, - timeout_in_seconds, retry_attempts, cache_size, - use_hosts_file, max_concurrent_requests_for_query, preserve_intermediates, shuffle_dns_servers, + allow_mdns, )| DnsConfig { built_in, local_domain, search_domains, name_servers, - timeout_in_seconds, retry_attempts, cache_size, - use_hosts_file, max_concurrent_requests_for_query, preserve_intermediates, shuffle_dns_servers, + allow_mdns, }, ) }) @@ -172,34 +159,34 @@ impl Arbitrary for BuiltinDnsOption { } } -#[derive(Debug, PartialEq, Deserialize, Serialize)] -struct ServerConfig { - address: SocketAddr, +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct NameServerConfig { + pub address: SocketAddr, #[serde(default)] - trust_negatives: bool, + pub timeout_in_seconds: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - bind_address: Option, + pub bind_address: Option, } -impl Arbitrary for ServerConfig { +impl Arbitrary for NameServerConfig { type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with((): Self::Parameters) -> Self::Strategy { ( SocketAddr::arbitrary(), - bool::arbitrary(), + proptest::option::of(u64::arbitrary()), proptest::option::of(SocketAddr::arbitrary()), ) - .prop_map(|(mut address, trust_negatives, mut bind_address)| { + .prop_map(|(mut address, timeout_in_seconds, mut bind_address)| { clear_flow_and_scope_info(&mut address); if let Some(bind_address) = bind_address.as_mut() { clear_flow_and_scope_info(bind_address); } - ServerConfig { + NameServerConfig { address, - trust_negatives, + timeout_in_seconds, bind_address, } }) @@ -214,86 +201,6 @@ fn clear_flow_and_scope_info(address: &mut SocketAddr) { } } -#[derive(Debug, Error)] -pub enum ResolverConfigError { - #[error("Bad local domain name '{name}' provided: {error}")] - BadDomainName { name: String, error: ProtoError }, - #[error("Bad domain name for search '{name}' provided: {error}")] - BadSearchName { name: String, error: ProtoError }, - #[error("No DNS hosts found to search")] - NoHosts, -} - -impl DnsConfig { - /// Convert this resolver configuration into an actual ResolverConfig, or say - /// why it's bad. - pub fn resolver(&self) -> error_stack::Result { - let mut config = match &self.built_in { - None => ResolverConfig::new(), - Some(BuiltinDnsOption::Cloudflare) => ResolverConfig::cloudflare(), - Some(BuiltinDnsOption::Google) => ResolverConfig::google(), - Some(BuiltinDnsOption::Quad9) => ResolverConfig::quad9(), - }; - - if let Some(local_domain) = &self.local_domain { - let name = Name::from_utf8(local_domain).map_err(|error| { - report!(ResolverConfigError::BadDomainName { - name: local_domain.clone(), - error, - }) - })?; - config.set_domain(name); - } - - for name in self.search_domains.iter() { - let name = Name::from_utf8(name).map_err(|error| { - report!(ResolverConfigError::BadSearchName { - name: name.clone(), - error, - }) - })?; - config.add_search(name); - } - - for ns in self.name_servers.iter() { - let mut nsconfig = NameServerConfig::new(ns.address, Protocol::Udp); - - nsconfig.trust_negative_responses = ns.trust_negatives; - nsconfig.bind_addr = ns.bind_address; - - config.add_name_server(nsconfig); - } - - if config.name_servers().is_empty() { - return Err(report!(ResolverConfigError::NoHosts)); - } - - let mut options = ResolverOpts::default(); - - if let Some(seconds) = self.timeout_in_seconds { - options.timeout = tokio::time::Duration::from_secs(seconds as u64); - } - if let Some(retries) = self.retry_attempts { - options.attempts = retries as usize; - } - if let Some(cache_size) = self.cache_size { - options.cache_size = cache_size as usize; - } - options.use_hosts_file = self.use_hosts_file.unwrap_or(true); - if let Some(max) = self.max_concurrent_requests_for_query { - options.num_concurrent_reqs = max as usize; - } - if let Some(preserve) = self.preserve_intermediates { - options.preserve_intermediates = preserve; - } - if let Some(shuffle) = self.shuffle_dns_servers { - options.shuffle_dns_servers = shuffle; - } - - Ok(TokioAsyncResolver::tokio(config, options)) - } -} - proptest::proptest! { #[test] fn valid_configs_parse(config in DnsConfig::arbitrary_with(false)) { @@ -302,3 +209,98 @@ proptest::proptest! { assert_eq!(config, reversed); } } + +impl DnsConfig { + /// Return the configurations for all of the name servers that the user + /// has selected. + pub fn name_servers(&self) -> Vec { + let mut results = self.name_servers.clone(); + + match self.built_in { + None => {} + Some(BuiltinDnsOption::Cloudflare) => { + results.push(NameServerConfig { + address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 1, 1, 1), 53)), + timeout_in_seconds: None, + bind_address: None, + }); + results.push(NameServerConfig { + address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 0, 0, 1), 53)), + timeout_in_seconds: None, + bind_address: None, + }); + results.push(NameServerConfig { + address: SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111), + 53, + 0, + 0, + )), + timeout_in_seconds: None, + bind_address: None, + }); + results.push(NameServerConfig { + address: SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1001), + 53, + 0, + 0, + )), + timeout_in_seconds: None, + bind_address: None, + }); + } + Some(BuiltinDnsOption::Google) => { + results.push(NameServerConfig { + address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(8, 8, 8, 8), 53)), + timeout_in_seconds: None, + bind_address: None, + }); + results.push(NameServerConfig { + address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(8, 8, 4, 4), 53)), + timeout_in_seconds: None, + bind_address: None, + }); + results.push(NameServerConfig { + address: SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888), + 53, + 0, + 0, + )), + timeout_in_seconds: None, + bind_address: None, + }); + results.push(NameServerConfig { + address: SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844), + 53, + 0, + 0, + )), + timeout_in_seconds: None, + bind_address: None, + }); + } + Some(BuiltinDnsOption::Quad9) => { + results.push(NameServerConfig { + address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(9, 9, 9, 9), 53)), + timeout_in_seconds: None, + bind_address: None, + }); + results.push(NameServerConfig { + address: SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2620, 0, 0, 0, 0, 0, 0xfe, 0xfe), + 53, + 0, + 0, + )), + timeout_in_seconds: None, + bind_address: None, + }); + } + } + + results + } +} diff --git a/src/config/runtime.rs b/src/config/runtime.rs new file mode 100644 index 0000000..61f6605 --- /dev/null +++ b/src/config/runtime.rs @@ -0,0 +1,19 @@ +use crate::config::console::ConsoleConfiguration; + +pub struct RuntimeConfiguration { + pub tokio_worker_threads: usize, + pub tokio_blocking_threads: usize, + pub console: Option, +} + +impl Default for RuntimeConfiguration { + fn default() -> Self { + RuntimeConfiguration { + tokio_worker_threads: 4, + tokio_blocking_threads: 16, + console: None, + } + } +} + + diff --git a/src/config/server.rs b/src/config/server.rs new file mode 100644 index 0000000..56c0b97 --- /dev/null +++ b/src/config/server.rs @@ -0,0 +1,67 @@ +use clap::Parser; +use crate::config::config_file::{ConfigFile, SocketConfig}; +use crate::config::command_line::ServerArguments; +use crate::config::error::ConfigurationError; +use crate::config::logging::LoggingConfiguration; +use crate::config::runtime::RuntimeConfiguration; +use std::collections::HashMap; +use std::ffi::OsString; +use tokio::net::UnixListener; + +#[derive(Default)] +pub struct ServerConfiguration { + pub runtime: RuntimeConfiguration, + pub logging: LoggingConfiguration, + pub sockets: HashMap, +} + +impl ServerConfiguration { + /// Load a server configuration for this run. + /// + /// This will parse the process's command line arguments, and parse + /// a config file if given, so it can take awhile. Even though this + /// function does a bunch of IO, it is not async, because it is expected + /// ro run before we have a tokio runtime fully established. (This + /// function will determine a bunch of things related to the runtime, + /// like how many threads to run, what tracing subscribers to include, + /// etc.) + pub fn new(args: I) -> Result + where + I: IntoIterator, + T: Into + Clone, + { + let mut new_configuration = Self::default(); + let mut command_line = ServerArguments::try_parse_from(args)?; + let mut config_file = ConfigFile::new(command_line.arguments.config_file.take())?; + + // we prefer the command line to the config file, so first merge + // in the config file so that when we later merge the command line, + // it overwrites any config file options. + if let Some(config_file) = config_file.as_mut() { + config_file.merge_standard_options_into( + &mut new_configuration.runtime, + &mut new_configuration.logging, + ); + + new_configuration.sockets = config_file.sockets.take().unwrap_or_default(); + } + + command_line.arguments.merge_standard_options_into( + &mut new_configuration.runtime, + &mut new_configuration.logging, + ); + + Ok(new_configuration) + } + + /// Generate a series of UNIX sockets that clients will attempt to connect to. + pub async fn generate_listener_sockets(&mut self) -> Result, ConfigurationError> { + let mut results = HashMap::new(); + + for (name, config) in std::mem::take(&mut self.sockets).into_iter() { + results.insert(name, config.into_listener().await?); + } + + Ok(results) + } +} diff --git a/src/lib.rs b/src/lib.rs index 55d5ed9..f6a77a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod crypto; pub mod encodings; pub mod network; mod operational_error; +pub mod server; pub mod ssh; pub use operational_error::OperationalError; diff --git a/src/network.rs b/src/network.rs index 5361e40..5e50823 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1 +1,2 @@ pub mod host; +pub mod resolver; diff --git a/src/network/host.rs b/src/network/host.rs index 4078f7d..d1f2925 100644 --- a/src/network/host.rs +++ b/src/network/host.rs @@ -1,8 +1,7 @@ +use crate::network::resolver::{ResolveError, Resolver}; use error_stack::{report, ResultExt}; use futures::stream::{FuturesUnordered, StreamExt}; -use hickory_resolver::error::ResolveError; -use hickory_resolver::name_server::ConnectionProvider; -use hickory_resolver::AsyncResolver; +use hickory_client::rr::Name; use std::collections::HashSet; use std::fmt; use std::net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; @@ -10,10 +9,11 @@ use std::str::FromStr; use thiserror::Error; use tokio::net::TcpStream; +#[derive(Debug)] pub enum Host { IPv4(Ipv4Addr), IPv6(Ipv6Addr), - Hostname(String), + Hostname(Name), } #[derive(Debug, Error)] @@ -49,20 +49,8 @@ impl FromStr for Host { return Ok(Host::IPv6(addr)); } - if s.starts_with('[') && s.ends_with(']') { - match s.trim_start_matches('[').trim_end_matches(']').parse() { - Ok(x) => return Ok(Host::IPv6(x)), - Err(e) => { - return Err(HostParseError::CouldNotParseIPv6 { - address: s.to_string(), - error: e, - }) - } - } - } - - if hostname_validator::is_valid(s) { - return Ok(Host::Hostname(s.to_string())); + if let Ok(name) = Name::from_utf8(s) { + return Ok(Host::Hostname(name)); } Err(HostParseError::InvalidHostname { @@ -95,18 +83,11 @@ impl Host { /// are no relevant records for us to use for IPv4 or IPv6 connections. There /// is also no guarantee that the host will have both IPv4 and IPv6 addresses, /// so you may only see one or the other. - pub async fn resolve( - &self, - resolver: &AsyncResolver

, - ) -> Result, ResolveError> { + pub async fn resolve(&self, resolver: &mut Resolver) -> Result, ResolveError> { match self { Host::IPv4(addr) => Ok(HashSet::from([IpAddr::V4(*addr)])), Host::IPv6(addr) => Ok(HashSet::from([IpAddr::V6(*addr)])), - Host::Hostname(name) => { - let resolve_result = resolver.lookup_ip(name).await?; - let possibilities = resolve_result.iter().collect(); - Ok(possibilities) - } + Host::Hostname(name) => resolver.lookup(name).await, } } @@ -117,9 +98,9 @@ impl Host { /// connections fail, it will return the first error it receives. This routine /// will also return an error if there are no addresses to connect to (which /// can happen in cases in which [`Host::resolve`] would return an empty set. - pub async fn connect( + pub async fn connect( &self, - resolver: &AsyncResolver

, + resolver: &mut Resolver, port: u16, ) -> error_stack::Result { let addresses = self @@ -131,6 +112,7 @@ impl Host { let mut connectors = FuturesUnordered::new(); for address in addresses.into_iter() { + tracing::trace!(?address, "adding possible target address"); let connect_future = TcpStream::connect(SocketAddr::new(address, port)); connectors.push(connect_future); } diff --git a/src/network/resolver.rs b/src/network/resolver.rs new file mode 100644 index 0000000..70b0663 --- /dev/null +++ b/src/network/resolver.rs @@ -0,0 +1,261 @@ +use crate::config::resolver::{DnsConfig, NameServerConfig}; +use error_stack::report; +use futures::stream::{SelectAll, StreamExt}; +use hickory_client::rr::Name; +use hickory_proto::error::ProtoError; +use hickory_proto::op::message::Message; +use hickory_proto::op::query::Query; +use hickory_proto::rr::record_data::RData; +use hickory_proto::rr::resource::Record; +use hickory_proto::rr::RecordType; +use hickory_proto::udp::UdpClientStream; +use hickory_proto::xfer::dns_request::DnsRequest; +use hickory_proto::xfer::{DnsRequestSender, DnsResponseStream}; +use std::collections::{HashMap, HashSet}; +use std::net::{IpAddr, SocketAddr}; +use thiserror::Error; +use tokio::net::UdpSocket; +use tokio::time::{Duration, Instant}; + +#[derive(Debug, Error)] +pub enum ResolverConfigError { + #[error("Bad local domain name '{name}' provided: '{error}'")] + BadDomainName { name: String, error: ProtoError }, + #[error("Bad domain name for search '{name}' provided: '{error}'")] + BadSearchName { name: String, error: ProtoError }, + #[error("Couldn't set up client for server at '{address}': '{error}'")] + FailedToCreateDnsClient { + address: SocketAddr, + error: ProtoError, + }, + #[error("No DNS servers found to search, and mDNS not enabled")] + NoHosts, +} + +#[derive(Debug, Error)] +pub enum ResolveError { + #[error("No servers available for query")] + NoServersAvailable, + #[error("No responses found for query")] + NoResponses, + #[error("Error reading response: {error}")] + ResponseError { error: ProtoError }, +} + +pub struct Resolver { + search_domains: Vec, + client_connections: Vec<(NameServerConfig, UdpClientStream)>, + cache: HashMap>, +} + +impl Resolver { + /// Create a new DNS resolution engine for use by some part of the system. + pub async fn new(config: &DnsConfig) -> error_stack::Result { + let mut search_domains = Vec::new(); + + if let Some(local) = config.local_domain.as_ref() { + search_domains.push(Name::from_utf8(local).map_err(|e| { + report!(ResolverConfigError::BadDomainName { + name: local.clone(), + error: e, + }) + })?); + } + + for local_domain in config.search_domains.iter() { + search_domains.push(Name::from_utf8(local_domain).map_err(|e| { + report!(ResolverConfigError::BadSearchName { + name: local_domain.clone(), + error: e, + }) + })?); + } + + let mut client_connections = Vec::new(); + + for name_server_config in config.name_servers() { + let stream = UdpClientStream::with_bind_addr_and_timeout( + name_server_config.address, + name_server_config.bind_address.clone(), + Duration::from_secs(name_server_config.timeout_in_seconds.unwrap_or(3)), + ) + .await + .map_err(|error| ResolverConfigError::FailedToCreateDnsClient { + address: name_server_config.address, + error, + })?; + + client_connections.push((name_server_config, stream)); + } + + Ok(Resolver { + search_domains, + client_connections, + cache: HashMap::new(), + }) + } + + /// Look up the address of the given name, returning either a set of results + /// we've received in a reasonable amount of time, or an error. + pub async fn lookup(&mut self, name: &Name) -> Result, ResolveError> { + let names = self.expand_name(name); + let mut response_stream = self.create_response_stream(names).await; + + if response_stream.is_empty() { + return Err(ResolveError::NoServersAvailable); + } + + let mut first_error = None; + + while let Some(response) = response_stream.next().await { + match response { + Err(e) => { + if first_error.is_none() { + first_error = Some(e); + } + } + + Ok(response) => { + for answer in response.into_message().take_answers() { + self.handle_response(name, answer).await; + } + } + } + } + + match first_error { + None => Err(ResolveError::NoResponses), + Some(error) => Err(ResolveError::ResponseError { error }), + } + } + + async fn create_response_stream(&mut self, names: Vec) -> SelectAll { + let connections = std::mem::take(&mut self.client_connections); + let mut response_stream = futures::stream::SelectAll::new(); + + for (config, mut client) in connections.into_iter() { + if client.is_shutdown() { + let stream = UdpClientStream::with_bind_addr_and_timeout( + config.address, + config.bind_address.clone(), + Duration::from_secs(config.timeout_in_seconds.unwrap_or(3)), + ) + .await; + + match stream { + Ok(stream) => client = stream, + Err(_) => continue, + } + } + + let mut message = Message::new(); + + for name in names.iter() { + message.add_query(Query::query(name.clone(), RecordType::A)); + message.add_query(Query::query(name.clone(), RecordType::AAAA)); + } + message.set_recursion_desired(true); + + let request = DnsRequest::new(message, Default::default()); + response_stream.push(client.send_message(request)); + self.client_connections.push((config, client)); + } + + response_stream + } + + /// Expand the given input name into the complete set of names that we should search for. + fn expand_name(&self, name: &Name) -> Vec { + let mut names = vec![name.clone()]; + + for search_domain in self.search_domains.iter() { + if let Ok(combined) = name.clone().append_name(search_domain) { + names.push(combined); + } + } + + names + } + + /// Handle an individual response from a server. + /// + /// Returns true if we got an answer from the server, and updated the internal cache. + /// This answer is used externally to set a timer, so that we don't wait the full DNS + /// timeout period for answers to come back. + async fn handle_response(&mut self, query: &Name, record: Record) -> bool { + let ttl = record.ttl(); + + let Some(rdata) = record.into_data() else { + tracing::error!("for some reason, couldn't process incoming message"); + return false; + }; + + let response: IpAddr = match rdata { + RData::A(arec) => arec.0.into(), + RData::AAAA(arec) => arec.0.into(), + + _ => { + tracing::warn!(record_type = %rdata.record_type(), "skipping unknown / unexpected record type"); + return false; + } + }; + + let expire_at = Instant::now() + Duration::from_secs(ttl as u64); + + match self.cache.entry(query.clone()) { + std::collections::hash_map::Entry::Vacant(vec) => { + vec.insert(vec![(expire_at, response.clone())]); + } + + std::collections::hash_map::Entry::Occupied(mut occ) => { + clean_expired_entries(occ.get_mut()); + occ.get_mut().push((expire_at, response.clone())); + } + } + + true + } +} + +fn clean_expired_entries(list: &mut Vec<(Instant, IpAddr)>) { + let now = Instant::now(); + list.retain(|(expire_at, _)| expire_at > &now); +} + +#[tokio::test] +async fn uhsure() { + let mut resolver = Resolver::new(&DnsConfig::default()).await.unwrap(); + let name = Name::from_ascii("uhsure.com").unwrap(); + let result = resolver.lookup(&name).await.unwrap(); + println!("result = {:?}", result); + assert!(!result.is_empty()); +} + +#[tokio::test] +async fn manual_lookup() { + let mut stream: UdpClientStream = UdpClientStream::with_bind_addr_and_timeout( + SocketAddr::V4(std::net::SocketAddrV4::new( + std::net::Ipv4Addr::new(172, 19, 207, 1), + 53, + )), + None, + Duration::from_secs(3), + ) + .await + .expect("can create UDP DNS client stream"); + + let mut message = Message::new(); + let name = Name::from_ascii("uhsure.com").unwrap(); + message.add_query(Query::query(name.clone(), RecordType::A)); + message.add_query(Query::query(name.clone(), RecordType::AAAA)); + message.set_recursion_desired(true); + + let request = DnsRequest::new(message, Default::default()); + let mut responses = stream.send_message(request); + + while let Some(response) = responses.next().await { + println!("response: {:?}", response); + } + + unimplemented!(); + } diff --git a/src/operational_error.rs b/src/operational_error.rs index 557810a..4c4e1e4 100644 --- a/src/operational_error.rs +++ b/src/operational_error.rs @@ -5,6 +5,8 @@ use thiserror::Error; pub enum OperationalError { #[error("Configuration error")] ConfigurationError, + #[error("DNS client configuration error")] + DnsConfig, #[error("Failed to connect to target address")] Connection, #[error("Failure during key exchange / agreement protocol")] diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..ed92313 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,28 @@ +mod socket; +mod state; + +use crate::config::server::ServerConfiguration; +use error_stack::ResultExt; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum TopLevelError { + #[error("Configuration error")] + ConfigurationError, + #[error("Failure running UNIX socket handling task")] + SocketHandlerFailure, +} + +pub async fn run(mut config: ServerConfiguration) -> error_stack::Result<(), TopLevelError> { + let mut server_state = state::ServerState::default(); + + let listeners = config.generate_listener_sockets().await + .change_context(TopLevelError::ConfigurationError)?; + + for (name, listener) in listeners.into_iter() { + } + + Ok(()) +} + + diff --git a/src/server/socket.rs b/src/server/socket.rs new file mode 100644 index 0000000..4bbe2d2 --- /dev/null +++ b/src/server/socket.rs @@ -0,0 +1,75 @@ +use crate::server::TopLevelError; +use error_stack::ResultExt; +use tokio::net::{UnixListener, UnixStream}; +use tracing::Instrument; + +pub struct SocketServer { + name: String, + path: String, + num_sessions_run: u64, + listener: UnixListener, +} + +impl SocketServer { + /// Create a new server that will handle inputs from the client program. + /// + /// This function will just generate the function required, without starting the + /// underlying task. To start the task, use [`SocketServer::start`], although that + /// method will take ownership of the object. + pub fn new(name: String, listener: UnixListener) -> Self { + let path = listener + .local_addr() + .map(|x| x.as_pathname().map(|p| format!("{}", p.display()))) + .unwrap_or_else(|_| None) + .unwrap_or_else(|| format!("unknown")); + + tracing::trace!(%name, %path, "Creating new socket listener"); + + SocketServer { + name, + path, + num_sessions_run: 0, + listener, + } + } + + /// Start running the service, returning a handle that will pass on an error if + /// one occurs in the core of this task. + /// + /// Typically, errors shouldn't happen in the core task, as all it does is listen + /// for new connections and then spawn other tasks based on them. If errors occur + /// there, the core task should be unaffected. + pub async fn start(mut self) -> error_stack::Result<(), TopLevelError> { + loop { + let (stream, addr) = self.listener.accept().await.change_context(TopLevelError::SocketHandlerFailure)?; + let remote_addr = addr + .as_pathname() + .map(|x| x.display()) + .map(|x| format!("{}", x)) + .unwrap_or("".to_string()); + + let span = tracing::debug_span!( + "unix socket handler", + socket_name = %self.name, + socket_path = %self.path, + session_no = %self.num_sessions_run, + %remote_addr, + ); + + self.num_sessions_run += 1; + + tokio::task::spawn(Self::run_session(stream) + .instrument(span)) + .await + .change_context(TopLevelError::SocketHandlerFailure)?; + } + } + + /// Run a session. + /// + /// This is here because it's convenient, not because it shares state (obviously, + /// given the type signature). But it's somewhat logically associated with this type, + /// so it seems reasonable to make it an associated function. + async fn run_session(handle: UnixStream) { + } +} diff --git a/src/server/state.rs b/src/server/state.rs new file mode 100644 index 0000000..6bfe7ba --- /dev/null +++ b/src/server/state.rs @@ -0,0 +1,8 @@ +use crate::server::TopLevelError; +use tokio::task::JoinSet; + +#[derive(Default)] +pub struct ServerState { + /// The set of top level tasks that are currently running. + top_level_tasks: JoinSet>, +} diff --git a/src/ssh.rs b/src/ssh.rs index 7f09015..8565485 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -5,5 +5,5 @@ mod preamble; pub use channel::SshChannel; pub use message_ids::SshMessageID; -pub use packets::SshKeyExchangeProcessingError; +pub use packets::{SshKeyExchange, SshKeyExchangeProcessingError}; pub use preamble::Preamble; diff --git a/src/ssh/packets.rs b/src/ssh/packets.rs index e772fe6..7220d20 100644 --- a/src/ssh/packets.rs +++ b/src/ssh/packets.rs @@ -1,3 +1,3 @@ mod key_exchange; -pub use key_exchange::SshKeyExchangeProcessingError; +pub use key_exchange::{SshKeyExchange, SshKeyExchangeProcessingError}; diff --git a/src/ssh/packets/key_exchange.rs b/src/ssh/packets/key_exchange.rs index 62be714..981cb70 100644 --- a/src/ssh/packets/key_exchange.rs +++ b/src/ssh/packets/key_exchange.rs @@ -1,4 +1,4 @@ -use crate::config::ClientConnectionOpts; +use crate::config::connection::ClientConnectionOpts; use crate::ssh::channel::SshPacket; use crate::ssh::message_ids::SshMessageID; use bytes::{Buf, BufMut, Bytes, BytesMut};