diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d165ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/target +README-PRIVATE.md +README-RU.md +cloud-init.yml +*.crt +*.key \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..512ebab --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1287 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "daemonize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" +dependencies = [ + "libc", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[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 = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "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", +] + +[[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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxerver" +version = "0.1.0" +dependencies = [ + "base64", + "chrono", + "clap", + "daemonize", + "futures-util", + "hyper", + "hyper-tls", + "lazy_static", + "rand", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "sha2", + "tokio", + "tokio-rustls", + "wildmatch", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +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 = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198514704ca887dd5a1e408c6c6cdcba43672f9b4062e1b24aa34e74e6d7faae" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "wildmatch" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" + +[[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-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..93e8bc5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "proxerver" +version = "0.1.0" +edition = "2021" +authors = ["doroved"] +description = "User Friendly HTTP and HTTPS (HTTP over TLS) proxy server." +readme = "README.md" +repository = "https://github.com/doroved/proxerver" +license = "MIT OR Apache-2.0" +keywords = ["proxy", "proxy-server", "http", "https", "http-over-tls"] +categories = ["command-line-utilities", "network-programming"] + +[dependencies] +hyper = { version = "0.14", features = [ + "client", + "server", + "http1", + "runtime", +] } +tokio = { version = "1", features = [ + "net", + "rt-multi-thread", + "macros", + "io-util", +] } +base64 = "0.22.1" +wildmatch = "2.3.0" +daemonize = "0.5.0" +rand = "0.8.5" +futures-util = "0.3.30" +clap = { version = "4.5.20", features = ["derive"] } +sha2 = "0.10.8" + +# http over tls. Если обновить 3 крейта ниже, то все сломается в https.rs +rustls = "0.20" +tokio-rustls = "0.23" +hyper-tls = "0.5" + +rustls-pemfile = "2.2.0" +rustls-pki-types = "1.9.0" +lazy_static = "1.5.0" +chrono = "0.4.38" + +[profile.release] +panic = "abort" # Strip expensive panic clean-up logic +codegen-units = 1 # Compile crates one after another so the compiler can optimize better +lto = true # Enables link to optimizations +opt-level = "z" # Optimize for binary size s = 1.9 mb (aarch64) | z = 1.8 mb (aarch64) +strip = true # Remove debug symbols diff --git a/README.md b/README.md new file mode 100644 index 0000000..f372c9e --- /dev/null +++ b/README.md @@ -0,0 +1,166 @@ +# User-Friendly HTTP and HTTPS (HTTP over TLS) Proxy Server + +Create your own HTTP and/or HTTPS (HTTP over TLS) proxy server with one click. Use this proxy to enhance the security of your internet connections or to bypass restrictions. + +Available on Linux `x86_64` and `aarch64`. Minimum Ubuntu 22.04. + +![proxerver screenshot](screenshot.png) + +## How to Install + +Just log into your server/VPS terminal and run the command: + +```bash +curl -fsSL https://proxerver.pages.dev | bash +``` + +After installation, be sure to run this command to make proxerver available in the current terminal session: + +```bash +export PATH=$PATH:~/.proxerver/bin +``` + +To update proxerver to the latest version, use the same command that was used for installation. + +## Key Features: + +- Easy to set up and use. +- Support for HTTP and HTTPS (HTTP over TLS). +- Installation of multiple credentials for authentication. +- Traffic filtering based on hosts. +- Setting a secret token for additional [Proxer Client](https://github.com/doroved/proxer) authentication as a protection against proxy detection. + +``` +proxerver --help + +User Friendly HTTP and HTTPS (HTTP over TLS) proxy server. + +Usage: proxerver [OPTIONS] + +Options: + --http-port Specify the HTTP port. Default: 8080 + --https-port Specify the HTTPS port. Default: 443 + --no-http-server Disable the HTTP proxy server + --no-https-server Disable the HTTPS proxy server + --auth Comma-separated list of basic credentials. Example: 'login:password, login2:password2' + --hosts Comma-separated list of allowed hosts. Example: 'site.com, *.site.com' + --token Secret token to access the HTTP/S proxy server from Proxer Client. The proxy server will only process requests if the client sends an `x-http(s)-secret-token` header with a valid token. Example: mysecrettoken123 + --no-http-token Disable using the secret token to access the HTTP proxy server from Proxer Client + --no-https-token Disable using the secret token to access the HTTPS proxy server from Proxer Client + --cert Path to the TLS certificate file. Example: '/path/to/fullchain.(pem|cer|crt|...)' + --pkey Path to the TLS private key file. Example: '/path/to/privkey.(pem|key|...)' + -h, --help Print help + -V, --version Print version +``` + +## Command Examples to Start the Proxy Server + +Starting the HTTP and HTTPS proxy server on ports 8080 and 443 without authentication: + +```bash +proxerver --cert cert.crt --pkey private.key +``` + +Starting the HTTP and HTTPS proxy server on ports 9999 and 8443 with username and password authentication: + +```bash +proxerver --http-port 9999 --https-port 8443 --cert cert.crt --pkey private.key --auth login:password +``` + +Starting the HTTP proxy server (without HTTPS): + +```bash +proxerver --no-https-server +``` + +Starting the HTTP and HTTPS proxy server with multiple credentials authentication and allowing requests only for specific hosts: + +```bash +proxerver --cert cert.crt --pkey private.key --hosts '*.example.com,example.com' --auth 'user:pass,user2:pass2' +``` + +Starting the HTTP and HTTPS proxy server with authentication and setting a secret token for protection against proxy detection. If the [Proxer Client](https://github.com/doroved/proxer) sends a header with an invalid token, the proxy server will respond with a 400 error: + +```bash +proxerver --cert cert.crt --pkey private.key --token mysecrettoken123 +``` + +Starting the HTTP and HTTPS proxy server with setting a secret token for protection against proxy detection. Disabling token verification for the HTTPS server: + +```bash +proxerver --cert cert.crt --pkey private.key --token mysecrettoken123 --no-https-token +``` + +To run the proxy server in the background, use nohup, for example: + +```bash +nohup proxerver [OPTIONS] >/dev/null 2>&1 & +``` + +Running the proxy server in the background using nohup and saving the output to a file: + +```bash +nohup proxerver [OPTIONS] > ~/.proxerver/log.txt 2>&1 & +``` + +Remove the background process proxerver: + +```bash +kill $(pgrep proxerver) +``` + +## Local Build via OrbStack + +1. Install OrbStack https://orbstack.dev/download and create 2 virtual machines Ubuntu 22.04 x86_64 (amd64) and aarch64 (arm64). +2. On each machine, install rust and all necessary packages for successful program compilation. To do this, run `install_rust.sh` using these commands: + +```bash +cd path/to/proxerver +orb -m ubuntu-22.04-amd64 bash install_rust.sh +orb -m ubuntu-22.04-arm64 bash install_rust.sh +``` + +\* - **ubuntu-22.04-amd/arm64** - this is the name you assign yourself when creating the machine in OrbStack, it may differ from your name. + +3. Creating releases. Run `release.sh` and files for different architectures will be created in the `./target/release` folder. + +```bash +bash release.sh +``` + +Or just run in development mode: + +```bash +orb -m ubuntu-22.04-amd64 cargo run -- --no-https-server +# OR +ssh ubuntu-22.04-amd64@orb +cd /path/to/proxerver +cargo run -- --no-https-server +``` + +## Local HTTPS Server Launch + +To locally run the HTTPS server, you need to generate a self-signed certificate, add it to the keychain, and start the proxy server: + +```bash +cd path/to/proxerver +``` + +```bash +openssl genpkey -algorithm RSA -out private.key -pkeyopt rsa_keygen_bits:2048 +``` + +```bash +openssl req -new -x509 -key private.key -out cert.crt -days 365 -subj "/C=RU/ST=Moscow/L=Moscow/O=MyOrg/OU=MyUnit/CN=localhost" +``` + +```bash +cargo run -- --cert cert.crt --pkey private.key --https-port 8443 +``` + +## TODO: + +- [ ] Automatic creation and renewal of Let's Encrypt certificates for custom domains +- [ ] Automatic issuance of a public free domain with a Let's Encrypt certificate when creating an HTTPS proxy server +- [ ] GeoIP whitelist with caching for access to the proxy server. Cache IPs and compare network inclusion rather than exact match. +- [ ] Daemonization of the process to run the program in the background. diff --git a/cf/_redirects b/cf/_redirects new file mode 100644 index 0000000..139214d --- /dev/null +++ b/cf/_redirects @@ -0,0 +1 @@ +/ /install.sh 302 \ No newline at end of file diff --git a/cf/install.sh b/cf/install.sh new file mode 100644 index 0000000..5203f45 --- /dev/null +++ b/cf/install.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Check architecture +arch=$(uname -m) +if [[ "$arch" != "x86_64" && "$arch" != "aarch64" ]]; then + echo "Error: Unsupported architecture $arch. Exiting script." + exit 1 +fi + +# Function to add PATH to the configuration file +add_to_path() { + local rc_file=$1 + if ! grep -q "export PATH=.*proxerver/bin" "$rc_file"; then + echo "# Proxerver" >> "$rc_file" + echo "export PATH=\$PATH:~/.proxerver/bin" >> "$rc_file" + source "$rc_file" + echo "Updated $rc_file" + else + echo "Path already added in $rc_file" + fi +} + +# Fetch the latest release from GitHub +curl "https://api.github.com/repos/doroved/proxerver/releases/latest" | + grep '"tag_name":' | + sed -E 's/.*"([^"]+)".*/\1/' | + xargs -I {} curl -OL "https://github.com/doroved/proxerver/releases/download/"\{\}"/proxerver.${arch}.tar.gz" + +# Create directory for installation +mkdir -p ~/.proxerver/bin + +# Extract and move the files +tar -xzvf ./proxerver.${arch}.tar.gz && \ + rm -rf ./proxerver.${arch}.tar.gz && \ + rm ./._proxerver && \ + mv ./proxerver ~/.proxerver/bin + +# Check for errors in the previous commands +if [ $? -ne 0 ]; then + echo "Error. Exiting now." + exit +fi + +# Add to PATH +export PATH=$PATH:~/.proxerver/bin + +# Check for .bashrc and .zshrc and append PATH export if they exist +if [ -f ~/.bashrc ]; then + add_to_path ~/.bashrc +fi + +if [ -f ~/.zshrc ]; then + add_to_path ~/.zshrc +fi + +# Success message with version +proxerver_version=$(proxerver -V) +echo "" +echo "Successfully installed $proxerver_version" + +# Run the proxerver help command +proxerver --help +echo "" +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; +echo "Please copy and paste this command into the terminal and press Enter:" +echo "export PATH=\$PATH:~/.proxerver/bin" \ No newline at end of file diff --git a/install_rust.sh b/install_rust.sh new file mode 100644 index 0000000..9cfc344 --- /dev/null +++ b/install_rust.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Updating package lists +echo "--> Updating package lists..." +sudo apt update + +# Installing necessary packages +echo "--> Installing build-essential, pkg-config, and libssl-dev..." +sudo apt install build-essential pkg-config libssl-dev -y + +# Installing Rust +echo "--> Installing Rust..." +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Checking Ubuntu version +echo "--> Checking Ubuntu version..." +# cat /etc/os-release +lsb_release -a + +# Checking GLIBC version +echo "--> Checking GLIBC version..." +ldd --version + +# Checking libssl version +echo "--> Checking libssl version..." +ldconfig -p | grep libssl + +# Setting environment variables for OpenSSL +echo "--> Setting environment variables for OpenSSL..." +export OPENSSL_LIB_DIR=/usr/lib/$(arch)-linux-gnu +export OPENSSL_INCLUDE_DIR=/usr/include/openssl + +echo "--> Installation completed!" +echo "--> Setting up environment..." +echo "To activate changes, run the command: source \$HOME/.cargo/env" +echo "--> Checking rustc version: rustc --version" \ No newline at end of file diff --git a/release.sh b/release.sh new file mode 100644 index 0000000..6994faa --- /dev/null +++ b/release.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Extract project name and version from Cargo.toml +project_name=$(grep '^name' Cargo.toml | sed 's/name = "\(.*\)"/\1/' | tr -d '[:space:]') +version=$(grep '^version' Cargo.toml | sed 's/version = "\(.*\)"/\1/' | tr -d '[:space:]') + +# Define architectures for Linux +architectures=("x86_64-unknown-linux-gnu" "aarch64-unknown-linux-gnu") + +# Build for each architecture +for arch in "${architectures[@]}"; do + # Extract architecture for naming + short_arch=$(echo $arch | sed 's/-unknown-linux-gnu//') + + # Determine the appropriate architecture for the orb command + if [ "$short_arch" = "x86_64" ]; then + orb_arch="amd64" + elif [ "$short_arch" = "aarch64" ]; then + orb_arch="arm64" + else + echo "Unsupported architecture: $short_arch" + exit 1 + fi + + # The build must be on Ubuntu 22.04, if you build on 24.04, you will get an error when running the binary on 22.04: + # ./proxerver-v0.1.0-x86_64: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.39' not found (required by ./proxerver-v0.1.0-x86_64) + # If you build on 20.04, you will get an error when running the binary on 22/24.04: + # ./proxerver: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory + # https://docs.orbstack.dev/machines/commands#orb + orb -m ubuntu-22.04-$orb_arch cargo build --release --target=$arch + orbctl stop ubuntu-22.04-$orb_arch + + # Move the binary to the release directory with a new name + mkdir -p ./target/release/v${version} + mv ./target/$arch/release/$project_name ./target/release/v${version}/${project_name}.${short_arch} +done + +# Change to the release directory +cd ./target/release/v${version} || exit + +# Create tar.gz and delete the original binaries +for arch in "${architectures[@]}"; do + short_arch=$(echo $arch | sed 's/-unknown-linux-gnu//') + binary_name="${project_name}.${short_arch}" + mv ${binary_name} ${project_name} + tar -czf "${binary_name}.tar.gz" "${project_name}" + rm "${project_name}" +done \ No newline at end of file diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..7271a7a Binary files /dev/null and b/screenshot.png differ diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..a8856b3 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,182 @@ +use crate::{ + options::Opt, + utils::{ + formatted_time, get_current_server_ip, get_rand_ipv4_socket_addr, is_allowed_credentials, + is_host_allowed, require_basic_auth, to_sha256, + }, +}; +use clap::Parser; +use hyper::{ + client::HttpConnector, + header::PROXY_AUTHORIZATION, + server::conn::AddrStream, + service::{make_service_fn, service_fn}, + Body, Client, Method, Request, Response, Server, StatusCode, +}; +use std::{ + net::{IpAddr, SocketAddr, ToSocketAddrs}, + sync::{Arc, Mutex}, +}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::TcpSocket, +}; + +#[derive(Debug, Clone)] +pub(crate) struct Proxy { + pub allowed_credentials: Arc>>, + pub allowed_hosts: Arc>>, + pub secret_token: Arc>, +} + +impl Proxy { + pub(crate) async fn proxy(self, req: Request) -> Result, hyper::Error> { + println!("Method: {:?}", req.method()); + println!("URI: {:?}", req.uri()); + println!("Version: {:?}", req.version()); + println!("Headers: {:?}", req.headers()); + println!("Body: {:?}", req.body()); + + let options = Opt::parse(); + + // Check request for inclusion in the white list of hosts that can be proxied + let host = req.uri().host().unwrap_or(""); + let allowed_hosts = self.allowed_hosts.lock().unwrap().to_vec(); + if !allowed_hosts.is_empty() && !is_host_allowed(host, &allowed_hosts) { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::empty()) + .unwrap()); + } + + // If secret token is not empty and no_http_token is false, check if the secret token is valid + let secret_token = self.secret_token.lock().unwrap().to_string(); + if !secret_token.is_empty() && !options.no_http_token { + if let Some(secret_token_header) = req.headers().get("x-http-secret-token") { + if secret_token_header.to_str().unwrap_or_default().trim() + != to_sha256(secret_token.trim()) + { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::empty()) + .unwrap()); + } + } else if req.headers().get("x-https-secret-token").is_none() { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::empty()) + .unwrap()); + } + } + + // Process authentication if a list of login:password pairs is specified + let allowed_credentials = self.allowed_credentials.lock().unwrap().to_vec(); + if !allowed_credentials.is_empty() { + if let Some(auth_header) = req.headers().get(PROXY_AUTHORIZATION) { + let header_credentials = auth_header.to_str().unwrap_or_default(); + + if !is_allowed_credentials(&header_credentials, allowed_credentials) { + return Ok(require_basic_auth()); + } + } else { + return Ok(require_basic_auth()); + } + } + + // Process method and call the appropriate handler + match req.method() { + &Method::CONNECT => self.process_connect(req).await, + _ => self.process_request(req).await, + } + } + + async fn process_connect(self, req: Request) -> Result, hyper::Error> { + tokio::task::spawn(async move { + let remote_addr = req.uri().authority().map(|auth| auth.to_string()).unwrap(); + let mut upgraded = hyper::upgrade::on(req).await.unwrap(); + + self.tunnel(&mut upgraded, remote_addr).await + }); + + Ok(Response::new(Body::empty())) + } + + async fn process_request(self, req: Request) -> Result, hyper::Error> { + let bind_addr = get_current_server_ip().parse::().unwrap(); + + let mut http = HttpConnector::new(); + http.set_local_address(Some(bind_addr)); + + let client = Client::builder() + .http1_title_case_headers(true) + .http1_preserve_header_case(true) + .build(http); + let res = client.request(req).await?; + + Ok(res) + } + + async fn tunnel(self, upgraded: &mut A, addr_str: String) -> std::io::Result<()> + where + A: AsyncRead + AsyncWrite + Unpin + ?Sized, + { + if let Ok(addrs) = addr_str.to_socket_addrs() { + for addr in addrs { + let socket = TcpSocket::new_v4()?; + let bind_addr = get_rand_ipv4_socket_addr(); + + if socket.bind(bind_addr).is_ok() { + if let Ok(mut server) = socket.connect(addr).await { + tokio::io::copy_bidirectional(upgraded, &mut server).await?; + return Ok(()); + } + } + } + } else { + println!("error: {addr_str}"); + } + + Ok(()) + } +} + +pub async fn start_proxy( + listen_addr: SocketAddr, + allowed_credentials: Vec, + allowed_hosts: Vec, + secret_token: String, +) -> Result<(), Box> { + let allowed_credentials_arc = Arc::new(Mutex::new(allowed_credentials)); + let allowed_hosts_arc = Arc::new(Mutex::new(allowed_hosts)); + let secret_token_arc = Arc::new(Mutex::new(secret_token)); + + let make_service = make_service_fn(move |addr: &AddrStream| { + let time = formatted_time(); + println!( + "\n\x1b[1m[{time}] [HTTP server] New connection from: {}\x1b[0m", + addr.remote_addr() + ); + + let allowed_credentials_clone = allowed_credentials_arc.clone(); + let allowed_hosts_clone = allowed_hosts_arc.clone(); + let secret_token_clone = secret_token_arc.clone(); + + async move { + Ok::<_, hyper::Error>(service_fn(move |req| { + Proxy { + allowed_credentials: allowed_credentials_clone.clone(), + allowed_hosts: allowed_hosts_clone.clone(), + secret_token: secret_token_clone.clone(), + } + .proxy(req) + })) + } + }); + + Server::bind(&listen_addr) + .http1_preserve_header_case(true) + .http1_title_case_headers(true) + .serve(make_service) + .await + .map_err(|err| err.into()) +} diff --git a/src/https.rs b/src/https.rs new file mode 100644 index 0000000..2734def --- /dev/null +++ b/src/https.rs @@ -0,0 +1,373 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufReader, Error as IoError, ErrorKind}; +use std::net::SocketAddr; +use std::net::ToSocketAddrs; +use std::sync::Arc; + +use clap::Parser; +use hyper::header::{HeaderName, HeaderValue}; +use hyper::{Body, StatusCode}; +use hyper_tls::HttpsConnector; +use rustls::{Certificate, PrivateKey, ServerConfig}; +use rustls_pemfile::read_one; + +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpSocket, TcpStream}; +use tokio_rustls::TlsAcceptor; + +use crate::options::Opt; +use crate::utils::{ + create_basic_auth_response, formatted_time, is_allowed_credentials, is_host_allowed, to_sha256, +}; + +use hyper::http::HeaderMap; +use hyper::{Client, Request as HttpRequest}; +// use hyper_tls::HttpsConnector; + +async fn tunnel_to_remote(upgraded: &mut A, addr: String) -> std::io::Result<()> +where + A: AsyncRead + AsyncWrite + Unpin + ?Sized, +{ + if let Ok(addrs) = addr.to_socket_addrs() { + for addr in addrs { + let socket = TcpSocket::new_v4()?; + if let Ok(mut server) = socket.connect(addr).await { + tokio::io::copy_bidirectional(upgraded, &mut server).await?; + return Ok(()); + } + } + } + eprintln!("Failed to connect to {addr}"); + Ok(()) +} + +fn load_certs(filename: &str) -> std::io::Result> { + let cert_file = &mut BufReader::new(File::open(filename)?); + let certs: Vec = rustls_pemfile::certs(cert_file) + .filter_map(|item| item.ok()) + .map(|cert| Certificate(cert.to_vec())) + .collect(); + + if certs.is_empty() { + return Err(IoError::new( + ErrorKind::InvalidInput, + "No valid certs found", + )); + } + Ok(certs) +} + +fn load_private_key(filename: &str) -> std::io::Result { + let key_file = &mut BufReader::new(File::open(filename)?); + let mut keys: Vec = Vec::new(); + + while let Ok(Some(item)) = read_one(key_file) { + match item { + rustls_pemfile::Item::Pkcs8Key(key) => { + keys.push(PrivateKey(key.secret_pkcs8_der().to_vec())); + } + rustls_pemfile::Item::Pkcs1Key(key) => { + keys.push(PrivateKey(key.secret_pkcs1_der().to_vec())); + } + rustls_pemfile::Item::Sec1Key(key) => { + keys.push(PrivateKey(key.secret_sec1_der().to_vec())); + } + _ => continue, // Ignore other key types and items + } + } + + if keys.is_empty() { + return Err(IoError::new( + ErrorKind::InvalidInput, + "No valid private key found", + )); + } + + Ok(keys.remove(0)) +} + +fn create_server_config(certs: Vec, key: PrivateKey) -> Result { + let config = ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs, key) + .map_err(|err| IoError::new(ErrorKind::InvalidInput, err))?; + + Ok(config) +} + +pub async fn start_proxy( + listen_addr: SocketAddr, + allowed_credentials: Vec, + allowed_hosts: Vec, + secret_token: String, + cert_file_path: String, + key_file_path: String, +) -> Result<(), Box> { + let certs = load_certs(&cert_file_path)?; + let key = load_private_key(&key_file_path)?; + + let config = create_server_config(certs, key)?; + + let acceptor = TlsAcceptor::from(Arc::new(config)); + let listener = TcpListener::bind(listen_addr).await?; + + loop { + let (stream, addr) = listener.accept().await?; + let acceptor = acceptor.clone(); + + let allowed_credentials = allowed_credentials.clone(); + let allowed_hosts = allowed_hosts.clone(); + let secret_token = secret_token.clone(); + + tokio::spawn(async move { + let mut stream = match acceptor.accept(stream).await { + Ok(s) => s, + Err(_) => return, // Обработка ошибок TLS + }; + + let mut buffer = vec![0; 1024]; + match stream.read(&mut buffer).await { + Ok(n) => { + let request = String::from_utf8_lossy(&buffer[..n]); + + let options = Opt::parse(); + + match parse_request(&request) { + Ok((method, uri, version, headers)) => { + let time = formatted_time(); + + println!("\n\x1b[38;5;28m\x1b[1m[{time}] [HTTPS server] New connection from: {}\x1b[0m", addr); + + println!("Method: {}", method); + println!("URI: {}", uri); + println!("Version: {}", version); + println!("Headers: {:?}", headers); + + // Check request for inclusion in the white list of hosts that can be proxied + // let host = headers.get("host").unwrap().split(':').next().unwrap_or(""); + let host = headers + .get("host") + .and_then(|h| h.split(':').next()) + .unwrap_or(""); + if !allowed_hosts.is_empty() && !is_host_allowed(host, &allowed_hosts) { + let error_response = create_error_response(StatusCode::BAD_REQUEST); + if let Err(e) = stream.write_all(&error_response).await { + eprintln!("Failed to write error response to client: {:?}", e); + } + return; + } + + // If secret token is not empty and no_http_token is false, check if the secret token is valid + if !secret_token.is_empty() && !options.no_http_token { + if let Some(secret_token_header) = + headers.get("x-https-secret-token") + { + if secret_token_header.trim() != to_sha256(secret_token.trim()) + { + let error_response = + create_error_response(StatusCode::BAD_REQUEST); + + if let Err(e) = stream.write_all(&error_response).await { + eprintln!( + "Failed to write error response to client: {:?}", + e + ); + } + return; + } + } else if headers.get("x-http-secret-token").is_none() { + let error_response = + create_error_response(StatusCode::BAD_REQUEST); + + if let Err(e) = stream.write_all(&error_response).await { + eprintln!( + "Failed to write error response to client: {:?}", + e + ); + } + return; + } + } + + // Process authentication if a list of login:password pairs is specified + if !allowed_credentials.is_empty() { + if let Some(header_credentials) = headers.get("proxy-authorization") + { + if !is_allowed_credentials( + &header_credentials, + allowed_credentials, + ) { + let auth_response = create_basic_auth_response(); + if let Err(e) = stream.write_all(&auth_response).await { + eprintln!("Failed to write authentication response to client: {:?}", e); + } + return; + } + } else { + let auth_response = create_basic_auth_response(); + if let Err(e) = stream.write_all(&auth_response).await { + eprintln!("Failed to write authentication response to client: {:?}", e); + } + return; + } + } + } + Err(err) => { + println!("Error parsing request: {}", err); + } + } + + // Process request method and call the appropriate handler + if request.starts_with("CONNECT") { + // Process CONNECT request + let parts: Vec<&str> = request.split_whitespace().collect(); + if parts.len() >= 2 { + let remote_addr = parts[1].to_string(); + + // Send confirmation of connection setup + let response = "HTTP/1.1 200 Connection Established\r\n\r\n"; + if let Err(e) = stream.write_all(response.as_bytes()).await { + eprintln!("Failed to write response to client {}: {:?}", addr, e); + return; + } + + // Create a tunnel + if let Err(e) = tunnel_to_remote(&mut stream, remote_addr).await { + eprintln!("Tunneling error for {}: {:?}", addr, e); + } + } else { + eprintln!("Invalid CONNECT request from {}", addr); + } + } else { + // Process regular HTTP requests + handle_http_request(stream, request.to_string()).await; + } + } + Err(e) => { + eprintln!("Error reading from client {}: {:?}", addr, e); + } + } + + // println!("Connection closed: {}", addr); + }); + } + // Ok(()) +} + +fn create_error_response(status_code: StatusCode) -> Vec { + let response = format!( + "HTTP/1.1 {} {}\r\nContent-Length: 0\r\n\r\n", + status_code.as_u16(), + status_code.canonical_reason().unwrap_or("Unknown") + ); + response.into_bytes() +} + +// Process regular HTTP requests +async fn handle_http_request( + mut stream: tokio_rustls::server::TlsStream, + request: String, +) { + match parse_request(&request) { + Ok((method, uri, _, headers)) => { + // Create a HTTPS client + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, hyper::Body>(https); + + // Create a new HTTP request + let mut http_request = HttpRequest::builder() + .method(method.as_str()) + .uri(uri) + .body(Body::empty()) + .expect("Failed to build request"); + + // Add the headers from the original request + *http_request.headers_mut() = hash_map_to_header_map(headers.clone()); + + // Send the request to the final server + match client.request(http_request).await { + Ok(response) => { + // Отправляем ответ обратно клиенту + let status = response.status(); + let response_body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + + let response_header = format!("HTTP/1.1 {}\r\n", status); + let response_length = response_body.len(); + let response = format!( + "{}Content-Length: {}\r\n\r\n", + response_header, response_length + ); + let full_response = [response.into_bytes(), response_body.to_vec()].concat(); + + // Отправка полного ответа обратно клиенту + if let Err(e) = stream.write_all(&full_response).await { + eprintln!("Failed to write response to client: {:?}", e); + } + } + Err(e) => { + eprintln!("Error while forwarding request: {:?}", e); + // Можно отправить ошибку клиенту + } + } + } + Err(err) => { + println!("Error parsing HTTP request: {}", err); + } + } +} + +fn parse_request( + request: &str, +) -> Result<(String, String, String, HashMap), &'static str> { + let mut lines = request.lines(); + + // Получаем первую строку с методом, URI и версией + let request_line = lines.next().ok_or("Missing request line")?; + let mut request_parts = request_line.split_whitespace(); + + let method = request_parts.next().ok_or("Missing method")?.to_string(); + let uri = request_parts.next().ok_or("Missing URI")?.to_string(); + let version = request_parts.next().ok_or("Missing version")?.to_string(); + + // Инициализируем хэш-карту для заголовков + let mut headers = HashMap::new(); + + // Обрабатываем остальные строки как заголовки + for line in lines { + let line = line.trim(); + if line.is_empty() { + continue; // Пропускаем пустые строки + } + let (key, value) = parse_header(line)?; + headers.insert(key.to_string().to_lowercase(), value.to_string()); + } + + Ok((method, uri, version, headers)) +} + +fn parse_header(line: &str) -> Result<(String, String), &'static str> { + let mut parts = line.splitn(2, ':'); + let key = parts.next().ok_or("Missing header key")?.trim(); + let value = parts.next().ok_or("Missing header value")?.trim(); + + Ok((key.to_string(), value.to_string())) +} + +fn hash_map_to_header_map(headers: HashMap) -> HeaderMap { + let mut header_map = HeaderMap::new(); + + for (key, value) in headers { + // Parse the key into a HeaderName + let header_name: HeaderName = key.parse().expect("Invalid header name"); + + // Parse the value into a HeaderValue + let header_value: HeaderValue = value.parse().expect("Invalid header value"); + + // Insert into the HeaderMap + header_map.insert(header_name, header_value); + } + + header_map +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..531ee35 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,140 @@ +mod http; +mod https; +mod options; +mod utils; + +use clap::Parser; +use options::Opt; +use utils::{get_current_server_ip, update_server_ip}; + +#[tokio::main] +async fn main() { + // Get server IP or use 0.0.0.0 if failed + update_server_ip().await; + let server_ip = get_current_server_ip(); + + // Parse and validate CLI arguments + let options = Opt::parse(); + options.validate(); + + // Prepare allowed credentials from CLI options + let allowed_credentials = if let Some(allowed_credentials) = options.auth { + allowed_credentials + .split(',') + .map(|credentials| credentials.trim().to_string()) + .collect::>() + } else { + Vec::::new() + }; + + // Prepare allowed hosts from CLI options + let allowed_hosts = if let Some(allowed_hosts) = options.hosts { + allowed_hosts + .split(',') + .map(|host| host.trim().to_string()) + .collect::>() + } else { + Vec::::new() + }; + + // Get secret token from CLI options + let secret_token = options.token.unwrap_or_default(); + + // Create future for HTTP server + let http_future = async { + if options.no_http_server { + return; + } + + let http_port = options.http_port.unwrap_or(8080); + let proxy_url = format!("http://{server_ip}:{http_port}"); + + if allowed_credentials.is_empty() { + println!("\n\x1B[34m\x1B[1mRunning HTTP server:\x1B[0m\n{proxy_url}\nTest: curl -v -x {proxy_url} https://api.ipify.org"); + } else { + println!("\n\x1B[34m\x1B[1mRunning HTTP server with credentials:\x1B[0m"); + + for credentials in &allowed_credentials { + let proxy_url = format!("http://{credentials}@{server_ip}:{http_port}"); + + println!( + "Proxy Url: {proxy_url}\nTest: curl -v -x {proxy_url} https://api.ipify.org" + ); + } + } + + // Print allowed hosts + if !allowed_hosts.is_empty() { + println!("Allowed Hosts: {allowed_hosts:?}"); + } + + // Print secret token + if !secret_token.is_empty() { + println!("Secret Token: {secret_token}"); + } + + let bind_addr = format!("{}:{}", server_ip, http_port).parse().unwrap(); + + if let Err(e) = http::start_proxy( + bind_addr, + allowed_credentials.clone(), + allowed_hosts.clone(), + secret_token.clone(), + ) + .await + { + println!("Error starting HTTP server: {e}"); + } + }; + + // Create future for HTTPS server + let https_future = async { + if options.no_https_server { + return; + } + + let https_port = options.https_port.unwrap_or(443); + let host = if cfg!(debug_assertions) { + format!("localhost:{https_port}") + } else { + format!("YOUR_DOMAIN:{https_port}") + }; + + if allowed_credentials.is_empty() { + println!("\n\x1B[34m\x1B[1mRunning HTTPS server:\x1B[0m\nhttps://{host}\nTest: curl -v -x https://{host} https://api.ipify.org"); + } else { + println!("\n\x1B[34m\x1B[1mRunning HTTPS server with credentials:\x1B[0m"); + for credentials in &allowed_credentials { + println!("Proxy Url: https://{credentials}@{host}\nTest: curl -v -x https://{credentials}@{host} https://api.ipify.org"); + } + } + + // Print allowed hosts + if !allowed_hosts.is_empty() { + println!("Allowed Hosts: {allowed_hosts:?}"); + } + + // Print secret token + if !secret_token.is_empty() { + println!("Secret Token: {secret_token}"); + } + + let bind_addr = format!("{}:{}", server_ip, https_port).parse().unwrap(); + + if let Err(e) = https::start_proxy( + bind_addr, + allowed_credentials.clone(), + allowed_hosts.clone(), + secret_token.clone(), + options.cert.unwrap(), + options.pkey.unwrap(), + ) + .await + { + println!("Error starting HTTPS server: {e}"); + } + }; + + // Join futures and wait for them to complete + tokio::join!(http_future, https_future); +} diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..eb1b25d --- /dev/null +++ b/src/options.rs @@ -0,0 +1,100 @@ +use clap::Parser; +use std::process::exit; + +#[derive(Parser, Debug, Clone)] +#[clap(author, version, about, long_about = None)] +pub struct Opt { + #[clap( + long, + value_name = "u16", + help = "Specify the HTTP port. Default: 8080" + )] + pub http_port: Option, + + #[clap( + long, + value_name = "u16", + help = "Specify the HTTPS port. Default: 443" + )] + pub https_port: Option, + + #[clap( + long, + default_value_t = false, + conflicts_with = "no_https_server", + help = "Disable the HTTP proxy server" + )] + pub no_http_server: bool, + + #[clap( + long, + default_value_t = false, + conflicts_with = "no_http_server", + help = "Disable the HTTPS proxy server" + )] + pub no_https_server: bool, + + #[clap( + long, + value_name = "string", + help = "Comma-separated list of basic credentials. Example: 'login:password, login2:password2'" + )] + pub auth: Option, + + #[clap( + long, + value_name = "string", + help = "Comma-separated list of allowed hosts. Example: 'site.com, *.site.com'" + )] + pub hosts: Option, + + #[clap( + long, + value_name = "string", + help = "Secret token to access the HTTP/S proxy server from Proxer Client. The proxy server will only process requests if the client sends an `x-http(s)-secret-token` header with a valid token. Example: mysecrettoken123" + )] + pub token: Option, + + #[clap( + long, + default_value_t = false, + conflicts_with = "no_http_server", + help = "Disable using the secret token to access the HTTP proxy server from Proxer Client" + )] + pub no_http_token: bool, + + #[clap( + long, + default_value_t = false, + conflicts_with = "no_https_server", + help = "Disable using the secret token to access the HTTPS proxy server from Proxer Client" + )] + pub no_https_token: bool, + + #[clap( + long, + help = "Path to the TLS certificate file. Example: '/path/to/fullchain.(pem|cer|crt|...)'", + value_name = "string", + required_unless_present("no_https_server") + )] + pub cert: Option, + + #[clap( + long, + help = "Path to the TLS private key file. Example: '/path/to/privkey.(pem|key|...)'", + value_name = "string", + required_unless_present("no_https_server") + )] + pub pkey: Option, +} + +impl Opt { + pub fn validate(&self) { + if self.no_https_server { + if self.cert.is_some() || self.pkey.is_some() { + eprintln!("Error: --cert or --pkey cannot be used with --no-https"); + exit(1); + } + } + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..b5d5b07 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,104 @@ +use base64::{engine::general_purpose::STANDARD as b64, Engine}; +use chrono::Local; +use hyper::{header::PROXY_AUTHENTICATE, Body, Response, StatusCode}; +use lazy_static::lazy_static; +use rand::Rng; +use sha2::{Digest, Sha256}; +use std::net::{IpAddr, SocketAddr}; +use std::process::Command; +use std::sync::Mutex; +use wildmatch::WildMatch; + +lazy_static! { + pub static ref SERVER_IP: Mutex = Mutex::new("0.0.0.0".to_string()); +} + +pub fn get_rand_ipv4_socket_addr() -> SocketAddr { + let mut rng = rand::thread_rng(); + let server_ip_addr = get_current_server_ip().parse::().unwrap(); + + SocketAddr::new(server_ip_addr, rng.gen::()) +} + +pub fn require_basic_auth() -> Response { + Response::builder() + .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED) + .header(PROXY_AUTHENTICATE, "Basic realm=\"proxerver\"") + .body(Body::empty()) + .unwrap() +} + +pub fn create_basic_auth_response() -> Vec { + let status = StatusCode::PROXY_AUTHENTICATION_REQUIRED; + let response = format!( + "HTTP/1.1 {} {}\r\n\ + Proxy-Authenticate: Basic realm=\"proxerver\"\r\n\ + Content-Length: 0\r\n\ + \r\n", + status.as_u16(), + status.canonical_reason().unwrap_or("Unknown") + ); + response.into_bytes() +} + +pub fn is_host_allowed(req_host: &str, allowed_hosts: &[String]) -> bool { + for allowed_host in allowed_hosts { + if WildMatch::new(allowed_host.as_str()).matches(req_host) { + return true; + } + } + false +} + +pub fn is_allowed_credentials(credentials_header: &str, allowed_credentials: Vec) -> bool { + for credentials in allowed_credentials { + let allowed_credentials = b64.encode(credentials); + + if credentials_header.contains(&allowed_credentials) { + return true; + } + } + false +} + +pub async fn get_server_ip() -> String { + let output = Command::new("sh") + .arg("-c") + .arg("hostname -I | awk '{print $1}'") + .output() + .expect("Failed to execute command"); + + if output.status.success() { + if output.stdout.is_empty() { + return "0.0.0.0".to_string(); + } else { + return String::from_utf8_lossy(&output.stdout).trim().to_string(); + } + } else { + panic!("Failed to get Server IP: {:?}", output.status); + } +} + +pub async fn update_server_ip() { + let server_ip = get_server_ip().await; + + let mut ip = SERVER_IP.lock().unwrap(); + *ip = server_ip; +} + +pub fn get_current_server_ip() -> String { + SERVER_IP.lock().unwrap().clone() +} + +pub fn to_sha256(input: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(input); + + let result = hasher.finalize(); + format!("{:x}", result) +} + +pub fn formatted_time() -> String { + let now = Local::now(); + now.format("%Y-%m-%d %H:%M:%S").to_string() +}