work/0000775000000000000000000000000014766655527006757 5ustar work/.dir-locals.el0000664000000000000000000000005314766655527011406 0ustar ((rust-mode . ((rust-indent-offset . 2)))) work/.gitignore0000664000000000000000000000011414766655527010743 0ustar /target /docs/html /docs/doctrees /stamp /tmp /hippotat-setup-permissions.8 work/.gitlab-ci.yml0000664000000000000000000000767714766655527011434 0ustar stages: - check - test variables: BUILD_VERBOSE: "true" MAKE_CHECK_PACKAGES: "util-linux iproute2 userv-utils net-tools iputils-ping" image: "debian:bookworm-slim" default: before_script: - rustc --version ||true - cargo --version ||true image: "debian:bookworm-slim" cargo-check: stage: check image: "rust:latest" script: - cargo check --locked --workspace --verbose --all-features cargo-test: stage: test image: "rust:latest" script: - cargo test --locked --workspace --verbose --all-features cargo-update-test-nightly: stage: test image: "rustlang/rust:nightly" allow_failure: true script: - cargo update - cargo test --locked --workspace --verbose --all-features build-docs: stage: check script: - apt-get -y update - apt-get -y -Pupstream-cargo build-dep . - make doc maint-checks: stage: check script: - apt-get -y update - apt-get -y install libtoml-perl git - debian/update-build-deps --check - set +e; env git grep -i 'XX[X]'; test $? = 1 # This can start to generate new warnings as new issues are found. # And nearly all of the issues are not a crisis. Often the are # irrelevant, or "unmaintained code". So make this a warning. cargo-audit: stage: test allow_failure: true image: "rust:bookworm" script: - ./maint/via-cargo-install-in-ci cargo-audit - cargo audit cache: when: 'always' paths: - cache/* package-rust-upstream: stage: test image: "rust:bookworm" script: - apt-get -y update - apt-get -y -Pupstream-cargo build-dep . - apt-get -y install $MAKE_CHECK_PACKAGES - dpkg-buildpackage -Pupstream-cargo -uc -b # This test is marked allow-fail because Debian development breaks # things on a regular basis. # Specifically, it can be impossible (or at least very inconvenient) # to make code that works both before and after some dependency crate # version transition. # If multiple such transitions are occurring at once, both unstable # and testing might be broken, halfway through our response. package-debian-trixie: stage: test image: "debian:trixie-slim" allow_failure: true script: - apt-get -y update - apt-get -y build-dep . - apt-get -y install $MAKE_CHECK_PACKAGES - dpkg-buildpackage -uc -b # This test is marked allow-fail because Debian development breaks # things on a regular basis. See also, above. package-debian-unstable: stage: test image: "debian:unstable-slim" allow_failure: true script: - apt-get -y update - apt-get -y build-dep . - apt-get -y install $MAKE_CHECK_PACKAGES - dpkg-buildpackage -uc -b # Does -Z minimal-versions work today? # Use some pinned version of nightly minimal-versions-nightly: stage: test # This is "rustlang/rust:nightly-bookworm-slim" as of 2023-06-15 13:23 Z image: rustlang/rust@sha256:0153f6bde60303fbcc38c94dc7f4b451c8ac7b46d4602291bd5a15a3317d9ec8 script: - apt-get -y update - apt-get -y install build-essential pkg-config libssl-dev - maint/update-minimal-versions - cp Cargo.lock.minimal Cargo.lock - cargo test --locked --workspace --verbose --all-features # We have a thought-to-be-known-working lockfile in tree, test that minimal-versions-pinned: stage: test image: "rust:bookworm" script: - apt-get -y update - apt-get -y install build-essential pkg-config libssl-dev - cp Cargo.lock.minimal Cargo.lock - cargo test --locked --workspace --verbose --all-features # Ideally we'd run autopkgtest here. # However, our autopkgtests use overlayfs which doesn't seem available. # The adt-initscript test seemed to work but probably isn't worth the candle. #autopkgtest: # stage: comprehensive # script: # - apt-get -y update # - apt-get -y -Pupstream-cargo build-dep . # - apt-get -y install autopkgtest # - DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -Pupstream-cargo -uc -us --build=full # - autopkgtest --ignore-restrictions=isolation-machine ../hippotat_*.changes --- null work/COPYING0000777000000000000000000000000014766655527013162 2debian/copyrightustar work/Cargo.lock0000664000000000000000000017071014766655527010672 0ustar # 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 = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[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 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[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.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_derive" version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[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.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 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 = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "derive-deftly" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0015cb20a284ec944852820598af3aef6309ea8dc317a0304441272ed620f196" dependencies = [ "derive-deftly-macros", "heck", ] [[package]] name = "derive-deftly-macros" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b48e8e38a4aa565da767322b5ca55fb0f8347983c5bc7f7647db069405420479" dependencies = [ "heck", "indexmap", "itertools", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum", "syn 2.0.100", "void", ] [[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 = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "easy-ext" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5d6d6a8504f8caedd7de14576464383900cd3840b7033a7a3dce5ac00121ca" [[package]] name = "educe" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" dependencies = [ "enum-ordinalize", "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "enum-ordinalize" version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "env_filter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", "jiff", "log", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fehler" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" dependencies = [ "fehler-macros", ] [[package]] name = "fehler-macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[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 2.0.100", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[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-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "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 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hippotat" version = "1.2.2" dependencies = [ "backtrace", "base64", "cfg-if", "clap", "derive-deftly", "easy-ext", "educe", "either", "env_logger", "eyre", "fehler", "futures", "heck", "hippotat-macros", "http-body", "http-body-util", "hyper", "hyper-util", "indenter", "ipnet", "itertools", "lazy-regex", "lazy_static", "libc", "log", "memchr", "mime", "nix", "parking_lot", "pin-project-lite", "regex", "reqwest", "semver", "serde", "serde_json", "sha2", "subtle", "syslog", "thiserror", "tokio", "void", ] [[package]] name = "hippotat-macros" version = "1.2.2" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "hostname" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ "cfg-if", "libc", "windows", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indenter" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", "serde", ] [[package]] name = "jiff-static" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "lazy-regex" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.100", ] [[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.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linux-raw-sys" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] name = "litemap" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "native-tls" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num_threads" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "openssl" version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" 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 2.0.100", ] [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[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.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "redox_syscall" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "windows-registry", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", "untrusted", "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 = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustls" version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[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.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" version = "0.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.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.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest", "keccak", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn 2.0.100", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "syslog" version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "019f1500a13379b7d051455df397c75770de6311a7a188a699499502704d9f10" dependencies = [ "hostname", "libc", "log", "time", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "terminal_size" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ "rustix", "windows-sys 0.59.0", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "time" version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" dependencies = [ "deranged", "itoa", "libc", "num-conv", "num_threads", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[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.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[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.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 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.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "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 = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[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 = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.100", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "windows" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", "windows-targets 0.52.6", ] [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", "windows-strings", "windows-targets 0.53.0", ] [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[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_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[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_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[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_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[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_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[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_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] work/Cargo.lock.minimal0000664000000000000000000017641514766655527012327 0ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e60698898f23be659cb86289e5805b1e059a5fe1cd95c9a1d4def50369e74b31" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2abc612600451a4beeff27bf046474b29f7eab30b15846975949f30f9e54afad" dependencies = [ "anstyle", "anstyle-parse", "anstyle-wincon", "concolor-override", "concolor-query", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" [[package]] name = "anstyle-parse" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f2360dcc613cdbc517b980de2a56874c6e715db3e37256721faabed99ccf0f" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-wincon" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" dependencies = [ "anstyle", "windows-sys 0.45.0", ] [[package]] name = "arc-swap" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e43c468bcaa343ddcad9e46806e066e39f62434898b20f5af21261da910d5c7" [[package]] name = "atty" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" dependencies = [ "libc", "termion", "winapi", ] [[package]] name = "autocfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23213af7601f0f2d929f73d2a772804562cb09063f50bba9c361f86d6a0376f8" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if 1.0.0", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "base64" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitflags" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f6e5df9abedba5099a01a6567c6086a6fbcff57af07c360d356737f9e0c644" [[package]] name = "block-buffer" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" [[package]] name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "c2-chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" dependencies = [ "lazy_static", "ppv-lite86", ] [[package]] name = "cc" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6efb5f0a41b5ef5b50c5da28c07609c20091df0c1fc33d418fa2a7e693c2b624" dependencies = [ "clap_builder", "clap_derive", "once_cell", ] [[package]] name = "clap_builder" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "671fcaa5debda4b9a84aa7fde49c907c8986c0e6ab927e04217c9cb74e7c8bc9" dependencies = [ "anstream", "anstyle", "bitflags 1.2.1", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_derive" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.53", ] [[package]] name = "clap_lex" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f0807fb6f644c83f3e4ec014fec9858c1c8b26a7db8eb5f0bde5817df9c1df7" [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ "bitflags 1.2.1", ] [[package]] name = "cloudabi" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" dependencies = [ "bitflags 1.2.1", ] [[package]] name = "concolor-override" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" [[package]] name = "concolor-query" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e26417f76e1387253c4d2bdac4de27c3f1ff9b78d1c0c07ce35f88bc7eb697fd" dependencies = [ "windows-sys 0.45.0", ] [[package]] name = "core-foundation" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b5ed8e7e76c45974e15e41bfa8d5b0483cd90191639e01d8f5f1e606299d3fb" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6" [[package]] name = "cpufeatures" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] [[package]] name = "crypto-common" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06" dependencies = [ "generic-array", ] [[package]] name = "derive-deftly" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39502f680ea10252d02b2809e56239acb3aa68d3659c0e0171ade79c04c48086" dependencies = [ "derive-deftly-macros", "heck", ] [[package]] name = "derive-deftly-macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c964673dd1023a358bbc0090f6761aee64dddbd21bc7fa91853e92201110e8f" dependencies = [ "heck", "indexmap 1.8.0", "itertools", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum", "syn 2.0.53", "void", ] [[package]] name = "digest" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "easy-ext" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2ddb5d6d3904e83114add6bbadf2b8307b4ae9fb4b2202afde1fe7bf3b56c0" [[package]] name = "educe" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f77b1c2ba3e9edf0300314f4a29b694fcff606a39495aa8e2cd0f8b320f6f1" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "either" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c67353c641dc847124ea1902d69bd753dee9bb3beff9aa3662ecf86c971d1fac" [[package]] name = "encoding_rs" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6848cbd169668c2338be9940ac8968179edcd8704248e1e0c885a306c42772e" dependencies = [ "cfg-if 0.1.10", ] [[package]] name = "env_logger" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" [[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", "winapi", ] [[package]] name = "errno-dragonfly" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" dependencies = [ "gcc", "libc", ] [[package]] name = "error-chain" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" dependencies = [ "version_check", ] [[package]] name = "eyre" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0f9683839e579a53258d377fcc0073ca0bf2042ac5e6c60a598069e64403a6d" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fehler" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" dependencies = [ "fehler-macros", ] [[package]] name = "fehler-macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "fnv" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" [[package]] name = "foreign-types" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21b40436003b2a1e22483c5ed6c3d25e755b6b3120f601cc22aa57e25dc9065" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baa1839fc3c5487b5e129ea4f774e3fd84e6c4607127315521bc014a722ebc9e" [[package]] name = "form_urlencoded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "fuchsia-cprng" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81f7f8eb465745ea9b02e2704612a9946a59fa40572086c6fd49d6ddcf30bf31" [[package]] name = "futures" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fa4cc29d25b0687b8570b0da86eac698dcb525110ad8b938fe6712baa711ec" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0cb59f15119671c94cd9cc543dc9a50b8d5edc468b4ff5f0bb8567f66c6b48a" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[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 2.0.53", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[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-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "gcc" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e33f45ff9bef4a33df0e34df4d68ee016762d11f24e8d536e5e294096cc2b96" [[package]] name = "generic-array" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e738b1f02e4d17217cae7648e774c03a19cd9de18bc294c538cc3e780f8c3bbd" dependencies = [ "cloudabi 0.0.3", "fuchsia-cprng", "libc", "winapi", ] [[package]] name = "getrandom" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "libc", "wasi", ] [[package]] name = "gimli" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "h2" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap 2.0.0", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "362385356d610bd1e5a408ddf8d022041774b683f345a1d2cfcb4f60f8ae2db5" [[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "heck" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hippotat" version = "1.2.2" dependencies = [ "backtrace", "base64 0.21.0", "cfg-if 1.0.0", "clap", "derive-deftly", "easy-ext", "educe", "either", "env_logger", "eyre", "fehler", "futures", "heck", "hippotat-macros", "http-body", "http-body-util", "hyper", "hyper-util", "indenter", "ipnet", "itertools", "lazy-regex", "lazy_static", "libc", "log", "memchr", "mime", "nix", "parking_lot 0.11.0", "pin-project-lite", "regex", "reqwest", "semver", "serde", "serde_json", "sha2", "subtle", "syslog", "thiserror", "tokio", "void", ] [[package]] name = "hippotat-macros" version = "1.2.2" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "hostname" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", "winapi", ] [[package]] name = "http" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" dependencies = [ "bytes", "fnv", "itoa 1.0.0", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05842d0d43232b23ccb7060ecb0f0626922c21f30012e97b767b30afd4a5d4b9" [[package]] name = "humantime" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9b6c53306532d3c8e8087b44e6580e10db51a023cf9b433cea2ac38066b92da" [[package]] name = "hyper" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa 1.0.0", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "736f15a50e749d033164c56c09783b6102c4ff8da79ad77dbddbbaea0f8567f7" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indenter" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0bd112d44d9d870a6819eb505d04dd92b5e4d94bb8c304924a0872ae7016fb5" [[package]] name = "indexmap" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg 1.1.0", "hashbrown 0.11.0", ] [[package]] name = "indexmap" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", ] [[package]] name = "instant" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7777a24a1ce5de49fcdde84ec46efa487c3af49d5b6e6e0a50367cc5c1096182" [[package]] name = "io-lifetimes" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c958a7c5a0f36eff1c25617d11d439de0887e69526ab74ae95600924a73e5b" [[package]] name = "io-lifetimes" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" dependencies = [ "libc", "windows-sys 0.42.0", ] [[package]] name = "ipnet" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" [[package]] name = "is-terminal" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" dependencies = [ "hermit-abi", "io-lifetimes 1.0.1", "rustix 0.36.4", "windows-sys 0.45.0", ] [[package]] name = "itertools" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "itoa" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f3e61cf687687b30c9e6ddf0fc36cf15f035e66d491e6da968fa49ffa9a378" [[package]] name = "js-sys" version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "lazy-regex" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb06a211c24037a055686e0b2d79cb7f4c14d124a63a9097fdfcc05880f8ce28" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e256930eb532edd31ff0103cebde847040897b31d3cb344dd2c46d96c6f346f7" dependencies = [ "proc-macro2", "quote", "regex", "syn 1.0.103", ] [[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.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "linux-raw-sys" version = "0.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" [[package]] name = "linux-raw-sys" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb68f22743a3fb35785f1e7f844ca5a3de2dde5bd0c0ef5b372065814699b121" [[package]] name = "lock_api" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memoffset" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" dependencies = [ "autocfg 1.1.0", ] [[package]] name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "native-tls" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "nix" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" dependencies = [ "autocfg 1.1.0", "bitflags 1.2.1", "cfg-if 1.0.0", "libc", "memoffset", "pin-utils", ] [[package]] name = "object" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.2.1", "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.53", ] [[package]] name = "openssl-probe" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756d49c8424483a3df3b5d735112b4da22109ced9a8294f1f5cdf80fb3810919" [[package]] name = "openssl-sys" version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "parking_lot" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" dependencies = [ "instant", "lock_api", "parking_lot_core 0.8.0", ] [[package]] name = "parking_lot" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", "parking_lot_core 0.9.0", ] [[package]] name = "parking_lot_core" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" dependencies = [ "cfg-if 0.1.10", "cloudabi 0.1.0", "instant", "libc", "redox_syscall 0.1.56", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2f4f894f3865f6c0e02810fc597300f34dc2510f66400da262d8ae10e75767d" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.8", "smallvec", "windows-sys 0.29.0", ] [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" [[package]] name = "ppv-lite86" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe1c37e6347ad1a8351171bee25a92342401f8cd550f76e153724e765ac76bca" [[package]] name = "proc-macro-crate" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", ] [[package]] name = "proc-macro2" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" dependencies = [ "getrandom 0.1.1", "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e193067942ef6f485a349a113329140d0ab9e2168ce92274499bb0e9a4190d9d" dependencies = [ "autocfg 0.1.0", "c2-chacha", "rand_core", ] [[package]] name = "rand_core" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca" dependencies = [ "getrandom 0.1.1", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "redox_syscall" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" dependencies = [ "bitflags 1.2.1", ] [[package]] name = "redox_termios" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc495930de8d330f14856cface52561b7d79a072c76e438cf8f34d7233a35fa7" dependencies = [ "redox_syscall 0.1.56", ] [[package]] name = "regex" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc5b3ce5d5ea144bb04ebd093a9e14e9765bcfec866aecda9b6dec43b3d1e24" dependencies = [ "winapi", ] [[package]] name = "reqwest" version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.0", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "windows-registry", ] [[package]] name = "ring" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb9d44f9bf6b635117787f72416783eb7e4227aaf255e5ce739563d817176a7e" dependencies = [ "cc", "getrandom 0.2.8", "libc", "spin", "untrusted", "windows-sys 0.48.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.35.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" dependencies = [ "bitflags 1.2.1", "errno", "io-lifetimes 0.7.0", "libc", "linux-raw-sys 0.0.46", "windows-sys 0.36.0", ] [[package]] name = "rustix" version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" dependencies = [ "bitflags 1.2.1", "errno", "io-lifetimes 1.0.1", "libc", "linux-raw-sys 0.1.2", "windows-sys 0.42.0", ] [[package]] name = "rustls" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53e56521f047352df0db9a3c5aafc573eeb8909ab80f9d4cba201d8d73539009" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ "base64 0.21.0", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a716eb65e3158e90e17cd93d855216e27bde02745ab842f2cab4a39dba1bacf" [[package]] name = "rustls-webpki" version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c48f91977f4ef3be5358c15d131d3f663f6b4d7a112555bf3bf52ad23b6659e5" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "ryu" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" [[package]] name = "schannel" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" dependencies = [ "lazy_static", "winapi", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "security-framework" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" dependencies = [ "bitflags 1.2.1", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "serde_json" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" dependencies = [ "itoa 0.4.7", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa 1.0.0", "ryu", "serde", ] [[package]] name = "sha2" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest", ] [[package]] name = "sha3" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" dependencies = [ "digest", "keccak", ] [[package]] name = "signal-hook-registry" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc" dependencies = [ "arc-swap", "libc", ] [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" [[package]] name = "socket2" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", ] [[package]] name = "spin" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn 1.0.103", ] [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" dependencies = [ "futures-core", ] [[package]] name = "syslog" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7caf36e4fc5d8ccee2ff4a19e27185339cf00338bb0c68f284dc5a295e9cc045" dependencies = [ "error-chain", "hostname", "log", "time", ] [[package]] name = "system-configuration" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" dependencies = [ "bitflags 2.0.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ "cfg-if 0.1.10", "libc", "rand", "redox_syscall 0.1.56", "remove_dir_all", "winapi", ] [[package]] name = "termcolor" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3390f44f1f706d8870297b6a2c4f92d9ab65a37c265fbbc6ac4ee72bcc2f3698" dependencies = [ "wincolor", ] [[package]] name = "terminal_size" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440c860cf79def6164e4a0a983bcc2305d82419177a0e0c71930d049e3ac5a1" dependencies = [ "rustix 0.35.7", "windows-sys 0.36.0", ] [[package]] name = "termion" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8affd752d0f2c7127d6d5f1b98182a5471606b48b1a955165d39eb5e4887ceba" dependencies = [ "libc", "redox_syscall 0.1.56", "redox_termios", ] [[package]] name = "thiserror" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "time" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" dependencies = [ "itoa 0.4.7", "libc", ] [[package]] name = "tinyvec" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f331a553cacb14e99d183e5573c86044dd177b5a5277b21e562fd1bd5e1076e1" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", "libc", "mio", "parking_lot 0.12.0", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn 2.0.53", ] [[package]] name = "tokio-native-tls" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "toml" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a54ae44b0b2c443e7ef6dd3be16a776bae4daa40684f81e15126bc04e7747308" dependencies = [ "serde", ] [[package]] name = "tower-service" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "tracing" version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "tracing-core" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a31f2d8ccabc3089a7aad162f96d4513034a6c5d2385e93023984c645e65fb6" [[package]] name = "typenum" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicode-bidi" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vcpkg" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" [[package]] name = "version_check" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45d3d553fd9413fffe7147a20171d640eda0ad4c070acd7d0c885a21bcd2e8b7" [[package]] name = "void" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9190d4fdcc6b93d290236afff590896050a971233ec853958c0e52d42bdeb72c" [[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ "log", "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.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.53", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" dependencies = [ "cfg-if 0.1.10", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", "syn 2.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 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 = "wincolor" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9dc3aa9dcda98b5a16150c54619c1ead22e3d3a5d458778ae914be760aa981a" dependencies = [ "winapi", ] [[package]] name = "windows-registry" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", "windows-targets 0.52.6", ] [[package]] name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceb069ac8b2117d36924190469735767f0990833935ab430155e71a44bafe148" dependencies = [ "windows_aarch64_msvc 0.29.0", "windows_i686_gnu 0.29.0", "windows_i686_msvc 0.29.0", "windows_x86_64_gnu 0.29.0", "windows_x86_64_msvc 0.29.0", ] [[package]] name = "windows-sys" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f375ae76a43fd649c5a3482a4a3e28eced2267adaefa55422bf7e92696a7dac5" dependencies = [ "windows_aarch64_msvc 0.36.0", "windows_i686_gnu 0.36.0", "windows_i686_msvc 0.36.0", "windows_x86_64_gnu 0.36.0", "windows_x86_64_msvc 0.36.0", ] [[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.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.1", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.0", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm 0.42.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] [[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-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" [[package]] name = "windows_aarch64_msvc" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bee8cd327bbef19bf86d30bd66379f57905166d3103b0e2eff4a491b85e421d" [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[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.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" [[package]] name = "windows_i686_gnu" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b759cc6e3d97970c98cffe461739e89ab6d424ba5e2e7d3b9b05a2d56116057" [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[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_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.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" [[package]] name = "windows_i686_msvc" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0cee91bff283876711f91e7db0aa234438bc663a9d8304596df00b0a6fd6ef" [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[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.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" [[package]] name = "windows_x86_64_gnu" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e8c6f778aa4383b033ff785191aea0f1ebeceedc160c2c92f944ef7e191476" [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[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_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.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[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_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.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" [[package]] name = "windows_x86_64_msvc" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd6a8b0b1ea4331e4db47192729fce42ac8a110fd22bb3abac555d8d7700f29" [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" work/Cargo.toml0000664000000000000000000000356714766655527010722 0ustar # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. [package] name = "hippotat" version = "1.2.2" edition = "2018" description="Asinine HTTP-over-IP" license="GPL-3.0-or-later" # ^ Actually, it's WITH LicenseRef-Hippotat-OpenSSL-Exception repository="https://salsa.debian.org/iwj/hippotat" homepage="https://www.chiark.greenend.org.uk/~ianmdlvl/hippotat/current/docs/" rust-version = "1.63" [workspace] members = ["macros"] [[bin]] name="hippotat" path="client/client.rs" [[bin]] name="hippotatd" path="server/server.rs" [dependencies] derive-deftly = "1" hippotat-macros = { version = "=1.2.2", path = "macros" } backtrace = "0.3.74" base64 = ">=0.21, <0.23" cfg-if = "1" # clap 3 would work too at the time of writing, but it lacks the `wrap_help` # feature - that's built-in there. clap = { version = "4", features = ["derive", "wrap_help"] } easy-ext = "1" educe = ">=0.4.1, <0.7" either = "1.5.1" env_logger = ">=0.9, <0.12" eyre = "0.6" fehler = "1" futures = "0.3" heck = ">=0.4, <0.6" hyper = { version = "1", features = ["full"] } hyper-util = "0.1.10" indenter = "0.3" ipnet = "2.3" itertools = ">=0.10.1, <0.15" lazy-regex = ">=2.4, <4" lazy_static = "1.4" libc = "0.2" # just for EISDIR due to IsADirectory log = "0.4.14" memchr = "2" mime = "0.3.4" nix = { version = ">=0.25, <0.30", features = ["fs", "process", "signal", "term", "uio"] } parking_lot = ">= 0.11, < 0.13" pin-project-lite = "0.2" reqwest = { version = "0.12.8", features = [ "default-tls" ] } regex = "1.5" sha2 = "0.10" subtle = "2" syslog = ">=6, <8" thiserror = ">=1.0.2, <3" tokio = { version = "1.43", features = ["full"] } void = "1" http-body = "1.0.1" http-body-util = "0.1.2" [build-dependencies] semver = "1.0.14" serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.41" work/DEVELOPER-CERTIFICATE0000664000000000000000000000261614766655527011754 0ustar Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. work/GPLv30000664000000000000000000010451314766655527007601 0ustar GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . work/HACKING.md0000664000000000000000000000060114766655527010342 0ustar # Hacking on hippotat (This file is not very comprehensive.) ## Testing Using upstream dependencies ``` NAILING_CARGO=nailing-cargo make check ``` Using dependencies from Debian ``` autopkgtest --ignore-restrictions=isolation-machine . --- schroot build ``` Run one test, ad-hoc, using a debug build ``` nailing-cargo build nailing-cargo --- test/with-unshare test/t-basic ``` work/Makefile0000664000000000000000000001053114766655527010417 0ustar # Copyright 2020-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. SHELL=/bin/bash default: all # the fifo-based jobserver doesn't work through nailing-cargo, obviously MAKEFLAGS += --jobserver-style=pipe SPHINXBUILD ?= sphinx-build INSTALL ?= install ifneq (,$(NAILING_CARGO)) NAILING_CARGO ?= nailing-cargo CARGO = $(NAILING_CARGO) BUILD_SUBDIR ?= ../Build TARGET_DIR ?= $(BUILD_SUBDIR)/$(notdir $(PWD))/target NAILING_CARGO_JUST_RUN ?= $(NAILING_CARGO) --no-nail --no-cargo-lock-manip -q --- else CARGO ?= cargo TARGET_DIR ?= target endif # $(NAILING_CARGO) CARGO_RELEASE ?= release TARGET_RELEASE_DIR ?= $(TARGET_DIR)/$(CARGO_RELEASE) ifneq (debug,$(CARGO_RELEASE)) CARGO_RELEASE_ARG ?= --$(CARGO_RELEASE) endif rsrcs = $(shell $(foreach x,$(MAKEFILE_FIND_X),set -$x;)\ find -H $1 \( -name Cargo.toml -o -name Cargo.lock -o -name Cargo.lock.example -o -name \*.rs \) ) stamp=@mkdir -p stamp; touch $@ TESTS=$(notdir $(wildcard test/t-*[^~])) MAN8PAGES=hippotat-setup-permissions.8 MANPAGES=$(MAN8PAGES) man8dir=/usr/share/man/man8 all: cargo-build doc check: cargo-test $(addprefix stamp/,$(TESTS)) cargo-build: stamp/cargo-build cargo-test: stamp/cargo-test stamp/cargo-%: $(call rsrcs,.) +$(CARGO) $* $(CARGO_RELEASE_ARG) $(CARGO_BUILD_OPTIONS) --workspace $(stamp) stamp/t-%: test/t-% stamp/cargo-build $(wildcard test/*[^~]) TARGET_RELEASE_DIR=$(abspath $(TARGET_RELEASE_DIR)) \ $(NAILING_CARGO_JUST_RUN) \ $(abspath test/capture-log) tmp/t-$*.log \ $(abspath test/go-with-unshare test/t-$*) @echo OK t-$*; touch $@ doc: docs/html/index.html $(MANPAGES) @echo 'Documentation can now be found here:' @echo ' file://$(PWD)/$<' docs/html/index.html: docs/conf.py $(wildcard docs/*.md docs/*.rst docs/*.png) rm -rf docs/html $(SPHINXBUILD) -M html docs docs $(SPHINXOPTS) hippotat-setup-permissions%: hippotat-setup-permissions%.pod m=$@; pod2man --section=$${m##*.} --date="Hippotat" \ --center=" " --name=$${m%.*} \ $^ $@ doch=/usr/share/doc/hippotat/ install: all $(INSTALL) -d $(DESTDIR)/usr/{bin,sbin} $(INSTALL) -d $(DESTDIR)$(doch) $(DESTDIR)$(man8dir) $(INSTALL) -m 755 $(TARGET_RELEASE_DIR)/hippotat $(DESTDIR)/usr/bin/. $(INSTALL) -m 755 $(TARGET_RELEASE_DIR)/hippotatd $(DESTDIR)/usr/sbin/. $(INSTALL) -m 755 hippotat-setup-permissions $(DESTDIR)/usr/bin/. cp -r docs/html $(DESTDIR)$(doch) $(INSTALL) -m 644 PROTOCOL.txt $(DESTDIR)$(doch)/ $(INSTALL) -m 644 $(MAN8PAGES) $(DESTDIR)$(man8dir)/. clean: rm -rf stamp/* tmp rm -rf docs/html docs/doctrees rm -f hippotat-setup-permissions.8 very-clean: clean +$(CARGO) clean #---------- release process ---------- # # Preparatory # cargo update and/or upgrades # # Checks # nailing-cargo -o audit # git clean -xdff && NAILING_CARGO=nailing-cargo make check # dgit -wgfa sbuild -c build -A # autopkgtest --ignore-restrictions=isolation-machine . --- schroot build # # Update release notes in debian/changelog: # # Update versions # * Cargo.toml version # * macros/Cargo.toml version # * debian/changelog finalise # # Squash branch if need be. # # VERSION=$(dpkg-parsechangelog -SVersion); echo $VERSION # # Rerun checks # * See "Check" above # * release-rust-crate --dry-run hippotat $VERSION # # == commitment point == # # make version update MR (see above), merge into main # # release-rust-crate hippotat $VERSION # make publish publish-make-current PUBLISH_VERSION=$VERSION # dgit -wgfa push-source sid # git-push --dry-run --follow-tags origin # git-push --follow-tags origin # # release announcement on mailing list and/or blog post #---------- docs publication ---------- PUBLISHED_BRANCH=published PUBLISH_VERSION=unreleased PUBLISH_USER=ianmdlvl@login.chiark.greenend.org.uk PUBLISH_DOC_SPHINX_BASE=public-html/hippotat PUBLISH_DOC_SPHINX_TAIL=$(PUBLISH_VERSION)/docs PUBLISH_DOC_SPHINX=$(PUBLISH_USER):$(PUBLISH_DOC_SPHINX_BASE)/$(PUBLISH_DOC_SPHINX_TAIL) publish: doc ssh $(PUBLISH_USER) 'cd $(PUBLISH_DOC_SPHINX_BASE) && mkdir -p $(PUBLISH_DOC_SPHINX_TAIL)' rsync -r --delete-delay docs/html/. $(PUBLISH_DOC_SPHINX)/. git branch -f $(PUBLISHED_BRANCH) publish-make-current: ssh $(PUBLISH_USER) 'set -e; cd $(PUBLISH_DOC_SPHINX_BASE); rm -f current.tmp; ln -s $(PUBLISH_VERSION) current.tmp; mv -T current.tmp current' .PHONY: cargo-build all doc clean work/PROTOCOL.txt0000664000000000000000000000315014766655527010760 0ustar Server maintains a queue of outbound packets for each user Packets which are older than the applicable max_queue_time are discarded Each incoming request to the server takes up to max_batch_down bytes from the queue and returns them as the POST response body payload Each incoming request contains up to max_batch_up bytes of payload. It's a multipart/form-data. Authentication: clock-based lifetime-limited bearer tokens. Encryption and integrity checking: none. Use a real VPN over this! Routing assistance: none in hippotat; can be requested on client from userv-ipif via `vroutes' parameter. Use with secnet polypath ideally uses the special support in secnet 0.4.x. Client form parameters (multipart/form-data): m metadata, newline-separated list (text file) of client ip address (textual) token target_requests_outstanding http_timeout mtu } not supplied max_batch_down } by older max_batch_up } clients d data (SLIP format, with SLIP_ESC and `-' swapped) Authentication token is: (separated by a single space). The hmac is HMAC(secret, ) and the hash function is SHA256 Possible future nonce-based authentication: server keeps big nonce counter for each client meaning is: nonce counter is most recent nonce client has sent also server keeps bitmap of the previous ?64 nonces, whether client has sent them difficult because client-generated nonces would have to never go backwaards which basically means never-rewinding state on the client. work/README.md0000664000000000000000000000417014766655527010240 0ustar Hippotat - asinine IP over HTTP =============================== Hippotat is a system to allow you to use your normal VPN, ssh, and other applications, even in broken network environments that are only ever tested with "web stuff". Packets are parcelled up into HTTP POST requests, resembling form submissions (or JavaScript XMLHttpRequest traffic), and the returned packets arrive via the HTTP response bodies. Scenario -------- You're in a cafe or a hotel, trying to use the provided wifi. But it's not working. You discover that port 80 and port 443 are open, but the wifi forbids all other traffic. Never mind, start up your hippotat client. Now you have connectivity. Your VPN and SSH and so on run over Hippotat. The result is not very efficient, but it does work. The design goal is that if the your barista's phone works OK, or the hotel concierge can see Google on their computer, you can use the internet properly, despite whatever breakage and nonsense. So Hippotat is an alternative to the futile strategy of trying to report technical bugs, or stupid portblocks, in terrible wifi systems. Of course it can't always help. If the wifi is bad enough that one's hosts' devices don't work reliably either, hopefully you can probably get them to reboot the magic box, or maybe get some money off, if wifi was supposed to be included. Non-goals --------- **Hippotat does not provide meaningful encryption**. You should use protocols over the top of it that you would be happy to run over the public internet: encrypted ones, like a VPN or SSH. Use of Hippotat is not intended to be undetectable, or even particularly hard to distinguish from other uses of HTTP, should someone want to go to the effort. Rather, it is intended to be deployed against idiocy, ignorance, and incompetence. Protection against interference is limited to trying to defend against off-path attackers, and arranging that formerly-on-path attackers' ability to do harm will expire reasonably soon. Hippotat is not designed to allow you to "leech" internet access from "closed" Wifi. It won't work if "normal web access" doesn't. You might try IP-over-DNS systems for that. work/adt/0000775000000000000000000000000014766655527007527 5ustar work/adt/acleanup0000775000000000000000000000045714766655527011253 0ustar #!/bin/bash set -e . "${0%/*}"/acommon if [ "$AUTOPKGTEST_TMP" ]; then tname="autopkgtest" tmp="$AUTOPKGTEST_TMP" host-cleanup client host-cleanup server else for tmp in tmp/adt-*; do tname=${tmp#tmp/} host-cleanup client host-cleanup server rm -r "$tmp" done fi echo ok. work/adt/acommon0000664000000000000000000001152714766655527011111 0ustar # -*- shell-script -*- # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -o pipefail set -x . "${0%/*}"/../test/tcommon exec >&2 on_failure=: test-prep () { trap ' rc=$? : =================== ^^ TEST FAILURE ^^ ==================== set +e tail -500 /var/log/daemon.log $on_failure exit $rc ' 0 determine-tname adt if [ "$AUTOPKGTEST_TMP" ]; then tmp=$AUTOPKGTEST_TMP else tmp=$PWD/tmp/$tname hosts-cleanup mkdir -p tmp rm -rf "$tmp" mkdir "$tmp" fi } filesystem-prep () { cs=$1; shift # expects $tmp to be set to test-specific temp dir (abs path) host-cleanup $cs mkdir "$tmp"/$cs-overlay for fs in root dev home; do case "$fs" in root) lower=/ ;; dev) lower=/dev ;; home) lower=/home ;; *) x-internal-error ;; esac rm -rf "$tmp"/$cs-$fs-{upper,work} mkdir "$tmp"/$cs-$fs-{upper,work} mount -t overlay -o \ lowerdir=$lower,upperdir="$tmp"/$cs-$fs-upper,workdir="$tmp"/$cs-$fs-work \ none "$tmp"/$cs-overlay$lower done } hosts-cleanup () { host-cleanup client host-cleanup server } host-cleanup () { cs=$1; shift fuser -Mkm "$tmp"/$cs-overlay ||: umount "$tmp"/$cs-overlay/proc ||: umount "$tmp"/$cs-overlay/dev ||: umount "$tmp"/$cs-overlay/home ||: umount "$tmp"/$cs-overlay ||: umount "$tmp"/$cs-pidns ||: if test -d "$tmp"/$cs-overlay; then rmdir "$tmp"/$cs-overlay fi } pidnamespace-prep () { cs=$1; shift touch "$tmp"/$cs-pidns rm -f "$tmp"/$cs-overlay/adt-pidns-sentinel mkfifo -m 600 "$tmp"/$cs-overlay/adt-pidns-sentinel unshare --fork --pid="$tmp"/$cs-pidns \ chroot "$tmp"/$cs-overlay \ sh -ec ' mount -t proc none /proc >/adt-pidns-sentinel sleep 10000000 ' & cat "$tmp"/$cs-overlay/adt-pidns-sentinel } configure () { cs=$1; shift in- $cs dd <$tmp/$cs-unwanted-deps local unwanted_deps=$(perl -ne ' next if m{^(?:userv-utils|libnetaddr-ip-perl|net-tools)$}; next if m{^hippotat-'"$cs"'$}; next if m{^(?:iptables|rsyslog|authbind)$} && "'"$cs"'" eq "server"; # TODO for rsyslog, record bug number here next if m{^(?:orphan-sysvinit-scripts)$} && "'"$cs"'" eq "server"; print; ' $tmp/$cs-unwanted-deps) in- $cs apt-mark auto $unwanted_deps in- $cs apt-get -y autoremove in- $cs service userv start configure $cs finish-setup-host-$cs } finish-setup-host-client () { yes '' | \ in- client adduser --disabled-password user ||: in- client adduser user _hippotat } finish-setup-host-server () { in- server iptables -D INPUT -j empty -s 192.0.2.0/24 ||: in- server iptables -N empty ||: in- server iptables -I INPUT -j empty -s 192.0.2.0/24 rsyslog-capture-daemon in- server in- server service rsyslog start || { # When the outer system is systemd, `service` doesn't find it # in the chroot, but the init script is in orphan-sysvinit-scripts # which is also missing. Run it by hand. in- server rsyslogd -n & sleep 5 } } in- () { cs=$1; shift in-ns $cs \ nsenter --pid="$tmp"/$cs-pidns \ chroot "$tmp"/$cs-overlay \ "$@" } setup-pair () { test-prep on_failure=pair-on-failure $test/netns-setup $tname setup-host client setup-host server } pair-on-failure () { tail -500 "$tmp"/server-overlay/var/log/daemon.log ||: hosts-cleanup } rsyslog-capture-daemon () { "$@" dd </etc/hippotat/main.cfg -pe ' s{^addrs *=.*}{addrs = 127.0.0.1}; ' cat >>/etc/default/hippotatd <&1 ||: ) \ | tee /dev/stderr | grep 'Connection refused' t-ok work/adt/in-0000775000000000000000000000026114766655527010137 0ustar #!/bin/bash set -e . "${0%/*}"/acommon tname="$1"; shift cs="$1"; shift if [ "$AUTOPKGTEST_TMP" ]; then tmp=$AUTOPKGTEST_TMP else tmp=$PWD/tmp/$tname fi in- $cs "$@" work/build.rs0000664000000000000000000000426114766655527010427 0ustar use serde::Deserialize; use std::process::Command; // We don't use cargo-metadata because it has a very unstable API! #[derive(Deserialize, Debug)] struct Metadata { workspace_members: Vec, resolve: MResolve, packages: Vec, } #[derive(Deserialize, Debug)] struct MResolve { nodes: Vec, } #[derive(Deserialize, Debug)] struct MNode { id: PackageId, dependencies: Vec, } #[derive(Deserialize, Debug)] struct MPackage { id: PackageId, version: String, name: String, } #[derive(Deserialize, Debug, Eq, PartialEq)] #[serde(transparent)] struct PackageId(String); impl Metadata { fn package(&self, id: &PackageId) -> &MPackage { self.packages.iter() .find(|p| &p.id == id).expect("missing") } } struct Context<'c> { hippo: &'c MNode, metadata: &'c Metadata, } impl Context<'_> { fn emit_for_package(&self, package: &str, versions: &[&str]) { let pkg = self.hippo.dependencies.iter() .map(|i| self.metadata.package(i)) .find(|i| i.name == package).expect("no nix in hippotat's deps"); let pkg = &pkg.version; let pkg: semver::Version = pkg.parse().expect(pkg); for test in versions { let cfg = test.replace('.', "_").replace(">=", "ge_"); let test: semver::VersionReq = test.parse().unwrap(); let cfg = format!("{package}_{cfg}"); println!(r#"cargo:rustc-check-cfg=cfg({cfg})"#); if test.matches(&pkg) { println!("cargo:rustc-cfg={cfg}"); } } } } fn main(){ let x = Command::new("cargo") .args(["metadata", "--format-version=1"]) .stderr(std::process::Stdio::inherit()) .output().unwrap(); if !x.status.success() { panic!() } let output = std::str::from_utf8(&x.stdout).unwrap(); // eprintln!("{}", output); let metadata: Metadata = serde_json::from_str(&output).unwrap(); let hippo = metadata.workspace_members.iter() .map(|i| metadata.package(i)) .find(|p| p.name == "hippotat").expect("no hippotat in workspace?"); let hippo = metadata.resolve.nodes.iter() .find(|n| n.id == hippo.id).expect("no hippotat in nodes!"); let ctx = Context { hippo, metadata: &metadata }; ctx.emit_for_package("nix", &[">=0.27", ">=0.28"]); } work/client/0000775000000000000000000000000014766655527010235 5ustar work/client/client.rs0000664000000000000000000002450114766655527012063 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. #![allow(clippy::style)] #![allow(clippy::unit_arg)] #![allow(clippy::useless_format)] #![allow(clippy::while_let_loop)] use hippotat::prelude::*; use hippotat_macros::into_crlfs; #[derive(clap::Parser,Debug)] pub struct Opts { #[clap(flatten)] log: LogOpts, #[clap(flatten)] config: config::CommonOpts, /// Print config item(s), do not actually run /// /// Argument is (comma-separated) list of config keys; /// values will be printed tab-separated. /// The key `pretty` dumps the whole config in a pretty debug format. /// /// One line is output for each association. /// Additional pseudo-config-keys are recognised: /// `client`: our client virtual IP address; /// `server`: server's logical name in the config; /// `link`: the link name including the `[ ]`. #[clap(long)] print_config: Option, } type OutstandingRequest<'r> = Pin>> + Send + 'r >>; struct ClientContext<'c> { ic: &'c InstanceConfig, hclient: &'c reqwest::Client, reporter: &'c parking_lot::Mutex>, } #[derive(Debug)] struct TxQueued { expires: Instant, data: Box<[u8]>, } #[throws(AE)] fn submit_request<'r, 'c:'r>( c: &'c ClientContext, req_num: &mut ReqNum, reqs: &mut Vec>, upbound: FramesData, ) { let show_timeout = c.ic.http_timeout .saturating_add(Duration::from_nanos(999_999_999)) .as_secs(); let time_t = time_t_now(); let time_t = format!("{:x}", time_t); let hmac = token_hmac(c.ic.secret.0.as_bytes(), time_t.as_bytes()); //dbg!(DumpHex(&hmac)); let mut token = time_t; write!(token, " ").unwrap(); BASE64_CONFIG.encode_string(hmac, &mut token); let req_num = { *req_num += 1; *req_num }; let prefix1 = format!(into_crlfs!( r#"--b Content-Type: text/plain; charset="utf-8" Content-Disposition: form-data; name="m" {} {} {} {} {} {} {}"#), &c.ic.link.client, token, c.ic.target_requests_outstanding, show_timeout, c.ic.mtu, c.ic.max_batch_down, c.ic.max_batch_up, ); let prefix2 = format!(into_crlfs!( r#" --b Content-Type: application/octet-stream Content-Disposition: form-data; name="d" "#), ); let suffix = format!(into_crlfs!( r#" --b-- "#), ); macro_rules! content { { $out:ty, $iter:ident, $into:ident, } => { itertools::chain![ IntoIterator::into_iter([ prefix1.$into(), prefix2.$into(), ]).take( if upbound.is_empty() { 1 } else { 2 } ), Itertools::intersperse( upbound.$iter().map(|u| { let out: $out = u.$into(); out }), SLIP_END_SLICE.$into() ), [ suffix.$into() ], ] }} let body_len: usize = content!( &[u8], iter, as_ref, ).map(|b| b.len()).sum(); trace!("{} #{}: req; tx body_len={} frames={}", &c.ic, req_num, body_len, upbound.len()); let body = http_body_util::StreamBody::new( futures::stream::iter( content!( Bytes, into_iter, into, ).map(|by| Ok::<_, Void>(http_body::Frame::data(by))) ) ); let req = { let url = c.ic.url.clone(); let mut req = reqwest::Request::new(reqwest::Method::POST, url); let h = req.headers_mut(); let ctype = r#"multipart/form-data; boundary="b""#; let ctype = reqwest::header::HeaderValue::from_static(ctype); h.insert("Content-Type", ctype); *req.body_mut() = Some(reqwest::Body::wrap(body)); req }; let resp = c.hclient.execute(req); let fut = Box::pin(async move { let r = async { tokio::time::timeout( c.ic.effective_http_timeout, async { let resp = resp.await.context("make request")?; let status = resp.status(); let max_body = c.ic.max_batch_down.sat() + MAX_OVERHEAD; let body = futures::stream::unfold(resp, |mut resp| async { resp.chunk().await.transpose().map(|r| (r, resp)) }); pin!(body); let resp = read_limited_bytes( max_body, default(), default(), body.as_mut(), ).await .context("fetching response body")?; if ! status.is_success() { throw!(anyhow!("HTTP error status={} body={:?}", &status, String::from_utf8_lossy(&resp))); } Ok::<_,AE>(resp) }).await? }.await; let r = c.reporter.lock().filter(Some(req_num), r); if let Some(r) = &r { trace!("{} #{}: rok; rx bytes={}", &c.ic, req_num, r.len()); } else { tokio::time::sleep(c.ic.http_retry).await; } r }); reqs.push(fut); } async fn run_client( ic: InstanceConfig, hclient: reqwest::Client, ) -> Result { debug!("{}: config: {:?}", &ic, &ic); let reporter = parking_lot::Mutex::new(Reporter::new(&ic)); let c = ClientContext { reporter: &reporter, hclient: &hclient, ic: &ic, }; let mut ipif = Ipif::start(&ic.ipif, Some(ic.to_string()))?; let mut req_num: ReqNum = 0; let mut tx_queue: VecDeque = default(); let mut upbound = Frames::default(); let mut reqs: Vec = Vec::with_capacity(ic.max_requests_outstanding.sat()); let mut rx_queue: FrameQueueBuf = default(); let trouble = async { loop { let rx_queue_space = if rx_queue.remaining() < ic.max_batch_down.sat() { Ok(()) } else { Err(()) }; select! { biased; y = ipif.rx.write_all_buf(&mut rx_queue), if ! rx_queue.is_empty() => { let () = y.context("write rx data to ipif")?; }, () = async { let expires = tx_queue.front().unwrap().expires; tokio::time::sleep_until(expires).await }, if ! tx_queue.is_empty() => { let _ = tx_queue.pop_front(); }, data = Ipif::next_frame(&mut ipif.tx), if tx_queue.is_empty() => { let data = data?; //eprintln!("data={:?}", DumpHex(&data)); match slip::process1(Slip2Mime, ic.mtu, &data, |header| { let saddr = ip_packet_addr::(header)?; if saddr != ic.link.client.0 { throw!(PE::Src(saddr)) } Ok(()) }) { Ok((data, ())) => tx_queue.push_back(TxQueued { data, expires: Instant::now() + ic.max_queue_time }), Err(PE::Empty) => { }, Err(e@ PE::Src(_)) => debug!("{}: tx discarding: {}", &ic, e), Err(e) => error!("{}: tx discarding: {}", &ic, e), }; }, _ = async { }, if ! upbound.tried_full() && ! tx_queue.is_empty() => { while let Some(TxQueued { data, expires }) = tx_queue.pop_front() { match upbound.add(ic.max_batch_up, data.into()/*todo:504*/) { Err(data) => { tx_queue.push_front(TxQueued { data: data.into(), expires }); break; } Ok(()) => { }, } } }, _ = async { }, if rx_queue_space.is_ok() && (reqs.len() < ic.target_requests_outstanding.sat() || (reqs.len() < ic.max_requests_outstanding.sat() && ! upbound.is_empty())) => { submit_request(&c, &mut req_num, &mut reqs, mem::take(&mut upbound).into())?; }, (got, goti, _) = async { future::select_all(&mut reqs).await }, if ! reqs.is_empty() => { // This future was Ready and has returned the value, // which is in `got`. We don't want the completed future. let _: Pin>> = reqs.swap_remove(goti); if let Some(got) = got { //eprintln!("got={:?}", DumpHex(&got)); match slip::processn(SlipNoConv,ic.mtu, &got, |header| { let addr = ip_packet_addr::(header)?; if addr != ic.link.client.0 { throw!(PE::Dst(addr)) } Ok(()) }, |(o,())| future::ready(Ok({ rx_queue.push_esc(o); })), |e| Ok::<_,SlipFramesError>( { error!("{} #{}: rx discarding: {}", &ic, req_num, e); })).await { Ok(()) => reporter.lock().success(), Err(SlipFramesError::ErrorOnlyBad) => { reqs.push(Box::pin(async { tokio::time::sleep(ic.http_retry).await; None })); }, Err(SlipFramesError::Other(v)) => unreachable!("{}", v), } } }, _ = tokio::time::sleep(c.ic.effective_http_timeout), if rx_queue_space.is_err() => { reporter.lock().filter(None, Err::( anyhow!("rx queue full, blocked") )); }, } } }.await; ipif.quitting(Some(&ic)).await; trouble } #[tokio::main] async fn main() { let opts = ::parse(); let (ics,) = config::startup( "hippotat", LinkEnd::Client, &opts.config, &opts.log, |_, ics| { PrintConfigOpt(&opts.print_config) .implement(ics, )?; Ok(()) }, |_, ics| async move { Ok((ics,)) }).await; let hclient = reqwest::Client::builder() .http1_title_case_headers() .build().expect("build reqwest Client"); info!("starting"); let () = future::select_all( ics.into_iter().map(|ic| Box::pin(async { let assocname = ic.to_string(); info!("{} starting", &assocname); let hclient = hclient.clone(); let join = task::spawn(async { run_client(ic, hclient).await.void_unwrap_err() }); match join.await { Ok(e) => { error!("{} failed: {}", &assocname, e); }, Err(je) => { error!("{} panicked!", &assocname); panic::resume_unwind(je.into_panic()); }, } })) ).await.0; error!("quitting because one of your client connections crashed"); process::exit(16); } #[test] fn verify_cli() { hippotat::utils::verify_cli::(); } work/debian/0000775000000000000000000000000014766655527010201 5ustar work/debian/.gitignore0000664000000000000000000000030014766655527012162 0ustar .debhelper debhelper-*-stamp *.debhelper.log files tmp hippotat-client hippotat-client.substvars hippotat-server hippotat-server.substvars hippotat-common hippotat-common.substvars cargo_home work/debian/changelog0000664000000000000000000002114714766655527012060 0ustar hippotat (1.2.2) unstable; urgency=medium * Replace config custom proc macro with derive-deftly. * Dependency updates: allow syn 2. * Makefile: Force pipe-based jobserver. -- Ian Jackson Thu, 20 Mar 2025 00:03:35 +0000 hippotat (1.2.1) unstable; urgency=medium * autopkgtests: Many fixes/improvements; should now work in ci.debian.net. * HACKING.md (internal docs): Minor improvement. * Includes all desired changes from 1.2.0+exp1 to 1.2.0+exp16 inclusive. * cargo update (upstream Cargo.lock), fixes RUSTSEC-2023-0067. -- Ian Jackson Wed, 05 Feb 2025 22:04:10 +0000 hippotat (1.2.0) unstable; urgency=medium HTTP implementation changed: * Client now uses reqwest (and thus hyper 1) * Server now uses hyper 1 * Closes: #1094093. [Report from Lucas Nussbaum] Build system: * Makefile: Add + sigil before cargo invocations. Fixes compatibility with recent GNU make. * autopkgtests: Declare isolation-machine restriction, rather than x-hippotat-adt-broken-in-debci. Dependency updates: * New deps: http-body 1.0.1, http-body-util 0.1.2, hyper-util 0.1.10 * Require backtrace 0.3.74, tokio 1.43 * Allow educe 0.6, itertools 0.14, thiserror 2 * cargo update (upstream Cargo.lock) Internal tidying up: * HACKING.md: New file with some notes in. * Clarifications to config item defaulting traits. -- Ian Jackson Sun, 02 Feb 2025 16:59:18 +0000 hippotat (1.1.12) unstable; urgency=medium Dependency updates: * Allow itertools 0.13. Closes: #1082550. * Allow base64. Closes: #1084523. * Allow nix 0.29 * Allow heck 0.5 * Allow syslog 7 * cargo update (upstream Cargo.lock) Internal tidying up: * build.rs: Suppress new cargo features warning * Decorate rust-toolchain build-deps with !upstream-cargo profile * Makefile: fix an endif comment * Add missing jobserver sigils * Avoid nailing-cargo --just-run * Move a misplaced comment in cargo.toml -- Ian Jackson Fri, 11 Oct 2024 15:27:37 +0100 hippotat (1.1.11) unstable; urgency=medium Debian package: * Fix clean target. Closes: #1064524. [Peter Green] * debian/update-build-deps: Handle build-dependencies too. Dependencies: * Be able to build with nix 0.28 too, and update Cargo.lock. * Update other dependencies. Build system: * Improve Makefile clean target. * Do compatibility with nix API changes via build.rs, cargo metadata, and #[cfg]. -- Ian Jackson Sun, 25 Feb 2024 18:47:15 +0000 hippotat (1.1.10) unstable; urgency=medium Dependencies and packaging: * Provide compatibility with nix 0.27. (0.26 still supported.) Closes: #1064487. [Report from Peter Green] * Relax many other dependencies to allow newer versions. * Declare MSRV of 1.63. Testing: * Fix/improve the minimal-versions-* tests in CI. Misc: * Suppress a (spurious) warning. * Minor updates to internal docs and CI organisation. -- Ian Jackson Fri, 23 Feb 2024 16:56:41 +0000 hippotat (1.1.9) unstable; urgency=medium Bugfix (for future compatibility): * base64: Tolerate lack of padding when decoding. (Bug introduced in 1.1.8.) Packaging: * Makefile: update release checklist for experience with 1.1.8 * Update the provided Cargo.lock. -- Ian Jackson Fri, 16 Jun 2023 12:18:32 +0100 hippotat (1.1.8) unstable; urgency=medium Dependencies: * base64: update to 0.21 and fix code to build with it [Peter Green, Ian Jackson] Closes: #1037351. We are now incompatible with Debian bookworm, and targeting trixie. * clap: update to clap 4 and fix code to build and run with it. Packaging: * Make version minima in Cargo.toml accurate (and tested). * Tidying-up of Cargo.toml. * Update the provided Cargo.lock. Debian packaging: * Add version specifiers for all Rust build-dependencies. (Extracted from the Cargo.toml's minima, which are now correct.) * debian/update-build-deps: Add --check mode * debian/update-build-deps: Use a real TOML parser * debian/update-build-deps: Scan macros/ too Tests: * test/capture-log: Make it possible to disable capturing * Add many gitlab (sal) CI tests of both upstream and Debian builds, cargo audit, metadata consistency, etc. Sadly no end-to-end client/server tests due to lack of a convenient machine-level isolation facility. -- Ian Jackson Thu, 15 Jun 2023 22:16:53 +0100 hippotat (1.1.7) unstable; urgency=medium Build system: * Makefile; Install the hippotat-setup-permissions(8) manpage to the right place. (Bug introduced in 1.1.6.) * debian: ship the hippotat-setup-permissions(8) manpage. * init script: Demote userv and $syslog to Should-Start. * Update dependencies in provided Cargo.lock. Changes specifically for uploading to Debian: * debian/update-build-deps: support replacement (override) * Declare that we need clap 3, with +derive * Run dh_missing --fail-missing (for QA test) * Change source format to 1.0 (native) so we can have a revision -- Ian Jackson Thu, 12 Jan 2023 18:50:36 +0000 hippotat (1.1.6) unstable; urgency=medium Improvements: * New script hippotat-setup-permissions to help with authbind and ipif. * Config inspection command options --print-config improved. Documentation: * install.md: Say to get hippotat from your distro. * install.md: Say to do the hippotat config first * install.md: Document new setup script (which also has manpage) * settings.md &c: Explain how to set up server with TLS (not built-in). Testing and administrivia: * Many improvements to the autopkgtests. * However, disable the autopkgtests as they still don't work. https://lists.debian.org/debian-devel/2023/01/msg00095.html * Improve release checklist. * cargo update (and add a note about base64 version). * Changelog entries for Debian-only 1.1.5+exp1 to 1.1.5+exp7 deleted. -- Ian Jackson Wed, 11 Jan 2023 02:15:01 +0000 hippotat (1.1.4) unstable; urgency=medium Bugfixes: * Improved error message about failure to initialise syslog. * Improved error reporting of ipif failures (and debug loggong). * Fix build on platforms with unsigned chars. Closes: #1028028. [Reported by Adrian Bunk] Packaging improvements: * Add empty /etc/hippotat/config.d (to hippotat-common). * init script: Print better error message about firewall. * debian/control: Add Homepage and Vcs-Git. [Reported by Axel Beckert] * debian/control: Add many missing Recommends. * LICENCE aka d/copyright improvements. [Requested by Debian ftpmaster] * Makefile: Fixes to release checklist Testing: * Add autopkgtest that tests a full-on setup, with two simulated hosts. * Add autopkgtest that tests the init script. * Supporting fixes and refactoring. * Debian-only release, for exposure to Debian infrastructure. -- Ian Jackson Sun, 08 Jan 2023 17:09:33 +0000 hippotat (1.1.3) unstable; urgency=medium Packaging and build improvements: * Change `cargo-upstream` build-profile to `upstream-cargo` as per debian-devel discussion. * Add link to ITP bug, here: Closes:#1025898. * Updated dependencies. Burned version numbers: * Burned 1.1.2 due to Debian #943374. -- Ian Jackson Sun, 18 Dec 2022 22:35:43 +0000 hippotat (1.1.1) unstable; urgency=medium Packaging and build improvements: * Adjusted dependencies --- generally, relaxed. * Updated and improved build system. * Updated and improved Debian packaging. * Internal improvements (clippy lints etc.) * Test suite fixes * Fix busted lockfile. (Fixes salsa #1.) * Docs refer to source code more prominently. (Fixes salsa #2.) * Can now build with sbuild in Debian bookworm (subject to build-deps getting through NEW). Burned version numbers: * Burned 1.1.0 due to cargo publish awkardness and fail. -- Ian Jackson Thu, 15 Dec 2022 01:16:59 +0000 hippotat (1.0) unstable; urgency=medium * Initial general release -- Ian Jackson Wed, 28 Sep 2022 19:52:51 +0100 hippotat (0.2) unstable; urgency=medium * Rust version. Testing some packaging. -- Ian Jackson Tue, 13 Sep 2022 14:03:22 +0100 hippotat (0.1~UNRELEASED) unstable; urgency=medium * Python version. Not released. -- Ian Jackson Sat, 08 Apr 2017 17:57:42 +0100 work/debian/compat0000664000000000000000000000000314766655527011400 0ustar 12 work/debian/control0000664000000000000000000001107114766655527011604 0ustar Source: hippotat Maintainer: Ian Jackson Section: net Priority: optional Homepage: https://www.chiark.greenend.org.uk/~ianmdlvl/hippotat/current/docs/ Vcs-Git: https://salsa.debian.org/iwj/hippotat Build-Depends: debhelper (>= 12), python3-sphinx, python3-recommonmark, python3-sphinx-markdown-tables, moreutils, libssl-dev (>= 1.1), pkg-config, cargo , rustc , # debian/update-build-deps manages these: librust-backtrace-dev (>= 0.3.74~) , librust-base64-dev (>= 0.21~) , librust-cfg-if-dev (>= 1~) , librust-clap+derive-dev (>= 4~) , librust-derive-deftly-dev (>= 1~) , librust-easy-ext-dev (>= 1~) , librust-educe-dev (>= 0.4.1~) , librust-either-dev (>= 1.5.1~) , librust-env-logger+default-dev (>= 0.9~) , librust-eyre-dev (>= 0.6~) , librust-fehler-dev (>= 1~) , librust-futures-dev (>= 0.3~) , librust-heck-dev (>= 0.4~) , librust-http-body-dev (>= 1.0.1~) , librust-http-body-util-dev (>= 0.1.2~) , librust-hyper-dev (>= 1~) , librust-hyper-util-dev (>= 0.1.10~) , librust-indenter-dev (>= 0.3~) , librust-ipnet-dev (>= 2.3~) , librust-itertools-dev (>= 0.10.1~) , librust-lazy-regex-dev (>= 2.4~) , librust-lazy-static-dev (>= 1.4~) , librust-libc-dev (>= 0.2~) , librust-log-dev (>= 0.4.14~) , librust-memchr-dev (>= 2~) , librust-mime-dev (>= 0.3.4~) , librust-nix-dev (>= 0.25~) , librust-parking-lot-dev (>= 0.11~) , librust-pin-project-lite-dev (>= 0.2~) , librust-proc-macro2-dev (>= 1~) , librust-quote-dev (>= 1~) , librust-regex-dev (>= 1.5~) , librust-reqwest-dev (>= 0.12.8~) , librust-semver-dev (>= 1.0.14~) , librust-serde-dev (>= 1.0.106~) , librust-serde-json-dev (>= 1.0.41~) , librust-sha2-dev (>= 0.10~) , librust-subtle-dev (>= 2~) , librust-syn-dev (>= 1~) , librust-syslog-dev (>= 6~) , librust-thiserror-dev (>= 1.0.2~) , librust-tokio-dev (>= 1.43~) , librust-void-dev (>= 1~) , Standards-Version: 4.6.1 Package: hippotat-client Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, hippotat-common Recommends: hippotat-doc, userv, userv-utils (>= 0.6.0~~iwj4), libnetaddr-ip-perl, net-tools Description: IP Over HTTP (Asinine) - client IP-over-HTTP client. . Hippotat is a system to allow you to use your normal VPN, ssh, and other applications, even in broken network environments that are only ever tested with “web stuff”. . This package contains the client, which you typically run on your laptop as and when required. Package: hippotat-server Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, hippotat-common, lsb-base Pre-Depends: ${misc:Pre-Depends} Recommends: hippotat-doc, userv, userv-utils (>= 0.6.0~~iwj4), libnetaddr-ip-perl, net-tools, iptables, rsyslog | system-log-daemon Suggests: authbind Description: IP Over HTTP (Asinine) - server IP-over-HTTP server. . Hippotat is a system to allow you to use your normal VPN, ssh, and other applications, even in broken network environments that are only ever tested with “web stuff”. . This package contains the server, which you would typically keep running on a stable and well-connected machine. Package: hippotat-common Architecture: all Depends: ${misc:Depends}, ${shlibs:Depends}, adduser Recommends: ${sphinxdoc:Depends} Description: IP Over HTTP (Asinine) - common files, including docs IP-over-HTTP system common files, including documentation. . Hippotat is a system to allow you to use your normal VPN, ssh, and other applications, even in broken network environments that are only ever tested with “web stuff”. . This package contains the common files need by both client and server, and the documentation. work/debian/copyright0000664000000000000000000001665214766655527012146 0ustar Hippotat - Asinine IP Over HTTP program Copyright 2017,2019-2022 Ian Jackson and contributors to Hippotat The client and server are: GPLv3+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program, in the file GPLv3. If not, see . On a Debian system, the GNU General Public License v3 can be found in /usr/share/common-licenses/GPL-3 Formalities ----------- The licence is generally indicated in each file with an SPDX delcaration; as is conventional, this should be read as a licence grant. Contributions to Hippotat are accepted based on the git commit Signed-off-by convention, by which the contributors' certify their contributions according to the Developer Certificate of Origin version 1.1 - see the file DEVELOPER-CERTIFICATE. If you create a new file please be sure to add an appropriate licence header, probably something like this: // Copyright 2017-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: AGPL-3.0-or-later // There is NO WARRANTY. OpenSSL Licence Exception ------------------------- Additional permission under GNU GPL version 3 section 7 [LicenseRef-Hippotat-OpenSSL-Exception]: In addition, as a special exception, the copyright holders give permission to link the code of this program with the OpenSSL Library (or with modified versions of OpenSSL that use the same license as OpenSSL 1.1), and distribute linked combinations including the two. BUT this does NOT apply to version 3.0 or any later version of the OpenSSL library (nor to any modified versions thereof). (We make this restriction because the exception is not needed with OpenSSL 3.) FURTHERMORE to take advantage of this exception you must obey the GNU Lesser General Public License (version 3, or, at your option, any later version) in all respects for all of the code used other than OpenSSL. This additional permission is indicated in the source code with the following SPDX licence declaration: SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception (which is roughly but not exactly in accordance with the SPDX spec.) If you modify a file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete the exception statement from your version, changing it to: SPDX-License-Identifier: GPL-3.0-or-later The authors of Hippotat will probably withdrawn this exception in a future versions of Hippotat, when OpenSSL 3.0 (which has a GPLv3+-compatible licence) has become sufficiently widespread. But that will not affect already-released versions of hippotat. Licences of other code in the source tree ----------------------------------------- The uml/ directory contains some currently non-functional test arrangements. This is not currently used (or built or installed). Within this directory there is: test/via-cargo-install-in-ci Originally copied from arti.git#7aacbc617ca62b0371a908b5edca9a10aa4d36fa into derive-adhoc.git, where it was further modified. We copied from derive-adhoc.git#c33a2b02ce0d2a7e2a318abec50e6eab8e7bf880 MIT License Copyright 2019-2022, The Tor Project, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. uml/rndaddtoentcnt/ MIT License Copyright (c) 2018 Jumpnow Technologies, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. uml/psusan-*/ Contains an example by Simon Tatham taken from the PuTTY documentation. PuTTY is copyright 1997-2022 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff, and CORE SDI S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. work/debian/hippotat-client.install0000664000000000000000000000002114766655527014666 0ustar usr/bin/hippotat work/debian/hippotat-common.dirs0000664000000000000000000000004514766655527014201 0ustar /etc/hippotat /etc/hippotat/config.d work/debian/hippotat-common.install0000664000000000000000000000015214766655527014705 0ustar usr/bin/hippotat-setup-permissions usr/share/doc/hippotat usr/share/man/man8/hippotat-setup-permissions.8 work/debian/hippotat-common.postinst0000664000000000000000000000034714766655527015130 0ustar #!/bin/sh set -e #DEBHELPER# adduser --system --group --force-badname --shell=/bin/bash \ --quiet --home /etc/hippotat _hippotat umask 077 pd=/etc/hippotat/secrets.d test -d $pd || \ install -m 750 -o root -g _hippotat -d $pd work/debian/hippotat-common.postrm0000664000000000000000000000012214766655527014560 0ustar #!/bin/sh set -e #DEBHELPER# case "$1" in purge) rm -rf /etc/hippotat ;; esac work/debian/hippotat-server.hippotatd.init0000664000000000000000000000433014766655527016215 0ustar #!/bin/sh ### BEGIN INIT INFO # Provides: hippotatd # Required-Start: $network $local_fs # Required-Stop: $network $local_fs # Should-Start: userv $syslog # Should-Stop: userv $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: hippotatd # Description: Asinine IP over HTTP server ### END INIT INFO DAEMON=/usr/sbin/hippotatd MAIN_CONFIG=/etc/hippotat/main.cfg USER=_hippotat GROUP=_hippotat PIDFILE=/var/run/hippotat/hippotatd.pid LOGFACILITY=daemon CHECK_FIREWALL=true # HIPPOTATD_ARGS AS_USER=as_user_userv DESCRIPTION='Asinine IP over HTTP server' if type authbind >/dev/null 2>&1; then AUTHBIND=authbind; fi test -e /etc/default/hippotatd && . /etc/default/hippotatd set -e test -f $DAEMON || exit 0 egrep '^[^ #]' $MAIN_CONFIG >/dev/null 2>&1 || exit 0 . /lib/lsb/init-functions as_user_userv () { userv --override ' execute-from-path no-suppress-args ' $USER "$@" } ssd () { set +e start-stop-daemon --quiet --user $USER --pidfile=$PIDFILE "$@" rc=$? set -e } ensure_dirs () { pidfiledir=${PIDFILE%/*} if test -d ${pidfiledir}; then return; fi mkdir -m 755 $pidfiledir chown $USER $pidfiledir } dump_firewall () { iptables -L -v -n } print_config () { $AS_USER $DAEMON $HIPPOTATD_ARGS --print-config "$1" } check_firewall () { $CHECK_FIREWALL || return 0 vnetwork=$(print_config vnetwork) if dump_firewall | fgrep " $vnetwork " >/dev/null; then :; else log_failure_msg \ "error: no entry in firewall for insecure vnetwork $vnetwork" exit 1 fi } do_start () { check_firewall ensure_dirs ssd --chuid $USER --start \ --startas /bin/sh -- -ec '"$@"' x \ $AUTHBIND $DAEMON --daemon --pidfile=$PIDFILE \ --syslog-facility=$LOGFACILITY $HIPPOTATD_ARGS } do_stop () { ssd --stop --oknodo --retry 5 } case "$1" in start) log_daemon_msg "Starting $DESCRIPTION" hippotatd do_start log_end_msg $rc exit $rc ;; stop) log_daemon_msg "Stopping $DESCRIPTION" hippotatd do_stop log_end_msg $rc exit $rc ;; restart|force-reload) log_daemon_msg "Restarting $DESCRIPTION" hippotatd do_stop sleep 1 do_start log_end_msg $rc ;; reload) log_failure_msg "Cannot reload hippotat - need restart" exit 1 ;; *) echo >&2 "$0: unknown action $1" exit 1 ;; esac exit 0 work/debian/hippotat-server.install0000664000000000000000000000002314766655527014720 0ustar usr/sbin/hippotatd work/debian/rules0000775000000000000000000000231714766655527011264 0ustar #!/usr/bin/make -f %: dh $@ --with sphinxdoc override_dh_installinit: dh_installinit --name=hippotatd # For local testing with upstream dependencies: # NAILING_CARGO=nailing-cargo make check # NAILING_CARGO=nailing-cargo dpkg-buildpackage -Pupstream-cargo -uc - ifeq (,$(filter upstream-cargo, $(DEB_BUILD_PROFILES))) include /usr/share/dpkg/architecture.mk include /usr/share/dpkg/buildflags.mk include /usr/share/rustc/architecture.mk export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS export DEB_HOST_RUST_TYPE DEB_HOST_GNU_TYPE PATH:=/usr/share/cargo/bin:$(PATH) CARGO_HOME=$(shell pwd)/debian/cargo_home DEB_CARGO_CRATE=hippotat export PATH CARGO_HOME DEB_CARGO_CRATE TARGET_RELEASE_DIR = target/$(DEB_HOST_RUST_TYPE)/release export TARGET_RELEASE_DIR execute_before_dh_auto_build: if test -f Cargo.lock; then mv Cargo.lock Cargo.lock.upstream; fi cargo prepare-debian /usr/share/cargo/registry execute_before_dh_auto_clean: if test -f Cargo.lock.upstream; \ then mv Cargo.lock.upstream Cargo.lock; fi execute_after_dh_auto_clean: $(MAKE) very-clean rm -rf debian/cargo_home override_dh_auto_test: @echo not running tests during build - they need unshare override_dh_missing: dh_missing --fail-missing endif work/debian/source/0000775000000000000000000000000014766655527011501 5ustar work/debian/source/format0000664000000000000000000000000414766655527012706 0ustar 1.0 work/debian/source.lintian-overrides0000664000000000000000000000021514766655527015057 0ustar # This is registered (at https://wiki.debian.org/BuildProfileSpe): hippotat source: invalid-profile-name-in-source-relation upstream-cargo * work/debian/tests/0000775000000000000000000000000014766655527011343 5ustar work/debian/tests/control0000664000000000000000000000104614766655527012747 0ustar Tests: adt-full Tests-Directory: adt Depends: hippotat-client, hippotat-server, userv-utils, libnetaddr-ip-perl, net-tools, rsyslog, authbind, iptables, bash, iproute2, util-linux, iputils-ping, orphan-sysvinit-scripts | systemd-sysv, psmisc Restrictions: needs-root, allow-stderr, isolation-machine Tests: adt-initscript Tests-Directory: adt Depends: hippotat-server, net-tools, rsyslog, orphan-sysvinit-scripts | systemd-sysv, bash, userv-utils, netcat-openbsd, curl Restrictions: needs-root, allow-stderr, isolation-machine work/debian/update-build-deps0000775000000000000000000000367714766655527013454 0ustar #!/usr/bin/perl -w use strict; use IO::Handle; use TOML qw(from_toml); our $mode = "@ARGV" eq '--check' ? 'check' : "@ARGV" eq '' ? 'install' : die "$0: bad usage\n"; open I, "debian/control" or die $!; open O, ">debian/control.new" or die $!; our %need_features = qw( clap +derive env-logger +default ); our %replace = ( ); sub convert_version ($) { local ($_) = @_; s{^\>= ?([^,]+)\, \<[^,]+$}{$1}; if (m{^\d+(?:\.\d+){0,2}$}) { return "(>= $_~)"; } else { warn "don't know how to convert Cargo version spec \`$_`"; } } while () { if (m{^\# debian/update-build-deps}i...m{^\S}) { next if m{^ +librust[-+a-z0-9.]+ (?:\(.*\) )?\,?\s*$}; if (m{^\S} && !m{^\#}i) { local ($_); my %outputs; foreach my $file (qw(Cargo.toml macros/Cargo.toml)) { open C, $file or die "$file $!"; my $toml = do { local $/ = undef; // die "$file $!"; }; C->error and die "$file $!"; $toml = from_toml($toml) || die "$file ?"; foreach my $kw (qw(dependencies build-dependencies)) { foreach my $p (keys %{ $toml->{$kw} }) { my $info = $toml->{$kw}{$p}; if (!ref $info) { $info = { 'version' => $info }; } next if length $info->{path}; my $version = $info->{version} // die "$file $p ?"; $version = convert_version($version); $version = " $version" if length $version; $p =~ y/_/-/; my $dep = $replace{$p}; if (!defined $dep) { my $f = $need_features{$p} // ''; $dep = "librust-$p$f-dev"; } if (length $dep) { $outputs{"$dep$version "}++; } } } } foreach my $output (sort keys %outputs) { print O " $output,\n" or die $!; } } } print O or die $!; } I->error and die $!; close O or die $!; if ($mode eq 'install') { rename "debian/control.new", "debian/control" or die $!; } else { exec qw(diff -u), "debian/control", "debian/control.new"; } work/docs/0000775000000000000000000000000014766655527007707 5ustar work/docs/README.md0000777000000000000000000000000014766655527012653 2../README.mdustar work/docs/colophon.md0000664000000000000000000000133114766655527012050 0ustar Colophon and references ======================= Hippotat is Copyright 2017-2022 Ian Jackson and contributors. Hippotat is released under the GNU GPLv3+ with an OpenSSL linking exception. See the file `COPYING` in the source tree for the full licence text. There is NO WARRANTY. The [Documentation for the current version](https://www.chiark.greenend.org.uk/~ianmdlvl/hippotat/current/docs/) is online, as well as [for earlier versons](https://www.chiark.greenend.org.uk/~ianmdlvl/hippotat/). The release notes are maintained in the [Debian-for amt changelog](https://salsa.debian.org/iwj/hippotat/-/blob/main/debian/changelog) [Hippotat's source repository](https://salsa.debian.org/iwj/hippotat) is hosted on Debian Salsa. work/docs/conf.py0000664000000000000000000001266014766655527011213 0ustar # -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'Hippotat' copyright = '2021 Ian Jackson and the contributors to Hippotat' author = 'Ian Jackson et al' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags release = '' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'recommonmark', 'sphinx.ext.autosectionlabel', 'sphinx_markdown_tables', ] autosectionlabel_prefix_document = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # #source_suffix = ['.rst', '.md'] #source_suffix = '.rst' # https://github.com/readthedocs/recommonmark (retrieved 8.4.2021) from recommonmark.parser import CommonMarkParser source_parsers = { '.md': CommonMarkParser, } source_suffix = { '.rst': 'restructuredtext', '.txt': 'markdown', '.md': 'markdown', } # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'classic' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'Hippotatdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Hippotat.tex', 'Hippotat Documentation', 'Ian Jackson and the contributors to Hippotat', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'otter', 'Hippotat Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Hippotat', 'Hippotat Documentation', author, 'Hippotat', 'Online Table Top Environment Renderer', 'Games'), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] work/docs/config.md0000664000000000000000000000412014766655527011473 0ustar Configuration scheme ==================== Configuration is in an INI-like format. Sections start with lines `[...]`. Every setting must be in a section. `#` and `;` comment lines are supported. Settings are `nmae = value`. Whitespace around the name and value is ignored. The configuration files are resolved to a configuration for each pairwise **link** between a client and a server. Sections -------- The same config key may appear in multiple sections; for example, in a section specific to a link, as well as one for all links to a server. Unless otherwise specified, any particular config setting for a particular link is the value from the first of the following applicable sections, or failing that the built-in default: * `[ ]` * `[]` * `[]` (often `[SERVER]`) * `[COMMON]` `` is the client's virtual address in IPv4 or IPv6 literal syntax (without any surrounding `[..]`. `` must be in the syntrax of a valid lowercase DNS hostname (and not look like an address), or be literally `SERVER`. There are also these special sections: * `[ LIMIT]` * `[LIMIT]` Files ----- Both client and server read the files * `/etc/hippotat/main.cfg` (if it exists) * `/etc/hippotat/config.d/*` * `/etc/hippotat/secrets.d/*` Here `*` means all contained files whose names consists of only ascii alphanumerics plus `-` and `_`. The `--config` option can be used to override the directory (usually `/etc/hippotat`). Additonally each `--extra-config` option names an existing file or directory to be processed analagously. The ini file format sections from these files are all unioned. Later files (in the list above, or alphabetically later) can override settings from earlier ones. Note that although it is conventional for information for a particular server or client to be in a file named after that endpoint, there is no semantic link: all the files are always read and the appropriate section from each is applied to every link. (If `main.cfg` does not exist, `master.cfg` will be tried for backward compatibility reasons.) work/docs/index.rst0000664000000000000000000000040614766655527011550 0ustar Hippotat - Asinine IP over HTTP =============================== .. toctree:: :maxdepth: 2 :caption: Contents: README.md install.md config.md settings.md colophon.md Indices and tables ================== * :ref:`genindex` * :ref:`search` work/docs/install.md0000664000000000000000000001241414766655527011701 0ustar Installation ============ Hippotat is written in Rust. The documentation is procssed with Sphinx. It is most convenient to install (especailly the server) as a `.deb`. On a recent-enough Debian derivative you can simply run (on each end as applicable): ``` apt install hippotat-server apt install hippotat-client ``` Building -------- Obtain the source code from the canonical server: ### Building with vanilla Debian tooling On a Debian (or derivative) with compatible dependencies, you can use the standard Debian build runes: ``` apt-get build-dep . dpkg-buildpackage -uc -b ``` At the time of writing this works on Debian testing (aka "bookworm"). ### Building binary packages with hybrid tooling Because of impedance mismatches between distros' approaches to packaging, and the language specifkc package manager treadmill, it can easily happy that you can't build on your Debian release. You can use a hybrid approach instead: On a system with a new enough `cargo` and `rustc`: ``` apt-get -Pcargo-upstream build-dep . CARGO='cargo --locked' dpkg-buildpackage -Pcargo-upstream -uc -b ``` Sadly, Debian bullseye is not new enough; to build there you will need to install Rust from upstream e.g. [using Rustup](https://www.rust-lang.org/learn/get-started). In any case, the build runes above will download dependencies from the upstream Rust package repository. The precise versions of those dependencies, and their precise contents, are controlled and checked by the `Cargo.lock` file shipped in the Hippotat source tree, provided that `cargo --locked` is used. So the invocation above is *not* equivalent to some `curl|bash` rune. ### Building executables with upstream Rust ecosystem tooling If you don't want to install debs, you can also just use cargo to build the two binaries, `hippotat` and `hippotatd`: ``` cargo build --locked --release ``` Hippotat releases are also published to `crates.io`, so you could perhaps even `cargo install hippotat`. Like any `cargo install`, this *is* morally equivalent to `curl|bash`. Installation ------------ * On the server: `apt install hippotat-{server,common}_*.deb` * On the client: `apt install hippotat-{client,common}_*.deb` This will also install the `userv` privsep tool, and the `userv-ipif` utility for unprivileged network interface management. Configuration ------------- You will need to: 1. Choose a suitable URL that Hippotat will be able to listen on. You can use a reverse proxy (but you may want to suppress some of the logging). The URL should be port 80, or 443 with TLS (with a reverse proxy). If using a reverse proxy, you must choose an internal IP port for the Hippotat server to use. 2. Select a private network range for use by the IP-over-HTTP system, and assign addresses to the server and to each client. If you use a range from RFC1918, choose it at random, eg using the [Cambridge G-RIN](https://www.ucam.org/cam-grin/). 3. Configure your firewalls to restrict access from that range to internal resources (eg, which might otherwise trust Hippotat addresses due to them being in RFC1918 private use ranges). Note that **Hippotat does not provide strong authentication or confidentiality**. 4. Configure `hippotat` itself, at both ends. 5. Configure `authbind` to allow the service user `_hippotat` (created by the package installation) to bind to the port you have chosen, on the server. 6. Configure `ipif` to allow the service user `_hippotat` (on the server) or your own user (on the client) to create network interfaces using addresses in the range you have assigned. The last two steps can be done automatically with the `hippotat-setup-permissions` script: run `hippotat-setup-permissions server` on the server, and `hippotat-setup-permissions client` on the client, after having configured hippotat itself. For more information on what this script does, consult its manpage. Startup - server ---------------- The `hippotat-server` package supplies an init script which will start the `hippotatd` server program, if `/etc/hippotat/main.cfg` exists. If you just created that file, `service hippotatd start` will start the server.. Consult the init script to see options you can put in `/etc/default/hippotat`. Usage - client -------------- It is not usual to have hippotat running all the time, since its approach is rather wasteful, and not needed in a sensible network environment. When you find yourself in a bad network environment, run `hippotat` from a shell. It will bring up the hippotat link. When you don't need Hippotat any more, simply `^C` it. With the link up you can `ssh` (or, maybe, `mosh`) to the server, using the server's Hippotat IP address. If you are using [secnet](https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git/secnet.git/)'s polypath feature, secnet will automatically start to use hippotat. Don't send traffic you care about unencrypted over Hippotat. Hippotat's security mechanisms are designed to try to minimise denial of service, especially by off-path entities, but **data confidentiality and integrity are not provided**. Troubleshooting --------------- The server will log to syslog, facility `daemon`, by default. The client will report its progress to stderr. work/docs/settings.md0000664000000000000000000001552514766655527012101 0ustar Configuration settings ====================== Exceptional settings -------------------- * `server` Specifies ``. Is looked up in `[SERVER]` and `[COMMON]` only. If not specified there, it is `SERVER`. Used by server to select the appropriate parts of the rest of the configuration. Ignored by the client. * `secret` Looked up in the usual way, but used by client and server to determine which possible peerings to try to set up, and which to ignore. We define the sets of putative clients and servers, as follows: all those, for which there is any section (even an empty one) whose name is based on `` or `` (as applicable). (`LIMIT` sections do not count.) The server queue packets for, and accept requests from, each putative client for which the config search yields a secret. Each client will create a local interface, and try to communicate with the server, for each possible pair (putative server, putative client) for which the config search yields a secret. The value is a string, fed directly into HMAC. * `ipif` Command to run to create and communicate with local network interface. Passed to sh -c. Must speak SLIP on stdin/stdout. (For compatibility with older hippotat, `%(var)s` is supported too but this is deprecated since the extra `s` is confusing.) On server: applies to all clients; not looked up in client-specific sections. On client: may be different for different servers. [string; `userv root ipif %{local},%{peer},%{mtu},slip '%{rnets}'`] ### `ipif` interpolations: The following interpolations aare substituted in the value for `ipif`: | Input | `%{local}` | `%{peer}` | `%{rnets}` | `%{ifname}` | | -------------- | ----------- | ---------- | ----------- | -------------- | | **on server** | `vaddr` | `vrelay` | `vnetwork` | `ifname_server` | | **on client** | `client` | `vaddr` | `vroutes` | `ifname_client` | **Always:** `%{mtu}`, and `%%` to indicate a literal `%`. Capped settings --------------- Values in `[ LIMIT]` and `[LIMIT]` are a cap (maximum) on those from the other sections (including `COMMON`). If a larger value is obtained, it is (silently) reduced to the limit value. * `max_batch_down` Size limit for response payloads. On client, incoming response bodies are limited to this (plus a fixed constant metadata overhead). Server uses minimum of client's and server's configured values (old servers just use server's value). [`65536` (bytes); `LIMIT`: `262144`] * `max_batch_up` Size limit for request upbound payloads. On client, used directly, with `LIMIT` applied. On server, only `LIMIT` is relevant, and must be at least the client's configured value (checked). [`4000` (bytes); `LIMIT`: `262144`] * `max_queue_time` Discard packets after they have been queued this long waiting for http. On server: setting applies to downward packets. On client: setting applies to upward packets. [`10` (s); `LIMIT`: `121`] * `http_timeout` On server: return with empty payload any http request oustanding for this long. On client: give up on any http request outstanding for for this long plus `http_timeout_grace`. Warning messages about link problems, printed by the client, are rate limited to no more than one per effective timeout. Client's effective timeout must be at least server's (checked). [`30` (s); `LIMIT`: `121`] * `target_requests_outstanding` On client: try to keep this many requests outstanding, to allow for downbound data transfer. On server: whenever number of outstanding requests for a client exceeds this, returns oldest with empty payload. Must match between client and server (checked). [`3`; `LIMIT`: `10`] Ordinary settings, used by both, not client-specific ---------------------------------------------------- On the server these are forbidden in the client-specific config sections. * `addrs` Public IP (v4 or v6) address(es) of the server; space-separated. On server: mandatory; used for bind. On client: used only to construct default `url`. No default. * `vnetwork` Private network range. Must contain all ``s. Must contain `vaddr` and `vrelay`, and is used to compute their defaults. [CIDR syntax (`/`); `172.24.230.192/28`] * `vaddr` Address of server's virtual interface. [default: first host entry in `vnetwork`, so `172.24.230.193`] * `vrelay` Virtual point-to-point address used for tunnel routing (does not appear in packets). [default: first host entry in `vnetwork` other than `vaddr`, so `172.24.230.194`] * `port` Public port number of the server. On server: used for bind. On client: used only to construct default url. [`80`] Do not set this to `443` - the server will speak plain unencrypted HTTP on the port you specify, which would be wrong for `443`. While the client has integrated TLS support, the server does not. To use hippotat with TLS: - Set up a TLS reverse proxy (such as apache or nginx), probably with a certificate from Let's Encrypt. - Configure `port` and `addrs` to the internal address and port (to which the reverse proxy forwards the requests). - Configure `url` to the public URL of the reverse proxy. * `mtu` Of virtual interface. Must match exactly at each end (checked). [`1500` (bytes)] Ordinary settings, used by server only -------------------------------------- * `max_clock_skew` Permissible clock skew between client and server. Hippotat will not work if clock skew is more than this. Conversely: when moving client from one public network to another, the first network can deny service to the client for this period after the client leaves the first network. [`300` (s)] * `ifname_server` Virtual interface name on the server. [`shippo%d`] NB: any `%d` is interpolated (by the kernel). Ordinary settings, used by client only -------------------------------------- * `http_timeout_grace` See `http_timeout`. [`5` (s)] * `max_requests_outstanding` Client will hold off sending more requests than this to server even if it has data to send. [`6`] * `success_report_interval` If nonzero, report success periodically. Otherwise just report it when we first have success. [`3600` (s)] * `http_retry` If a request fails, wait this long before considering it "finished" - to limit rate of futile requests (and also to limit rate of moaning on stderr). [`5` s] * `url` Public url of server. [`http://:/`] * `vroutes` Additional virtual addresses to be found at the server end, space-separated. Routes to those will be created on the client. `vrelay` is included implicitly. [CIDR syntax, space separated; default: none] * `ifname_client` Virtual interface name on the client. [`hippo%d`] NB: any `%d` is interpolated (by the kernel). work/hippotat-setup-permissions0000775000000000000000000000444714766655527014255 0ustar #!/bin/sh set -e usage () { cat <&2 "bad usage: unknown arguments/options" usage >&2 exit 12 ;; esac DAEMON=/usr/sbin/hippotatd USER=_hippotat GROUP=_hippotat test -e /etc/default/hippotatd && . /etc/default/hippotatd uid=$(id -u "$USER") if ! test -e /etc/userv/services.d/ipif; then ln -s ../services-available/ipif /etc/userv/services.d/ipif echo 'enabled ipif userv service' fi case "$USER" in root) echo "USER=root, revoking permissions" cs=revoke ;; esac remove_file () { if test -e "$f"; then echo "Removing $f" fi rm -f "$f" "$f~new~" } start_file () { exec 3>"$f~new~" echo >&3 '# created by hippotat-setup-permissions' } install_file () { mv -f "$f~new~" "$f" echo "Installed $f" } f=/etc/authbind/byuid/$uid case "$cs" in client|revoke) remove_file ;; server) start_file $DAEMON --print-config port,addrs | \ while read port addrs; do for addr in $addrs; do echo >&3 "$addr,$port" done done install_file ;; esac permit_ipif () { user_spec=$1 printf >&3 "permit %s ifname %s local %s" "$user_spec" "$ifname" "$vaddr" for vnet in $vnets; do printf >&3 " remote %s" "$vnet" done echo >&3 } f=/etc/userv/ipif-access/hippotat start_file case "$cs" in *server*) $DAEMON --print-config ifname_server,vaddr,vnetwork,vroutes | \ while read ifname vaddr vnets; do permit_ipif "user $USER" done ;; esac case "$cs" in *client*) hippotat --print-config ifname_client,client,vnetwork,vroutes | \ while read ifname vaddr vnets; do permit_ipif "group $GROUP" done ;; esac if test -s "$f~new~"; then install_file else case "$cs" in revoke) ;; *) echo 'No hippotat configuration.' ;; esac remove_file echo "Revoked virtual network interface permissions." fi if grep -q '^permit user ' $f; then echo "Granted user $USER permissions needed for running the server." fi if grep -q '^permit group ' $f; then echo "Granted group $GROUP permissions needed for running the client." echo "Consider putting yourself in that group!" fi work/hippotat-setup-permissions.8.pod0000664000000000000000000000436414766655527015177 0ustar =head1 NAME hippotat-setup-permissions - set up permissions for (non-root) use of hippotat =head1 SYNOPSYS hippotat-setup-permissions client hippotat-setup-permissions server hippotat-setup-permissions revoke =head1 DESCRIPTION Sets up (or revokes) the permissions to allow hippotat and/or hippotatd to run. With C permissions needed for the server are granted to the C<_hippotat> user (or other user set using C in C.) With C permissions needed for the client are granted to the C<_hippotat> I (or other group set using C in C.) Required permissions are determined based on the hippotat configuration in C. (The C or C program is run in a special mode to query the configuration.) In every run, revokes permissions granted to the configured user and/or group by previous invocations of this script, but which are not any longer needed according to the configuration and command line. So C revokes all permissions, and C and C each revoke the other. (Only permissions granted in the specific files used by this script will be amended or revoked.) =head1 FILES =over =item C. Grants to the appropriate user or group the ability to make the virtual network interfaces, and route traffic to them. Created on both clients and servers. =item CI Grants the server the ability to bind to the configured ports and addresses. The uid is that for the C<_hippotat> user, or C. Created on servers. =item C Enables the C userv service, which is itself controlled by C etc. Will be made a symlink to C. Created on both clients and servers. Not removed during revocation, since other programs on the system may need it, Makes the symlink in . (This is not undone by C, since that might disturb other services which are relying on it.) =item C Shell script fragment sourced by the init script and by hippotat-setup-permissions, and the hippotatd init script. Can set C and C (and other variables that control the init script). =back work/macros/0000775000000000000000000000000014766655527010243 5ustar work/macros/Cargo.toml0000664000000000000000000000122514766655527012173 0ustar # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. [package] name = "hippotat-macros" version = "1.2.2" edition = "2018" description="Asinine HTTP-over-IP, proc-macros" license="GPL-3.0-or-later" # ^ Actually, it's WITH LicenseRef-Hippotat-OpenSSL-Exception repository="https://salsa.debian.org/iwj/hippotat" homepage="https://www.chiark.greenend.org.uk/~ianmdlvl/hippotat/current/docs/" [dependencies] syn = { version = ">= 1, < 3", features=["extra-traits"] } proc-macro2 = "1" quote = "1" [lib] path = "macros.rs" proc-macro = true work/macros/macros.rs0000664000000000000000000000205114766655527012073 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. //! Hippotat - proc macros //! //! This crate is an internal detail of hippotat. //! It does not adhere to semver. #![allow(clippy::style)] #![allow(clippy::expect_fun_call)] #![allow(clippy::map_flatten)] #![allow(clippy::single_char_pattern)] use syn::LitStr; use quote::quote; #[proc_macro] pub fn into_crlfs(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: proc_macro2::TokenStream = input.into(); let token: LitStr = syn::parse2(input).expect("expected literal"); let input = token.value(); let output = input.split_inclusive('\n') .map(|s| s.trim_start_matches(&[' ','\t'][..])) .map(|s| match s.strip_suffix("\n") { None => [s, ""], Some(l) => [l, "\r\n"], }) .flatten() .collect::(); //dbg!(&output); let output = LitStr::new(&output, token.span()); let output = quote!(#output); output.into() } work/maint/0000775000000000000000000000000014766655527010067 5ustar work/maint/update-minimal-versions0000775000000000000000000000053414766655527014573 0ustar #!/bin/sh set -e # CARGO='nailing-cargo -u' maint/update-minimal-versions : ${CARGO:=cargo} test -f Cargo.lock.normal || cp Cargo.lock Cargo.lock.normal cp Cargo.lock.minimal Cargo.lock $CARGO +nightly update -Z minimal-versions $CARGO +nightly update -p openssl -p openssl-sys cp Cargo.lock Cargo.lock.minimal mv Cargo.lock.normal Cargo.lock work/maint/via-cargo-install-in-ci0000775000000000000000000000036414766655527014331 0ustar #!/bin/bash set -euo pipefail cache="$*" cmd="$1"; shift cache="${cache// /,}" cache="cache/$cache" if cp -v "$cache" "$CARGO_HOME"/bin/"$cmd"; then exit 0; fi mkdir -p cache cargo install "$@" "$cmd" cp -v "$CARGO_HOME/bin/$cmd" "$cache" work/old-python/0000775000000000000000000000000014766655527011054 5ustar work/old-python/.gitignore0000664000000000000000000000033714766655527013047 0ustar data.dump.dbg [tuv] tmp srcbomb.tar.gz srcpkgsbomb.tar build .pybuild hippotat.egg-info debian/files debian/debhelper-*-stamp debian/*.debhelper.log debian/hippotat.substvars debian/hippotat.*.debhelper debian/hippotat/ work/old-python/debian/0000775000000000000000000000000014766655527012276 5ustar work/old-python/debian/hippotat.dirs0000664000000000000000000000004514766655527015010 0ustar /etc/hippotat /etc/hippotat/config.d work/old-python/debian/hippotat.install0000664000000000000000000000017614766655527015522 0ustar README.config PROTOCOL CONTRIBUTING /usr/share/doc/hippotat simple.cfg sgo-demo.cfg test.cfg /usr/share/doc/hippotat/examples work/old-python/debian/rules0000775000000000000000000000076614766655527013367 0ustar #!/usr/bin/make -f SHELL=/bin/bash export PYBUILD_INSTALL_DIR=/usr/share/hippotat/python3 %: dh $@ --with python3 --buildsystem=pybuild i=debian/hippotat debian/copyright: COPYING AGPLv3+CAFv2 cat $^ >$@.tmp && mv -f $@.tmp $@ override_dh_python3: dh_python3 -O--buildsystem=pybuild dh_installdirs /usr/sbin mv $i/usr/{bin,sbin}/hippotatd override_dh_installinit: dh_installinit --name=hippotatd override_dh_compress: find $i/usr/{bin,sbin} -type f | xargs ./subst-sys-path dh_compress work/old-python/fake-userv0000775000000000000000000000031514766655527013051 0ustar #!/bin/sh set -ex echo >&2 "$0: invoked as $0 $*" exec 3<&0 4>&1 5>&2 >&2 &4 2>&5 & sleep 0.1 env - bash -i ' x "$@" work/old-python/form.html0000664000000000000000000000067014766655527012710 0ustar

no data

with data

work/old-python/setup.py0000775000000000000000000000142314766655527012571 0ustar #!/usr/bin/python3 from setuptools import setup, find_packages import re as regexp import glob import sys scripts = ['hippotat','hippotatd'] scan = scripts + glob.glob('hippotatlib/*.py') def find_requires(): mod_pat = r'[._0-9a-zA-Z]+' res = list(map(regexp.compile, [r'from\s+('+mod_pat+r')\s+import\b', r'import\s+('+mod_pat+r')\s'])) reqs = { } for scanf in scan: print('scanning %s' % scanf, file=sys.stderr) for l in open(scanf): for re in res: m = re.match(l) if m is not None: reqs[m.group(1)] = True break print(repr(reqs), file=sys.stderr) return list(reqs.keys()) setup( name="hippotat", packages=find_packages(), install_requires=find_requires(), scripts=scripts ) work/old-python/sgo-demo.cfg0000664000000000000000000000107014766655527013245 0ustar # -- in config.d/chiark [chiark] vnetwork vaddr vrelay addrs # port # mtu # limits # -- in config.d/davenant [davenant] vnetwork vaddr vrelay addrs # port # mtu # limits eg max_batch_down = 65536 [192.0.2.4] # adjusts eg http_timeout = 15 # ^ applies to all servers [davenant 192.0.2.4] # adjusts eg max_batch_down = 32768 # -- in chiark:master.cfg [SERVER] server = chiark # -- in secrets.d/chiark-zealot (on zealot and chiark) [chiark 192.0.2.4] secret = sesame # zealot knows it's 192.0.2.4 because that's the only client # for which it has a secret work/old-python/simple.cfg0000664000000000000000000000024614766655527013030 0ustar # -- in master.cfg (on both client and server) [SERVER] addrs = 203.0.113.46 # -- in secrets.d/secret (on both client and server) [172.24.230.195] secret = sesame work/old-python/srcbombtest.py0000775000000000000000000000026414766655527013762 0ustar #!/usr/bin/python3 from hippotatlib.ownsource import SourceShipmentPreparer import twisted import sys p = SourceShipmentPreparer('tmp') p.stream_debug = sys.stdout p.generate() work/old-python/subst-sys-path0000775000000000000000000000016214766655527013707 0ustar #!/usr/bin/perl -pi next unless m{^#\@ }; my $ok = 1; s{@(\w+)@}{ $ENV{$1} // ($ok=0, $&) }ge; s{^#\@ }{} if $ok; work/old-python/test.cfg0000664000000000000000000000122414766655527012513 0ustar [SERVER] ipif = PATH=/usr/local/sbin:/sbin:/usr/sbin:$PATH really /home/ian/things/Userv/userv-utils.git/ipif/service \* -- %(local)s,%(peer)s,%(mtu)s,slip '%(rnets)s' addrs = 127.0.0.1 port = 8099 vnetwork = 192.0.2.0/24 # ./hippotatd --debug-select=+ -c test.cfg # nc -n -v -l -p 8100 -c 'dd of=/dev/null' [192.0.2.3] secret = sesame [192.0.2.3] ipif = PATH=/usr/local/sbin:/sbin:/usr/sbin:$PATH really ./fake-userv /home/ian/things/Userv/userv-utils.git/ipif/service \* -- %(local)s,%(peer)s,%(mtu)s,slip '%(rnets)s' # ./hippotat -D -c test.cfg [192.0.2.4] #secret = zorkmids # dd if=/dev/urandom bs=1024 count=16384 | nc -q 0 -n -v 192.0.2.1 8100 work/old-python/w3mstracetodump0000775000000000000000000000040514766655527014142 0ustar #!/usr/bin/perl -n # strace -s70000 -ot w3m ./form.html next unless (m/^connect\((\d+),.*AF_INET/ and $fd = $1) .. m/^close\($fd\)/; next unless s{^write\($fd, "}{}; s{", \d+\)\s+= \d+\n}{}; s{\\r}{\r}g; s{\\n}{\n}g; s{\\(.)}{$1}g; print or die $!; work/server/0000775000000000000000000000000014766655527010265 5ustar work/server/daemon.rs0000664000000000000000000001305714766655527012104 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use std::convert::TryInto; use std::ffi::CStr; use std::io::IoSlice; use std::os::raw::{c_char, c_int}; use std::os::unix::io::RawFd; use std::slice; use std::str; use std::thread::panicking; use easy_ext::ext; use nix::errno::*; use nix::fcntl::*; use nix::unistd::*; use nix::sys::stat::*; use nix::sys::signal::*; use nix::sys::wait::*; use hippotat::prelude as prelude; use prelude::{compat, default}; pub struct Daemoniser { drop_bomb: Option<()>, intermediate_pid: Pid, null_fd: RawFd, st_wfd: RawFd, } fn crashv(ms: &[IoSlice<'_>]) -> ! { unsafe { let _ = compat::writev(2, ms); libc::_exit(18); } } macro_rules! crashv { { $( $m:expr ),* $(,)? } => { match [ "hippotatd: ", $( $m, )* "\n", ] { ms => { let ms = ms.map(|m| IoSlice::new(m.as_bytes())); crashv(&ms) } } } } macro_rules! cstr { { $b:tt } => { CStr::from_bytes_with_nul($b) .unwrap_or_else(|_| crashm("cstr not nul terminated?! bug!")) } } fn crashm(m: &str) -> ! { crashv!(m) } fn crashe(m: &str, e: Errno) -> ! { crashv!(m, ": ", e.desc()) } #[ext] impl nix::Result { fn context(self, m: &str) -> T { match self { Ok(y) => y, Err(e) => crashe(m, e), } } } const ITOA_BUFL: usize = 12; fn c_itoa(value: c_int, buf: &mut [u8; ITOA_BUFL]) -> &str { unsafe { *buf = [b'.'; ITOA_BUFL]; libc::snprintf({ let buf: *mut u8 = buf.as_mut_ptr(); buf as *mut c_char }, ITOA_BUFL-2, cstr!(b"%x\0").as_ptr(), value); } let s = buf.splitn(2, |&c| c == b'\0').next() .unwrap_or_else(|| crashm("splitn no next")); str::from_utf8(s).unwrap_or_else(|_| crashm("non-utf-8 from snprintf!")) } unsafe fn mdup2(oldfd: RawFd, newfd: RawFd, what: &str) { match dup2(oldfd, newfd) { Ok(got) if got == newfd => { }, Ok(_) => crashm("dup2 gave wrong return value"), Err(e) => crashv!("dup2 ", what, ": ", e.desc()), } } unsafe fn write_status(st_wfd: RawFd, estatus: u8) { match compat::write(st_wfd, slice::from_ref(&estatus)) { Ok(1) => {} Ok(_) => crashm("write child startup exit status: short write"), Err(e) => crashe("write child startup exit status", e), } } unsafe fn parent(st_rfd: RawFd) -> ! { let mut exitstatus = 0u8; loop { match read(st_rfd, slice::from_mut(&mut exitstatus)) { Ok(0) => crashm("startup/daemonisation failed"), Ok(1) => libc::_exit(exitstatus.into()), Ok(_) => crashm("read startup: excess read!"), Err(e) if e == Errno::EINTR => continue, Err(e) => crashe("read startup signal pipe", e), } } } unsafe fn intermediate(child: Pid, st_wfd: RawFd) -> ! { let mut wstatus: c_int = 0; let r = libc::waitpid(child.as_raw(), &mut wstatus, 0); if r == -1 { crashe("await child startup status", compat::nix_last_errno()) } if r != child.as_raw() { crashm("await child startup status: wrong pid") } let cooked = WaitStatus::from_raw(child, wstatus) .context("await child startup status: convert wait status"); match cooked { WaitStatus::Exited(_, estatus) => { let estatus: u8 = estatus.try_into() .unwrap_or_else(|_| crashm( "await child startup status: exit status out of range!")); write_status(st_wfd, estatus); libc::_exit(0); } WaitStatus::Signaled(_, signal, coredump) => { crashv!("startup failed: died due to signal: ", signal.as_str(), if coredump { " (core dumped)" } else { "" }); }, _ => { crashv!("child startup exit status was strange! 0x", c_itoa(wstatus, &mut default())) } } } impl Daemoniser { /// Start daemonising - call before any threads created! pub fn phase1() -> Self { unsafe { let null_fd = open(cstr!(b"/dev/null\0"), OFlag::O_RDWR, Mode::empty()) .context("open /dev/null"); mdup2(null_fd, 0, "null onto stdin"); let (st_rfd, st_wfd) = compat::pipe().context("pipe"); match fork().context("fork (1)") { ForkResult::Child => { } ForkResult::Parent { child: _ } => { close(st_wfd).context("close st_wfd pipe"); parent(st_rfd) }, } close(st_rfd).context("close st_rfd pipe"); setsid().context("setsid"); let intermediate_pid = Pid::this(); match fork().context("fork (2)") { ForkResult::Child => { } ForkResult::Parent { child } => { intermediate(child, st_wfd) }, } Daemoniser { drop_bomb: Some(()), intermediate_pid, null_fd, st_wfd, } } } pub fn complete(mut self) { unsafe { mdup2(self.null_fd, 1, "null over stdin"); if Pid::parent() != self.intermediate_pid { crashm( "startup complete, but our parent is no longer the intermediate?"); } kill(self.intermediate_pid, Some(Signal::SIGKILL)) .context("kill intermediate (after startup complete)"); write_status(self.st_wfd, 0); mdup2(self.null_fd, 2, "null over stderrr"); self.drop_bomb.take(); } } } impl Drop for Daemoniser { fn drop(&mut self) { if let Some(()) = self.drop_bomb.take() { if panicking() { // We will crash in due course, having printed some messages // to stderr, presumably. return } else { panic!("Daemonizer object dropped unexpectedly, startup failed"); } } } } work/server/server.rs0000664000000000000000000002374314766655527012152 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. #![allow(clippy::style)] #![allow(clippy::unit_arg)] #![allow(clippy::useless_format)] #![allow(clippy::while_let_loop)] use hippotat::prelude::*; mod daemon; mod suser; mod slocal; mod sweb; pub use daemon::Daemoniser; pub use sweb::{WebRequest, WebResponse, WebResponseBody}; pub use suser::User; #[derive(clap::Parser,Debug)] pub struct Opts { #[clap(flatten)] pub log: LogOpts, #[clap(flatten)] pub config: config::CommonOpts, /// Daemonise #[clap(long)] daemon: bool, /// Write our pid to this file #[clap(long)] pidfile: Option, /// Print config item(s), do not actually run /// /// Argument is (comma-separated) list of config keys; /// values will be printed tab-separated. /// The key `pretty` dumps the whole config in a pretty debug format. /// /// If none of the specified config keys are client-specific, /// only one line will be printed. Otherwise the output will /// have one line per client association. /// /// Additional pseudo-config-keys are recognised: /// `client`: our client virtual IP address; /// `server`: server's logical name in the config; /// `link`: the link name including the `[ ]`. #[clap(long)] print_config: Option, } pub const METADATA_MAX_LEN: usize = MAX_OVERHEAD; // ----- Backpressure discussion ----- // These two kinds of channels are sent blockingly, so this means the // task which calls route_packet can get this far ahead, before a // context switch to the receiving task is forced. pub const MAXQUEUE_ROUTE2USER: usize = 15; pub const MAXQUEUE_ROUTE2LOCAL: usize = 50; // This channel is sent with try_send, ie non-blocking. If the user // task becomes overloaded, requests will start to be rejected. pub const MAXQUEUE_WEBREQ2USER: usize = 5; // The user task prioritises 1. returning requests or discarding data, // 2. handling data routed to it. Ie it prefers to drain queues. // // The slocal task prioritises handling routed data and writing it // (synchronously) to the local kernel. So if the local kernel starts // blocking, all tasks may end up blocked waiting for things to drain. #[derive(Debug)] pub struct Global { config: config::InstanceConfigGlobal, local_rx: mpsc::Sender, all_clients: HashMap, } pub struct RoutedPacket { pub data: RoutedPacketData, // pub source: Option, // for eh, tracing, etc. } // not MIME data, valid SLIP (checked) pub type RoutedPacketData = Box<[u8]>; // loop prevention // we don't decrement the ttl (naughty) but loops cannot arise // because only the server has any routing code, and server // has no internal loops, so worst case is // client if -> client -> server -> client' -> client if' // and the ifs will decrement the ttl. mod may_route { #[derive(Clone,Debug)] pub struct MayRoute(()); impl MayRoute { pub fn came_from_outside_hippotatd() -> Self { Self(()) } } } pub use may_route::MayRoute; pub async fn route_packet(global: &Global, transport_conn: &str, source: Option<&ClientName>, packet: RoutedPacketData, daddr: IpAddr, _may_route: MayRoute) { let c = &global.config; let len = packet.len(); let trace = |how: &str, why: &str| { trace!("{} to={:?} came=={} user={} len={} {}", how, daddr, transport_conn, match source { Some(s) => s as &dyn Display, None => &"local", }, len, why); }; let (dest, why) = if daddr == c.vaddr || ! c.vnetwork.iter().any(|n| n.contains(&daddr)) { (Some(&global.local_rx), "via=local") } else if daddr == c.vrelay { (None, " vrelay") } else if let Some(client) = global.all_clients.get(&ClientName(daddr)) { (Some(&client.route), "via=client") } else { (None, "no-client") }; let dest = if let Some(d) = dest { d } else { trace("discard ", why); return; }; let packet = RoutedPacket { data: packet, // source: source.cloned(), }; match dest.send(packet).await { Ok(()) => trace("forward", why), Err(_) => trace("task-crashed!", why), } } fn main() { let opts = ::parse(); let daemon = if opts.daemon && opts.print_config.is_none() { Some(Daemoniser::phase1()) } else { None }; async_main(opts, daemon); } #[tokio::main] async fn async_main(opts: Opts, daemon: Option) { let mut tasks: Vec<( JoinHandle, String, )> = vec![]; config::startup( "hippotatd", LinkEnd::Server, &opts.config, &opts.log, |server_name, ics| { let server_name = server_name.expect("LinkEnd::Server didn't do its job"); let pc = PrintConfigOpt(&opts.print_config); if ics.is_empty() { pc.implement(ics)?; return Ok(None); } let global_config = config::InstanceConfigGlobal::from(ics); let gc = (&server_name, &global_config); if pc.keys().all(|k| gc.inspect_key(k).is_some()) { pc.implement([&gc])?; } else { pc.implement(ics)?; } Ok(Some(global_config)) }, |global_config, ics| async { let global_config = global_config.expect("some instances"); if let Some(pidfile_path) = opts.pidfile.as_ref() { (||{ let mut pidfile = fs::File::create(pidfile_path).context("create")?; writeln!(pidfile, "{}", process::id()).context("write")?; pidfile.flush().context("write (flush)")?; Ok::<_,AE>(()) })().with_context(|| format!("pidfile {:?}", pidfile_path))?; } let ipif = Ipif::start(&global_config.ipif, None)?; let ics = ics.into_iter().map(Arc::new).collect_vec(); let (client_handles_send, client_handles_recv) = ics.iter() .map(|_ic| { let (web_send, web_recv) = mpsc::channel( MAXQUEUE_WEBREQ2USER ); let (route_send, route_recv) = mpsc::channel( MAXQUEUE_ROUTE2USER ); ((web_send, route_send), (web_recv, route_recv)) }).unzip::<_,_,Vec<_>,Vec<_>>(); let all_clients = izip!( &ics, client_handles_send, ).map(|(ic, (web_send, route_send))| { (ic.link.client, User { ic: ic.clone(), web: web_send, route: route_send, }) }).collect(); let (local_rx_send, local_tx_recv) = mpsc::channel( MAXQUEUE_ROUTE2LOCAL ); let global = Arc::new(Global { config: global_config, local_rx: local_rx_send, all_clients, }); let max_buffer = chain!( [16384], // hyper demands at least 8192 ics.iter().map(|ic| { [ic.max_batch_up, ic.max_batch_down] }).flatten(), ).max().expect("not empty since we have at least one [item]") .try_into().unwrap_or_else(|_: TryFromIntError| usize::MAX); for (ic, (web_recv, route_recv)) in izip!( ics, client_handles_recv, ) { let global_ = global.clone(); let ic_ = ic.clone(); tasks.push((tokio::spawn(async move { suser::run(global_, ic_, web_recv, route_recv) .await.void_unwrap_err() }), format!("client {}", &ic))); } let listeners = { let mut listeners = vec![]; for saddr in &global.config.addrs { let saddr = SocketAddr::new(*saddr, global.config.port); let listener = tokio::net::TcpListener::bind(saddr) .await .with_context(|| format!("bind {}", saddr))?; listeners.push((saddr, listener)); } listeners }; for (saddr, listener) in listeners { info!("listening on {}", &saddr); let global = global.clone(); let task = tokio::task::spawn(async move { loop { let (conn, caddr) = match listener.accept().await { Ok(y) => y, Err(e) => { debug!("{saddr}: listen error: {e:#}"); continue; } }; let caddr = Arc::new(format!("[{caddr}]")); let service = hyper::service::service_fn({ let global = global.clone(); let caddr = caddr.clone(); move |req| { let global = global.clone(); let caddr = caddr.clone(); async move { AssertUnwindSafe( sweb::handle(caddr, global, req) ) .catch_unwind().await .unwrap_or_else(|_| { crash(Err("panicked".into()), "webserver request task") }) } } }); let conn = hyper_util::rt::tokio::TokioIo::new(conn); let conn_fut = hyper::server::conn::http1::Builder::new() .half_close(true) .title_case_headers(true) .max_buf_size(max_buffer) .serve_connection(conn, service); tokio::task::spawn(async move { match conn_fut.await { Ok(()) => {}, Err(e) => trace!("{}: client connection from {} failed: {:#}", saddr, caddr, e), }; }); } }); tasks.push((task, format!("http server {}", saddr))); } #[allow(clippy::redundant_clone)] let global_ = global.clone(); let ipif = tokio::task::spawn(async move { slocal::run(global_, local_tx_recv, ipif).await .void_unwrap_err() }); tasks.push((ipif, format!("ipif"))); Ok(()) }).await; if let Some(daemon) = daemon { daemon.complete(); } let (output, died_i, _) = future::select_all( tasks.iter_mut().map(|e| &mut e.0) ).await; let task = &tasks[died_i].1; let output = output.map_err(|je| je.to_string()); crash(output, task); } pub fn crash(what_happened: Result, task: &str) -> ! { match what_happened { Err(je) => error!("task crashed! {}: {}", task, &je), Ok(e) => error!("task failed! {}: {}", task, &e ), } process::exit(12); } #[test] fn verify_cli() { hippotat::utils::verify_cli::(); } work/server/slocal.rs0000664000000000000000000000442214766655527012112 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use super::*; pub async fn run(global: Arc, mut rx: mpsc::Receiver, mut ipif: Ipif) -> Result { let r = async { let mut goodness: i32 = 0; const GOODNESS_SHIFT: u8 = 8; const GOODNESS_MIN: i32 = -16; loop { select!{ biased; data = rx.recv() => { let data = data.ok_or_else(|| anyhow!("rx stream end!"))?; let mut data = &*data.data; let mut slip_end = &[SLIP_END][..]; let mut buf = Buf::chain(&mut data, &mut slip_end); ipif.rx.write_all_buf(&mut buf).await .context("write to ipif")?; }, data = Ipif::next_frame(&mut ipif.tx) => { let data = data?; let may_route = MayRoute::came_from_outside_hippotatd(); goodness -= goodness >> GOODNESS_SHIFT; match process1(SlipNoConv, global.config.mtu, &data, |header|{ let saddr = ip_packet_addr::(header)?; let daddr = ip_packet_addr::(header)?; Ok((saddr,daddr)) }) { Err(PE::Empty) => { }, Err(pe) => { goodness -= 1; error!("[good={}] invalid data from local tx ipif {}", goodness, pe); if goodness < GOODNESS_MIN { throw!(anyhow!("too many bad packets, too few good ones!")) } }, Ok((ref data, (ref saddr, ref daddr))) if ! global.config.vnetwork.iter().any(|n| n.contains(saddr)) => { // pretent as if this came from route trace!( target: "hippotatd", "discard to={:?} came=ipif user=local len={} outside-vnets: from={:?}", daddr, saddr, data.len()); }, Ok((data, (_saddr, daddr))) => { goodness += 1; route_packet( &global, "ipif", None, data, daddr, may_route.clone() ).await; } } }, } } }.await; ipif.quitting(None).await; r } work/server/suser.rs0000664000000000000000000001523114766655527011776 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use super::*; #[derive(Debug)] pub struct User { pub ic: Arc, pub web: mpsc::Sender, pub route: mpsc::Sender, } pub async fn run(global: Arc, ic: Arc, mut web: mpsc::Receiver, mut routed: mpsc::Receiver) -> Result { struct Outstanding { reply_to: oneshot::Sender, oi: OutstandingInner, } #[derive(Debug)] struct OutstandingInner { deadline: Instant, target_requests_outstanding: u32, max_batch_down: u32, } let mut outstanding: VecDeque = default(); let mut downbound: PacketQueue = default(); let try_send_response = | reply_to: oneshot::Sender, response: WebResponse | { reply_to.send(response) .unwrap_or_else(|_: WebResponse| { /* oh dear */ trace!("unable to send response back to webserver! user={}", &ic.link.client); }); }; loop { let eff_max_batch_down = outstanding .iter() .map(|o| o.oi.max_batch_down) .min() .unwrap_or(ic.max_batch_down) .sat(); let earliest_deadline = outstanding .iter() .map(|o| o.oi.deadline) .min(); if let Some(req) = { let now = Instant::now(); if ! downbound.is_empty() { outstanding.pop_front() } else if let Some((i,_)) = outstanding.iter().enumerate().find({ |(_,o)| { outstanding.len() > o.oi.target_requests_outstanding.sat() || o.oi.deadline < now } }) { Some(outstanding.remove(i).unwrap()) } else { None } } { let mut build: FrameQueueBuf = default(); loop { let next = if let Some(n) = downbound.peek_front() { n } else { break }; // Don't add 1 for the ESC since we will strip one if build.len() + next.len() >= eff_max_batch_down { break } build.esc_push(downbound.pop_front().unwrap()); } if ! build.is_empty() { // skip leading ESC build.advance(1); } let response = WebResponse { data: Ok(build), warnings: default(), }; try_send_response(req.reply_to, response); } let max = usize::saturating_mul( ic.max_requests_outstanding.sat(), eff_max_batch_down, ).saturating_add(1 /* one boundary SLIP_ESC which we'll trim */); while downbound.total_len() > max { let _ = downbound.pop_front(); trace!("{} discarding downbound-queue-full", &ic.link); } select!{ biased; data = routed.recv() => { let data = data.ok_or_else(|| anyhow!("routers shut down!"))?; downbound.push_back(data.data); }, req = web.recv() => { let WebRequest { initial, initial_remaining, length_hint, mut body, boundary_finder, reply_to, conn, mut warnings, may_route, } = req.ok_or_else(|| anyhow!("webservers all shut down!"))?; match async { let initial_used = initial.len() - initial_remaining; let whole_request = read_limited_bytes( ic.max_batch_up.sat(), initial, length_hint, Pin::new(&mut body), ).await.context("read request body")?; let (meta, mut comps) = multipart::ComponentIterator::resume_mid_component( &whole_request[initial_used..], boundary_finder ).context("resume parsing body, after auth checks")?; let mut meta = MetadataFieldIterator::new(&meta); macro_rules! meta { { $v:ident, ( $( $badcmp:tt )? ), $ret:expr, let $server:ident, $client:ident $($code:tt)* } => { let $v = (||{ let $server = ic.$v; let $client $($code)* $( if $client $badcmp $server { throw!(anyhow!("mismatch: client={:?} {} server={:?}", $client, stringify!($badcmp), $server)); } )? Ok::<_,AE>($ret) })().context(stringify!($v))?; //dbg!(&$v); } } meta!{ target_requests_outstanding, ( != ), client, let server, client: u32 = meta.need_parse()?; } meta!{ http_timeout, ( > ), client, let server, client = Duration::from_secs(meta.need_parse()?); } meta!{ mtu, ( != ), client, let server, client: u32 = meta.parse()?.unwrap_or(server); } meta!{ max_batch_down, (), min(client, server), let server, client: u32 = meta.parse()?.unwrap_or(server); } meta!{ max_batch_up, ( > ), client, let server, client = meta.parse()?.unwrap_or(server); } let _ = max_batch_up; // we don't use this further while let Some(comp) = comps.next(&mut warnings, PartName::d)? { if comp.name != PartName::d { warnings.add(&format_args!("unexpected part {:?}", comp.name))?; } slip::processn(Mime2Slip, mtu, comp.payload, |header| { let saddr = ip_packet_addr::(header)?; if saddr != ic.link.client.0 { throw!(PE::Src(saddr)) } let daddr = ip_packet_addr::(header)?; Ok(daddr) }, |(daddr,packet)| route_packet( &global, &conn, Some(&ic.link.client), daddr, packet, may_route.clone(), ).map(Ok), |e| Ok::<_,SlipFramesError<_>>({ warnings.add(&e)?; }) ).await?; } let deadline = Instant::now() + http_timeout; let oi = OutstandingInner { target_requests_outstanding, max_batch_down, deadline, }; Ok::<_,AE>(oi) }.await { Ok(oi) => outstanding.push_back(Outstanding { reply_to, oi }), Err(e) => { try_send_response(reply_to, WebResponse { data: Err(e), warnings, }); }, } } () = async {if let Some(deadline) = earliest_deadline { tokio::time::sleep_until(deadline).await; } else { future::pending().await } } => { } } } } work/server/sweb.rs0000664000000000000000000001753514766655527011606 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use super::*; /// Sent from hyper worker pool task to client task #[derive(Debug)] pub struct WebRequest { // initial part of body // used up to and including first 2 lines of metadata // end delimiter for the metadata not yet located, but in here somewhere pub initial: Box<[u8]>, pub initial_remaining: usize, pub length_hint: usize, pub body: http_body_util::BodyDataStream, pub boundary_finder: multipart::BoundaryFinder, pub reply_to: oneshot::Sender, pub warnings: Warnings, pub conn: Arc, pub may_route: MayRoute, } /// Reply from client task to hyper worker pool task #[derive(Debug)] pub struct WebResponse { pub warnings: Warnings, pub data: Result, } pub type WebResponseData = FrameQueueBuf; pub type WebResponseBody = BufBody; pub async fn handle( conn: Arc, global: Arc, req: hyper::Request ) -> Result, hyper::http::Error> { if req.method() == Method::GET { let mut resp = hyper::Response::new(BufBody::display("hippotat\r\n")); resp.headers_mut().insert( "Content-Type", "text/plain; charset=US-ASCII".try_into().unwrap() ); return Ok(resp) } let mut warnings: Warnings = default(); async { let get_header = |hn: &str| { let mut values = req.headers().get_all(hn).iter(); let v = values.next().ok_or_else(|| anyhow!("missing {}", hn))?; if values.next().is_some() { throw!(anyhow!("multiple {}!", hn)); } let v = v.to_str().context(anyhow!("interpret {} as UTF-8", hn))?; Ok::<_,AE>(v) }; let mkboundary = |b: &'_ _| format!("\n--{}", b).into_bytes(); let boundary = match (||{ let t = get_header("Content-Type")?; let t: mime::Mime = t.parse().context("parse Content-Type")?; if t.type_() != "multipart" { throw!(anyhow!("not multipart/")) } let b = mime::BOUNDARY; let b = t.get_param(b).ok_or_else(|| anyhow!("missing boundary=..."))?; if t.subtype() != "form-data" { warnings.add(&"Content-Type not /form-data")?; } let b = mkboundary(b.as_str()); Ok::<_,AE>(b) })() { Ok(y) => y, Err(e) => { warnings.add(&e.wrap_err("guessing boundary"))?; mkboundary("b") }, }; let length_hint: usize = (||{ let clength = get_header("Content-Length")?; let clength = clength.parse().context("parse Content-Length")?; Ok::<_,AE>(clength) })().unwrap_or_else( |e| { let _ = warnings.add(&e.wrap_err("parsing Content-Length")); 0 } ); let body = req.into_body(); let mut body = http_body_util::BodyDataStream::new(body); let initial = match read_limited_bytes( METADATA_MAX_LEN, default(), length_hint, Pin::new(&mut body), ).await { Ok(all) => all, Err(ReadLimitedError::Truncated { sofar,.. }) => sofar, Err(ReadLimitedError::Http(e)) => throw!(e), }; let boundary_finder = memmem::Finder::new(&boundary); let mut boundary_iter = boundary_finder.find_iter(&initial); let start = if initial.starts_with(&boundary[1..]) { boundary.len()-1 } else if let Some(start) = boundary_iter.next() { start + boundary.len() } else { throw!(anyhow!("initial boundary not found")) }; let comp = multipart::process_boundary (&mut warnings, &initial[start..], PartName::m)? .ok_or_else(|| anyhow!(r#"no "m" component"#))?; if comp.name != PartName::m { throw!(anyhow!( r#"first multipart component must be name="m""# )) } let mut meta = MetadataFieldIterator::new(comp.payload); let client: ClientName = meta.need_parse().context("client addr")?; let mut hmac_got = [0; HMAC_L]; let (client_time, hmac_got_l) = (||{ let token: &str = meta.need_next().context(r#"find in "m""#)?; let (time_t, hmac_b64) = token.split_once(' ') .ok_or_else(|| anyhow!("split"))?; let time_t = u64::from_str_radix(time_t, 16).context("parse time_t")?; let l = io::copy( &mut base64::read::DecoderReader::new(&mut hmac_b64.as_bytes(), &BASE64_CONFIG), &mut &mut hmac_got[..] ).context("parse b64 token")?; let l = l.try_into()?; Ok::<_,AE>((time_t, l)) })().context("token")?; let hmac_got = &hmac_got[0..hmac_got_l]; let client_name = client; let client = global.all_clients.get(&client_name); // We attempt to hide whether the client exists we don't try to // hide the hash lookup computationgs, but we do try to hide the // HMAC computation by always doing it. We hope that the compiler // doesn't produce a specialised implementation for the dummy // secret value. let client_exists = subtle::Choice::from(client.is_some() as u8); let secret = client.map(|c| c.ic.secret.0.as_bytes()); let secret = secret.unwrap_or(&[0x55; HMAC_B][..]); let client_time_s = format!("{:x}", client_time); let hmac_exp = token_hmac(secret, client_time_s.as_bytes()); // We also definitely want a consttime memeq for the hmac value let hmac_ok = hmac_got.ct_eq(&hmac_exp); //dbg!(DumpHex(&hmac_exp), client.is_some()); //dbg!(DumpHex(hmac_got), hmac_ok, client_exists); if ! bool::from(hmac_ok & client_exists) { debug!("{} rejected client {}", &conn, &client_name); let body = BufBody::display("Not authorised\r\n"); return Ok( hyper::Response::builder() .status(hyper::StatusCode::FORBIDDEN) .header("Content-Type", r#"text/plain; charset="utf-8""#) .body(body) ) } let client = client.unwrap(); let now = time_t_now(); let chk_skew = |a: u64, b: u64, c_ahead_behind| { if let Some(a_ahead) = a.checked_sub(b) { if a_ahead > client.ic.max_clock_skew.as_secs() { throw!(anyhow!("too much clock skew (client {} by {})", c_ahead_behind, a_ahead)); } } Ok::<_,AE>(()) }; chk_skew(client_time, now, "ahead")?; chk_skew(now, client_time, "behind")?; let initial_remaining = meta.remaining_bytes_len(); //eprintln!("boundary={:?} start={} name={:?} client={}", // boundary, start, &comp.name, &client.ic); let (reply_to, reply_recv) = oneshot::channel(); trace!("{} {} request, Content-Length={}", &conn, &client_name, length_hint); let wreq = WebRequest { initial, initial_remaining, length_hint, boundary_finder: boundary_finder.into_owned(), body, warnings: mem::take(&mut warnings), reply_to, conn: conn.clone(), may_route: MayRoute::came_from_outside_hippotatd(), }; client.web.try_send(wreq) .map_err(|_| anyhow!("client user task overloaded"))?; let reply: WebResponse = reply_recv.await?; warnings = reply.warnings; let data = reply.data?; if warnings.warnings.is_empty() { trace!("{} {} responding, {}", &conn, &client_name, data.len()); } else { debug!("{} {} responding, {} warnings={:?}", &conn, &client_name, data.len(), &warnings.warnings); } let data = BufBody::new(data); Ok::<_,AE>( hyper::Response::builder() .header("Content-Type", r#"application/octet-stream"#) .body(data) ) }.await.unwrap_or_else(|e| { debug!("{} error {:#}", &conn, &e); let mut errmsg = format!("ERROR\n\n{:?}\n\n", &e); for w in warnings.warnings { writeln!(errmsg, "warning: {}", w).unwrap(); } hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) .header("Content-Type", r#"text/plain; charset="utf-8""#) .body(BufBody::display(errmsg)) }) } work/src/0000775000000000000000000000000014766655527007546 5ustar work/src/compat.rs0000664000000000000000000000251414766655527011401 0ustar #![allow(unused_imports)] use std::os::fd::IntoRawFd; use cfg_if::cfg_if; use crate::prelude::*; /// Version of [`nix::sys::uio::writev`] with a fixed type for the fd // /// * nix <=0.26 has `fd: c_int` /// * nix >=0.27 has `fd: impl AsFd` pub unsafe fn writev(fd: c_int, iov: &[IoSlice]) -> nix::Result { nix::sys::uio::writev( { cfg_if! { if #[cfg(nix_ge_0_27)] { BorrowedFd::borrow_raw(fd) } else { fd } }}, iov, ) } /// Version of [`nix::unistd::write`] with a fixed type for the fd // /// * nix <=0.27 has `fd: c_int` /// * nix >=0.28 has `fd: impl AsFd` pub unsafe fn write(fd: c_int, buf: &[u8]) -> nix::Result { nix::unistd::write( { cfg_if! { if #[cfg(nix_ge_0_28)] { BorrowedFd::borrow_raw(fd) } else { fd } }}, buf, ) } /// Version of [`nix::unistd::pipe`] with a fixed type for the fd // /// * nix <=0.27 returns a pair of `c_int` /// * nix >=0.28 returns a pair of `OwnedFd` pub fn pipe() -> nix::Result<(c_int, c_int)> { let (a, b) = nix::unistd::pipe()?; let map = |fd| { cfg_if! { if #[cfg(nix_ge_0_28)] { OwnedFd::into_raw_fd(fd) } else { fd } }}; Ok((map(a), map(b))) } #[allow(deprecated)] pub fn nix_last_errno() -> nix::errno::Errno { use nix::errno::*; from_i32(errno()) } work/src/config.rs0000664000000000000000000007532414766655527011374 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; #[derive(Debug,Clone)] #[derive(Deftly)] #[derive_deftly(InspectableConfigAuto, InstanceConfig)] pub struct InstanceConfig { // Exceptional settings #[deftly(special, skl="SKL::None")] pub link: LinkName, #[deftly(per_client)] pub secret: Secret, #[deftly(global, special, skl="SKL::PerClient")] pub ipif: String, // Capped settings: #[deftly(limited)] pub max_batch_down: u32, #[deftly(limited)] pub max_queue_time: Duration, #[deftly(limited)] pub http_timeout: Duration, #[deftly(limited)] pub target_requests_outstanding: u32, #[deftly(special, skl="SKL::Limited")] pub max_batch_up: u32, // Ordinary settings, used by both, not client-specifi: #[deftly(global)] pub addrs: Vec, #[deftly(global)] pub vnetwork: Vec, #[deftly(global)] pub vaddr: IpAddr, #[deftly(global)] pub vrelay: IpAddr, #[deftly(global)] pub port: u16, #[deftly(global)] pub mtu: u32, // Ordinary settings, used by server only: #[deftly(server, per_client)] pub max_clock_skew: Duration, #[deftly(server, global)] pub ifname_server: String, // Ordinary settings, used by client only: #[deftly(client)] pub http_timeout_grace: Duration, #[deftly(client)] pub max_requests_outstanding: u32, #[deftly(client)] pub http_retry: Duration, #[deftly(client)] pub success_report_interval: Duration, #[deftly(client)] pub url: Url, #[deftly(client)] pub vroutes: Vec, #[deftly(client)] pub ifname_client: String, // Computed, rather than looked up. Client only: #[deftly(computed)] pub effective_http_timeout: Duration, } static DEFAULT_CONFIG: &str = r#" [COMMON] max_batch_down = 65536 max_queue_time = 10 target_requests_outstanding = 3 http_timeout = 30 http_timeout_grace = 5 max_requests_outstanding = 6 max_batch_up = 4000 http_retry = 5 port = 80 vroutes = '' ifname_client = hippo%d ifname_server = shippo%d max_clock_skew = 300 success_report_interval = 3600 ipif = userv root ipif %{local},%{peer},%{mtu},slip,%{ifname} '%{rnets}' mtu = 1500 vnetwork = 172.24.230.192 [LIMIT] max_batch_up = 262144 max_batch_down = 262144 max_queue_time = 121 http_timeout = 121 target_requests_outstanding = 10 "#; #[derive(clap::Args,Debug)] pub struct CommonOpts { /// Top-level config file or directory /// /// Look for `main.cfg`, `config.d` and `secrets.d` here. /// /// Or if this is a file, just read that file. #[clap(long, default_value="/etc/hippotat")] pub config: PathBuf, /// Additional config files or dirs, which can override the others #[clap(long, action=clap::ArgAction::Append)] pub extra_config: Vec, } pub trait InspectableConfigAuto { fn inspect_key_auto(&self, field: &'_ str) -> Option<&dyn InspectableConfigValue>; } pub trait InspectableConfig: Debug { fn inspect_key(&self, field: &'_ str) -> Option<&dyn InspectableConfigValue>; } impl InspectableConfig for (&ServerName, &InstanceConfigGlobal) { fn inspect_key(&self, field: &'_ str) -> Option<&dyn InspectableConfigValue> { Some(match field { "server" => self.0, k => return self.1.inspect_key_auto(k), }) } } impl InspectableConfig for InstanceConfig { fn inspect_key(&self, field: &'_ str) -> Option<&dyn InspectableConfigValue> { Some(match field { "server" => &self.link.server, "client" => &self.link.client, k => return self.inspect_key_auto(k), }) } } #[derive(Debug,Clone,Copy)] pub struct PrintConfigOpt<'a>(pub &'a Option); impl PrintConfigOpt<'_> { #[throws(AE)] pub fn implement<'c, C: InspectableConfig + 'c>( self, configs: impl IntoIterator, ) { if let Some(arg) = self.0 { for config in configs { Self::print_one_config(arg, config)?; } process::exit(0); } } pub fn keys(&self) -> impl Iterator { self.0.as_ref().map(|arg| Self::split(arg)).into_iter().flatten() } fn split(arg: &str) -> impl Iterator { arg.split(',') } #[throws(AE)] fn print_one_config( arg: &str, config: &dyn InspectableConfig, ) { let output = Self::split(arg) .map(|key| { if key == "pretty" { return Ok(format!("{:#?}", &config)); } let insp = config.inspect_key(key) .ok_or_else(|| anyhow!("unknown config key {:?}", key))?; Ok::<_,AE>(DisplayInspectable(insp).to_string()) }) .collect::,_>>()? .join("\t"); println!("{}", output); } } pub trait InspectableConfigValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result; } #[macro_export] macro_rules! impl_inspectable_config_value { { $t:ty as $trait:path } => { impl InspectableConfigValue for $t { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ::fmt(self, f) } } }; { Vec<$t:ty> } => { impl InspectableConfigValue for Vec<$t> { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { let mut first = Some(()); for v in self.iter() { if first.take().is_none() { write!(f, " ")?; } InspectableConfigValue::fmt(v, f)?; } } } }; } pub struct DisplayInspectable<'i>(pub &'i dyn InspectableConfigValue); impl<'i> Display for DisplayInspectable<'i> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { InspectableConfigValue::fmt(self.0, f) } } impl_inspectable_config_value!{ String as Display } impl_inspectable_config_value!{ ServerName as Display } impl_inspectable_config_value!{ ClientName as Display } impl_inspectable_config_value!{ u16 as Display } impl_inspectable_config_value!{ u32 as Display } impl_inspectable_config_value!{ reqwest::Url as Display } impl_inspectable_config_value!{ IpAddr as Display } impl_inspectable_config_value!{ ipnet::IpNet as Display } impl_inspectable_config_value!{ Vec } impl_inspectable_config_value!{ Vec } impl InspectableConfigValue for Duration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let v = self.as_secs_f64(); Display::fmt(&v, f) } } #[ext(U32Ext)] pub impl u32 { fn sat(self) -> usize { self.try_into().unwrap_or(usize::MAX) } } #[ext] impl<'s> Option<&'s str> { #[throws(AE)] fn value(self) -> &'s str { self.ok_or_else(|| anyhow!("value needed"))? } } #[derive(Clone)] pub struct Secret(pub String); impl Parseable for Secret { #[throws(AE)] fn parse(s: Option<&str>) -> Self { let s = s.value()?; if s.is_empty() { throw!(anyhow!("secret value cannot be empty")) } Secret(s.into()) } #[throws(AE)] fn default_for_ordinary() -> Self { Parseable::unspecified() } fn unspecified() -> Self { Secret(default()) } } impl Debug for Secret { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "Secret(***)")? } } impl_inspectable_config_value!{ Secret as Debug } #[derive(Debug,Clone,Hash,Eq,PartialEq)] pub enum SectionName { Link(LinkName), Client(ClientName), Server(ServerName), // includes SERVER, which is slightly special ServerLimit(ServerName), GlobalLimit, Common, } pub use SectionName as SN; #[derive(Debug)] struct RawValRef<'v,'l,'s> { raw: Option<&'v str>, // todo: not Option any more key: &'static str, loc: &'l ini::Loc, section: &'s SectionName, } impl<'v> RawValRef<'v,'_,'_> { #[throws(AE)] fn try_map(&self, f: F) -> T where F: FnOnce(Option<&'v str>) -> Result { f(self.raw) .with_context(|| format!(r#"file {:?}, section {}, key "{}""#, self.loc, self.section, self.key))? } } pub struct Config { pub opts: CommonOpts, } static OUTSIDE_SECTION: &str = "["; static SPECIAL_SERVER_SECTION: &str = "SERVER"; #[derive(Debug)] struct Aggregate { end: LinkEnd, keys_allowed: HashMap<&'static str, SectionKindList>, sections: HashMap, } type OkAnyway<'f,A> = &'f dyn Fn(&io::Error) -> Option; #[ext] impl<'f,A> OkAnyway<'f,A> { fn ok(self, r: &Result) -> Option { let e = r.as_ref().err()?; let a = self(e)?; Some(a) } } impl FromStr for SectionName { type Err = AE; #[throws(AE)] fn from_str(s: &str) -> Self { match s { "COMMON" => return SN::Common, "LIMIT" => return SN::GlobalLimit, _ => { } }; if let Ok(n@ ServerName(_)) = s.parse() { return SN::Server(n) } if let Ok(n@ ClientName(_)) = s.parse() { return SN::Client(n) } let (server, client) = s.split_ascii_whitespace().collect_tuple() .ok_or_else(|| anyhow!( "bad section name {:?} \ (must be COMMON, , , or ", s ))?; let server = server.parse().context("server name in link section name")?; if client == "LIMIT" { return SN::ServerLimit(server) } let client = client.parse().context("client name in link section name")?; SN::Link(LinkName { server, client }) } } impl Display for InstanceConfig { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.link, f)? } } impl Display for SectionName { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { match self { SN::Link (ref l) => Display::fmt(l, f)?, SN::Client(ref c) => write!(f, "[{}]" , c)?, SN::Server(ref s) => write!(f, "[{}]" , s)?, SN::ServerLimit(ref s) => write!(f, "[{} LIMIT] ", s)?, SN::GlobalLimit => write!(f, "[LIMIT]" )?, SN::Common => write!(f, "[COMMON]" )?, } } } impl Aggregate { fn new( end: LinkEnd, keys_allowed: HashMap<&'static str, SectionKindList> ) -> Self { Aggregate { end, keys_allowed, sections: default(), } } #[throws(AE)] // AE does not include path fn read_file(&mut self, path: &Path, anyway: OkAnyway) -> Option { let f = fs::File::open(path); if let Some(anyway) = anyway.ok(&f) { return Some(anyway) } let mut f = f.context("open")?; let mut s = String::new(); let y = f.read_to_string(&mut s); if let Some(anyway) = anyway.ok(&y) { return Some(anyway) } y.context("read")?; self.read_string(s, path)?; None } #[throws(AE)] // AE does not include path fn read_string(&mut self, s: String, path_for_loc: &Path) { let mut map: ini::Parsed = default(); ini::read(&mut map, &mut s.as_bytes(), path_for_loc) .context("parse as INI")?; if map.get(OUTSIDE_SECTION).is_some() { throw!(anyhow!("INI file contains settings outside a section")); } for (sn, section) in map { let sn = sn.parse().dcontext(&sn)?; let vars = §ion.values; for (key, val) in vars { (||{ let skl = if key == "server" { SKL::ServerName } else { *self.keys_allowed.get(key.as_str()).ok_or_else( || anyhow!("unknown configuration key") )? }; if ! skl.contains(&sn, self.end) { throw!(anyhow!("key not applicable in this kind of section")) } Ok::<_,AE>(()) })() .with_context(|| format!("key {:?}", key)) .with_context(|| val.loc.to_string())? } let ent = self.sections.entry(sn) .or_insert_with(|| ini::Section { loc: section.loc.clone(), values: default(), }); for (key, ini::Val { val: raw, loc }) in vars { let val = if raw.starts_with('\'') || raw.starts_with('"') { (||{ if raw.contains('\\') { throw!( anyhow!("quoted value contains backslash, not supported") ); } let quote = &raw[0..1]; let unq = raw[1..].strip_suffix(quote) .ok_or_else( || anyhow!("mismatched quotes around quoted value") )? .to_owned(); if unq.contains(quote) { throw!(anyhow!( "quoted value contains quote (escaping not supported)" )) } Ok::<_,AE>(unq) })() .with_context(|| format!("key {:?}", key)) .with_context(|| loc.to_string())? } else { raw.clone() }; let key = key.replace('-',"_"); ent.values.insert(key, ini::Val { val, loc: loc.clone() }); } } } #[throws(AE)] // AE includes path fn read_dir_d(&mut self, path: &Path, anyway: OkAnyway) -> Option { let dir = fs::read_dir(path); if let Some(anyway) = anyway.ok(&dir) { return Some(anyway) } let dir = dir.context("open directory").dcontext(path)?; for ent in dir { let ent = ent.context("read directory").dcontext(path)?; let leaf = ent.file_name(); let leaf = leaf.to_str(); let leaf = if let Some(leaf) = leaf { leaf } else { continue }; //utf8? if leaf.len() == 0 { continue } if ! leaf.chars().all( |c| c=='-' || c=='_' || c.is_ascii_alphanumeric() ) { continue } // OK we want this one let ent = ent.path(); self.read_file(&ent, &|_| None::).dcontext(&ent)?; } None } #[throws(AE)] // AE includes everything fn read_toplevel(&mut self, toplevel: &Path) { enum Anyway { None, Dir } match self.read_file(toplevel, &|e| match e { e if e.kind() == EK::NotFound => Some(Anyway::None), e if e.is_is_a_directory() => Some(Anyway::Dir), _ => None, }) .dcontext(toplevel).context("top-level config directory (or file)")? { None | Some(Anyway::None) => { }, Some(Anyway::Dir) => { struct AnywayNone; let anyway_none = |e: &io::Error| match e { e if e.kind() == EK::NotFound => Some(AnywayNone), _ => None, }; let mk = |leaf: &str| { [ toplevel, &PathBuf::from(leaf) ] .iter().collect::() }; for &(try_main, desc) in &[ ("main.cfg", "main config file"), ("master.cfg", "obsolete-named main config file"), ] { let main = mk(try_main); match self.read_file(&main, &anyway_none) .dcontext(main).context(desc)? { None => break, Some(AnywayNone) => { }, } } for &(try_dir, desc) in &[ ("config.d", "per-link config directory"), ("secrets.d", "per-link secrets directory"), ] { let dir = mk(try_dir); match self.read_dir_d(&dir, &anyway_none).context(desc)? { None => { }, Some(AnywayNone) => { }, } } } } } #[throws(AE)] // AE includes extra, but does that this is extra fn read_extra(&mut self, extra: &Path) { struct AnywayDir; match self.read_file(extra, &|e| match e { e if e.is_is_a_directory() => Some(AnywayDir), _ => None, }) .dcontext(extra)? { None => return, Some(AnywayDir) => { self.read_dir_d(extra, &|_| None::)?; } } } } impl Aggregate { fn instances(&self, only_server: Option<&ServerName>) -> BTreeSet { let mut links: BTreeSet = default(); let mut secrets_anyserver: BTreeSet<&ClientName> = default(); let mut secrets_anyclient: BTreeSet<&ServerName> = default(); let mut secret_global = false; let mut putative_servers = BTreeSet::new(); let mut putative_clients = BTreeSet::new(); let mut note_server = |s| { if let Some(only) = only_server { if s != only { return false } } putative_servers.insert(s); true }; let mut note_client = |c| { putative_clients.insert(c); }; for (section, vars) in &self.sections { let has_secret = || vars.values.contains_key("secret"); //dbg!(§ion, has_secret()); match section { SN::Link(l) => { if ! note_server(&l.server) { continue } note_client(&l.client); if has_secret() { links.insert(l.clone()); } }, SN::Server(ref s) => { if ! note_server(s) { continue } if has_secret() { secrets_anyclient.insert(s); } }, SN::Client(ref c) => { note_client(c); if has_secret() { secrets_anyserver.insert(c); } }, SN::Common => { if has_secret() { secret_global = true; } }, _ => { }, } } //dbg!(&putative_servers, &putative_clients); //dbg!(&secrets_anyserver, &secrets_anyclient, &secret_global); // Add links which are justified by blanket secrets for (client, server) in iproduct!( putative_clients.into_iter().filter( |c| secret_global || secrets_anyserver.contains(c) || ! secrets_anyclient.is_empty() ), putative_servers.iter().cloned().filter( |s| secret_global || secrets_anyclient.contains(s) || ! secrets_anyserver.is_empty() ) ) { links.insert(LinkName { client: client.clone(), server: server.clone(), }); } links } } struct ResolveContext<'c> { agg: &'c Aggregate, link: &'c LinkName, end: LinkEnd, all_sections: Vec, } trait Parseable: Sized { fn parse(s: Option<&str>) -> Result; /// Used for lookups with [`ResolveContext::ordinary`] etc. /// /// Fails, if this setting ought to have been specified. /// The caller will add a key name to the error. fn default_for_ordinary() -> Result { Err(anyhow!("setting must be specified")) } /// Placeholder (infalliable) /// /// Used (sometimes) for lookups with /// [`ResolveContext::client`], /// [`server`](`ResolveContext::server`) and /// [`computed`](`ResolveContext::server`). /// /// Ie, when the value need not be specified because /// it may not be applicable, or could be computed. /// /// We could use `Default::default` but /// not all the types we want to use implement that. fn unspecified() -> Self; } impl Parseable for Duration { #[throws(AE)] fn parse(s: Option<&str>) -> Duration { // todo: would be nice to parse with humantime maybe Duration::from_secs( s.value()?.parse()? ) } fn unspecified() -> Duration { Duration::ZERO } } macro_rules! parseable_from_str { ($t:ty, $def:expr) => { impl Parseable for $t { #[throws(AE)] fn parse(s: Option<&str>) -> $t { s.value()?.parse()? } #[throws(AE)] fn default_for_ordinary() -> Self { Parseable::unspecified() } fn unspecified() -> Self { $def } } } } parseable_from_str!{u16, default() } parseable_from_str!{u32, default() } parseable_from_str!{String, default() } parseable_from_str!{IpNet, default() } parseable_from_str!{IpAddr, Ipv4Addr::UNSPECIFIED.into() } parseable_from_str!{ Url, "hippotat-unspecified:".parse() .expect("failed to parse `hippotat-unspecified:` as a url") } impl Parseable for Vec { #[throws(AE)] fn parse(s: Option<&str>) -> Vec { s.value()? .split_ascii_whitespace() .map(|s| Parseable::parse(Some(s))) .collect::,_>>()? } #[throws(AE)] fn default_for_ordinary() -> Self { Parseable::unspecified() } fn unspecified() -> Self { default() } } #[derive(Debug,Copy,Clone,Eq,PartialEq)] enum SectionKindList { PerClient, Limited, Limits, Global, ServerName, None, } use SectionKindList as SKL; impl SectionName { fn special_server_section() -> Self { SN::Server(ServerName( SPECIAL_SERVER_SECTION.into() )) } } impl SectionKindList { fn contains(self, s: &SectionName, end: LinkEnd) -> bool { match (self, end) { (SKL::PerClient,_) | (SKL::Global, LinkEnd::Client) => matches!(s, SN::Link(_) | SN::Client(_) | SN::Server(_) | SN::Common), (SKL::Limits,_) => matches!(s, SN::ServerLimit(_) | SN::GlobalLimit), (SKL::Global, LinkEnd::Server) => matches!(s, SN::Common | SN::Server(_)), (SKL::Limited,_) => SKL::PerClient.contains(s, end) | SKL::Limits .contains(s, end), (SKL::ServerName,_) => matches!(s, SN::Common) | matches!(s, SN::Server(ServerName(name)) if name == SPECIAL_SERVER_SECTION), (SKL::None,_) => false, } } } impl Aggregate { fn lookup_raw<'a,'s,S>(&'a self, key: &'static str, sections: S) -> Option> where S: Iterator { for section in sections { if let Some(val) = self.sections .get(section) .and_then(|s: &ini::Section| s.values.get(key)) { return Some(RawValRef { raw: Some(&val.val), loc: &val.loc, section, key, }) } } None } #[throws(AE)] pub fn establish_server_name(&self) -> ServerName { let key = "server"; let raw = match self.lookup_raw( key, [ &SectionName::Common, &SN::special_server_section() ].iter().cloned() ) { Some(raw) => raw.try_map(|os| os.value())?, None => SPECIAL_SERVER_SECTION, }; ServerName(raw.into()) } } impl<'c> ResolveContext<'c> { fn first_of_raw(&'c self, key: &'static str, sections: SectionKindList) -> Option> { self.agg.lookup_raw( key, self.all_sections.iter() .filter(|s| sections.contains(s, self.end)) ) } #[throws(AE)] fn first_of(&self, key: &'static str, sections: SectionKindList) -> Option where T: Parseable { match self.first_of_raw(key, sections) { None => None, Some(raw) => Some(raw.try_map(Parseable::parse)?), } } #[throws(AE)] pub fn ordinary(&self, key: &'static str, skl: SKL) -> T where T: Parseable { match self.first_of(key, skl)? { Some(y) => y, None => Parseable::default_for_ordinary() .with_context(|| key.to_string())?, } } #[throws(AE)] pub fn limited(&self, key: &'static str, skl: SKL) -> T where T: Parseable + Ord { assert_eq!(skl, SKL::Limited); let val = self.ordinary(key, SKL::PerClient)?; if let Some(limit) = self.first_of(key, SKL::Limits)? { min(val, limit) } else { val } } #[throws(AE)] pub fn client(&self, key: &'static str, skl: SKL) -> T where T: Parseable { match self.end { LinkEnd::Client => self.ordinary(key, skl)?, LinkEnd::Server => Parseable::unspecified(), } } #[throws(AE)] pub fn server(&self, key: &'static str, skl: SKL) -> T where T: Parseable { match self.end { LinkEnd::Server => self.ordinary(key, skl)?, LinkEnd::Client => Parseable::unspecified(), } } #[throws(AE)] pub fn computed(&self, _key: &'static str, skl: SKL) -> T where T: Parseable { assert_eq!(skl, SKL::None); Parseable::unspecified() } #[throws(AE)] pub fn special_ipif(&self, key: &'static str, skl: SKL) -> String { assert_eq!(skl, SKL::PerClient); // we tolerate it in per-client sections match self.end { LinkEnd::Client => self.ordinary(key, SKL::PerClient)?, LinkEnd::Server => self.ordinary(key, SKL::Global)?, } } #[throws(AE)] pub fn special_link(&self, _key: &'static str, skl: SKL) -> LinkName { assert_eq!(skl, SKL::None); self.link.clone() } #[throws(AE)] pub fn special_max_batch_up(&self, key: &'static str, skl: SKL) -> u32 { assert_eq!(skl, SKL::Limited); match self.end { LinkEnd::Client => self.ordinary(key, SKL::Limited)?, LinkEnd::Server => self.ordinary(key, SKL::Limits)?, } } } impl InstanceConfig { #[throws(AE)] fn complete(&mut self, end: LinkEnd) { let mut vhosts = self.vnetwork.iter() .map(|n| n.hosts()).flatten() .filter({ let vaddr = self.vaddr; move |v| v != &vaddr }); if self.vaddr.is_unspecified() { self.vaddr = vhosts.next().ok_or_else( || anyhow!("vnetwork too small to generate vaddrr") )?; } if self.vrelay.is_unspecified() { self.vrelay = vhosts.next().ok_or_else( || anyhow!("vnetwork too small to generate vrelay") )?; } let check_batch = { let mtu = self.mtu; move |max_batch, key| { if max_batch/2 < mtu { throw!(anyhow!("max batch {:?} ({}) must be >= 2 x mtu ({}) \ (to allow for SLIP ESC-encoding)", key, max_batch, mtu)) } Ok::<_,AE>(()) } }; match end { LinkEnd::Client => { if self.url == Url::unspecified() { let addr = self.addrs.get(0).ok_or_else( || anyhow!("client needs addrs or url set") )?; self.url = format!( "http://{}{}/", match addr { IpAddr::V4(a) => format!("{}", a), IpAddr::V6(a) => format!("[{}]", a), }, match self.port { 80 => format!(""), p => format!(":{}", p), }) .parse().unwrap() } self.effective_http_timeout = { let a = self.http_timeout; let b = self.http_timeout_grace; a.checked_add(b).ok_or_else( || anyhow!("calculate effective http timeout ({:?} + {:?})", a, b) )? }; { let t = self.target_requests_outstanding; let m = self.max_requests_outstanding; if t > m { throw!(anyhow!( "target_requests_outstanding ({}) > max_requests_outstanding ({})", t, m )) } } check_batch(self.max_batch_up, "max_batch_up")?; }, LinkEnd::Server => { if self.addrs.is_empty() { throw!(anyhow!("missing 'addrs' setting")) } check_batch(self.max_batch_down, "max_batch_down")?; }, } #[throws(AE)] fn subst(var: &mut String, kv: &mut dyn Iterator ) { let substs = kv .map(|(k,v)| (k.to_string(), v.to_string())) .collect::>(); let bad = parking_lot::Mutex::new(vec![]); *var = regex_replace_all!( r#"%(?:%|\((\w+)\)s|\{(\w+)\}|.)"#, var, |whole, k1, k2| (|| Ok::<_,String>({ if whole == "%%" { "%" } else if let Some(&k) = [k1,k2].iter().find(|&&s| s != "") { substs.get(k).ok_or_else( || format!("unknown key %({})s", k) )? } else { throw!(format!("bad percent escape {:?}", &whole)); } }))().unwrap_or_else(|e| { bad.lock().push(e); "" }) ).into_owned(); let bad = bad.into_inner(); if ! bad.is_empty() { throw!(anyhow!("substitution failed: {}", bad.iter().format("; "))); } } { use LinkEnd::*; type DD<'d> = &'d dyn Display; fn dv(v: &[T]) -> String { format!("{}", v.iter().format(" ")) } let mut ipif = mem::take(&mut self.ipif); // lets us borrow all of self let s = &self; // just for abbreviation, below let vnetwork = dv(&s.vnetwork); let vroutes = dv(&s.vroutes); let keys = &["local", "peer", "rnets", "ifname"]; let values = match end { Server => [&s.vaddr as DD , &s.vrelay, &vnetwork, &s.ifname_server], Client => [&s.link.client as DD, &s.vaddr, &vroutes, &s.ifname_client], }; let always = [ ( "mtu", &s.mtu as DD ), ]; subst( &mut ipif, &mut keys.iter().cloned() .zip_eq(values) .chain(always.iter().cloned()), ).context("ipif")?; self.ipif = ipif; } } } trait ResolveGlobal<'i> where Self: 'i { fn resolve(it: I) -> Self where I: Iterator; } impl<'i,T> ResolveGlobal<'i> for T where T: Eq + Clone + Debug + 'i { fn resolve(mut it: I) -> Self where I: Iterator { let first = it.next().expect("empty instances no global!"); for x in it { assert_eq!(x, first); } first.clone() } } #[throws(AE)] pub fn read(opts: &CommonOpts, end: LinkEnd) -> (Option, Vec) { let agg = (||{ let mut agg = Aggregate::new( end, InstanceConfig::FIELDS.iter().cloned().collect(), ); agg.read_string(DEFAULT_CONFIG.into(), "".as_ref()) .expect("builtin configuration is broken"); agg.read_toplevel(&opts.config)?; for extra in &opts.extra_config { agg.read_extra(extra).context("extra config")?; } //eprintln!("GOT {:#?}", agg); Ok::<_,AE>(agg) })().context("read configuration")?; let server_name = match end { LinkEnd::Server => Some(agg.establish_server_name()?), LinkEnd::Client => None, }; let instances = agg.instances(server_name.as_ref()); let mut ics = vec![]; //dbg!(&instances); for link in instances { let rctx = ResolveContext { agg: &agg, link: &link, end, all_sections: vec![ SN::Link(link.clone()), SN::Client(link.client.clone()), SN::Server(link.server.clone()), SN::Common, SN::ServerLimit(link.server.clone()), SN::GlobalLimit, ], }; if rctx.first_of_raw("secret", SKL::PerClient).is_none() { continue } let mut ic = InstanceConfig::resolve_instance(&rctx) .with_context(|| format!("resolve config for {}", &link))?; ic.complete(end) .with_context(|| format!("complete config for {}", &link))?; ics.push(ic); } (server_name, ics) } pub async fn startup(progname: &str, end: LinkEnd, opts: &CommonOpts, logopts: &LogOpts, f: F, g: G) -> U where F: FnOnce(Option, &[InstanceConfig]) -> Result, G: FnOnce(T, Vec) -> GFut, GFut: Future>, { async { dedup_eyre_setup()?; let (server_name, ics) = config::read(opts, end)?; let t = f(server_name, &ics)?; if ics.is_empty() { throw!(anyhow!("no associations, quitting")); } logopts.log_init()?; let u = g(t, ics).await?; Ok::<_,AE>(u) }.await.unwrap_or_else(|e| { eprintln!("{}: startup error: {}", progname, &e); process::exit(8); }) } work/src/config_derive.rs0000664000000000000000000001124214766655527012717 0ustar use crate::prelude::*; define_derive_deftly! { /// Implements `InspectableConfigAuto` InspectableConfigAuto for struct, expect items: impl InspectableConfigAuto for $ttype { fn inspect_key_auto(&self, field: &'_ str) -> Option<&dyn InspectableConfigValue> { Some(match field { $( stringify!($fname) => &self.$fname, ) _ => return None, }) } } } define_derive_deftly! { /// Generates config resolver method, only for `InstanceConfig` /// /// Each field ends up having an SKL and a method. /// The method actually looks up the value in a particular link context. /// SKL is passed to the method, which usually uses it to decide which /// sections to look in. But it is also used by general validation, /// unconditionally, to reject settings in the wrong section. /// /// # Atrributes `#[deftly(ATTR)]` /// /// ## Overriding attributes (these take precedence) /// /// * `skl = EXOR`: Set the SKL to `EXPR`. /// * `special`: use `ResolveContext::special_FIELDNAME` /// /// ## Other attributes /// /// | attribute | SKL | `ResolveContext` | `I.C.Global` | notes | /// |--------------|-------------|------------------|-|-| /// | `global` | `Global` | | global | | /// | `per_client` | `PerClient` | | | | /// | `client` | `PerClient` | `client` | | | /// | `limited` | `Limited` | `limited` | | | /// | `computed`: | `None` | `computed` | | | /// | `server` | | `server` | | usu. add `per_client`/`global` | /// | _default otherwise unspecified_ | _error_ | `ordinary` | not global | /// /// (Blank cells indicate that the attribute doesn't affect that output.) /// /// ## Resulting information for each field /// /// * SKL of type `SectionKindList`, ends in up in `FIELDS`. /// Either `skl`, or exactly one of the SKL-setting attributes, /// must be provided. /// /// * How to resolve it: which method on `ResolveContext` to call. /// Defaults to `ResolveContext::ordinary`. /// /// * Whether to include the field in `InstanceConfigGlobal`. /// (`global`). /// /// Generated code /// /// ```rust,ignore /// impl<'c> ResolveContext<'c> { /// /// // SKL here is used by SectionKindList::contains() /// const FIELDS: &'static [(&'static str, SectionKindList)] = &[ ... ]; /// /// #[throws(AE)] /// fn resolve_instance(&self) -> InstanceConfig { /// InstanceConfig { /// ... /// // SKL here is usually passed to first_of, but the method /// // can do something more special. /// max_batch_down: self.limited("max_batch_down", SKL::PerClient)?, /// ... /// } /// } /// } /// /// pub struct InstanceConfigGlobal { ... } /// impl InspectableConfigAuto for InstanceConfigGlobal { ... } /// impl From<&'_ [InstanceConfig]> for InstanceConfigGlobal { .. } /// ``` InstanceConfig expect items: ${define FIELD_SKL { ${if fmeta(skl) { ${fmeta(skl) as expr} } else { ${select1 fmeta( per_client ) { SKL::PerClient } fmeta( client ) { SKL::PerClient } fmeta( global ) { SKL::Global } fmeta( limited ) { SKL::Limited } fmeta( computed ) { SKL::None } } }} }} impl InstanceConfig { const FIELDS : & 'static [(& 'static str, SectionKindList)] = &[ $( ( stringify!($fname), $FIELD_SKL, ), ) ]; #[throws(AE)] fn resolve_instance(rctx: &ResolveContext) -> InstanceConfig { InstanceConfig { $( $fname: rctx. ${if fmeta(special) { ${paste special_ $fname} } else { ${select1 fmeta( server ) { server } fmeta( client ) { client } fmeta( limited ) { limited } fmeta( computed ) { computed } else { ordinary } } }} ( stringify!($fname), $FIELD_SKL, )?, ) } } } #[derive(Debug)] #[derive(Deftly)] #[derive_deftly(InspectableConfigAuto)] pub struct InstanceConfigGlobal { $( ${when fmeta(global)} pub $fname: $ftype, ) } impl From<&'_ [InstanceConfig]> for InstanceConfigGlobal { fn from(l: &[InstanceConfig]) -> InstanceConfigGlobal { InstanceConfigGlobal { $( ${when fmeta(global)} $fname: <$ftype as ResolveGlobal> ::resolve(l.iter().map(|e| &e.$fname)), ) } } } } work/src/ini.rs0000664000000000000000000000562314766655527010701 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; use std::io::BufRead; use std::rc::Rc; #[derive(Debug,Clone)] #[derive(Hash,Eq,PartialEq,Ord,PartialOrd)] pub struct Loc { pub file: Arc, pub lno: usize, pub section: Option>, } #[derive(Debug,Clone)] pub struct Val { pub val: String, pub loc: Loc, } pub type Parsed = HashMap, Section>; #[derive(Debug)] pub struct Section { /// Location of first encounter pub loc: Loc, pub values: HashMap, } impl Display for Loc { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "{:?}:{}", &self.file, self.lno)?; if let Some(s) = &self.section { write!(f, " ")?; let dbg = format!("{:?}", &s); if let Some(mid) = (||{ let mid = dbg.strip_prefix(r#"""#)?; let mid = mid.strip_suffix(r#"""#)?; Some(mid) })() { write!(f, "[{}]", mid)?; } else { write!(f, "{}", dbg)?; } } } } #[throws(AE)] pub fn read(parsed: &mut Parsed, file: &mut dyn BufRead, path_for_loc: &Path) //->Result<(), AE> { let parsed = Rc::new(RefCell::new(parsed)); let path: Arc = path_for_loc.to_owned().into(); let mut section: Option> = None; for (lno, line) in file.lines().enumerate() { let line = line.context("read")?; let line = line.trim(); if line.is_empty() { continue } if regex_is_match!(r#"^ [;\#] "#x, line) { continue } let loc = Loc { lno, file: path.clone(), section: section.as_ref().map(|s| s.loc.section.as_ref().unwrap().clone()), }; (|| Ok::<(),AE>({ if let Some((_,new,)) = regex_captures!(r#"^ \[ \s* (.+?) \s* \] $"#x, line) { let new: Arc = new.to_owned().into(); section.take(); // drops previous RefCell borrow of parsed let new_section = RefMut::map(parsed.borrow_mut(), |p| { p.entry(new.clone()) .or_insert_with(|| { Section { loc: Loc { section: Some(new), file: path.clone(), lno }, values: default(), } }) }); section = Some(new_section); } else if let Some((_, key, val)) = regex_captures!(r#"^ ( [^\[] .*? ) \s* = \s* (.*) $"#x, line) { let val = Val { loc: loc.clone(), val: val.into() }; section .as_mut() .ok_or_else(|| anyhow!("value outside section"))? .values .insert(key.into(), val); } else { throw!(if line.starts_with("[") { anyhow!(r#"syntax error (section missing final "]"?)"#) } else { anyhow!(r#"syntax error (setting missing "="?)"#) }) } }))().with_context(|| loc.to_string())? } } work/src/ipif.rs0000664000000000000000000000505014766655527011043 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; type Tx = t_io::Split>; pub struct Ipif { pub tx: Tx, pub rx: t_proc::ChildStdin, stderr_task: JoinHandle>, child: t_proc::Child, cmd: String, } impl Ipif { #[throws(AE)] pub fn start(cmd: &str, ic_name: Option) -> Self { debug!("{}ipif: running command: {}", OptionPrefixColon(ic_name.as_ref()), cmd); let mut child = tokio::process::Command::new("sh") .args(["-c", cmd]) .stdin (process::Stdio::piped()) .stdout(process::Stdio::piped()) .stderr(process::Stdio::piped()) .kill_on_drop(true) .spawn().context("spawn ipif")?; let stderr = child.stderr.take().unwrap(); let stderr_task = task::spawn(async move { let mut stderr = t_io::BufReader::new(stderr).lines(); while let Some(l) = stderr.next_line().await? { error!("{}ipif stderr: {}", OptionPrefixColon(ic_name.as_ref()), l.trim_end()); } Ok::<_,io::Error>(()) }); let tx = child.stdout.take().unwrap(); let rx = child.stdin .take().unwrap(); let tx = t_io::BufReader::new(tx).split(SLIP_END); Ipif { tx, rx, stderr_task, child, cmd: cmd.to_owned(), } } pub async fn quitting(mut self, ic: Option<&InstanceConfig>) { let icd = OptionPrefixColon(ic); drop(self.rx); error!("{}failed ipif command: {}", icd, &self.cmd); match self.child.wait().await { Err(e) => error!("{}also, failed to await ipif child: {}", icd, e), Ok(st) => { let stderr_timeout = Duration::from_millis(1000); match tokio::time::timeout(stderr_timeout, self.stderr_task).await { Err::<_,tokio::time::error::Elapsed>(_) => warn!("{}ipif stderr task continues!", icd), Ok(Err(e)) => error!("{}ipif stderr task crashed: {}", icd, e), Ok(Ok(Err(e))) => error!("{}ipif stderr read failed: {}", icd, e), Ok(Ok(Ok(()))) => { }, } if ! st.success() { error!("{}ipif process failed: {}", icd, st); } } } drop(self.tx); } #[throws(AE)] pub async fn next_frame(tx: &mut Tx) -> Vec { let data = tx.next_segment().await; (||{ data?.ok_or_else(|| io::Error::from(io::ErrorKind::UnexpectedEof)) })().context("read from ipif")? } } work/src/lib.rs0000664000000000000000000000156414766655527010670 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. //! Library crate for Hippotat //! //! These libraries are for use within the Hippotat cargo package. //! They do not have a stable API. //! //! Please refer to the //! [project documentation](https://www.chiark.greenend.org.uk/~ianmdlvl/hippotat/current/docs/) #![allow(clippy::style)] #![allow(clippy::clone_on_copy)] #![allow(clippy::map_flatten)] #![allow(clippy::match_single_binding)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unit_arg)] #![allow(clippy::useless_format)] pub mod prelude; #[macro_use] pub mod config_derive; pub mod compat; pub mod config; pub mod ipif; pub mod multipart; pub mod slip; pub mod reporter; pub mod queue; pub mod types; pub mod utils; pub mod ini; work/src/multipart.rs0000664000000000000000000001367514766655527012151 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; #[derive(Debug)] pub struct Component<'b> { pub name: PartName, pub payload: &'b [u8], } #[derive(Debug)] #[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] #[allow(non_camel_case_types)] pub enum PartName { m, d, Other } pub type BoundaryFinder = memchr::memmem::Finder<'static>; #[throws(AE)] /// Processes the start of a component (or terminating boundary). /// /// Returned payload is only the start of the payload; the next /// boundary has not been identified. pub fn process_boundary<'b>(warnings: &mut Warnings, after_leader: &'b [u8], expected: PartName) -> Option> { let rhs = after_leader; let mut rhs = if let Some(rhs) = rhs.strip_prefix(b"\r\n") { rhs } else if let Some(_ ) = rhs.strip_prefix(b"--" ) { return None } else if let Some(rhs) = rhs.strip_prefix(b"\n" ) { rhs } else { throw!(anyhow!("invalid multipart delimiter")) }; let mut part_name = None; loop { // RHS points to the start of a header line let nl = memchr::memchr(b'\n', rhs) .ok_or_else(|| anyhow!("part headers truncated"))?; let l = &rhs[0..nl]; rhs = &rhs[nl+1..]; if l == b"\r" || l == b"" { break } // end of headers if l.starts_with(b"--") { throw!(anyhow!("boundary in part headers")) } match (||{ let l = str::from_utf8(l).context("interpret part headers as utf-8")?; let (_, disposition) = if let Some(y) = regex_captures!(r#"^Content-Disposition[ \t]*:[ \t]*(.*)$"#i, l) { y } else { return Ok(()) }; let disposition = disposition.trim_end(); if disposition.len() >= 100 { throw!(anyhow!( "Content-Disposition value implausibly long" )) } // todo: replace with mailparse? // (not in side, dep on charset not in sid) // also seems to box for all the bits // This let's us pretend it's a mime type, so we can use mime::Mime let disposition = format!("dummy/{}", disposition); let disposition: mime::Mime = disposition.parse() .context("parse Content-Disposition")?; let name = disposition.get_param("name") .ok_or_else(|| anyhow!(r#"find "name" in Content-Disposition"#))?; let name = match name.as_ref() { "m" => PartName::m, "d" => PartName::d, _ => PartName::Other, }; if let Some(_) = mem::replace(&mut part_name, Some(name)) { throw!(anyhow!(r#"multiple "name"s in Content-Disposition(s)"#)) } Ok::<_,AE>(()) })() { Err(e) => warnings.add(&e)?, Ok(()) => { }, }; } //dbg!(DumpHex(rhs)); Some(Component { name: part_name.unwrap_or(expected), payload: rhs }) } pub struct ComponentIterator<'b> { at_boundary: &'b [u8], boundary_finder: BoundaryFinder, } #[derive(Error,Debug)] #[error("missing mime multipart boundary")] pub struct MissingBoundary; impl<'b> ComponentIterator<'b> { #[throws(MissingBoundary)] pub fn resume_mid_component(buf: &'b [u8], boundary_finder: BoundaryFinder) -> (&'b [u8], Self) { let next_boundary = boundary_finder.find(buf).ok_or(MissingBoundary)?; let part = &buf[0..next_boundary]; let part = Self::payload_trim(part); //dbg!(DumpHex(part)); (part, ComponentIterator { at_boundary: &buf[next_boundary..], boundary_finder, }) } fn payload_trim(payload: &[u8]) -> &[u8] { payload.strip_suffix(b"\r").unwrap_or(payload) } #[throws(AE)] pub fn next(&mut self, warnings: &mut Warnings, expected: PartName) -> Option> { if self.at_boundary.is_empty() { return None } let mut comp = match { //dbg!(DumpHex(self.boundary_finder.needle())); let boundary_len = self.boundary_finder.needle().len(); //dbg!(boundary_len); process_boundary(warnings, &self.at_boundary[boundary_len..], expected)? } { None => { self.at_boundary = &self.at_boundary[0..0]; return None; }, Some(c) => c, }; let next_boundary = self.boundary_finder.find(comp.payload) .ok_or(MissingBoundary)?; self.at_boundary = &comp.payload[next_boundary..]; comp.payload = Self::payload_trim(&comp.payload[0..next_boundary]); //dbg!(DumpHex(comp.payload)); //dbg!(DumpHex(&self.at_boundary[0..5])); Some(comp) } } pub struct MetadataFieldIterator<'b> { buf: &'b [u8], last: Option, iter: memchr::Memchr<'b>, } impl<'b> MetadataFieldIterator<'b> { pub fn new(buf: &'b [u8]) -> Self { Self { buf, last: Some(0), iter: memchr::Memchr::new(b'\n', buf), } } #[throws(AE)] pub fn need_next(&mut self) -> &'b str { self.next().ok_or_else(|| anyhow!("missing"))?? } #[throws(AE)] pub fn need_parse(&mut self) -> T where T: FromStr, AE: From, { self.parse()?.ok_or_else(|| anyhow!("missing"))? } #[throws(AE)] pub fn parse(&mut self) -> Option where T: FromStr, AE: From, { let s = if let Some(r) = self.next() { r? } else { return None }; Some(s.parse()?) } pub fn remaining_bytes_len(&self) -> usize { if let Some(last) = self.last { self.buf.len() - last } else { 0 } } } impl<'b> Iterator for MetadataFieldIterator<'b> { type Item = Result<&'b str, std::str::Utf8Error>; fn next(&mut self) -> Option> { let last = self.last?; let (s, last) = match self.iter.next() { Some(nl) => (&self.buf[last..nl], Some(nl+1)), None => (&self.buf[last..], None), }; self.last = last; let s = str::from_utf8(s).map(|s| s.trim()); Some(s) } } impl<'b> std::iter::FusedIterator for MetadataFieldIterator<'b> { } work/src/prelude.rs0000664000000000000000000000700014766655527011551 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. pub use std::array; pub use std::collections::{BTreeSet, HashMap, VecDeque}; pub use std::convert::{Infallible, TryFrom, TryInto}; pub use std::borrow::Cow; pub use std::cell::{RefCell, RefMut}; pub use std::cmp::{min, max}; pub use std::env; pub use std::fs; pub use std::fmt::{self, Debug, Display, Write as _}; pub use std::future::Future; pub use std::io::{self, Cursor, ErrorKind, IoSlice, Read as _, Write as _}; pub use std::iter; pub use std::mem; pub use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; pub use std::num::TryFromIntError; pub use std::os::fd::{BorrowedFd, OwnedFd}; pub use std::os::raw::c_int; pub use std::path::{Path, PathBuf}; pub use std::panic::{self, AssertUnwindSafe}; pub use std::process; pub use std::pin::Pin; pub use std::str::{self, FromStr}; pub use std::sync::Arc; pub use std::task::Poll; pub use std::time::{SystemTime, UNIX_EPOCH}; pub use derive_deftly::{define_derive_deftly, Deftly}; pub use educe::Educe; pub use either::Either; pub use easy_ext::ext; pub use fehler::{throw, throws}; pub use futures::{poll, future, FutureExt, StreamExt, TryStreamExt}; pub use hyper::body::{Bytes, Buf}; pub use hyper::{Method}; pub use ipnet::IpNet; pub use itertools::{chain, iproduct, izip, Itertools}; pub use lazy_regex::{regex_captures, regex_is_match, regex_replace_all}; pub use lazy_static::lazy_static; pub use log::{trace, debug, info, warn, error}; pub use memchr::memmem; pub use pin_project_lite::pin_project; pub use reqwest::Url; pub use subtle::ConstantTimeEq; pub use thiserror::Error; pub use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; pub use tokio::pin; pub use tokio::select; pub use tokio::sync::{mpsc, oneshot}; pub use tokio::task::{self, JoinError, JoinHandle}; pub use tokio::time::{Duration, Instant}; pub use void::{self, Void, ResultVoidExt, ResultVoidErrExt}; pub use eyre as anyhow; pub use eyre::eyre as anyhow; pub use eyre::WrapErr; pub use eyre::Error as AE; pub use crate::compat; pub use crate::config::{self, InstanceConfig, PrintConfigOpt}; pub use crate::config::{InspectableConfig, InspectableConfigValue}; pub use crate::config::{DisplayInspectable, U32Ext as _}; pub use crate::impl_inspectable_config_value; pub use crate::ini; pub use crate::ipif::Ipif; pub use crate::multipart::{self, PartName, MetadataFieldIterator}; pub use crate::utils::*; pub use crate::queue::*; pub use crate::reporter::*; pub use crate::types::*; pub use crate::slip::{self, *}; pub type ReqNum = u64; pub use ErrorKind as EK; pub use PacketError as PE; pub use tokio::io as t_io; pub use tokio::process as t_proc; pub const SLIP_END: u8 = 0o300; // c0 pub const SLIP_ESC: u8 = 0o333; // db pub const SLIP_ESC_END: u8 = 0o334; // dc pub const SLIP_ESC_ESC: u8 = 0o335; // dd pub const SLIP_MIME_ESC: u8 = b'-'; // 2d pub const MAX_OVERHEAD: usize = 2_000; mod base64_config { use base64::engine::*; // Emit padding when we base64 encode things, but tolerate its lack // hippotat 1.x always ignored padding (except for 1.1.8). // Eventually we plan to stop emitting padding. pub const BASE64_CONFIG: GeneralPurpose = GeneralPurpose::new( &base64::alphabet::STANDARD, GeneralPurposeConfig::new() .with_encode_padding(true) .with_decode_padding_mode(DecodePaddingMode::Indifferent) ); } pub use base64_config::*; pub use base64::Engine as _; pub fn default() -> T { Default::default() } work/src/queue.rs0000664000000000000000000001003214766655527011234 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; #[derive(Default,Clone)] pub struct PacketQueue { queue: VecDeque, content: usize, } impl PacketQueue where D: AsRef<[u8]> { pub fn push_back(&mut self, data: D) { self.content += data.as_ref().len(); self.queue.push_back(data); } pub fn pop_front(&mut self) -> Option { let data = self.queue.pop_front()?; self.content -= data.as_ref().len(); Some(data) } pub fn content_count(&self) -> usize { self.queue.len() } pub fn content_len(&self) -> usize { self.content } pub fn total_len(&self) -> usize { self.content_count() + self.content_len() } pub fn is_empty(&self) -> bool { self.queue.is_empty() } pub fn peek_front(&self) -> Option<&D> { self.queue.front() } } #[derive(Educe,Clone)] #[educe(Default)] pub struct QueueBuf { content: usize, eaten1: usize, // 0 <= eaten1 < queue.front()...len() queue: VecDeque, } #[derive(Default,Debug,Clone)] pub struct FrameQueueBuf { queue: QueueBuf, } pub type QueuedBytes = Either, &'static [u8]>; use Either::Left as QueuedBytesOwned; use Either::Right as QueuedBytesBorrowed; impl Debug for QueueBuf where E: AsRef<[u8]> { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "Queue{{content={},eaten1={},queue=[", self.content, self.eaten1)?; for q in &self.queue { write!(f, "{},", q.as_ref().len())?; } write!(f, "]}}")?; } } impl QueueBuf where E: AsRef<[u8]> { pub fn push>(&mut self, b: B) { self.push_(b.into()); } fn push_(&mut self, b: E) { let l = b.as_ref().len(); self.queue.push_back(b); self.content += l; } pub fn is_empty(&self) -> bool { self.content == 0 } pub fn len(&self) -> usize { self.content } } impl FrameQueueBuf { pub fn push_esc>>(&mut self, b: B) { self.push_esc_(b.into()); } fn push_esc_(&mut self, b: Box<[u8]>) { self.queue.push_( QueuedBytesOwned(b)); self.queue.push_(QueuedBytesBorrowed(SLIP_END_SLICE)); } pub fn esc_push(&mut self, b: Box<[u8]>) { self.queue.push_(QueuedBytesBorrowed(SLIP_END_SLICE)); self.queue.push_(QueuedBytesOwned(b)); } pub fn push_raw(&mut self, b: Box<[u8]>) { self.queue.push_(QueuedBytesOwned(b)); } pub fn is_empty(&self) -> bool { self.queue.is_empty() } pub fn len(&self) -> usize { self.queue.len() } } impl hyper::body::Buf for QueueBuf where E: AsRef<[u8]> { fn remaining(&self) -> usize { self.content } fn chunk(&self) -> &[u8] { let front = if let Some(f) = self.queue.front() { f } else { return &[] }; &front.as_ref()[ self.eaten1.. ] } fn advance(&mut self, cnt: usize) { self.content -= cnt; self.eaten1 += cnt; loop { if self.eaten1 == 0 { break } let front = self.queue.front().unwrap(); if self.eaten1 < front.as_ref().len() { break; } self.eaten1 -= front.as_ref().len(); self.queue.pop_front().unwrap(); } } } impl hyper::body::Buf for FrameQueueBuf { fn remaining(&self) -> usize { self.queue.remaining() } fn chunk(&self) -> &[u8] { self.queue.chunk() } fn advance(&mut self, cnt: usize) { self.queue.advance(cnt) } } pin_project!{ pub struct BufBody { body: Option, } } impl BufBody { pub fn new(body: B) -> Self { Self { body: Some(body ) } } } impl BufBody { pub fn display(s: S) -> Self { let s = s.to_string().into_bytes(); let mut buf: FrameQueueBuf = default(); buf.push_raw(s.into()); Self::new(buf) } } impl hyper::body::Body for BufBody { type Error = Void; type Data = B; fn poll_frame(self: Pin<&mut Self>, _: &mut std::task::Context<'_>) -> Poll, Void>>> { Poll::Ready(Ok(self.project().body.take() .map(hyper::body::Frame::data) ).transpose()) } } work/src/reporter.rs0000664000000000000000000002012414766655527011755 0ustar // Copyright 2021-2022 Ian Jackson, yaahc and contributors to Hippotat and Eyre // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; #[derive(clap::Parser,Debug)] pub struct LogOpts { /// Increase debug level /// /// May be repeated for more verbosity. /// /// When using syslog, one `-D` this arranges to send to syslog even /// trace messages (mapped onto syslog level `DEBUG`); /// and two -`D` means to send to syslog even messages from lower layers /// (normally just the hippotat modules log to /// syslog). #[clap(long, short='D', action=clap::ArgAction::Count)] debug: u8, /// Syslog facility to use #[clap(long, value_parser=parse_syslog_facility)] syslog_facility: Option, } #[throws(AE)] fn parse_syslog_facility(s: &str) -> syslog::Facility { s.parse().map_err(|()| anyhow!("unrecognised syslog facility: {:?}", s))? } #[derive(Debug)] struct LogWrapper{ debug: u8, output: T, } impl LogWrapper { fn wanted(&self, md: &log::Metadata<'_>) -> bool { let first = |mod_path| { let mod_path: &str = mod_path; // can't do in args as breaks lifetimes mod_path.split_once("::").map(|s| s.0).unwrap_or(mod_path) }; self.debug >= 2 || first(md.target()) == first(module_path!()) } fn set_max_level(&self) { log::set_max_level(if self.debug < 1 { log::LevelFilter::Debug } else { log::LevelFilter::Trace }); } } impl log::Log for LogWrapper where T: log::Log { fn enabled(&self, md: &log::Metadata<'_>) -> bool { self.wanted(md) && self.output.enabled(md) } fn log(&self, record: &log::Record<'_>) { if self.wanted(record.metadata()) { let mut wrap = log::Record::builder(); macro_rules! copy { { $( $f:ident ),* $(,)? } => { $( wrap.$f(record.$f()); )* } } copy!{ level, target, module_path, file, line }; match format_args!("{}: {}", heck::AsKebabCase(record.level().as_str()), record.args()) { args => { wrap.args(args); self.output.log(&wrap.build()); } } } } fn flush(&self) { self.output.flush() } } impl LogOpts { #[throws(AE)] pub fn log_init(&self) { if let Some(facility) = self.syslog_facility { let f = syslog::Formatter3164 { facility, hostname: None, process: "hippotatd".into(), pid: std::process::id(), }; let l = syslog::unix(f) // syslog::Error is not Sync. // https://github.com/Geal/rust-syslog/issues/65 .map_err(|e| anyhow!(DisplayError(&e).to_string())) .context("set up syslog logger")?; let l = syslog::BasicLogger::new(l); let l = LogWrapper { output: l, debug: self.debug }; l.set_max_level(); let l = Box::new(l) as _; log::set_boxed_logger(l).context("install syslog logger")?; } else { let env = env_logger::Env::new() .filter("HIPPOTAT_LOG") .write_style("HIPPOTAT_LOG_STYLE"); let mut logb = env_logger::Builder::new(); logb.filter(Some("hippotat"), *[ log::LevelFilter::Info, log::LevelFilter::Debug ] .get(usize::from(self.debug)) .unwrap_or( &log::LevelFilter::Trace )); logb.parse_env(env); logb.init(); } } } pub struct OptionPrefixColon(pub Option); impl Display for OptionPrefixColon { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { if let Some(x) = &self.0 { write!(f, "{}: ", x)? } } } // For clients only, really. pub struct Reporter<'r> { ic: &'r InstanceConfig, successes: u64, last_report: Option, } #[derive(Debug)] struct Report { when: Instant, ok: Result<(),()>, } // Reporting strategy // - report all errors // - report first success after a period of lack of messages // - if error, report last success impl<'r> Reporter<'r> { pub fn new(ic: &'r InstanceConfig) -> Self { Reporter { ic, successes: 0, last_report: None, } } pub fn success(&mut self) { self.successes += 1; let now = Instant::now(); if let Some(rep) = &self.last_report { if now - rep.when < match rep.ok { Ok(()) => match self.ic.success_report_interval { z if z == Duration::default() => return, nonzero => nonzero, }, Err(()) => self.ic.effective_http_timeout, } { return } } info!(target:"hippotat", "{} ({}ok): running", self.ic, self.successes); self.last_report = Some(Report { when: now, ok: Ok(()) }); } pub fn filter(&mut self, req_num: Option, r: Result) -> Option { let now = Instant::now(); match r { Ok(t) => { Some(t) }, Err(e) => { let m = (||{ let mut m = self.ic.to_string(); if let Some(req_num) = req_num { write!(m, " #{}", req_num)?; } if self.successes > 0 { write!(m, " ({}ok)", self.successes)?; self.successes = 0; } write!(m, ": {}", e)?; Ok::<_,fmt::Error>(m) })().unwrap(); warn!(target:"hippotat", "{}", m); self.last_report = Some(Report { when: now, ok: Err(()) }); None }, } } } use backtrace::Backtrace; use eyre::Chain; use indenter::indented; #[derive(Debug)] struct EyreDedupHandler { backtrace: Option>>, } type DynError<'r> = &'r (dyn std::error::Error + 'static); struct DisplayError<'r>(DynError<'r>); impl Display for DisplayError<'_> { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { let mut last: Option = None; let mut error = Some(self.0); while let Some(e) = error { let m = e.to_string(); match last { None => write!(f, "{}", m)?, Some(l) if l.contains(&m) => { }, Some(_) => write!(f, ": {}", m)?, } last = Some(m); error = e.source(); } } } impl eyre::EyreHandler for EyreDedupHandler { #[throws(fmt::Error)] fn display(&self, error: DynError, f: &mut fmt::Formatter) { Display::fmt(&DisplayError(error), f)?; } #[throws(fmt::Error)] fn debug(&self, error: DynError, f: &mut fmt::Formatter) { if f.alternate() { return core::fmt::Debug::fmt(error, f)?; } write!(f, "{}", error)?; if let Some(cause) = error.source() { write!(f, "\n\nCaused by:")?; let multiple = cause.source().is_some(); for (n, error) in Chain::new(cause).enumerate() { writeln!(f)?; if multiple { write!(indented(f).ind(n), "{}", error)?; } else { write!(indented(f), "{}", error)?; } } } if let Some(bt) = &self.backtrace { let mut bt = bt.lock(); bt.resolve(); write!(f, "\n\nStack backtrace:\n{:?}", bt)?; } } } #[throws(AE)] pub fn dedup_eyre_setup() { eyre::set_hook(Box::new(|_error| { lazy_static! { static ref BACKTRACE: bool = { match env::var("RUST_BACKTRACE") { Ok(s) if s.starts_with("1") => true, Ok(s) if s == "0" => false, Err(env::VarError::NotPresent) => false, x => { eprintln!("warning: RUST_BACKTRACE not understood: {:?}", x); false }, } }; } let backtrace = if *BACKTRACE { let bt = Backtrace::new_unresolved(); let bt = Arc::new(bt.into()); Some(bt) } else { None }; Box::new(EyreDedupHandler { backtrace }) })) .context("set error handler")?; } const MAX_WARNINGS: usize = 15; #[derive(Debug,Default)] pub struct Warnings { pub warnings: Vec, } #[derive(Debug,Error)] #[error("too many warnings")] pub struct TooManyWarnings; impl Warnings { #[throws(TooManyWarnings)] pub fn add(&mut self, e: &dyn Display) { if self.warnings.len() >= MAX_WARNINGS { throw!(TooManyWarnings) } self.warnings.push(e.to_string()); } } work/src/rope.rs0000664000000000000000000000207114766655527011061 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; #[derive(Default,Clone)] pub struct Queue { content: usize, eaten1: usize, // 0 <= eaten1 < queue.front()...len() queue: VecDeque>, } pub impl Queue { pub fn push>>(&mut self, b: B) { self.push_(b.into()); } pub fn push_(&mut self, b: Box<[u8]>) { let l = b.len(); self.push(b); b.content += b; } pub fn is_empty(&self) { self.content == 0 } } impl bytes::Buf for Queue { fn remaining(&self) -> usize { self.content } fn chunk(&self) -> usize { let front = if let(f) = self.queue.front() { f } else { return &[] }; front[ self.eaten1.. ] } fn advance(&self, cnt: usize) { eaten1 += cnt; loop { if eaten1 == 0 { break } let front = self.queue.front().unwrap(); if eaten1 < front.len() { break; } eaten1 -= front.len(); self.queue.pop_front().unwrap(); } } } work/src/slip.rs0000664000000000000000000001711414766655527011067 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; pub static SLIP_END_SLICE: &[u8] = &[SLIP_END]; #[derive(Error,Debug,Copy,Clone,Eq,PartialEq)] pub enum PacketError { #[error("empty packet")] Empty, #[error("MTU exceeded ({len} > {mtu})")] MTU { len: usize, mtu: u32 }, #[error("Invalid SLIP escape sequence")] SLIP, #[error("unexpected src addr {0:?}")] Src(IpAddr), #[error("unexpected dst addr {0:?}")] Dst(IpAddr), #[error("truncated, IPv{vsn}, len={len}")] Truncated { len: usize, vsn: u8 }, } pub trait SlipMime: Copy { const CONV_TO: Option; } #[derive(Copy,Clone,Debug)] pub struct Slip2Mime; #[derive(Copy,Clone,Debug)] pub struct Mime2Slip; #[derive(Copy,Clone,Debug)] pub struct SlipNoConv; impl SlipMime for Slip2Mime { const CONV_TO: Option = Some(true); } impl SlipMime for Mime2Slip { const CONV_TO: Option = Some(false); } impl SlipMime for SlipNoConv { const CONV_TO: Option = None; } #[derive(Debug,Error,Eq,PartialEq)] pub enum SlipFramesError where E: std::error::Error + 'static { #[error("only bad IP datagrams")] ErrorOnlyBad, #[error("{0}")] Other(#[from] E), } #[throws(SlipFramesError)] pub async fn processn( mime: M, mtu: u32, data: &[u8], addr_chk: AC, mut out: OUT, mut error_handler: EH ) where AC: Fn(&[u8]) -> Result + Copy + Send, OUT: FnMut((Box<[u8]>, ACR)) -> FOUT + Send, FOUT: Future> + Send, EH: FnMut(PacketError) -> Result<(), SlipFramesError> + Send, EHE: std::error::Error + Send + 'static, { // eprintln!("before: {:?}", DumpHex(data)); if data.is_empty() { return } let mut ok = false; let mut err = false; for packet in data.split(|&c| c == SLIP_END) { match async { let checked = process1(mime, mtu, packet, addr_chk); if matches!(checked, Err(PacketError::Empty)) { return Ok::<_,PE>(()) } out(checked?).await?; ok = true; Ok::<_,PE>(()) }.await { Ok(()) => { }, Err(e) => { err=true; error_handler(e)?; }, } } // eprintln!(" after: {:?}", DumpHex(data)); if err && !ok { throw!(SlipFramesError::ErrorOnlyBad) } } #[throws(PacketError)] pub fn process1( _mime: M, mtu: u32, packet: &[u8], addr_chk: AC, ) -> (Box<[u8]>, ACR) where AC: Fn(&[u8]) -> Result, { if packet.len() == 0 { throw!(PacketError::Empty) } let mut packet: Box<[u8]> = packet.to_owned().into(); let mut walk: &mut [u8] = &mut packet; let mut header = [0u8; HEADER_FOR_ADDR]; let mut wheader = &mut header[..]; let mut escapes = 0; while let Some((i, was_mime)) = walk.iter().enumerate().find_map( |(i,&c)| match c { SLIP_ESC => Some((i,false)), SLIP_MIME_ESC if M::CONV_TO.is_some() => Some((i,true)), _ => None, } ) { let _ = wheader.write(&walk[0..i]); if M::CONV_TO.is_some() { walk[i] = if was_mime { SLIP_ESC } else { SLIP_MIME_ESC }; } if Some(was_mime) != M::CONV_TO { let c = match walk.get(i+1) { Some(&SLIP_ESC_ESC) => SLIP_ESC, Some(&SLIP_ESC_END) => SLIP_END, _ => throw!(PacketError::SLIP), }; let _ = wheader.write(&[c]); walk = &mut walk[i+2 ..]; escapes += 1; } else { let _ = wheader.write(&[SLIP_MIME_ESC]); walk = &mut walk[i+1 ..]; } } let _ = wheader.write(walk); let wheader_len = wheader.len(); let header = &header[0.. header.len() - wheader_len]; let decoded_len = packet.len() - escapes; if decoded_len > mtu.sat() { throw!(PacketError::MTU { len: decoded_len, mtu }); } let acr = addr_chk(header)?; (packet, acr) } pub type Frame = Vec; pub type FramesData = Vec>; // todo: https://github.com/tokio-rs/bytes/pull/504 // pub type Frame = Box<[u8]>; // pub type FramesData = Vec; // `From>` is not implemented for `Bytes` // when this is fixed, there are two `into`s in client.rs which // become redundant (search for todo:504) #[derive(Default)] pub struct Frames { frames: FramesData, total_len: usize, tried_full: bool, } impl Debug for Frames { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "Frames{{n={},len={}}}", &self.frames.len(), &self.total_len)?; } } impl Frames { #[throws(Frame)] pub fn add(&mut self, max: u32, frame: Frame) { if frame.len() == 0 { return } let new_total = self.total_len + frame.len() + 1; if new_total > max.sat() { self.tried_full = true; throw!(frame); } self.total_len = new_total; self.frames.push(frame); } #[inline] pub fn tried_full(&self) -> bool { self.tried_full } #[inline] pub fn is_empty(&self) -> bool { self.frames.is_empty() } } impl From for FramesData { fn from(frames: Frames) -> FramesData { frames.frames } } const HEADER_FOR_ADDR: usize = 40; #[throws(PacketError)] pub fn ip_packet_addr(header: &[u8]) -> IpAddr { let vsn = (header.get(0).ok_or_else(|| PE::Empty)? & 0xf0) >> 4; match vsn { 4 if header.len() >= 20 => { let slice = &header[if DST { 16 } else { 12 }..][0..4]; Ipv4Addr::from(*<&[u8;4]>::try_from(slice).unwrap()).into() }, 6 if header.len() >= 40 => { let slice = &header[if DST { 24 } else { 8 }..][0..16]; Ipv6Addr::from(*<&[u8;16]>::try_from(slice).unwrap()).into() }, _ => throw!(PE::Truncated{ vsn, len: header.len() }), } } #[derive(Copy,Clone,Eq,PartialEq,Ord,PartialOrd,Hash)] pub struct DumpHex<'b>(pub &'b [u8]); impl Debug for DumpHex<'_> { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { for v in self.0 { write!(f, "{:02x}", v)?; } match str::from_utf8(self.0) { Ok(s) => write!(f, "={:?}", s)?, Err(x) => write!(f, "={:?}..", str::from_utf8(&self.0[0..x.valid_up_to()]).unwrap() )?, } } } #[tokio::test] async fn mime_slip_to_mime() { use PacketError as PE; const MTU: u32 = 10; async fn chk(m: M, i: &[u8], exp_p: &[&[u8]], exp_e: &[PacketError], exp_r: Result<(),SlipFramesError>) { dbg!(M::CONV_TO, DumpHex(i)); let mut got_e = vec![]; let mut got_p = vec![]; let got_r = processn( m, MTU, i, |_|Ok(()), |(p,())| { got_p.push(p); async { Ok(()) } }, |e| Ok::<_,SlipFramesError>(got_e.push(e)) ).await; assert_eq!( got_p.iter().map(|b| DumpHex(b)).collect_vec(), exp_p.iter().map(|b| DumpHex(b)).collect_vec() ); assert_eq!( got_e, exp_e ); assert_eq!( got_r, exp_r ); } use SlipFramesError::ErrorOnlyBad; chk(Slip2Mime, &[ SLIP_END, SLIP_ESC, SLIP_ESC_END, b'-', b'X' ], &[ &[ b'-', SLIP_ESC_END, SLIP_ESC, b'X' ] ], &[ ], Ok(())).await; chk(Slip2Mime, &[ SLIP_END, SLIP_ESC, b'y' ], &[], &[ PE::SLIP ], Err(ErrorOnlyBad)).await; chk(Slip2Mime, &[ SLIP_END, b'-', b'y' ], &[ &[ SLIP_ESC, b'y' ] ], &[ ], Ok(())).await; chk(Slip2Mime, &[b'x'; 20], &[ ], &[ PE::MTU { len: 20, mtu: MTU } ], Err(ErrorOnlyBad)).await; chk(SlipNoConv, &[ SLIP_END, SLIP_ESC, SLIP_ESC_END, b'-', b'X' ], &[ &[ SLIP_ESC, SLIP_ESC_END, b'-', b'X' ] ], &[ ], Ok(())).await; } work/src/types.rs0000664000000000000000000000437414766655527011270 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; #[derive(Debug,Copy,Clone)] pub enum LinkEnd { Server, Client } #[derive(Debug,Clone,Hash,Eq,PartialEq,Ord,PartialOrd)] pub struct ServerName(pub String); #[derive(Debug,Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd)] pub struct ClientName(pub IpAddr); #[derive(Clone,Hash,Eq,PartialEq,Ord,PartialOrd)] pub struct LinkName { pub server: ServerName, pub client: ClientName, } impl Debug for LinkName { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "LinkName({})", self)?; } } impl FromStr for ClientName { type Err = AE; #[throws(AE)] fn from_str(s: &str) -> Self { ClientName( if let Ok(v4addr) = s.parse::() { if s != v4addr.to_string() { throw!(anyhow!("invalid client name (unusual IPv4 address syntax)")); } v4addr.into() } else if let Ok(v6addr) = s.parse::() { if s != v6addr.to_string() { throw!(anyhow!("invalid client name (non-canonical IPv6 address)")); } v6addr.into() } else { throw!(anyhow!("invalid client name (IPv4 or IPv6 address)")) } ) } } impl FromStr for ServerName { type Err = AE; #[throws(AE)] fn from_str(s: &str) -> Self { if ! regex_is_match!(r" ^ (?: SERVER | [0-9a-z][-0-9a-z]* (:? \. [0-9a-z][-0-9a-z]* )* ) $"x, s) { throw!(anyhow!("bad syntax for server name")); } if ! regex_is_match!(r"[A-Za-z-]", s) { throw!(anyhow!("bad syntax for server name \ (too much like an IPv4 address)")); } ServerName(s.into()) } } impl Display for ServerName { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.0, f)?; } } impl Display for ClientName { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.0, f)?; } } impl Display for LinkName { #[throws(fmt::Error)] fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "[{} {}]", &self.server, &self.client)?; } } impl_inspectable_config_value!{ LinkName as Display } work/src/utils.rs0000664000000000000000000001733014766655527011260 0ustar // Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use crate::prelude::*; #[ext] pub impl T where T: Debug { fn to_debug(&self) -> String { format!("{:?}", self) } } #[ext] pub impl Result where AE: From { fn dcontext(self, d: D) -> anyhow::Result { self.map_err(|e| AE::from(e)).with_context(|| d.to_debug()) } } #[derive(Error,Debug)] pub enum ReadLimitedError { #[error("maximum size {limit} exceeded")] Truncated { sofar: Box<[u8]>, limit: usize }, #[error("HTTP error {0}")] Http(#[from] H), } impl ReadLimitedError { pub fn discard_data(&mut self) { match self { ReadLimitedError::Truncated { sofar,.. } => { mem::take(sofar); }, _ => { }, } } } #[ext] pub impl Result> { fn discard_data(self) -> Self { self.map_err(|mut e| { e.discard_data(); e }) } } // Works around the lack of ErrorKind::IsADirectory // #![feature(io_error_more)] // https://github.com/rust-lang/rust/issues/86442 #[ext] pub impl io::Error { fn is_is_a_directory(&self) -> bool { self.raw_os_error() .unwrap_or_else(|| panic!( "trying to tell whether Kind is IsADirectory for non-OS error io::Error {}", self)) == libc::EISDIR } } #[throws(ReadLimitedError)] pub async fn read_limited_bytes(limit: usize, initial: Box<[u8]>, capacity: usize, mut stream: Pin<&mut S>) -> Box<[u8]> where S: futures::Stream>, H: std::error::Error + 'static, // we also require that the Stream is cancellation-safe { let mut accum = initial.into_vec(); let capacity = min(limit, capacity); if capacity > accum.len() { accum.reserve(capacity - accum.len()); } while let Some(item) = stream.next().await { let b = item?; accum.extend(b); if accum.len() > limit { throw!(ReadLimitedError::Truncated { limit, sofar: accum.into() }) } } accum.into() } pub fn time_t_now() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_else(|_| Duration::default()) // clock is being weird .as_secs() } use sha2::Digest as _; use sha2::digest::Update as _; pub type HmacH = sha2::Sha256; pub const HMAC_B: usize = 64; pub const HMAC_L: usize = 32; pub fn token_hmac(key: &[u8], message: &[u8]) -> [u8; HMAC_L] { let key = { let mut padded = [0; HMAC_B]; if key.len() > padded.len() { let digest: [u8; HMAC_L] = HmacH::digest(key).into(); padded[0..HMAC_L].copy_from_slice(&digest); } else { padded[0.. key.len()].copy_from_slice(key); } padded }; let mut ikey = key; for k in &mut ikey { *k ^= 0x36; } let mut okey = key; for k in &mut okey { *k ^= 0x5C; } //dbg!(DumpHex(&key), DumpHex(message), DumpHex(&ikey), DumpHex(&okey)); let h1 = HmacH::new() .chain(ikey) .chain(message) .finalize(); let h2 = HmacH::new() .chain(okey) .chain(h1) .finalize(); h2.into() } #[test] fn hmac_test_vectors(){ // C&P from RFC 4231 let vectors = r#" Key = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b 0b0b0b0b (20 bytes) Data = 4869205468657265 ("Hi There") HMAC-SHA-256 = b0344c61d8db38535ca8afceaf0bf12b 881dc200c9833da726e9376c2e32cff7 Key = 4a656665 ("Jefe") Data = 7768617420646f2079612077616e7420 ("what do ya want ") 666f72206e6f7468696e673f ("for nothing?") HMAC-SHA-256 = 5bdcc146bf60754e6a042426089575c7 5a003f089d2739839dec58b964ec3843 Key = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaa (20 bytes) Data = dddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddd dddd (50 bytes) HMAC-SHA-256 = 773ea91e36800e46854db8ebd09181a7 2959098b3ef8c122d9635514ced565fe Key = 0102030405060708090a0b0c0d0e0f10 111213141516171819 (25 bytes) Data = cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd cdcd (50 bytes) HMAC-SHA-256 = 82558a389a443c0ea4cc819899f2083a 85f0faa3e578f8077a2e3ff46729665b Key = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaa (131 bytes) Data = 54657374205573696e67204c61726765 ("Test Using Large") 72205468616e20426c6f636b2d53697a ("r Than Block-Siz") 65204b6579202d2048617368204b6579 ("e Key - Hash Key") 204669727374 (" First") HMAC-SHA-256 = 60e431591ee0b67f0d8a26aacbf5b77f 8e0bc6213728c5140546040f0ee37f54 Key = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaa (131 bytes) Data = 54686973206973206120746573742075 ("This is a test u") 73696e672061206c6172676572207468 ("sing a larger th") 616e20626c6f636b2d73697a65206b65 ("an block-size ke") 7920616e642061206c61726765722074 ("y and a larger t") 68616e20626c6f636b2d73697a652064 ("han block-size d") 6174612e20546865206b6579206e6565 ("ata. The key nee") 647320746f2062652068617368656420 ("ds to be hashed ") 6265666f7265206265696e6720757365 ("before being use") 642062792074686520484d414320616c ("d by the HMAC al") 676f726974686d2e ("gorithm.") HMAC-SHA-256 = 9b09ffa71b942fcb27635fbcd5b0e944 bfdc63644f0713938a7f51535c3a35e2 "#; let vectors = regex_replace_all!{ r#"\(.*\)"#, vectors.trim_end(), |_| "", }; let vectors = regex_replace_all!{ r#" *\n "#, &vectors, |_| "", }; let vectors = regex_replace_all!{ r#"\s*\n"#, &vectors, |_| "\n", }; let mut lines = vectors.split('\n'); assert_eq!( lines.next().unwrap(), "" ); let mut get = |prefix| { let l = lines.next()?; dbg!(l); let b = l.strip_prefix(prefix).unwrap().as_bytes().chunks(2) .map(|s| str::from_utf8(s).unwrap()) .map(|s| { assert_eq!(s.len(), 2); u8::from_str_radix(s,16).unwrap() }) .collect::>(); Some(b) }; while let Some(key) = get(" Key = ") { let data = get(" Data = ").unwrap(); let exp = get(" HMAC-SHA-256 = ").unwrap(); let got = token_hmac(&key, &data); assert_eq!(&got[..], &exp); } } pub fn verify_cli() { let app: clap::Command = F::command(); app.debug_assert(); } work/test/0000775000000000000000000000000014766655527007736 5ustar work/test/capture-log0000775000000000000000000000061014766655527012103 0ustar #!/bin/bash # Copyright 2020-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -e set -o pipefail actually_capture () { if [ "$BUILD_VERBOSE" ]; then tee "$log" | sed "s#^#$log #" else cat >"$log" fi } log="$1"; shift mkdir -p tmp "$@" 2>&1 | ts | actually_capture work/test/go-with-unshare0000775000000000000000000000056614766655527012714 0ustar #!/bin/bash # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -e . "${0%/*}"/tcommon case "$1" in */*) ;; ?*) tname="$1"; shift; set -- "$test/$tname" "$@" ;; '') fail 'bad usage: need program or test name' ;; esac $src/test/with-unshare "$@" work/test/netns-setup0000775000000000000000000000157714766655527012163 0ustar #!/bin/bash # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -ex slug=$1 c_ns=hippotat-t-$slug-client s_ns=hippotat-t-$slug-server ip netns delete $s_ns 2>/dev/null ||: ip netns delete $c_ns 2>/dev/null ||: ip netns add $c_ns ip netns add $s_ns ip link add t.s.$$ type veth peer name t.c.$$ move_to_netns () { cs=$1; ns=$2 ip link set t.$cs.$$ netns $ns ip netns exec $ns ip link set t.$cs.$$ name eth0 } move_to_netns s $s_ns move_to_netns c $c_ns config_netns () { ns=$1; num=$2; ip netns exec $ns ip addr add dev lo 127.0.0.1 ip netns exec $ns ip addr add dev eth0 198.51.100.$num/24 ip netns exec $ns ip link set lo up ip netns exec $ns ip link set eth0 up } config_netns $s_ns 1 config_netns $c_ns 2 work/test/t-basic0000775000000000000000000000052114766655527011204 0ustar #!/bin/bash # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -e . "${0%/*}"/tcommon test-prep start-server start-client in-ns client ping -i 0.1 -c 10 192.0.2.1 >$tmp/ping grep ' 0% packet loss' $tmp/ping work/test/tcommon0000664000000000000000000000326514766655527011343 0ustar # -*- shell-script -*- # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -o pipefail set -x ssrc="${0%/*}" src="$ssrc"/.. test="$src/test" target_bin_dir=${TARGET_RELEASE_DIR-target/debug} target_bin_prefix=${target_bin_dir}${target_bin_dir+/} fail () { echo >&2 "$0: fail: $*"; exit 1; } determine-tname () { local prefix=$1; shift case "${0##*/}" in $prefix-*) tname="${0##*/}" ;; *) fail "bad test script name $0" ;; esac } test-prep () { determine-tname t tmp=tmp/$tname rm -rf "$tmp" mkdir -p $tmp $test/netns-setup "$tname" trap ' rc=$? shutdown if [ $rc = 0 ]; then echo "OK $tname"; fi exit $rc ' 0 } kill-pids () { for p in $pids; do kill -9 $p; done } shutdown () { kill-pids } in-ns () { local client_server=$1; shift $exec ip netns exec hippotat-t-$tname-$client_server "$@" } run-client () { in-ns client \ ${target_bin_prefix}hippotat --config $test/test.cfg -DD "$@" } run-server () { in-ns server \ ${target_bin_prefix}hippotatd --config $test/test.cfg -DD "$@" } spawn () { { exec=exec; "$@"; } & pids+=" $!" } in-ns-await-up () { local sc="$1"; shift local addr="$1"; shift local t=1 while sleep $(( $t / 10 )).$(( $t % 10 )); do if in-ns $sc ip -o addr show | fgrep " inet $addr "; then return fi t=$(( $t + 1 )) if [ $t -gt 10 ]; then fail "$sc did not come up $addr"; fi done } start-server () { spawn run-server in-ns-await-up server 192.0.2.1 } start-client () { spawn run-client in-ns-await-up client 192.0.2.3 } work/test/test.cfg0000664000000000000000000000123714766655527011401 0ustar # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. [SERVER] ipif = exec /usr/lib/userv/ipif \* -- %(local)s,%(peer)s,%(mtu)s,slip '%(rnets)s' addrs = 198.51.100.1 port = 8099 vnetwork = 192.0.2.0/24 # ./hippotatd --debug-select=+ -c test.cfg # nc -n -v -l -p 8100 -c 'dd of=/dev/null' [192.0.2.3] secret = sesame [192.0.2.3] ipif = exec /usr/lib/userv/ipif \* -- %(local)s,%(peer)s,%(mtu)s,slip '%(rnets)s' # ./hippotat -D -c test.cfg [192.0.2.4] #secret = zorkmids # dd if=/dev/urandom bs=1024 count=16384 | nc -q 0 -n -v 192.0.2.1 8100 work/test/with-unshare0000775000000000000000000000064214766655527012304 0ustar #!/bin/bash # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -e . "${0%/*}"/tcommon #case "$1" in #T/*) prog="$src/test/${1#T/}"; shift; set -- "$prog" "$@" ;; #esac unshare -Urnm bash -xec ' mount -t tmpfs tmpfs /run PATH="$PATH:/usr/local/sbin:/sbin:/usr/sbin" exec "$@" ' x "$@" work/uml/0000775000000000000000000000000014766655527007554 5ustar work/uml/psusan-uml-inside0000775000000000000000000000164014766655527013060 0ustar #!/bin/bash # Copyright 2021-2022 Ian Jackson, Simon Tatham, and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -ex mkdir /dev/pts mount -t proc none /proc mount -t sysfs none /sys mount -t devpts none /dev/pts mount -t tmpfs none /tmp mount -t tmpfs none /run mount --bind /usr/lib/uml/modules/ /lib/modules/ modprobe tun exec 0<>/dev/tty1 1>&0 stty raw -echo # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991959 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin export PATH export SHELL=/bin/bash HOME=$(cat /proc/cmdline) case "$HOME" in *' psusan-uml-tmp='*) ;; *) echo >&2 'psusan-uml-tmp not found in /proc/cmdline'; exit 1;; esac HOME=${HOME##* psusan-uml-tmp=} HOME=${HOME%% *} export HOME cd "$HOME" dd if=random-seed of=/dev/urandom src/uml/rndaddtoentcnt/rndaddtoentcnt 4096 >&2 exec psusan work/uml/psusan-uml-psusan0000775000000000000000000000114214766655527013113 0ustar #!/bin/bash # Copyright 2021-2022 Ian Jackson, Simon Tatham, and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -e fifo=tmp/uml/q mkfifo -m600 $fifo ( # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958 : <$fifo cat ) | \ bwrap --dev-bind / / --tmpfs /dev/shm \ linux mem=512M rootfstype=hostfs rootflags=/ rw \ con=fd:2,fd:2 con1=fd:0,fd:1 init="${0%/*}"/psusan-uml-inside \ -- psusan-uml-tmp=$PWD/tmp/uml | \ ( read banner : >$fifo printf '%s\n' "$banner" cat ) work/uml/psusan-uml-run0000775000000000000000000000037314766655527012413 0ustar #!/bin/sh # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -e HOME=$PWD/tmp/uml plink -ssh-connection -share $PWD "$@" work/uml/psusan-uml-setup0000775000000000000000000000073114766655527012745 0ustar #!/bin/bash # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. set -e mkdir -p tmp rm -rf tmp/uml mkdir -p -m2700 tmp/uml ln -s "$PWD" tmp/uml/org-pwd ln -s org-pwd/target tmp/uml/target ln -s "${0%/*/*}" tmp/uml/src dd if=/dev/urandom of=tmp/uml/random-seed bs=1k count=4 "${0%/*}"/psusan-uml-run -proxycmd "${0%/*}"/psusan-uml-psusan -N -v -v work/uml/rndaddtoentcnt/0000775000000000000000000000000014766655527012567 5ustar work/uml/rndaddtoentcnt/.gitignore0000664000000000000000000000001714766655527014555 0ustar rndaddtoentcnt work/uml/rndaddtoentcnt/LICENSE0000664000000000000000000000207214766655527013575 0ustar MIT License Copyright (c) 2018 Jumpnow Technologies, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. work/uml/rndaddtoentcnt/Makefile0000664000000000000000000000017314766655527014230 0ustar rndaddtoentcnt: rndaddtoentcnt.c $(CC) rndaddtoentcnt.c -o rndaddtoentcnt .PHONY: clean clean: rm -f *.o rndaddtoentcnt work/uml/rndaddtoentcnt/README.md0000664000000000000000000000077214766655527014054 0ustar ### rndaddtoentcnt Seeding the random number generator by writing to /dev/urandom does not update the entropy count. This utility makes the RNDADDTOENTCNT ioctl call needed to do this. Used in startup scripts after initializing /dev/urandom with a presaved seed. Example: dd if=/path/to/some/random-seed-file of=/dev/urandom bs=512 count=1 /path/to/rdnaddtoentcnt where entropy-bit-count is a number between 1 and (8 * 512) depending on how much you trust the seed file. work/uml/rndaddtoentcnt/rndaddtoentcnt.c0000664000000000000000000000142714766655527015752 0ustar #include #include #include #include #include #include #include #include #include int main(int argc, char **argv) { int count, fd; if (argc != 2) { printf("Usage: rndaddtoentcnt \n"); exit(1); } count = strtoul(argv[1], NULL, 0); if (count < 1 || count > 4096) { printf("Count range is 1 to 4096\n"); exit(1); } fd = open("/dev/urandom", O_WRONLY); if (fd < 0) { perror("open(/dev/urandom)"); exit(1); } if (ioctl(fd, RNDADDTOENTCNT, &count) < 0) { perror("ioctl(RNDADDTOENTCNT)"); close(fd); exit(1); } close(fd); return 0; } work/uml/run-test0000775000000000000000000000101414766655527011257 0ustar #!/bin/sh # Copyright 2021-2022 Ian Jackson and contributors to Hippotat # SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception # There is NO WARRANTY. # *** This does not work. *** # UML is too horribly flaky. Use test/ instead, which just uses unshare! set -e set -x uml="${0%/*}"/psusan-uml if timeout --foreground 5 $uml-run true; then : else $uml-setup 2>&1 |ts >tmp/uml-setup & sleep 5 timeout --foreground 5 $uml-run true ||: timeout --foreground 5 $uml-run true fi echo hi