diff --git a/.gitignore b/.gitignore index fcf2338e..9b3eda0a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,8 @@ tags bin/ *~ .stack-work + + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..d6ef4f8f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1787 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[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 = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951dfc2e32ac02d67c90c0d65bd27009a635dc9b381a2cc7d284ab01e3a0150d" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body 0.4.5", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.0.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d280a71f348bcc670fc55b02b63c53a04ac0bf2daff2980795aeaf53edae10e6" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body 1.0.0-rc.2", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", + "tracing", + "want", +] + +[[package]] +name = "hyper-openssl" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ee5d7a8f718585d1c3c61dfde28ef5b0bb14734b4db13f5ada856cdc6c612b" +dependencies = [ + "http", + "hyper 0.14.27", + "linked_hash_set", + "once_cell", + "openssl", + "openssl-sys", + "parking_lot", + "tokio", + "tokio-openssl", + "tower-layer", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.27", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.4", + "widestring", + "windows-sys", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl" +version = "0.10.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "reqwest" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body 0.4.5", + "hyper 0.14.27", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "trust-dns-resolver", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg", +] + +[[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 = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b21f559e07218024e7e9f90f96f601825397de0e25420135f7f952453fed0b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +dependencies = [ + "deranged", + "itoa", + "libc", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.4", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", + "trust-dns-proto", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna 0.4.0", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] + +[[package]] +name = "wstunnel" +version = "0.1.0" +dependencies = [ + "clap", + "hyper 1.0.0-rc.4", + "hyper-openssl", + "reqwest", + "tokio", + "tracing", + "tracing-subscriber", + "url", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..50887986 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wstunnel" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.4.5", features = ["derive"]} +url = "2.4.1" + +reqwest = { version = "0.11.20", features = ["stream", "trust-dns"] } +hyper = { version = "1.0.0-rc.4", features = ["client", "http2"] } +hyper-openssl = {version = "0.9.2", features = []} + +tokio = { version = "1.32.0", features = ["full"] } + +tracing = { version = "0.1.37", features = ["log"] } +tracing-subscriber = { version = "0.3.17", features = ["env-filter", "fmt", "local-time"] } diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8cffd079..00000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Build Cache image -FROM fpco/stack-build-small:lts-19.2 as builder-cache - -COPY stack.yaml /mnt -COPY *.cabal /mnt -WORKDIR /mnt -RUN rm -rf ~/.stack && \ - stack config set system-ghc --global true && \ - stack setup && \ - stack install --ghc-options="-fPIC" --only-dependencies - - - -# Build phase -FROM builder-cache as builder -# FROM ghcr.io/erebe/wstunnel:build-cache as builder -COPY . /mnt - -RUN echo ' ld-options: -static' >> wstunnel.cabal ; \ - stack install --ghc-options="-fPIC" -#RUN upx /root/.local/bin/wstunnel - - - -# Final Image -FROM alpine:latest as runner - -LABEL org.opencontainers.image.source https://github.com/erebe/server - -COPY --from=builder /root/.local/bin/wstunnel / -RUN adduser -D abc && chmod +x /wstunnel - -USER abc -WORKDIR / - -CMD ["/wstunnel"] - diff --git a/Dockerfile.old b/Dockerfile.old deleted file mode 100644 index 347b8045..00000000 --- a/Dockerfile.old +++ /dev/null @@ -1,15 +0,0 @@ -FROM nixos/nix as builder -MAINTAINER github@erebe.eu - -RUN nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs -RUN nix-channel --update -RUN nix-env -i bash upx - -WORKDIR /mnt -COPY stack.yaml /mnt -COPY *.cabal /mnt -COPY default.nix /mnt - -RUN nix-build --no-link -A fullBuildScript -COPY . /mnt -RUN $(nix-build --no-link -A fullBuildScript) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 281cfae6..00000000 --- a/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -Copyright Romain Gerard (c) 2016-2023 - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of Author name here nor the names of other - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md deleted file mode 100644 index e1607013..00000000 --- a/README.md +++ /dev/null @@ -1,176 +0,0 @@ - -
- -
- -## Description - -Most of the time when you are using a public network, you are behind some kind of firewall or proxy. One of their purpose is to constrain you to only use certain kind of protocols. Nowadays, the most widespread protocol is http and is de facto allowed by third party equipment. - -This tool understands this fact and uses the websocket protocol which is compatible with http in order to bypass firewalls and proxies. Wstunnel allows you to tunnel what ever traffic you want. - -My inspiration came from [this project](https://www.npmjs.com/package/wstunnel) but as I don't want to install npm and nodejs to use this tool, I remade it in Haskell and improved it. - -**What to expect:** - -* Good error messages and debug informations -* Static tunneling (TCP and UDP) -* Dynamic tunneling (socks5 proxy) -* Support for http proxy (when behind one) -* Support for tls/https server (with embedded self signed certificate, see comment in the example section) -* Support IPv6 -* **Standalone binary for linux x86_64** (so just cp it where you want) -* Standalone archive for windows - -P.S: Please do not pay attention to Main.hs because as I hate to write command line code this file is crappy - -## Command line - -``` -Use the websockets protocol to tunnel {TCP,UDP} traffic -wsTunnelClient <---> wsTunnelServer <---> RemoteHost -Use secure connection (wss://) to bypass proxies - -wstunnel [OPTIONS] ws[s]://wstunnelServer[:port] - -Client options: - -L --localToRemote=[BIND:]PORT:HOST:PORT Listen on local and forwards - traffic from remote. Can be - used multiple time - -D --dynamicToRemote=[BIND:]PORT Listen on local and - dynamically (with socks5 proxy) - forwards traffic from remote - -u --udp forward UDP traffic instead - of TCP - --udpTimeoutSec=INT When using udp forwarding, - timeout in seconds after when - the tunnel connection is - closed. Default 30sec, -1 means - no timeout - -p --httpProxy=USER:PASS@HOST:PORT If set, will use this proxy - to connect to the server - --soMark=int (linux only) Mark network - packet with SO_MARK sockoption - with the specified value. You - need to use {root, sudo, - capabilities} to run wstunnel - when using this option - --upgradePathPrefix=String Use a specific prefix that - will show up in the http path - in the upgrade request. Useful - if you need to route requests - server side but don't have - vhosts - --hostHeader=String If set, add the custom string - as host http header - --tlsSNI=String If set, use custom string in - the SNI during TLS handshake - --websocketPingFrequencySec=int do a hearthbeat ping every x - seconds to maintain websocket - connection - --upgradeCredentials=USER[:PASS] Credentials for the Basic - HTTP authorization type sent - with the upgrade request. - -H --customHeaders="HeaderName: HeaderValue" Send custom headers in the - upgrade request. Can be used - multiple time - -h --help Display help message - -V --version Print version information -Server options: - --server Start a server that will - forward traffic for you - -r --restrictTo=HOST:PORT Accept traffic to be - forwarded only to this service - --tlsCertificate=FILE [optional] provide a custom - tls certificate (.crt) that the - server will use instead of the - embeded one - --tlsKey=FILE [optional] provide a custom - tls key (.key) that the server - will use instead of the embeded - one -Common options: - -v --verbose Print debug information - -q --quiet Print only errors -``` - -## Examples -### Simplest one -On your remote host, start the wstunnel's server by typing this command in your terminal -```bash -wstunnel --server ws://0.0.0.0:8080 -``` -This will create a websocket server listening on any interface on port 8080. -On the client side use this command to forward traffic through the websocket tunnel -```bash -wstunnel -D 8888 ws://myRemoteHost:8080 -``` -This command will create a sock5 server listening on port 8888 of a loopback interface and will forward traffic. - -With firefox you can setup a proxy using this tunnel, by setting in networking preferences 127.0.0.1:8888 and selecting socks5 proxy - -or with curl - -```bash -curl -x socks5h://127.0.0.1:8888 http://google.com/ -#Please note h after the 5, it is to avoid curl resolving DNS name locally -``` - -### As proxy command for SSH -You can specify `stdio` as source port on the client side if you wish to use wstunnel as part of a proxy command for ssh -``` -ssh -o ProxyCommand="wstunnel -L stdio:%h:%p ws://localhost:8080" my-server -``` - -### When behind a corporate proxy -An other useful example is when you want to bypass an http proxy (a corporate proxy for example) -The most reliable way to do it is to use wstunnel as described below - -Start your wstunnel server with tls activated -``` -wstunnel --server wss://0.0.0.0:443 -r 127.0.0.1:22 -``` -The server will listen on any interface using port 443 (https) and restrict traffic to be forwarded only to the ssh daemon. - -**Be aware that the server will use self signed certificate with weak cryptographic algorithm. -It was made in order to add the least possible overhead while still being compliant with tls.** - -**Do not rely on wstunnel to protect your privacy, as it only forwards traffic that is already secure by design (ex: https)** - -Now on the client side start the client with -``` -wstunnel -L 9999:127.0.0.1:22 -p mycorporateproxy:8080 wss://myRemoteHost:443 -``` -It will start a tcp server on port 9999 that will contact the corporate proxy, negotiate a tls connection with the remote host and forward traffic to the ssh daemon on the remote host. - -You may now access your server from your local machine on ssh by using -``` -ssh -p 9999 login@127.0.0.1 -``` - -### Wireguard and wstunnel -https://kirill888.github.io/notes/wireguard-via-websocket/ - -- If you see some throughput issue, be sure to lower the MTU of your wireguard interface (you can do it via config file) to something like 1300 or you will endup fragmenting udp packet (due to overhead of other layer) which is always causing issues -- If wstunnel cannot connect to server while wireguard is on, be sure you have added a static route via your main gateway for the ip of wstunnel server. -Else if you forward all the traffic without putting a static route, you will endup looping your traffic wireguard interface -> wstunnel client -> wireguard interface - - -## How to Build -Install the stack tool https://docs.haskellstack.org/en/stable/README/ or if you are a believer -``` -curl -sSL https://get.haskellstack.org/ | sh -``` -and run those commands at the root of the project -``` -stack init -stack install -``` - -## TODO -- [x] Add sock5 proxy -- [x] Add better logging -- [x] Add better error handling -- [x] Add httpProxy authentification -- [ ] Add Reverse tunnel -- [x] Add more tests for socks5 proxy diff --git a/app/Main.hs b/app/Main.hs deleted file mode 100644 index 2327b9e0..00000000 --- a/app/Main.hs +++ /dev/null @@ -1,333 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DeriveDataTypeable #-} -{-# LANGUAGE OverloadedStrings #-} -{-# OPTIONS_GHC -fno-cse #-} - -module Main where - -import ClassyPrelude hiding (getArgs, head) -import Data.CaseInsensitive ( CI ) -import qualified Data.CaseInsensitive as CI -import qualified Data.ByteString.Char8 as BC -import Data.List (head, (!!)) -import System.Console.CmdArgs -import System.Environment (getArgs, withArgs) - -import qualified Logger -import Tunnel -import Types -import Credentials -import Control.Concurrent.Async as Async - -data WsTunnel = WsTunnel - { localToRemote :: [String] - -- , remoteToLocal :: String - , dynamicToRemote :: String - , wsTunnelServer :: String - , udpMode :: Bool - , udpTimeout :: Int - , proxy :: String - , soMark :: Int - , verbose :: Bool - , quiet :: Bool - , pathPrefix :: String - , hostHeader :: String - , tlsSNI :: String - , tlsVerifyCertificate :: Bool - , websocketPingFrequencySec :: Int - , wsTunnelCredentials :: String - , customHeaders :: [String] - , serverMode :: Bool - , restrictTo :: String - , tlsCertificate :: FilePath - , tlsKey :: FilePath - } deriving (Show, Data, Typeable) - -data WsServerInfo = WsServerInfo - { useTls :: !Bool - , host :: !String - , port :: !Int - } deriving (Show) - -data TunnelInfo = TunnelInfo - { localHost :: !String - , localPort :: !Int - , remoteHost :: !String - , remotePort :: !Int - } deriving (Show) - - -cmdLine :: WsTunnel -cmdLine = WsTunnel - { localToRemote = def &= explicit &= name "L" &= name "localToRemote" &= typ "[BIND:]PORT:HOST:PORT" - &= help "Listen on local and forwards traffic from remote. Can be used multiple time" &= groupname "Client options" - -- , remoteToLocal = def &= explicit &= name "R" &= name "RemoteToLocal" &= typ "[BIND:]PORT:HOST:PORT" - -- &= help "Listen on remote and forward traffic from local" - , dynamicToRemote= def &= explicit &= name "D" &= name "dynamicToRemote" &= typ "[BIND:]PORT" - &= help "Listen on local and dynamically (with socks5 proxy) forwards traffic from remote" &= groupname "Client options" - , udpMode = def &= explicit &= name "u" &= name "udp" &= help "forward UDP traffic instead of TCP" &= groupname "Client options" - , udpTimeout = def &= explicit &= name "udpTimeoutSec" &= help "When using udp forwarding, timeout in seconds after when the tunnel connection is closed. Default 30sec, -1 means no timeout" - &= groupname "Client options" - , customHeaders = def &= explicit &= name "H" &= name "customHeaders" &= help "Send custom headers in the upgrade request. Can be used multiple time" - &= typ "\"HeaderName: HeaderValue\"" &= groupname "Client options" - , pathPrefix = def &= explicit &= name "upgradePathPrefix" - &= help "Use a specific prefix that will show up in the http path in the upgrade request. Useful if you need to route requests server side but don't have vhosts" - &= typ "String" &= groupname "Client options" - , wsTunnelCredentials - = def &= explicit &= name "upgradeCredentials" - &= help "Credentials for the Basic HTTP authorization type sent with the upgrade request." - &= typ "USER[:PASS]" - , proxy = def &= explicit &= name "p" &= name "httpProxy" - &= help "If set, will use this proxy to connect to the server" &= typ "USER:PASS@HOST:PORT" - , hostHeader = def &= explicit &= name "hostHeader" &= groupname "Client options" - &= help "If set, add the custom string as host http header" &= typ "String" &= groupname "Client options" - , tlsSNI = def &= explicit &= name "tlsSNI" &= groupname "Client options" - &= help "If set, use custom string in the SNI during TLS handshake" &= typ "String" &= groupname "Client options" - , tlsVerifyCertificate = def &= explicit &= name "tlsVerifyCertificate" &= groupname "Client options" - &= help "Verify tls server certificate. Default to false" - , soMark = def &= explicit &= name "soMark" - &= help "(linux only) Mark network packet with SO_MARK sockoption with the specified value. You need to use {root, sudo, capabilities} to run wstunnel when using this option" &= typ "int" - , websocketPingFrequencySec = def &= explicit &= name "websocketPingFrequencySec" - &= help "do a hearthbeat ping every x seconds to maintain websocket connection" &= typ "int" - , wsTunnelServer = def &= argPos 0 &= typ "ws[s]://wstunnelServer[:port]" - - , serverMode = def &= explicit &= name "server" - &= help "Start a server that will forward traffic for you" &= groupname "Server options" - , restrictTo = def &= explicit &= name "r" &= name "restrictTo" &= groupname "Server options" - &= help "Accept traffic to be forwarded only to this service" &= typ "HOST:PORT" - , tlsCertificate = def &= explicit &= name "tlsCertificate" &= groupname "Server options" - &= help "[optional] provide a custom tls certificate (.crt) that the server will use instead of the embeded one" &= typFile - , tlsKey = def &= explicit &= name "tlsKey" &= groupname "Server options" - &= help "[optional] provide a custom tls key (.key) that the server will use instead of the embeded one" &= typFile - , verbose = def &= groupname "Common options" &= help "Print debug information" - , quiet = def &= help "Print only errors" &= groupname "Common options" - } &= summary ( "Use the websockets protocol to tunnel {TCP,UDP} traffic\n" - ++ "wsTunnelClient <---> wsTunnelServer <---> RemoteHost\n" - ++ "Use secure connection (wss://) to bypass proxies" - ) - &= helpArg [explicit, name "help", name "h", groupname "Common options"] - - -toPort :: String -> Int -toPort "stdio" = 0 -toPort str = case readMay str of - Just por -> por - Nothing -> error $ "Invalid port number `" ++ str ++ "`" - -parseServerInfo :: WsServerInfo -> String -> WsServerInfo -parseServerInfo server [] = server -parseServerInfo server ('w':'s':':':'/':'/':xs) = parseServerInfo (server {Main.useTls = False, Main.port = 80}) xs -parseServerInfo server ('w':'s':'s':':':'/':'/':xs) = parseServerInfo (server {Main.useTls = True, Main.port = 443}) xs -parseServerInfo server (':':prt) = server {Main.port = toPort prt} -parseServerInfo server ('[':xs) = parseServerInfo (server {Main.host = BC.unpack . BC.init . fst $ BC.spanEnd (/= ']') (BC.pack xs)}) (BC.unpack . snd $ BC.spanEnd (/= ']') (BC.pack xs)) -parseServerInfo server hostPath = parseServerInfo (server {Main.host = takeWhile (/= ':') hostPath}) (dropWhile (/= ':') hostPath) - - -parseTunnelInfo :: String -> TunnelInfo -parseTunnelInfo strr = do - let str = BC.pack strr - if BC.count ']' str <= 0 then - mkIPv4 $ BC.unpack <$> BC.split ':' str - else - mkIPv6 $ str - - where - mkIPv4 [lPort, host, rPort] = TunnelInfo {localHost = "127.0.0.1", Main.localPort = toPort lPort, remoteHost = host, remotePort = toPort rPort} - mkIPv4 [bind,lPort, host,rPort] = TunnelInfo {localHost = bind, Main.localPort = toPort lPort, remoteHost = host, remotePort = toPort rPort} - mkIPv4 _ = error $ "Invalid tunneling information `" ++ strr ++ "`, please use format [BIND:]PORT:HOST:PORT" - - mkIPv6 str = do - let !(localHost, remain) = if BC.head str == '[' then - BC.drop 2 <$> BC.span (/= ']') (BC.drop 1 str) - else if BC.head str < '0' || BC.head str > '9' then - BC.drop 1 <$> BC.span (/= ':') str - else - ("", str) - - let (remain, rPort) = first BC.init . BC.spanEnd (/= ':') $ str - let (remain2, remoteHost) = if BC.last remain == ']' then - first (BC.init . BC.init) $ BC.spanEnd (/= '[') (BC.init remain) - else - first BC.init $ BC.spanEnd (/= ':') remain - - let (remain3, lPort) = BC.spanEnd (/= ':') $ remain2 - if remain3 == mempty then - TunnelInfo {localHost = "::1", Main.localPort = toPort (BC.unpack lPort), remoteHost = (BC.unpack remoteHost), remotePort = toPort (BC.unpack rPort)} - else - let localHost = BC.filter (\c -> c /= '[' && c /= ']') (BC.init remain3) in - TunnelInfo {localHost = BC.unpack localHost, Main.localPort = toPort (BC.unpack lPort), remoteHost = (BC.unpack remoteHost), remotePort = toPort (BC.unpack rPort)} - - - -parseRestrictTo :: String -> ((ByteString, Int) -> Bool) -parseRestrictTo "" = const True -parseRestrictTo str = let !(!h, !p) = fromMaybe (error "Invalid Parameter restart") parse - in (\(!hst, !port) -> hst == h && port == p) - where - parse = do - let (host, port) = BC.spanEnd (/= ':') (BC.pack str) - guard (host /= mempty) - portNumber <- readMay . BC.unpack $ port :: Maybe Int - return (BC.filter (\c -> c /= '[' && c /= ']') (BC.init host), portNumber) - -parseProxyInfo :: String -> Maybe ProxySettings -parseProxyInfo str = do - let ret = BC.split ':' (BC.pack str) - - guard (length ret >= 2) - if length ret == 3 - then do - portNumber <- readMay $ BC.unpack $ ret !! 2 :: Maybe Int - let cred = (head ret, head (BC.split '@' (ret !! 1))) - let h = BC.split '@' (ret !! 1) !! 1 - return $ ProxySettings (BC.unpack h) (fromIntegral portNumber) (Just cred) - else if length ret == 2 - then do - portNumber <- readMay . BC.unpack $ ret !! 1 :: Maybe Int - return $ ProxySettings (BC.unpack $ head ret) (fromIntegral portNumber) Nothing - else Nothing - -parseCustomHeader :: String -> (CI ByteString, ByteString) -parseCustomHeader header = (CI.mk . BC.pack $ takeWhile (/= ':') header, BC.pack . dropWhile (\c -> c == ' ' || c == ':') $ (dropWhile (/= ':') header)) - - -main :: IO () -main = do - args <- getArgs - cfg' <- if null args then withArgs ["--help"] (cmdArgs cmdLine) else cmdArgs cmdLine - let cfg = cfg' { pathPrefix = if pathPrefix cfg' == mempty then "wstunnel" else pathPrefix cfg' - , Main.udpTimeout = if Main.udpTimeout cfg' == 0 then 30 * 10^(6 :: Int) - else if Main.udpTimeout cfg' == -1 then -1 - else Main.udpTimeout cfg' * 10^(6:: Int) - , Main.websocketPingFrequencySec = if Main.websocketPingFrequencySec cfg' == 0 - then 30 - else Main.websocketPingFrequencySec cfg' - } - - let serverInfo = parseServerInfo (WsServerInfo False "" 0) (wsTunnelServer cfg) - Logger.init (if quiet cfg then Logger.QUIET - else if verbose cfg - then Logger.VERBOSE - else Logger.NORMAL) - - _ <- writeIORef sO_MARK_Value (soMark cfg) - runApp cfg serverInfo - putStrLn "Goodbye !" - return () - - -runApp :: WsTunnel -> WsServerInfo -> IO () -runApp cfg serverInfo - -- server mode - | serverMode cfg = do - putStrLn $ "Starting server with opts " <> tshow serverInfo - key <- if Main.tlsKey cfg /= mempty then readFile (Main.tlsKey cfg) else return Credentials.key - certificate <- if Main.tlsCertificate cfg /= mempty then readFile (Main.tlsCertificate cfg) else return Credentials.certificate - let tls = if Main.useTls serverInfo then Just (certificate, key) else Nothing - runServer tls (Main.host serverInfo, fromIntegral $ Main.port serverInfo) (parseRestrictTo $ restrictTo cfg) - - -- -L localToRemote tunnels - | not . null $ localToRemote cfg = do - let tunnelInfos = parseTunnelInfo <$> localToRemote cfg - let tunnelSettings = tunnelInfos >>= \tunnelInfo -> - if Main.localPort tunnelInfo == 0 then [toStdioLocalToRemoteTunnelSetting cfg serverInfo tunnelInfo] - else if udpMode cfg then [toUdpLocalToRemoteTunnelSetting cfg serverInfo tunnelInfo] - else [toTcpLocalToRemoteTunnelSetting cfg serverInfo tunnelInfo] - Async.mapConcurrently_ runClient tunnelSettings - - -- -D dynamicToRemote tunnels - | not . null $ dynamicToRemote cfg = do - let tunnelSetting = toDynamicTunnelSetting cfg serverInfo . parseTunnelInfo $ dynamicToRemote cfg ++ ":127.0.0.1:1212" - runClient tunnelSetting - - | otherwise = do - putStrLn "Cannot parse correctly the command line. Please fill an issue" - - where - toStdioLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) = - TunnelSettings { - localBind = lHost - , Types.localPort = fromIntegral lPort - , serverHost = Main.host serverInfo - , serverPort = fromIntegral $ Main.port serverInfo - , destHost = rHost - , destPort = fromIntegral rPort - , Types.useTls = Main.useTls serverInfo - , protocol = STDIO - , proxySetting = parseProxyInfo (proxy cfg) - , useSocks = False - , upgradePrefix = pathPrefix cfg - , upgradeCredentials = BC.pack $ wsTunnelCredentials cfg - , udpTimeout = Main.udpTimeout cfg - , tlsSNI = BC.pack $ Main.tlsSNI cfg - , tlsVerifyCertificate = Main.tlsVerifyCertificate cfg - , hostHeader = BC.pack $ Main.hostHeader cfg - , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg - , customHeaders = parseCustomHeader <$> Main.customHeaders cfg - } - - toTcpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) = - TunnelSettings { - localBind = lHost - , Types.localPort = fromIntegral lPort - , serverHost = Main.host serverInfo - , serverPort = fromIntegral $ Main.port serverInfo - , destHost = rHost - , destPort = fromIntegral rPort - , Types.useTls = Main.useTls serverInfo - , protocol = TCP - , proxySetting = parseProxyInfo (proxy cfg) - , useSocks = False - , upgradePrefix = pathPrefix cfg - , upgradeCredentials = BC.pack $ wsTunnelCredentials cfg - , udpTimeout = Main.udpTimeout cfg - , tlsSNI = BC.pack $ Main.tlsSNI cfg - , tlsVerifyCertificate = Main.tlsVerifyCertificate cfg - , hostHeader = BC.pack $ Main.hostHeader cfg - , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg - , customHeaders = parseCustomHeader <$> Main.customHeaders cfg - } - - toUdpLocalToRemoteTunnelSetting cfg serverInfo (TunnelInfo lHost lPort rHost rPort) = - TunnelSettings { - localBind = lHost - , Types.localPort = fromIntegral lPort - , serverHost = Main.host serverInfo - , serverPort = fromIntegral $ Main.port serverInfo - , destHost = rHost - , destPort = fromIntegral rPort - , Types.useTls = Main.useTls serverInfo - , protocol = UDP - , proxySetting = parseProxyInfo (proxy cfg) - , useSocks = False - , upgradePrefix = pathPrefix cfg - , upgradeCredentials = BC.pack $ wsTunnelCredentials cfg - , udpTimeout = Main.udpTimeout cfg - , tlsSNI = BC.pack $ Main.tlsSNI cfg - , tlsVerifyCertificate = Main.tlsVerifyCertificate cfg - , hostHeader = BC.pack $ Main.hostHeader cfg - , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg - , customHeaders = parseCustomHeader <$> Main.customHeaders cfg - } - - toDynamicTunnelSetting cfg serverInfo (TunnelInfo lHost lPort _ _) = - TunnelSettings { - localBind = lHost - , Types.localPort = fromIntegral lPort - , serverHost = Main.host serverInfo - , serverPort = fromIntegral $ Main.port serverInfo - , destHost = "" - , destPort = 0 - , Types.useTls = Main.useTls serverInfo - , protocol = SOCKS5 - , proxySetting = parseProxyInfo (proxy cfg) - , useSocks = True - , upgradePrefix = pathPrefix cfg - , upgradeCredentials = BC.pack $ wsTunnelCredentials cfg - , udpTimeout = Main.udpTimeout cfg - , tlsSNI = BC.pack $ Main.tlsSNI cfg - , tlsVerifyCertificate = Main.tlsVerifyCertificate cfg - , hostHeader = BC.pack $ Main.hostHeader cfg - , websocketPingFrequencySec = Main.websocketPingFrequencySec cfg - , customHeaders = parseCustomHeader <$> Main.customHeaders cfg - } diff --git a/build_aarch64.sh b/build_aarch64.sh deleted file mode 100644 index 7caaff7f..00000000 --- a/build_aarch64.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# Ubuntu 18 - -sudo apt-get install git llvm-6.0-tools zlib1g-dev -export PATH="/usr/lib/llvm-6.0/bin/:$PATH" - -wget https://github.com/commercialhaskell/stack/releases/download/v2.1.3/stack-2.1.3-linux-aarch64.tar.gz -tar xzvf stack-2.1.3-linux-aarch64.tar.gz -sudo cp stack-2.1.3-linux-aarch64/stack /usr/local/bin/ -git clone https://github.com/erebe/wstunnel -cd wstunnel -stack setup -stack build diff --git a/build_raspbian.sh b/build_raspbian.sh deleted file mode 100644 index afa1fffa..00000000 --- a/build_raspbian.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -# Raspbian - -sudo apt-get install git llvm-6.0-tools zlib1g-dev ghc -export PATH="/usr/lib/llvm-6.0/bin/:$PATH" - -wget https://github.com/commercialhaskell/stack/releases/download/v2.1.3/stack-2.1.3-linux-arm.tar.gz -tar xzvf stack-*.tar.gz -sudo cp stack-*/stack /usr/local/bin/ -git clone https://github.com/erebe/wstunnel -cd wstunnel - -stack config set system-ghc --global true -sed -i "s/resolver:.*/resolver: lts-12.26/g" stack.yaml -sed -i 's/-rtsopts ".*//g' wstunnel.cabal - -stack setup -stack build diff --git a/default.nix b/default.nix deleted file mode 100644 index d55a5ffe..00000000 --- a/default.nix +++ /dev/null @@ -1,52 +0,0 @@ -# Run using: -# -# $(nix-build --no-link -A fullBuildScript) -{ - stack2nix-output-path ? "custom-stack2nix-output.nix", -}: -let - cabalPackageName = "wstunnel"; - compiler = "ghc865"; # matching stack.yaml - - # Pin static-haskell-nix version. - static-haskell-nix = - if builtins.pathExists ../.in-static-haskell-nix - then toString ../. # for the case that we're in static-haskell-nix itself, so that CI always builds the latest version. - # Update this hash to use a different `static-haskell-nix` version: - else fetchTarball https://github.com/nh2/static-haskell-nix/archive/b402b38c3af2300e71caeebe51b5e4e1ae2e924c.tar.gz; - - # Pin nixpkgs version - # By default to the one `static-haskell-nix` provides, but you may also give - # your own as long as it has the necessary patches, using e.g. - # pkgs = import (fetchTarball https://github.com/nh2/nixpkgs/archive/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa123.tar.gz) {}; - pkgs = import "${static-haskell-nix}/nixpkgs.nix"; - - stack2nix-script = import "${static-haskell-nix}/static-stack2nix-builder/stack2nix-script.nix" { - inherit pkgs; - stack-project-dir = toString ./.; # where stack.yaml is - hackageSnapshot = "2019-10-21T00:00:00Z"; # pins e.g. extra-deps without hashes or revisions - }; - - static-stack2nix-builder = import "${static-haskell-nix}/static-stack2nix-builder/default.nix" { - normalPkgs = pkgs; - inherit cabalPackageName compiler stack2nix-output-path; - # disableOptimization = true; # for compile speed - }; - - # Full invocation, including pinning `nix` version itself. - fullBuildScript = pkgs.writeScript "stack2nix-and-build-script.sh" '' - #!/usr/bin/env bash - set -eu -o pipefail - STACK2NIX_OUTPUT_PATH=$(${stack2nix-script}) - export NIX_PATH=nixpkgs=${pkgs.path} - ${pkgs.nix}/bin/nix-build --no-link -A static_package --argstr stack2nix-output-path "$STACK2NIX_OUTPUT_PATH" "$@" - ''; - -in - { - static_package = static-stack2nix-builder.static_package; - inherit fullBuildScript; - # For debugging: - inherit stack2nix-script; - inherit static-stack2nix-builder; - } diff --git a/logo_wstunnel.png b/logo_wstunnel.png deleted file mode 100644 index a8f41baf..00000000 Binary files a/logo_wstunnel.png and /dev/null differ diff --git a/src/Credentials.hs b/src/Credentials.hs deleted file mode 100644 index 0a7d5c03..00000000 --- a/src/Credentials.hs +++ /dev/null @@ -1,44 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - -module Credentials where - -import ClassyPrelude - --- openssl genrsa 1024 > host.key --- openssl req -new -x509 -nodes -sha1 -days 9999 -key host.key > host.cert -key :: ByteString -key = "-----BEGIN RSA PRIVATE KEY-----\n" <> - "MIICXAIBAAKBgQCzP4dg89HDyWfe2k5KD8RdFNh7G9Rla8cjMtE6ccBx84B1WbG5\n" <> - "ziRpaCvsTdYSVRwcbR07+4oqR302vyCBZ+r/djpYuTyUTNRYC9+h4wdPGXKhKpeR\n" <> - "z1BNVKCsQ6qcBFLDb7l6ra+g36DMQuLcJvLx7LX7elW5w9M/I4FFfV+aeQIDAQAB\n" <> - "AoGAD744qa9AcS2zTcNmtOKFoJdAHC/pi67XoqPH9JYhDOESGzxxe5w7XnajxPFh\n" <> - "J+MJwQVkV+xTyjrVKIXI2RTDct6tdG2jDcH6P0Xf3I6BPBhvw9pLlisUHTqVxFpV\n" <> - "nAoUiyWYZcEiF37IT/uwdRAlhqgitjK7rhZfkM2XNpMb3gECQQDp1qpVk4y5smFE\n" <> - "IfZPr94paBZLRD9EwHnxZVM27oR0C95YIgcc12mNchYxIOW4szKwyaUCZLafiojA\n" <> - "+anojR/RAkEAxDxnn/3qWmHGYrs/1wrT9FEoC6XZGBHboQIcYYGihK/64P8E19WF\n" <> - "BmexzLZdlilieT0ATM5I9zOULSiZ4H/iKQJAC46PdpFHSDo3sm1XRhL0EOnTCD9E\n" <> - "PTqiDDssxK8/HpkjkQmFfnhrABGeZSkyEVHR9IjSve6KVBI9tgPg0NyAsQJAEZB+\n" <> - "jfmCQnjB8xBjlHHpqtKgzPoZRmhCylSQCcI6s7m0sPLikhcQgxRA+9vO4KPvpn5p\n" <> - "SnakXUwGlUwvCcMokQJBAKw9U5H88GyB4qWhnwhustnVnVg/bzkYGpryjDx6mLYh\n" <> - "eMPlv6aH546XMJbQ6fRe3tgMBBgOD1QN9WvKuFQo2K4=\n" <> - "-----END RSA PRIVATE KEY-----" - -certificate :: ByteString -certificate = "-----BEGIN CERTIFICATE-----\n" <> - "MIIC5DCCAk2gAwIBAgIUBjMRJwxK4qoz64RFZcHQorbfrucwDQYJKoZIhvcNAQEF\n" <> - "BQAwgYMxCzAJBgNVBAYTAkZSMRIwEAYDVQQIDAlBcXVpdGFpbmUxETAPBgNVBAcM\n" <> - "CEd1ZXRoYXJ5MRMwEQYDVQQKDApFcmViZSBDb3JwMRIwEAYDVQQLDAlIYWNrIEhh\n" <> - "Y2sxDjAMBgNVBAMMBWVyZWJlMRQwEgYJKoZIhvcNAQkBFgVlcmViZTAeFw0xOTEw\n" <> - "MjQxMTM5NDVaFw00NzAzMTAxMTM5NDVaMIGDMQswCQYDVQQGEwJGUjESMBAGA1UE\n" <> - "CAwJQXF1aXRhaW5lMREwDwYDVQQHDAhHdWV0aGFyeTETMBEGA1UECgwKRXJlYmUg\n" <> - "Q29ycDESMBAGA1UECwwJSGFjayBIYWNrMQ4wDAYDVQQDDAVlcmViZTEUMBIGCSqG\n" <> - "SIb3DQEJARYFZXJlYmUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALM/h2Dz\n" <> - "0cPJZ97aTkoPxF0U2Hsb1GVrxyMy0TpxwHHzgHVZsbnOJGloK+xN1hJVHBxtHTv7\n" <> - "iipHfTa/IIFn6v92Oli5PJRM1FgL36HjB08ZcqEql5HPUE1UoKxDqpwEUsNvuXqt\n" <> - "r6DfoMxC4twm8vHstft6VbnD0z8jgUV9X5p5AgMBAAGjUzBRMB0GA1UdDgQWBBRC\n" <> - "8mpWQdiOTYy+GBxUQ9vssIloMTAfBgNVHSMEGDAWgBRC8mpWQdiOTYy+GBxUQ9vs\n" <> - "sIloMTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAGkUgoDLmb5e\n" <> - "SWPR61QEByPkIji4DytJfzUeJBZKyRQSMGC08yUAPAmFbIt1jqBO6nTum3TjlV6S\n" <> - "7bv3kEhkgTdoKHyWtBitnR2wg90Ybm4K6OKLnoKZgvl1IZ6x8LCqI1RVIQMHaUkL\n" <> - "L3+otPXxpH1LXGnikOlwLkF2LPhRmX9X\n" <> - "-----END CERTIFICATE-----" diff --git a/src/HttpProxy.hs b/src/HttpProxy.hs deleted file mode 100644 index d516a156..00000000 --- a/src/HttpProxy.hs +++ /dev/null @@ -1,76 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE StrictData #-} -{-# LANGUAGE ViewPatterns #-} - -module HttpProxy () where - - - -import ClassyPrelude -import qualified Data.ByteString.Char8 as BC - -import Control.Monad.Except -import qualified Data.Streaming.Network as N - -import qualified Data.ByteString.Base64 as B64 -import Network.Socket (HostName, PortNumber) - -import Logger -import Types - - -data HttpProxySettings = HttpProxySettings - { proxyHost :: HostName - , proxyPort :: PortNumber - , credentials :: Maybe (ByteString, ByteString) - } deriving (Show) - - -httpProxyConnection :: MonadError Error m => HttpProxySettings -> (HostName, PortNumber) -> (Connection -> IO (m a)) -> IO (m a) -httpProxyConnection HttpProxySettings{..} (host, port) app = onError $ do - debug $ "Opening tcp connection to proxy " <> show proxyHost <> ":" <> show proxyPort - - ret <- N.runTCPClient (N.clientSettingsTCP (fromIntegral proxyPort) (fromString proxyHost)) $ \conn' -> do - let conn = toConnection conn' - _ <- sendConnectRequest conn - - -- wait 10sec for a reply before giving up - let _10sec = 1000000 * 10 - responseM <- timeout _10sec $ readConnectResponse mempty conn - - case responseM of - Just (isAuthorized -> True) -> app conn - Just response -> return . throwError $ ProxyForwardError (BC.unpack response) - Nothing -> return . throwError $ ProxyForwardError ("No response from the proxy after " - <> show (_10sec `div` 1000000) <> "sec" ) - - debug $ "Closing tcp connection to proxy " <> show proxyHost <> ":" <> show proxyPort - return ret - - where - credentialsToHeader :: (ByteString, ByteString) -> ByteString - credentialsToHeader (user, password) = "Proxy-Authorization: Basic " <> B64.encode (user <> ":" <> password) <> "\r\n" - - sendConnectRequest :: Connection -> IO () - sendConnectRequest h = write h $ "CONNECT " <> fromString host <> ":" <> fromString (show port) <> " HTTP/1.0\r\n" - <> "Host: " <> fromString host <> ":" <> fromString (show port) <> "\r\n" - <> maybe mempty credentialsToHeader credentials - <> "\r\n" - - readConnectResponse :: ByteString -> Connection -> IO ByteString - readConnectResponse buff conn = do - responseM <- read conn - case responseM of - Nothing -> return buff - Just response -> if "\r\n\r\n" `isInfixOf` response - then return $ buff <> response - else readConnectResponse (buff <> response) conn - - isAuthorized :: ByteString -> Bool - isAuthorized response = " 200 " `isInfixOf` response - - onError f = catch f $ \(e :: SomeException) -> return $ - if take 10 (show e) == "user error" - then throwError $ ProxyConnectionError (show e) - else throwError $ ProxyConnectionError ("Unknown Error :: " <> show e) diff --git a/src/Logger.hs b/src/Logger.hs deleted file mode 100644 index 89a08c0d..00000000 --- a/src/Logger.hs +++ /dev/null @@ -1,26 +0,0 @@ -module Logger where - -import ClassyPrelude -import Network.Socket (HostName, PortNumber) -import qualified System.Log.Logger as LOG - - -data Verbosity = QUIET | VERBOSE | NORMAL - -init :: Verbosity -> IO () -init lvl = LOG.updateGlobalLogger "wstunnel" $ case lvl of - QUIET -> LOG.setLevel LOG.ERROR - VERBOSE -> LOG.setLevel LOG.DEBUG - NORMAL -> LOG.setLevel LOG.INFO - -toStr :: (HostName, PortNumber) -> String -toStr (host, port) = fromString host <> ":" <> show port - -err :: String -> IO() -err msg = LOG.errorM "wstunnel" $ "ERROR :: " <> msg - -info :: String -> IO() -info = LOG.infoM "wstunnel" - -debug :: String -> IO() -debug msg = LOG.debugM "wstunnel" $ "DEBUG :: " <> msg diff --git a/src/Protocols.hs b/src/Protocols.hs deleted file mode 100644 index a5347161..00000000 --- a/src/Protocols.hs +++ /dev/null @@ -1,140 +0,0 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE OverloadedStrings #-} - -module Protocols where - -import ClassyPrelude -import Control.Concurrent (forkFinally, threadDelay) -import qualified Data.HashMap.Strict as H - -import qualified Data.ByteString.Char8 as BC - -import qualified Data.Streaming.Network as N - -import Network.Socket (HostName, PortNumber) -import qualified Network.Socket as N -import qualified Network.Socket.ByteString as N - -import Data.Binary (decode, encode) - -import Logger -import qualified Socks5 -import Types - - -runSTDIOServer :: (StdioAppData -> IO ()) -> IO () -runSTDIOServer app = do - stdin_old_buffering <- hGetBuffering stdin - stdout_old_buffering <- hGetBuffering stdout - - hSetBuffering stdin (BlockBuffering (Just 512)) - hSetBuffering stdout NoBuffering - - void $ forever $ app StdioAppData - - hSetBuffering stdin stdin_old_buffering - hSetBuffering stdout stdout_old_buffering - info $ "CLOSE stdio server" - -runTCPServer :: (HostName, PortNumber) -> (N.AppData -> IO ()) -> IO () -runTCPServer endPoint@(host, port) app = do - info $ "WAIT for tcp connection on " <> toStr endPoint - let srvSet = N.setReadBufferSize defaultRecvBufferSize $ N.serverSettingsTCP (fromIntegral port) (fromString host) - void $ N.runTCPServer srvSet app - info $ "CLOSE tcp server on " <> toStr endPoint - -runTCPClient :: (HostName, PortNumber) -> (N.AppData -> IO ()) -> IO () -runTCPClient endPoint@(host, port) app = do - info $ "CONNECTING to " <> toStr endPoint - let srvSet = N.setReadBufferSize defaultRecvBufferSize $ N.clientSettingsTCP (fromIntegral port) (BC.pack host) - void $ N.runTCPClient srvSet app - info $ "CLOSE connection to " <> toStr endPoint - - -runUDPClient :: (HostName, PortNumber) -> (UdpAppData -> IO ()) -> IO () -runUDPClient endPoint@(host, port) app = do - info $ "SENDING datagrammes to " <> toStr endPoint - bracket (N.getSocketUDP host (fromIntegral port)) (N.close . fst) $ \(socket, addrInfo) -> do - sem <- newEmptyMVar - app UdpAppData { appAddr = N.addrAddress addrInfo - , appSem = sem - , appRead = fst <$> N.recvFrom socket 4096 - , appWrite = \payload -> void $ N.sendAllTo socket payload (N.addrAddress addrInfo) - } - - info $ "CLOSE udp connection to " <> toStr endPoint - - -runUDPServer :: (HostName, PortNumber) -> Int -> (UdpAppData -> IO ()) -> IO () -runUDPServer endPoint@(host, port) cnxTimeout app = do - info $ "WAIT for datagrames on " <> toStr endPoint - clientsCtx <- newIORef mempty - void $ bracket (N.bindPortUDP (fromIntegral port) (fromString host)) N.close (forever . run clientsCtx) - info $ "CLOSE udp server" <> toStr endPoint - - where - addNewClient :: IORef (H.HashMap N.SockAddr UdpAppData) -> N.Socket -> N.SockAddr -> ByteString -> IO UdpAppData - addNewClient clientsCtx socket addr payload = do - sem <- newMVar payload - let appData = UdpAppData { appAddr = addr - , appSem = sem - , appRead = takeMVar sem - , appWrite = \payload' -> void $ N.sendAllTo socket payload' addr - } - void $ atomicModifyIORef' clientsCtx (\clients -> (H.insert addr appData clients, ())) - return appData - - removeClient :: IORef (H.HashMap N.SockAddr UdpAppData) -> UdpAppData -> IO () - removeClient clientsCtx clientCtx = do - void $ atomicModifyIORef' clientsCtx (\clients -> (H.delete (appAddr clientCtx) clients, ())) - debug "TIMEOUT connection" - - pushDataToClient :: UdpAppData -> ByteString -> IO () - pushDataToClient clientCtx payload = putMVar (appSem clientCtx) payload - `catch` (\(_ :: SomeException) -> debug $ "DROP udp packet, client thread dead") - -- If we are unlucky the client's thread died before we had the time to push the data on a already full mutex - -- and will leave us waiting forever for the mutex to empty. So catch the exeception and drop the message. - -- Udp is not a reliable protocol so transmission failure should be handled by the application layer - - -- We run the server inside another thread in order to avoid Haskell runtime sending to the main thread - -- the exception BlockedIndefinitelyOnMVar - -- We dont use also MVar to wait for the end of the thread to avoid also receiving this exception - run :: IORef (H.HashMap N.SockAddr UdpAppData) -> N.Socket -> IO () - run clientsCtx socket = do - _ <- forkFinally (runEventLoop clientsCtx socket) (\_ -> debug "UdpServer died") - threadDelay (maxBound :: Int) - - runEventLoop :: IORef (H.HashMap N.SockAddr UdpAppData) -> N.Socket -> IO () - runEventLoop clientsCtx socket = forever $ do - (payload, addr) <- N.recvFrom socket 4096 - clientCtx <- H.lookup addr <$> readIORef clientsCtx - - case clientCtx of - Just clientCtx' -> pushDataToClient clientCtx' payload - _ -> do - clientCtx <- addNewClient clientsCtx socket addr payload - _ <- forkFinally (void . timeout cnxTimeout $ app clientCtx) (\_ -> removeClient clientsCtx clientCtx) - return () - - -runSocks5Server :: Socks5.ServerSettings -> TunnelSettings -> (TunnelSettings -> N.AppData -> IO()) -> IO () -runSocks5Server socksSettings@Socks5.ServerSettings{..} cfg inner = do - info $ "Starting socks5 proxy " <> show socksSettings - - _ <- N.runTCPServer (N.serverSettingsTCP (fromIntegral listenOn) (fromString bindOn)) $ \cnx -> do - -- Get the auth request and response with a no Auth - authRequest <- decode . fromStrict <$> N.appRead cnx :: IO Socks5.RequestAuth - debug $ "Socks5 authentification request " <> show authRequest - let responseAuth = encode $ Socks5.ResponseAuth (fromIntegral Socks5.socksVersion) Socks5.NoAuth - N.appWrite cnx (toStrict responseAuth) - - -- Get the request and update dynamically the tunnel config - request <- decode . fromStrict <$> N.appRead cnx :: IO Socks5.Request - debug $ "Socks5 forward request " <> show request - let responseRequest = encode $ Socks5.Response (fromIntegral Socks5.socksVersion) Socks5.SUCCEEDED (Socks5.addr request) (Socks5.port request) (Socks5.addrType request) - let cfg' = cfg { destHost = Socks5.addr request, destPort = Socks5.port request } - N.appWrite cnx (toStrict responseRequest) - - inner cfg' cnx - - info $ "Closing socks5 proxy " <> show socksSettings diff --git a/src/Socks5.hs b/src/Socks5.hs deleted file mode 100644 index 4704ccd4..00000000 --- a/src/Socks5.hs +++ /dev/null @@ -1,243 +0,0 @@ -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE StrictData #-} - -module Socks5 where - - -import ClassyPrelude -import Data.Binary -import Data.Binary.Get -import Data.Binary.Put -import qualified Data.ByteString.Char8 as BC8 -import Data.Either -import qualified Data.Text as T -import qualified Data.Text.Read as T -import qualified Data.Text.Encoding as E -import Network.Socket (HostName, PortNumber) -import Numeric (showHex) - -socksVersion :: Word8 -socksVersion = 0x05 - -data AuthMethod = NoAuth - | GSSAPI - | Login - | Reserved - | NotAllowed - deriving (Show, Read) - -data AddressType = DOMAIN_NAME - | IPv4 - deriving (Show, Read, Eq) - -data RequestAuth = RequestAuth - { version :: Int - , methods :: Vector AuthMethod - } deriving (Show, Read) - -data ResponseAuth = ResponseAuth - { version :: Int - , method :: AuthMethod - } deriving (Show, Read) - -instance Binary ResponseAuth where - put ResponseAuth{..} = putWord8 (fromIntegral version) >> put method - get = ResponseAuth <$> (fromIntegral <$> getWord8) - <*> get - - -instance Binary AuthMethod where - put val = case val of - NoAuth -> putWord8 0x00 - GSSAPI -> putWord8 0x01 - Login -> putWord8 0x02 - NotAllowed -> putWord8 0xFF - _ {- Reserverd -} -> putWord8 0x03 - - get = do - method <- getWord8 - return $ case method of - 0x00 -> NoAuth - 0x01 -> GSSAPI - 0x02 -> Login - 0xFF -> NotAllowed - _ -> Reserved - - -instance Binary RequestAuth where - put RequestAuth{..} = do - putWord8 (fromIntegral version) - putWord8 (fromIntegral $ length methods) - mapM_ put methods - -- Check length <= 255 - - get = do - version <- fromIntegral <$> getWord8 - guard (version == 0x05) - nbMethods <- fromIntegral <$> getWord8 - guard (nbMethods > 0 && nbMethods <= 0xFF) - methods <- replicateM nbMethods get - return $ RequestAuth version methods - - - -data Request = Request - { version :: Int - , command :: Command - , addr :: HostName - , port :: PortNumber - , addrType :: AddressType - } deriving (Show) - -data Command = Connect - | Bind - | UdpAssociate - deriving (Show, Eq, Enum, Bounded) - - -instance Binary Command where - put = putWord8 . (+1) . fromIntegral . fromEnum - - get = do - cmd <- (\val -> fromIntegral val - 1) <$> getWord8 - guard $ cmd >= fromEnum (minBound :: Command) && cmd <= fromEnum (maxBound :: Command) - - return .toEnum $ cmd - - -instance Binary Request where - put Request{..} = do - putWord8 (fromIntegral version) - put command - putWord8 0x00 -- RESERVED - _ <- if addrType == DOMAIN_NAME - then do - putWord8 0x03 - let host = BC8.pack addr - putWord8 (fromIntegral . length $ host) - traverse_ put host - else do - putWord8 0x01 - let ipv4 = fst . Data.Either.fromRight (0, mempty) . T.decimal . T.pack <$> splitElem '.' addr - traverse_ putWord8 ipv4 - - putWord16be (fromIntegral port) - - - - get = do - version <- fromIntegral <$> getWord8 - guard (version == 5) - cmd <- get :: Get Command - _ <- getWord8 -- RESERVED - - opCode <- fromIntegral <$> getWord8 -- Addr type, we support only ipv4 and domainame - guard (opCode == 0x03 || opCode == 0x01) -- DOMAINNAME OR IPV4 - - host <- if opCode == 0x03 - then do - nbWords <- fromIntegral <$> getWord8 - fromRight T.empty . E.decodeUtf8' <$> replicateM nbWords getWord8 - else do - ipv4 <- replicateM 4 getWord8 :: Get [Word8] - let ipv4Str = T.intercalate "." $ fmap (tshow . fromEnum) ipv4 - return ipv4Str - - guard (not $ null host) - port <- fromIntegral <$> getWord16be - - return Request - { version = version - , command = cmd - , addr = unpack host - , port = port - , addrType = if opCode == 0x03 then DOMAIN_NAME else IPv4 - } - - - -toHex :: LByteString -> String -toHex = foldr showHex "" . unpack - -data Response = Response - { version :: Int - , returnCode :: RetCode - , serverAddr :: HostName - , serverPort :: PortNumber - , serverAddrType :: AddressType - } deriving (Show) - -data RetCode = SUCCEEDED - | GENERAL_FAILURE - | NOT_ALLOWED - | NO_NETWORK - | HOST_UNREACHABLE - | CONNECTION_REFUSED - | TTL_EXPIRED - | UNSUPPORTED_COMMAND - | UNSUPPORTED_ADDRESS_TYPE - | UNASSIGNED - deriving (Show, Eq, Enum, Bounded) - -instance Binary RetCode where - put = putWord8 . fromIntegral . fromEnum - get = toEnum . min maxBound . fromIntegral <$> getWord8 - - -instance Binary Response where - put Response{..} = do - putWord8 socksVersion - put returnCode - putWord8 0x00 -- Reserved - _ <- if serverAddrType == DOMAIN_NAME - then do - putWord8 0x03 - let host = BC8.pack serverAddr - putWord8 (fromIntegral . length $ host) - traverse_ put host - else do - putWord8 0x01 - let ipv4 = fst . Data.Either.fromRight (0, mempty) . T.decimal . T.pack <$> splitElem '.' serverAddr - traverse_ putWord8 ipv4 - - putWord16be (fromIntegral serverPort) - - - get = do - version <- fromIntegral <$> getWord8 - guard(version == fromIntegral socksVersion) - ret <- toEnum . min maxBound . fromIntegral <$> getWord8 - _ <- getWord8 -- RESERVED - opCode <- fromIntegral <$> getWord8 -- Type - guard(opCode == 0x03 || opCode == 0x01) - host <- if opCode == 0x03 - then do - nbWords <- fromIntegral <$> getWord8 - fromRight T.empty . E.decodeUtf8' <$> replicateM nbWords getWord8 - else do - ipv4 <- replicateM 4 getWord8 :: Get [Word8] - let ipv4Str = T.intercalate "." $ fmap (tshow . fromEnum) ipv4 - return ipv4Str - guard (not $ null host) - port <- getWord16be - - return Response - { version = version - , returnCode = ret - , serverAddr = unpack host - , serverPort = fromIntegral port - , serverAddrType = if opCode == 0x03 then DOMAIN_NAME else IPv4 - } - - - -data ServerSettings = ServerSettings - { listenOn :: PortNumber - , bindOn :: HostName - -- , onAuthentification :: (MonadIO m, MonadError IOException m) => RequestAuth -> m ResponseAuth - -- , onRequest :: (MonadIO m, MonadError IOException m) => Request -> m Response - } deriving (Show) \ No newline at end of file diff --git a/src/Tunnel.hs b/src/Tunnel.hs deleted file mode 100644 index ee292a84..00000000 --- a/src/Tunnel.hs +++ /dev/null @@ -1,303 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} - - -module Tunnel - ( runClient - , runServer - , rrunTCPClient - ) where - -import ClassyPrelude -import Data.Maybe (fromJust) - -import qualified Data.ByteString.Char8 as BC - -import qualified Data.Conduit.Network.TLS as N -import qualified Data.Streaming.Network as N - -import Network.Socket (HostName, PortNumber) -import qualified Network.Socket as N -import qualified Network.Socket.ByteString as N -import qualified Network.Socket.ByteString.Lazy as NL - -import qualified Network.WebSockets as WS -import qualified Network.WebSockets.Connection as WS -import qualified Network.WebSockets.Stream as WS - -import Control.Monad.Except -import qualified Network.Connection as NC - -import qualified Data.ByteString.Base64 as B64 - -import Types -import Protocols -import qualified Socks5 -import Logger - - - -rrunTCPClient :: MonadError Error m => N.ClientSettings -> (Connection -> IO (m a)) -> IO (m a) -rrunTCPClient cfg app = onError $ bracket - (do - let _10sec = 1000000 * 10 - ret <- timeout _10sec $ N.getSocketFamilyTCP (N.getHost cfg) (N.getPort cfg) (N.getAddrFamily cfg) - (s, addr) <- pure $ case ret of - Just (s, addr) -> (s, addr) - Nothing -> error $ "Cannot open tcp socket within 10 sec to " <> show (N.getHost cfg) <> ":" <> show (N.getPort cfg) - - so_mark_val <- readIORef sO_MARK_Value - when (so_mark_val /= 0 && N.isSupportedSocketOption sO_MARK) (N.setSocketOption s sO_MARK so_mark_val) - return (s,addr) - ) - (\r -> catch (N.close $ fst r) (\(_ :: SomeException) -> return ())) - (\(s, _) -> app Connection - { read = Just <$> N.safeRecv s defaultRecvBufferSize - , write = N.sendAll s - , close = N.close s - , rawConnection = Just s - }) - where - onError = flip catch (\(e :: SomeException) -> return . throwError . TunnelError $ show e) - --- --- Pipes --- -tunnelingClientP :: MonadError Error m => TunnelSettings -> (Connection -> IO (m ())) -> (Connection -> IO (m ())) -tunnelingClientP cfg@TunnelSettings{..} app conn = onError $ do - debug "Opening Websocket stream" - - stream <- connectionToStream conn - let authorization = ([("Authorization", "Basic " <> B64.encode upgradeCredentials) | not (null upgradeCredentials)]) - let headers = authorization <> customHeaders - let hostname = if not (null hostHeader) then BC.unpack hostHeader else serverHost - - ret <- WS.runClientWithStream stream hostname (toPath cfg) WS.defaultConnectionOptions headers run - - debug "Closing Websocket stream" - return ret - - where - connectionToStream Connection{..} = WS.makeStream read (write . toStrict . fromJust) - onError = flip catch (\(e :: SomeException) -> return . throwError . WebsocketError $ show e) - run cnx = WS.withPingThread cnx websocketPingFrequencySec mempty (app (toConnection cnx)) - - -tlsClientP :: MonadError Error m => TunnelSettings -> (Connection -> IO (m ())) -> (Connection -> IO (m ())) -tlsClientP TunnelSettings{..} app conn = onError $ do - debug "Doing tls Handshake" - - context <- NC.initConnectionContext - let socket = fromJust $ rawConnection conn - h <- N.socketToHandle socket ReadWriteMode - - connection <- NC.connectFromHandle context h connectionParams - ret <- app (toConnection connection) `finally` hClose h - - debug "Closing TLS" - return ret - - where - onError = flip catch (\(e :: SomeException) -> return . throwError . TlsError $ show e) - tlsSettings = NC.TLSSettingsSimple { NC.settingDisableCertificateValidation = not tlsVerifyCertificate - , NC.settingDisableSession = False - , NC.settingUseServerName = False - } - connectionParams = NC.ConnectionParams { NC.connectionHostname = if tlsSNI == mempty then serverHost else BC.unpack tlsSNI - , NC.connectionPort = serverPort - , NC.connectionUseSecure = Just tlsSettings - , NC.connectionUseSocks = Nothing - } - - --- --- Connectors --- -tcpConnection :: MonadError Error m => TunnelSettings -> (Connection -> IO (m ())) -> IO (m ()) -tcpConnection TunnelSettings{..} app = onError $ do - debug $ "Opening tcp connection to " <> fromString serverHost <> ":" <> show (fromIntegral serverPort :: Int) - - ret <- rrunTCPClient (N.clientSettingsTCP (fromIntegral serverPort) (fromString serverHost)) app - - debug $ "Closing tcp connection to " <> fromString serverHost <> ":" <> show (fromIntegral serverPort :: Int) - return ret - - where - onError = flip catch (\(e :: SomeException) -> return $ (throwError $ TunnelError $ show e)) - - - -httpProxyConnection :: MonadError Error m => TunnelSettings -> (Connection -> IO (m ())) -> IO (m ()) -httpProxyConnection TunnelSettings{..} app = onError $ do - let settings = fromJust proxySetting - debug $ "Opening tcp connection to proxy " <> show settings - - ret <- rrunTCPClient (N.clientSettingsTCP (fromIntegral (port settings)) (BC.pack $ host settings)) $ \conn -> do - _ <- sendConnectRequest settings conn - responseM <- timeout (1000000 * 10) $ readConnectResponse mempty conn - let response = fromMaybe "No response of the proxy after 10s" responseM - - if isAuthorized response - then app conn - else return . throwError . ProxyForwardError $ BC.unpack response - - debug $ "Closing tcp connection to proxy " <> show settings - return ret - - where - credentialsToHeader (user, password) = "Proxy-Authorization: Basic " <> B64.encode (user <> ":" <> password) <> "\r\n" - sendConnectRequest settings h = write h $ "CONNECT " <> fromString serverHost <> ":" <> fromString (show serverPort) <> " HTTP/1.0\r\n" - <> "Host: " <> fromString serverHost <> ":" <> fromString (show serverPort) <> "\r\n" - <> maybe mempty credentialsToHeader (credentials settings) - <> "\r\n" - - readConnectResponse buff conn = do - response <- fromJust <$> read conn - if "\r\n\r\n" `BC.isInfixOf` response - then return $ buff <> response - else readConnectResponse (buff <> response) conn - - isAuthorized response = " 200 " `BC.isInfixOf` response - - onError = flip catch (\(e :: SomeException) -> return $ when (take 10 (show e) == "user error") (throwError $ ProxyConnectionError $ show e)) - --- --- Client --- -runClient :: TunnelSettings -> IO () -runClient cfg@TunnelSettings{..} = do - let withEndPoint = if isJust proxySetting then httpProxyConnection cfg else tcpConnection cfg - let doTlsIf tlsNeeded app = if tlsNeeded then tlsClientP cfg app else app - let withTunnel cfg' app = withEndPoint (doTlsIf useTls . tunnelingClientP cfg' $ app) - - let app cfg' localH = do - ret <- withTunnel cfg' $ \remoteH -> do - ret <- remoteH <==> toConnection localH - info $ "CLOSE tunnel :: " <> show cfg' - return ret - - handleError ret - - case protocol of - UDP -> runUDPServer (localBind, localPort) udpTimeout (app cfg) - TCP -> runTCPServer (localBind, localPort) (app cfg) - STDIO -> runSTDIOServer (app cfg) - SOCKS5 -> runSocks5Server (Socks5.ServerSettings localPort localBind) cfg app - - - - --- --- Server --- -runTlsTunnelingServer :: (ByteString, ByteString) -> (HostName, PortNumber) -> ((ByteString, Int) -> Bool) -> IO () -runTlsTunnelingServer (tlsCert, tlsKey) endPoint@(bindTo, portNumber) isAllowed = do - info $ "WAIT for TLS connection on " <> toStr endPoint - - N.runTCPServerTLS (N.tlsConfigBS (fromString bindTo) (fromIntegral portNumber) tlsCert tlsKey) $ \sClient -> - runApp sClient WS.defaultConnectionOptions (serverEventLoop (N.appSockAddr sClient) isAllowed) - - info "SHUTDOWN server" - - where - runApp :: N.AppData -> WS.ConnectionOptions -> WS.ServerApp -> IO () - runApp appData opts app = do - stream <- WS.makeStream (N.appRead appData <&> \payload -> if payload == mempty then Nothing else Just payload) (N.appWrite appData . toStrict . fromJust) - --let socket = fromJust $ N.appRawSocket appData - --stream <- WS.makeStream (N.recv socket defaultRecvBufferSize <&> \payload -> if payload == mempty then Nothing else Just payload) (NL.sendAll socket . fromJust) - bracket (WS.makePendingConnectionFromStream stream opts) - (\conn -> catch (WS.close $ WS.pendingStream conn) (\(_ :: SomeException) -> return ())) - app - -runTunnelingServer :: (HostName, PortNumber) -> ((ByteString, Int) -> Bool) -> IO () -runTunnelingServer endPoint@(host, port) isAllowed = do - info $ "WAIT for connection on " <> toStr endPoint - - let srvSet = N.setReadBufferSize defaultRecvBufferSize $ N.serverSettingsTCP (fromIntegral port) (fromString host) - void $ N.runTCPServer srvSet $ \sClient -> do - let socket = fromJust $ N.appRawSocket sClient - stream <- WS.makeStream (N.recv socket defaultRecvBufferSize <&> \payload -> if payload == mempty then Nothing else Just payload) (NL.sendAll socket . fromJust) - runApp stream WS.defaultConnectionOptions (serverEventLoop (N.appSockAddr sClient) isAllowed) - - info "CLOSE server" - - where - runApp :: WS.Stream -> WS.ConnectionOptions -> WS.ServerApp -> IO () - runApp socket opts = bracket (WS.makePendingConnectionFromStream socket opts) - (\conn -> catch (WS.close $ WS.pendingStream conn) (\(_ :: SomeException) -> return ())) - -serverEventLoop :: N.SockAddr -> ((ByteString, Int) -> Bool) -> WS.PendingConnection -> IO () -serverEventLoop sClient isAllowed pendingConn = do - let path = fromPath . WS.requestPath $ WS.pendingRequest pendingConn - let forwardedFor = filter (\(header, _) -> header == "x-forwarded-for") $ WS.requestHeaders $ WS.pendingRequest pendingConn - info $ "NEW incoming connection from " <> show sClient <> " " <> show forwardedFor - case path of - Nothing -> info "Rejecting connection" >> WS.rejectRequest pendingConn "Invalid tunneling information" - Just (!proto, !rhost, !rport) -> - if not $ isAllowed (rhost, rport) - then do - info "Rejecting tunneling" - WS.rejectRequest pendingConn "Restriction is on, You cannot request this tunneling" - else do - conn <- WS.acceptRequest pendingConn - case proto of - UDP -> runUDPClient (BC.unpack rhost, fromIntegral rport) (\cnx -> void $ toConnection conn <==> toConnection cnx) - TCP -> runTCPClient (BC.unpack rhost, fromIntegral rport) (\cnx -> void $ toConnection conn <==> toConnection cnx) - STDIO -> mempty - SOCKS5 -> mempty - - -runServer :: Maybe (ByteString, ByteString) -> (HostName, PortNumber) -> ((ByteString, Int) -> Bool) -> IO () -runServer Nothing = runTunnelingServer -runServer (Just (tlsCert, tlsKey)) = runTlsTunnelingServer (tlsCert, tlsKey) - - - - --- --- Commons --- -toPath :: TunnelSettings -> String -toPath TunnelSettings{..} = "/" <> upgradePrefix <> "/" - <> toLower (show $ if protocol == UDP then UDP else TCP) - <> "/" <> destHost <> "/" <> show destPort - -fromPath :: ByteString -> Maybe (Protocol, ByteString, Int) -fromPath path = let rets = BC.split '/' . BC.drop 1 $ path - in do - guard (length rets == 4) - let [_, protocol, h, prt] = rets - prt' <- readMay . BC.unpack $ prt :: Maybe Int - proto <- readMay . toUpper . BC.unpack $ protocol :: Maybe Protocol - return (proto, h, prt') - -handleError :: Either Error () -> IO () -handleError (Right ()) = return () -handleError (Left errMsg) = - case errMsg of - ProxyConnectionError msg -> err "Cannot connect to the proxy" >> debugPP msg - ProxyForwardError msg -> err "Connection not allowed by the proxy" >> debugPP msg - TunnelError msg -> err "Cannot establish the connection to the server" >> debugPP msg - LocalServerError msg -> err "Cannot create the localServer, port already binded ?" >> debugPP msg - WebsocketError msg -> err "Cannot establish websocket connection with the server" >> debugPP msg - TlsError msg -> err "Cannot do tls handshake with the server" >> debugPP msg - Other msg -> debugPP msg - - where - debugPP msg = debug $ "====\n" <> msg <> "\n====" - -myTry :: MonadError Error m => IO a -> IO (m ()) -myTry f = either (\(e :: SomeException) -> throwError . Other $ show e) (const $ return ()) <$> try f - -(<==>) :: Connection -> Connection -> IO (Either Error ()) -(<==>) hTunnel hOther = - myTry $ race_ (propagateReads hTunnel hOther) (propagateWrites hTunnel hOther) - -propagateReads :: Connection -> Connection -> IO () -propagateReads hTunnel hOther = forever $ read hTunnel >>= write hOther . fromJust - - -propagateWrites :: Connection -> Connection -> IO () -propagateWrites hTunnel hOther = do - payload <- fromJust <$> read hOther - unless (null payload) (write hTunnel payload >> propagateWrites hTunnel hOther) diff --git a/src/Types.hs b/src/Types.hs deleted file mode 100644 index 832a3fd3..00000000 --- a/src/Types.hs +++ /dev/null @@ -1,148 +0,0 @@ -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE StrictData #-} - -module Types where - - -import ClassyPrelude -import Data.Maybe -import System.IO (stdin, stdout) -import Data.ByteString (hGetSome, hPutStr) - -import Data.CaseInsensitive ( CI ) -import qualified Data.Streaming.Network as N -import qualified Network.Connection as NC -import Network.Socket (HostName, PortNumber) -import qualified Network.Socket as N hiding (recv, recvFrom, send, sendTo) -import qualified Network.WebSockets.Connection as WS -import System.IO.Unsafe (unsafeDupablePerformIO) - - -instance Hashable PortNumber where - hashWithSalt s p = hashWithSalt s (fromEnum p) - -deriving instance Generic N.SockAddr -deriving instance Hashable N.SockAddr - - -{-# NOINLINE defaultRecvBufferSize #-} -defaultRecvBufferSize :: Int -defaultRecvBufferSize = unsafeDupablePerformIO $ - bracket (N.socket N.AF_INET N.Stream 0) N.close (\sock -> N.getSocketOption sock N.RecvBuffer) - -sO_MARK :: N.SocketOption -sO_MARK = N.SockOpt 1 36 -- https://elixir.bootlin.com/linux/latest/source/arch/alpha/include/uapi/asm/socket.h#L64 - -{-# NOINLINE sO_MARK_Value #-} -sO_MARK_Value :: IORef Int -sO_MARK_Value = unsafeDupablePerformIO $ (newIORef 0) - -data Protocol = UDP | TCP | STDIO | SOCKS5 deriving (Show, Read, Eq) - -data StdioAppData = StdioAppData - -data UdpAppData = UdpAppData - { appAddr :: N.SockAddr - , appSem :: MVar ByteString - , appRead :: IO ByteString - , appWrite :: ByteString -> IO () - } - -instance N.HasReadWrite UdpAppData where - readLens f appData = fmap (\getData -> appData { appRead = getData}) (f $ appRead appData) - writeLens f appData = fmap (\writeData -> appData { appWrite = writeData}) (f $ appWrite appData) - -data ProxySettings = ProxySettings - { host :: HostName - , port :: PortNumber - , credentials :: Maybe (ByteString, ByteString) - } deriving (Show) - -data TunnelSettings = TunnelSettings - { proxySetting :: Maybe ProxySettings - , localBind :: HostName - , localPort :: PortNumber - , serverHost :: HostName - , serverPort :: PortNumber - , destHost :: HostName - , destPort :: PortNumber - , protocol :: Protocol - , useTls :: Bool - , useSocks :: Bool - , upgradePrefix :: String - , upgradeCredentials - :: ByteString - , tlsSNI :: ByteString - , tlsVerifyCertificate :: Bool - , hostHeader :: ByteString - , udpTimeout :: Int - , websocketPingFrequencySec :: Int - , customHeaders :: [(CI ByteString, ByteString)] - } - -instance Show TunnelSettings where - show TunnelSettings{..} = localBind <> ":" <> show localPort - <> (if isNothing proxySetting - then mempty - else " <==PROXY==> " <> host (fromJust proxySetting) <> ":" <> (show . port $ fromJust proxySetting) - ) - <> " <==" <> (if useTls then "WSS" else "WS") <> "==> " - <> serverHost <> ":" <> show serverPort - <> " <==" <> show (if protocol == SOCKS5 then TCP else protocol) <> "==> " <> destHost <> ":" <> show destPort - - -data Connection = Connection - { read :: IO (Maybe ByteString) - , write :: ByteString -> IO () - , close :: IO () - , rawConnection :: Maybe N.Socket - } - -class ToConnection a where - toConnection :: a -> Connection - -instance ToConnection StdioAppData where - toConnection conn = Connection { read = Just <$> hGetSome stdin 512 - , write = hPutStr stdout - , close = return () - , rawConnection = Nothing - } - -instance ToConnection WS.Connection where - toConnection conn = Connection { read = Just <$> WS.receiveData conn - , write = WS.sendBinaryData conn - , close = WS.sendClose conn (mempty :: LByteString) - , rawConnection = Nothing - } - -instance ToConnection N.AppData where - toConnection conn = Connection { read = Just <$> N.appRead conn - , write = N.appWrite conn - , close = N.appCloseConnection conn - , rawConnection = Nothing - } - -instance ToConnection UdpAppData where - toConnection conn = Connection { read = Just <$> appRead conn - , write = appWrite conn - , close = return () - , rawConnection = Nothing - } - -instance ToConnection NC.Connection where - toConnection conn = Connection { read = Just <$> NC.connectionGetChunk conn - , write = NC.connectionPut conn - , close = NC.connectionClose conn - , rawConnection = Nothing - } - -data Error = ProxyConnectionError String - | ProxyForwardError String - | LocalServerError String - | TunnelError String - | WebsocketError String - | TlsError String - | Other String - deriving (Show) diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..0065e1ba --- /dev/null +++ b/src/main.rs @@ -0,0 +1,160 @@ +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::io::ErrorKind; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::str::FromStr; +use std::time::Duration; +use clap::Parser; +use hyper::body::Body; +use hyper::Request; +use hyper_openssl::HttpsConnector; +use url::{Host, Url, UrlQuery}; + +/// Simple program to greet a person +#[derive(clap::Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Wstunnel { + + #[command(subcommand)] + commands: Commands, +} + +#[derive(clap::Subcommand, Debug)] +enum Commands { + Client(Client), + Server(Server) +} +#[derive(clap::Args, Debug)] +struct Client { + /// Name of the person to greet + #[arg(short='L', long, value_name = "[BIND:]PORT:HOST:PORT", value_parser = parse_env_var)] + local_to_remote: Vec