commit 96b1a94a4a1181ba0f5638eaa61e5b3693a1cdfb Author: Michael Pfaff Date: Tue Jun 6 00:32:07 2023 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..181d157 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1026 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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 = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +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.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" + +[[package]] +name = "log" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", + "static_assertions", +] + +[[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-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[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.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quic-shell" +version = "0.1.0" +dependencies = [ + "anyhow", + "libc", + "nix", + "quinn", + "rcgen", + "rmp-serde", + "rustls", + "serde", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "quinn" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21252f1c0fc131f1b69182db8f34837e8a69737b8251dff75636a9be0518c324" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85af4ed6ee5a89f26a26086e9089a6643650544c025158449a3626ebf72884b3" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" +dependencies = [ + "bytes", + "libc", + "socket2 0.5.3", + "tracing", + "windows-sys 0.48.0", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +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 = "rcgen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +dependencies = [ + "pem", + "ring", + "time", + "yasna", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "regex-syntax 0.7.1", +] + +[[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-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[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", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rmp" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustls" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +dependencies = [ + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.2", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +dependencies = [ + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[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.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.4.9", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[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", +] + +[[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.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" + +[[package]] +name = "web-sys" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[patch.unused]] +name = "how" +version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9835aae --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "quic-shell" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.71" +libc = "0.2.145" +nix = "0.26.2" +quinn = "0.10.1" +rcgen = "0.10.0" +rmp-serde = "1.1.1" +rustls = { version = "0.21.1", default-features = false } +serde = { version = "1.0.163", features = ["derive"] } +#termion = "2.0.1" +tokio = { version = "1.28.2", default-features = false, features = ["rt-multi-thread", "macros", "process", "io-util", "io-std", "time", "fs", "signal"] } +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2d16f0a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,429 @@ +#[macro_use] +extern crate anyhow; +#[macro_use] +extern crate serde; +#[macro_use] +extern crate tracing; + +mod pty; + +use std::ffi::CStr; +use std::os::fd::FromRawFd; +use std::os::unix::process::CommandExt; +use std::process::Stdio; +use std::sync::Arc; + +use anyhow::{Context, Result}; +use quinn::{RecvStream, SendStream}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::process::{Child, Command}; +use tracing::Instrument; + +#[derive(Debug, Clone, Serialize, Deserialize)] +enum Stream { + Shell, + Heartbeat, +} + +#[tokio::main] +async fn main() -> Result<()> { + tracing::subscriber::set_global_default( + tracing_subscriber::FmtSubscriber::builder() + .with_writer(std::io::stderr) + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(), + ) + .unwrap(); + + let mut args = std::env::args(); + _ = args.next(); + + let ctrl_c = tokio::signal::ctrl_c(); + let fut = run_cmd(args); + + tokio::select! { + _ = ctrl_c => Ok(()), + r = fut => r, + } +} + +async fn run_cmd(mut args: std::env::Args) -> Result<()> { + let cmd = args.next().expect("COMMAND"); + match cmd.as_str() { + "server" => run_server().await, + "client" => run_client().await, + _ => Err(anyhow!("Unrecognized command: {}", cmd)), + } +} + +const ALPN_QUIC_SHELL: &str = "quic-shell"; + +async fn run_server() -> Result<()> { + let opt_shell = &*Box::leak( + std::env::var("SHELL") + .context("SHELL not defined")? + .into_boxed_str(), + ); + let opt_listen = std::env::var("BIND_ADDR") + .unwrap_or_else(|_| "127.0.0.1:8022".to_owned()) + .parse()?; + + let subject_alt_names = vec!["localhost".to_string()]; + + let cert = rcgen::generate_simple_self_signed(subject_alt_names)?; + let key = rustls::PrivateKey(cert.serialize_private_key_der()); + let cert = rustls::Certificate(cert.serialize_der()?); + + //std::fs::write("key.der", &key.0)?; + std::fs::write("cert.der", &cert.0)?; + + let mut server_crypto = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(vec![cert], key) + .unwrap(); + server_crypto.alpn_protocols = vec![ALPN_QUIC_SHELL.as_bytes().to_owned()]; + + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(server_crypto)); + let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); + transport_config.max_concurrent_uni_streams(0_u8.into()); + server_config.use_retry(true); + + let endpoint = quinn::Endpoint::server(server_config, opt_listen)?; + eprintln!("listening on {}", endpoint.local_addr()?); + + while let Some(conn) = endpoint.accept().await { + info!("connection incoming"); + tokio::spawn(async move { + if let Err(e) = handle_connection(opt_shell, conn).await { + error!("connection failed: {reason}", reason = e.to_string()) + } + }); + } + + Ok(()) +} + +fn is_broken_pipe(r: &Result) -> bool { + if let Err(e) = r { + e.kind() == std::io::ErrorKind::BrokenPipe + } else { + false + } +} + +async fn run_client() -> Result<()> { + info!("running client"); + + let mut roots = rustls::RootCertStore::empty(); + match std::fs::read("cert.der") { + Ok(cert) => { + roots.add(&rustls::Certificate(cert))?; + } + Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => { + info!("local server certificate not found"); + } + Err(e) => { + error!("failed to open local server certificate: {}", e); + } + } + + info!("read roots"); + + let mut client_crypto = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth(); + + client_crypto.alpn_protocols = vec![ALPN_QUIC_SHELL.as_bytes().to_owned()]; + + let mut transport = quinn::TransportConfig::default(); + transport.keep_alive_interval(Some(std::time::Duration::from_secs(5))); + + let mut client_config = quinn::ClientConfig::new(Arc::new(client_crypto)); + client_config.transport_config(transport.into()); + //let mut endpoint = quinn::Endpoint::client("[::]:0".parse().unwrap())?; + let mut endpoint = quinn::Endpoint::client("0.0.0.0:0".parse().unwrap())?; + endpoint.set_default_client_config(client_config); + + info!("connecting"); + + let conn = endpoint + .connect("127.0.0.1:8022".parse()?, "localhost")? + .await?; + let (mut send, mut recv) = conn.open_bi().await?; + + write_header(&mut send, Stream::Shell).await?; + + info!("connected"); + + let mut stdin = unsafe { tokio::fs::File::from_raw_fd(libc::STDIN_FILENO) }; + let mut stdout = unsafe { tokio::fs::File::from_raw_fd(libc::STDOUT_FILENO) }; + let mut stdin_buf = Vec::with_capacity(4096); + let mut stdout_buf = Vec::with_capacity(4096); + + let mut stdin_eof = false; + + { + use nix::sys::termios::*; + let mut termios = tcgetattr(libc::STDIN_FILENO)?; + termios.local_flags.remove(LocalFlags::ECHO); + termios.local_flags.remove(LocalFlags::ICANON); + termios.local_flags.remove(LocalFlags::ISIG); + termios.local_flags.remove(LocalFlags::IEXTEN); + termios.input_flags.remove(InputFlags::IXON); + termios.input_flags.remove(InputFlags::ICRNL); + termios.output_flags.remove(OutputFlags::OPOST); + tcsetattr(libc::STDIN_FILENO, SetArg::TCSAFLUSH, &termios)?; + } + + /*let mut heartbeat = { + let (mut send, recv) = conn.open_bi().await?; + + write_header(&mut send, Stream::Heartbeat).await?; + Box::pin(handle_stream_heartbeat(send, recv)) + };*/ + + loop { + tokio::select! { + //_ = &mut heartbeat => {} + r = stdin.read_buf(&mut stdin_buf), if !stdin_eof => { + if r? == 0 { + stdin_eof = true; + } + send.write_all(&stdin_buf).await?; + stdin_buf.clear(); + //info!("sent stdin"); + }, + r = recv.read_buf(&mut stdout_buf) => if r? > 0 { + stdout.write_all(&stdout_buf).await?; + stdout_buf.clear(); + //info!("recv stdout"); + }, + r = send.stopped() => { + info!("Remote disconnected"); + let code = r?.into_inner(); + if code == 0 { + return Ok(()); + } else { + return Err(anyhow!("Error code {}", code)); + } + } + e = conn.closed() => { + info!("Remote disconnected: {}", e); + return Ok(()); + } + } + } +} + +async fn write_header(send: &mut SendStream, header: Stream) -> Result<()> { + let buf = rmp_serde::to_vec(&header)?; + send.write_all(&u16::try_from(buf.len())?.to_le_bytes()) + .await?; + send.write_all(&buf).await?; + Ok(()) +} + +async fn read_header(recv: &mut RecvStream) -> Result { + let mut size = [0u8; 2]; + recv.read_exact(&mut size).await?; + let size = u16::from_le_bytes(size); + let mut buf = Vec::with_capacity(size.into()); + recv.take(size.into()).read_to_end(&mut buf).await?; + Ok(rmp_serde::from_slice(&buf)?) +} + +async fn handle_connection(opt_shell: &'static str, conn: quinn::Connecting) -> Result<()> { + let conn = conn.await?; + let span = info_span!( + "connection", + remote = %conn.remote_address(), + protocol = %conn + .handshake_data() + .unwrap() + .downcast::().unwrap() + .protocol + .map_or_else(|| "".into(), |x| String::from_utf8_lossy(&x).into_owned()) + ); + async { + info!("established"); + + loop { + let stream = conn.accept_bi().await; + let (send, mut recv) = match stream { + Err(quinn::ConnectionError::ApplicationClosed { .. }) => { + info!("connection closed"); + return Ok(()); + } + Err(e) => { + return Err(e.into()); + } + Ok(s) => s, + }; + + let stream = read_header(&mut recv).await?; + let span = info_span!( + "stream", + r#type = ?stream + ); + tokio::task::spawn( + async move { + let r = match stream { + Stream::Shell => handle_stream_shell(opt_shell, send, recv).await, + Stream::Heartbeat => handle_stream_heartbeat(send, recv).await, + }; + if let Err(e) = r { + error!("Error in stream handler: {e}"); + } + } + .instrument(span), + ); + } + } + .instrument(span) + .await +} + +async fn handle_stream_shell( + opt_shell: &str, + mut send: SendStream, + mut recv: RecvStream, +) -> Result<()> { + let use_pty = true; + if use_pty { + let args = if opt_shell == "bash" || opt_shell.ends_with("/bash") { + vec![CStr::from_bytes_with_nul(b"-i\0")?] + } else { + vec![] + }; + let mut opt_shell_with_nul = Vec::with_capacity(opt_shell.len() + 1); + opt_shell_with_nul.extend(opt_shell.as_bytes()); + opt_shell_with_nul.push(0); + let opt_shell = CStr::from_bytes_with_nul(&opt_shell_with_nul)?; + let mut sh = pty::create_pty(opt_shell, &args)?; + info!("Created pty"); + + let mut stdin_buf = Vec::with_capacity(4096); + let mut pty_buf = Vec::with_capacity(4096); + //let mut pty_buf = [0u8]; + + loop { + if let Some(code) = sh.try_wait()? { + send.finish().await?; + if code != 0 { + info!("Child exit: {}", code); + + recv.stop(1u8.into())?; + return Ok(()); + } else { + info!("Child exit"); + recv.stop(0u8.into())?; + return Ok(()); + } + } + + //let mut redraw = tokio::time::interval(std::time::Duration::from_millis(50)); + + tokio::select! { + /*_ = redraw.tick() => { + sh.pty.read_buf(&mut pty_buf).await?; + send.write_all(&pty_buf).await?; + pty_buf.clear(); + info!("redraw complete"); + }*/ + r = sh.pty.read_buf(&mut pty_buf) => { + //r = sh.pty.read_exact(&mut pty_buf) => { + if let Err(e) = r { + if e.raw_os_error() != Some(35) { + return Err(e.into()); + } + } + if pty_buf.len() > 0 { + send.write_all(&pty_buf).await?; + pty_buf.clear(); + info!("sent pty"); + } + } + r = recv.read_buf(&mut stdin_buf) => if r? > 0 { + sh.pty.write_all(&stdin_buf).await?; + stdin_buf.clear(); + info!("recv stdin"); + }, + } + } + } else { + let mut cmd = std::process::Command::new(opt_shell); + if opt_shell == "bash" || opt_shell.ends_with("/bash") { + cmd.arg("-i"); + } + cmd.stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()); + #[cfg(target_family = "unix")] + cmd.process_group(0); + info!("Running {:?}", cmd); + let mut sh = Command::from(cmd).kill_on_drop(true).spawn()?; + + let mut stdout = sh.stdout.take().unwrap(); + let mut stderr = sh.stderr.take().unwrap(); + let mut stdin = sh.stdin.take().unwrap(); + let mut stdout_buf = Vec::with_capacity(4096); + let mut stderr_buf = Vec::with_capacity(4096); + let mut stdin_buf = Vec::with_capacity(4096); + + let mut stdout_eof = false; + let mut stderr_eof = false; + + loop { + tokio::select! { + r = sh.wait() => { + let code = r?; + send.finish().await?; + if !code.success() { + info!("Child exit: {}", code); + + recv.stop(1u8.into())?; + return Ok(()); + } else { + info!("Child exit"); + recv.stop(0u8.into())?; + return Ok(()); + } + } + r = stdout.read_buf(&mut stdout_buf), if !stdout_eof => { + if is_broken_pipe(&r) || r? == 0 { + stdout_eof = true; + info!("stdout eof"); + } else { + send.write_all(&stdout_buf).await?; + info!("sent stdout: {:x?}", stdout_buf); + stdout_buf.clear(); + } + }, + r = stderr.read_buf(&mut stderr_buf), if !stderr_eof => { + if is_broken_pipe(&r) || r? == 0 { + stderr_eof = true; + info!("stderr eof"); + } else { + send.write_all(&stderr_buf).await?; + stderr_buf.clear(); + info!("sent stderr: {:x?}", stderr_buf); + } + }, + r = recv.read_buf(&mut stdin_buf) => if r? > 0 { + stdin.write_all(&stdin_buf).await?; + stdin_buf.clear(); + info!("recv stdin"); + }, + } + } + } +} + +async fn handle_stream_heartbeat(mut send: SendStream, _recv: RecvStream) -> Result<()> { + loop { + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + send.write_all(&[0u8]).await?; + } +} diff --git a/src/pty.rs b/src/pty.rs new file mode 100644 index 0000000..34b5722 --- /dev/null +++ b/src/pty.rs @@ -0,0 +1,121 @@ +use nix::fcntl::{fcntl, open, FcntlArg, OFlag}; +use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize}; +use nix::sys::stat::Mode; +use nix::unistd::{ForkResult, Pid}; +use nix::{ioctl_none_bad, ioctl_write_ptr_bad}; + +use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, TIOCSCTTY}; +// ioctl request code to set window size of pty: +use libc::TIOCSWINSZ; +use std::ffi::CStr; +use std::fs::File; +use std::os::unix::process::CommandExt; +use std::path::Path; +use std::process::{Command, Stdio}; + +use std::os::unix::io::{FromRawFd, IntoRawFd}; + +// nix macro that generates an ioctl call to set window size of pty: +ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize); + +// request to "Make the given terminal the controlling terminal of the calling process" +ioctl_none_bad!(set_controlling_terminal, TIOCSCTTY); + +pub struct Child { + pub pty: tokio::fs::File, + pub pid: Pid, +} + +impl Child { + // copied from + // https://doc.rust-lang.org/nightly/src/std/sys/unix/process/process_unix.rs.html#744-757 + pub fn try_wait(&self) -> std::io::Result> { + let mut status = 0; + let pid = cvt(unsafe { libc::waitpid(self.pid.as_raw(), &mut status, libc::WNOHANG) })?; + if pid == 0 { + Ok(None) + } else { + Ok(Some(status)) + } + } +} + +impl Drop for Child { + fn drop(&mut self) { + _ = nix::sys::signal::kill(self.pid, nix::sys::signal::Signal::SIGTERM); + } +} + +pub fn create_pty + std::fmt::Debug>(path: &CStr, argv: &[S]) -> nix::Result { + /* Create a new master */ + let master_fd = posix_openpt(OFlag::O_RDWR)?; + + /* For some reason, you have to give permission to the master to have a + * pty. What is it good for otherwise? */ + grantpt(&master_fd)?; + unlockpt(&master_fd)?; + + /* Get the path of the slave */ + let slave_name = unsafe { ptsname(&master_fd) }?; + + /* Try to open the slave */ + let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?; + info!("master opened the slave_fd!"); + + /* Launch our child process. The main application loop can inspect and then + pass the stdin data to it. */ + let child_pid = match unsafe { nix::unistd::fork() } { + Ok(ForkResult::Child) => { + init_child(path, argv, slave_name).unwrap(); + unreachable!() + } + Ok(ForkResult::Parent { child }) => child, + Err(e) => panic!("{}", e), + }; + + let winsize = Winsize { + ws_row: 25, + ws_col: 80, + ws_xpixel: 0, + ws_ypixel: 0, + }; + let master_fd = master_fd.into_raw_fd(); + /* Tell the master the size of the terminal */ + unsafe { set_window_size(master_fd, &winsize)? }; + fcntl(master_fd, FcntlArg::F_SETFL(OFlag::O_NDELAY)).unwrap(); + let master_file = unsafe { File::from_raw_fd(master_fd) }; + Ok(Child { + pty: master_file.into(), + pid: child_pid, + }) +} + +fn init_child + std::fmt::Debug>( + path: &CStr, + argv: &[S], + slave_name: String, +) -> anyhow::Result { + /* Open slave end for pseudoterminal */ + let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?; + info!("child opened the slave_fd!"); + + // assign stdin, stdout, stderr to the tty + nix::unistd::dup2(slave_fd, STDIN_FILENO)?; + nix::unistd::dup2(slave_fd, STDOUT_FILENO)?; + nix::unistd::dup2(slave_fd, STDERR_FILENO)?; + + nix::unistd::setsid().unwrap(); + unsafe { set_controlling_terminal(slave_fd) }.unwrap(); + + info!("running exec: {:?} {:?}", path, argv); + Ok(nix::unistd::execv(path, argv)?) +} + +// copied from... somewhere in std +pub fn cvt(t: i32) -> std::io::Result { + if t == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(t) + } +}