commit c5a6d7647cf3b78d7c2a21696459fba04c685329 Author: NotAShelf Date: Thu May 1 05:03:36 2025 +0300 initial commit diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3ab4b04 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2330 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa882656b67966045e4152c634051e70346939fced7117d5f0b52146a7c74c9" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6398974fd4284f4768af07965701efbbb5fdc0616bff20cade1bb14b77675e24" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e3b15b3dc6c6ed996e4032389e9849d4ab002b1e92fbfe85b5f307d1479b4d" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[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 = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.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 = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.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 = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[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 = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + +[[package]] +name = "clap" +version = "4.5.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[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.101", +] + +[[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 = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[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 = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[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_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "unicode-xid", +] + +[[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.101", +] + +[[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 = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +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 = "eris" +version = "0.1.0" +dependencies = [ + "actix-web", + "chrono", + "clap", + "env_logger", + "futures", + "ipnetwork", + "lazy_static", + "log", + "prometheus 0.14.0", + "prometheus_exporter", + "rand", + "rlua", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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.101", +] + +[[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.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.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "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 = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[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 = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[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.101", +] + +[[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 = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnetwork" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763" + +[[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.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom", + "libc", +] + +[[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 = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[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.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[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.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lua-src" +version = "547.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.5.12+a4f56a4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671" +dependencies = [ + "cc", + "which", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "mlua" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +dependencies = [ + "bstr", + "mlua-sys", + "mlua_derive", + "num-traits", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1901c1a635a22fe9250ffcc4fcc937c16b47c2e9e71adba8784af8bca1f69594" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "mlua_derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09697a6cec88e7f58a02c7ab5c18c611c6907c8654613df9cc0192658a4fb859" +dependencies = [ + "itertools", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.101", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[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", +] + +[[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 = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "thiserror 1.0.69", +] + +[[package]] +name = "prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror 2.0.12", +] + +[[package]] +name = "prometheus_exporter" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf17cbebe0bfdf4f279ef84eeefe0d50468b0b7116f078acf41d456e48fe81a" +dependencies = [ + "ascii", + "lazy_static", + "log", + "prometheus 0.13.4", + "thiserror 1.0.69", + "tiny_http", +] + +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + +[[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 = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +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-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rlua" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43f01c44a4ea0d2c7faadac231a0fd906a438723aa4ee26599e71fa7b915abd" +dependencies = [ + "mlua", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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.101", +] + +[[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 = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +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.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +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 = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[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.101", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "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.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny_http" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f8734c6d6943ad6df6b588d228a87b4af184998bcffa268ceddf05c2055a8c" +dependencies = [ + "ascii", + "chunked_transfer", + "log", + "time", + "url", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +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.101", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[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 = "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 = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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 = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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.101", + "wasm-bindgen-shared", +] + +[[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.101", + "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 = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +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", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[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.101", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[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.101", + "synstructure", +] + +[[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.101", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2292db4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "eris" +version = "0.1.0" +edition = "2024" + +[dependencies] +actix-web = "4.3.1" +clap = { version = "4.3", features = ["derive"] } +chrono = "0.4.24" +futures = "0.3.28" +ipnetwork = "0.21.1" +lazy_static = "1.4.0" +prometheus = "0.14.0" +prometheus_exporter = "0.8.5" +rand = "0.9.1" +rlua = "0.20.1" +serde = { version = "1.0.162", features = ["derive"] } +serde_json = "1.0.96" +tokio = { version = "1.28.0", features = ["full"] } +log = "0.4.27" +env_logger = "0.11.8" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..db740d4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,1442 @@ +use actix_web::{App, HttpRequest, HttpResponse, HttpServer, web}; +use clap::Parser; +use ipnetwork::IpNetwork; +use lazy_static::lazy_static; +use prometheus::{ + Counter, CounterVec, Gauge, register_counter, register_counter_vec, register_gauge, +}; +use rlua::{Function, Lua}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use std::env; +use std::fs; +use std::io::Write; +use std::net::IpAddr; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::process::Command; +use tokio::sync::RwLock; +use tokio::time::sleep; + +mod markov; +use markov::MarkovGenerator; + +// Command-line arguments using clap +#[derive(Parser, Debug, Clone)] +#[clap(author, version, about)] +struct Args { + #[clap(long, default_value = "0.0.0.0:8888")] + listen_addr: String, + + #[clap(long, default_value = "9100")] + metrics_port: u16, + + #[clap(long, default_value = "127.0.0.1:80")] + backend_addr: String, + + #[clap(long, default_value = "1000")] + min_delay: u64, + + #[clap(long, default_value = "15000")] + max_delay: u64, + + #[clap(long, default_value = "600")] + max_tarpit_time: u64, + + #[clap(long, default_value = "3")] + block_threshold: u32, + + #[clap(long)] + base_dir: Option, + + #[clap(long)] + config_file: Option, + + #[clap(long, default_value = "info")] + log_level: String, +} + +// Configuration structure +#[derive(Clone, Debug, Deserialize, Serialize)] +struct Config { + listen_addr: String, + metrics_port: u16, + backend_addr: String, + min_delay: u64, + max_delay: u64, + max_tarpit_time: u64, + block_threshold: u32, + trap_patterns: Vec, + whitelist_networks: Vec, + markov_corpora_dir: String, + lua_scripts_dir: String, + data_dir: String, + config_dir: String, + cache_dir: String, +} + +impl Default for Config { + fn default() -> Self { + Self { + listen_addr: "0.0.0.0:8888".to_string(), + metrics_port: 9100, + backend_addr: "127.0.0.1:80".to_string(), + min_delay: 1000, + max_delay: 15000, + max_tarpit_time: 600, + block_threshold: 3, + trap_patterns: vec![ + "/vendor/phpunit".to_string(), + "eval-stdin.php".to_string(), + "/wp-admin".to_string(), + "/wp-login.php".to_string(), + "/xmlrpc.php".to_string(), + "/phpMyAdmin".to_string(), + "/solr/".to_string(), + "/.env".to_string(), + "/config".to_string(), + "/api/".to_string(), + "/actuator/".to_string(), + ], + whitelist_networks: vec![ + "192.168.0.0/16".to_string(), + "10.0.0.0/8".to_string(), + "172.16.0.0/12".to_string(), + "127.0.0.0/8".to_string(), + ], + markov_corpora_dir: "./corpora".to_string(), + lua_scripts_dir: "./scripts".to_string(), + data_dir: "./data".to_string(), + config_dir: "./conf".to_string(), + cache_dir: "./cache".to_string(), + } + } +} + +// Gets standard XDG directory paths for config, data and cache +fn get_xdg_dirs() -> (PathBuf, PathBuf, PathBuf) { + let config_home = env::var_os("XDG_CONFIG_HOME") + .map(PathBuf::from) + .unwrap_or_else(|| { + let home = env::var_os("HOME") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(".")); + home.join(".config") + }); + + let data_home = env::var_os("XDG_DATA_HOME") + .map(PathBuf::from) + .unwrap_or_else(|| { + let home = env::var_os("HOME") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(".")); + home.join(".local").join("share") + }); + + let cache_home = env::var_os("XDG_CACHE_HOME") + .map(PathBuf::from) + .unwrap_or_else(|| { + let home = env::var_os("HOME") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(".")); + home.join(".cache") + }); + + let config_dir = config_home.join("eris"); + let data_dir = data_home.join("eris"); + let cache_dir = cache_home.join("eris"); + + (config_dir, data_dir, cache_dir) +} + +impl Config { + // Create configuration from command-line args + fn from_args(args: &Args) -> Self { + let (config_dir, data_dir, cache_dir) = if let Some(base_dir) = &args.base_dir { + let base_str = base_dir.to_string_lossy().to_string(); + ( + format!("{base_str}/conf"), + format!("{base_str}/data"), + format!("{base_str}/cache"), + ) + } else { + let (c, d, cache) = get_xdg_dirs(); + ( + c.to_string_lossy().to_string(), + d.to_string_lossy().to_string(), + cache.to_string_lossy().to_string(), + ) + }; + + Self { + listen_addr: args.listen_addr.clone(), + metrics_port: args.metrics_port, + backend_addr: args.backend_addr.clone(), + min_delay: args.min_delay, + max_delay: args.max_delay, + max_tarpit_time: args.max_tarpit_time, + block_threshold: args.block_threshold, + markov_corpora_dir: format!("{}/corpora", data_dir), + lua_scripts_dir: format!("{}/scripts", data_dir), + data_dir, + config_dir, + cache_dir, + ..Default::default() + } + } + + // Load configuration from a JSON file + fn load_from_file(path: &Path) -> std::io::Result { + let content = fs::read_to_string(path)?; + let config = serde_json::from_str(&content)?; + Ok(config) + } + + // Save configuration to a JSON file + fn save_to_file(&self, path: &Path) -> std::io::Result<()> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let content = serde_json::to_string_pretty(self)?; + fs::write(path, content)?; + Ok(()) + } + + // Create required directories if they don't exist + fn ensure_dirs_exist(&self) -> std::io::Result<()> { + let dirs = [ + &self.markov_corpora_dir, + &self.lua_scripts_dir, + &self.data_dir, + &self.config_dir, + &self.cache_dir, + ]; + + for dir in dirs { + fs::create_dir_all(dir)?; + log::debug!("Created directory: {}", dir); + } + + Ok(()) + } +} + +// Prometheus metrics. I'll expand this with more metrics as I need to. +lazy_static! { + static ref HITS_COUNTER: Counter = + register_counter!("eris_hits_total", "Total number of hits to honeypot paths").unwrap(); + static ref BLOCKED_IPS: Gauge = + register_gauge!("eris_blocked_ips", "Number of IPs permanently blocked").unwrap(); + static ref ACTIVE_CONNECTIONS: Gauge = register_gauge!( + "eris_active_connections", + "Number of currently active connections in tarpit" + ) + .unwrap(); + static ref PATH_HITS: CounterVec = + register_counter_vec!("eris_path_hits_total", "Hits by path", &["path"]).unwrap(); + static ref UA_HITS: CounterVec = + register_counter_vec!("eris_ua_hits_total", "Hits by user agent", &["user_agent"]).unwrap(); +} + +// State of bots/IPs hitting the honeypot +#[derive(Clone, Debug)] +struct BotState { + hits: HashMap, + blocked: HashSet, + active_connections: HashSet, + data_dir: String, + cache_dir: String, +} + +impl BotState { + fn new(data_dir: &str, cache_dir: &str) -> Self { + Self { + hits: HashMap::new(), + blocked: HashSet::new(), + active_connections: HashSet::new(), + data_dir: data_dir.to_string(), + cache_dir: cache_dir.to_string(), + } + } + + // Load previous state from disk + fn load_from_disk(data_dir: &str, cache_dir: &str) -> Self { + let mut state = Self::new(data_dir, cache_dir); + let blocked_ips_file = format!("{}/blocked_ips.txt", data_dir); + + if let Ok(content) = fs::read_to_string(&blocked_ips_file) { + let mut loaded = 0; + for line in content.lines() { + if let Ok(ip) = line.parse::() { + state.blocked.insert(ip); + loaded += 1; + } + } + log::info!("Loaded {} blocked IPs from {}", loaded, blocked_ips_file); + } else { + log::info!("No blocked IPs file found at {}", blocked_ips_file); + } + + // Check for temporary hit counter cache + let hit_cache_file = format!("{}/hit_counters.json", cache_dir); + if let Ok(content) = fs::read_to_string(&hit_cache_file) { + if let Ok(hit_map) = serde_json::from_str::>(&content) { + for (ip_str, count) in hit_map { + if let Ok(ip) = ip_str.parse::() { + state.hits.insert(ip, count); + } + } + log::info!("Loaded hit counters for {} IPs", state.hits.len()); + } + } + + BLOCKED_IPS.set(state.blocked.len() as f64); + state + } + + // Persist state to disk for later reloading + fn save_to_disk(&self) { + // Save blocked IPs + if let Err(e) = fs::create_dir_all(&self.data_dir) { + log::error!("Failed to create data directory: {}", e); + return; + } + + let blocked_ips_file = format!("{}/blocked_ips.txt", self.data_dir); + + match fs::File::create(&blocked_ips_file) { + Ok(mut file) => { + let mut count = 0; + for ip in &self.blocked { + if writeln!(file, "{ip}").is_ok() { + count += 1; + } + } + log::info!("Saved {} blocked IPs to {}", count, blocked_ips_file); + } + Err(e) => { + log::error!("Failed to create blocked IPs file: {}", e); + } + } + + // Save hit counters to cache + if let Err(e) = fs::create_dir_all(&self.cache_dir) { + log::error!("Failed to create cache directory: {}", e); + return; + } + + let hit_cache_file = format!("{}/hit_counters.json", self.cache_dir); + let mut hit_map = HashMap::new(); + for (ip, count) in &self.hits { + hit_map.insert(ip.to_string(), *count); + } + + match fs::File::create(&hit_cache_file) { + Ok(file) => { + if let Err(e) = serde_json::to_writer(file, &hit_map) { + log::error!("Failed to write hit counters to cache: {}", e); + } else { + log::debug!("Saved hit counters for {} IPs to cache", hit_map.len()); + } + } + Err(e) => { + log::error!("Failed to create hit counter cache file: {}", e); + } + } + } +} + +// Lua scripts for response generation and customization +struct ScriptManager { + script_content: String, + scripts_loaded: bool, +} + +impl ScriptManager { + fn new(scripts_dir: &str) -> Self { + let mut script_content = String::new(); + let mut scripts_loaded = false; + + // Try to load scripts from directory + let script_dir = Path::new(scripts_dir); + if script_dir.exists() { + log::debug!("Loading Lua scripts from directory: {}", scripts_dir); + if let Ok(entries) = fs::read_dir(script_dir) { + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if path.extension().and_then(|ext| ext.to_str()) == Some("lua") { + if let Ok(content) = fs::read_to_string(&path) { + log::debug!("Loaded Lua script: {}", path.display()); + script_content.push_str(&content); + script_content.push('\n'); + scripts_loaded = true; + } else { + log::warn!("Failed to read Lua script: {}", path.display()); + } + } + } + } + } + } else { + log::warn!("Lua scripts directory does not exist: {}", scripts_dir); + } + + // If no scripts were loaded, use a default script + if !scripts_loaded { + log::info!("No Lua scripts found, loading default scripts"); + script_content = r#" + function generate_honeytoken(token) + local token_types = {"API_KEY", "AUTH_TOKEN", "SESSION_ID", "SECRET_KEY"} + local prefix = token_types[math.random(#token_types)] + local suffix = string.format("%08x", math.random(0xffffff)) + return prefix .. "_" .. token .. "_" .. suffix + end + + function enhance_response(text, response_type, path, token) + local result = text + local honeytoken = generate_honeytoken(token) + + -- Add some fake sensitive data + result = result .. "\n" + result = result .. "\n
Server ID: " .. token .. "
" + + return result + end + "# + .to_string(); + scripts_loaded = true; + } + + Self { + script_content, + scripts_loaded, + } + } + + // Lua is a powerful configuration language we can use to expand functionality of + // Eris, e.g., with fake tokens or honeytrap content. + fn expand_response(&self, text: &str, response_type: &str, path: &str, token: &str) -> String { + if !self.scripts_loaded { + return format!("{text}\n"); + } + + let lua = Lua::new(); + if let Err(e) = lua.load(&self.script_content).exec() { + log::warn!("Error loading Lua script: {}", e); + return format!("{text}\n"); + } + + let globals = lua.globals(); + match globals.get::<_, Function>("enhance_response") { + Ok(enhance_func) => { + match enhance_func.call::<_, String>((text, response_type, path, token)) { + Ok(result) => result, + Err(e) => { + log::warn!("Error calling Lua function enhance_response: {}", e); + format!("{text}\n") + } + } + } + Err(e) => { + log::warn!("Lua enhance_response function not found: {}", e); + format!("{text}\n") + } + } + } +} + +// Main connection handler - decides whether to tarpit or proxy +async fn handle_connection( + mut stream: TcpStream, + config: Arc, + state: Arc>, + markov_generator: Arc, + script_manager: Arc, +) { + let peer_addr = match stream.peer_addr() { + Ok(addr) => addr.ip(), + Err(e) => { + log::debug!("Failed to get peer address: {}", e); + return; + } + }; + + log::debug!("New connection from: {}", peer_addr); + + // Check if IP is already blocked + if state.read().await.blocked.contains(&peer_addr) { + log::debug!("Rejected connection from blocked IP: {}", peer_addr); + let _ = stream.shutdown().await; + return; + } + + // Read the HTTP request + let mut buffer = [0; 8192]; + let mut request_data = Vec::new(); + + // Read with timeout to prevent hanging + let read_fut = async { + loop { + match stream.read(&mut buffer).await { + Ok(0) => break, + Ok(n) => { + request_data.extend_from_slice(&buffer[..n]); + // Stop reading at empty line, this is the end of HTTP headers + if request_data.len() > 2 && &request_data[request_data.len() - 2..] == b"\r\n" + { + break; + } + } + Err(e) => { + log::debug!("Error reading from stream: {}", e); + break; + } + } + } + }; + + let timeout_fut = sleep(Duration::from_secs(5)); + + tokio::select! { + () = read_fut => {}, + () = timeout_fut => { + log::debug!("Connection timeout from: {}", peer_addr); + let _ = stream.shutdown().await; + return; + } + } + + // Parse the request + let request_str = String::from_utf8_lossy(&request_data); + let request_lines: Vec<&str> = request_str.lines().collect(); + + if request_lines.is_empty() { + log::debug!("Empty request from: {}", peer_addr); + let _ = stream.shutdown().await; + return; + } + + // Parse request line + let request_parts: Vec<&str> = request_lines[0].split_whitespace().collect(); + if request_parts.len() < 3 { + log::debug!("Malformed request from {}: {}", peer_addr, request_lines[0]); + let _ = stream.shutdown().await; + return; + } + + let method = request_parts[0]; + let path = request_parts[1]; + let protocol = request_parts[2]; + + log::debug!( + "Request: {} {} {} from {}", + method, + path, + protocol, + peer_addr + ); + + // Parse headers + let mut headers = HashMap::new(); + for line in &request_lines[1..] { + if line.is_empty() { + break; + } + + if let Some(idx) = line.find(':') { + let key = line[..idx].trim(); + let value = line[idx + 1..].trim(); + headers.insert(key, value.to_string()); + } + } + + let user_agent = headers + .get("user-agent") + .cloned() + .unwrap_or_else(|| "unknown".to_string()); + + // Check if this request matches our tarpit patterns + let should_tarpit = should_tarpit(path, &peer_addr, &config).await; + + if should_tarpit { + log::info!( + "Tarpit triggered: {} {} from {} (UA: {})", + method, + path, + peer_addr, + user_agent + ); + + // Update metrics + HITS_COUNTER.inc(); + PATH_HITS.with_label_values(&[path]).inc(); + UA_HITS.with_label_values(&[&user_agent]).inc(); + + // Update state and check for blocking threshold + { + let mut state = state.write().await; + state.active_connections.insert(peer_addr); + ACTIVE_CONNECTIONS.set(state.active_connections.len() as f64); + + *state.hits.entry(peer_addr).or_insert(0) += 1; + let hit_count = state.hits[&peer_addr]; + log::debug!("Hit count for {}: {}", peer_addr, hit_count); + + // Block IPs that hit tarpits too many times + if hit_count >= config.block_threshold && !state.blocked.contains(&peer_addr) { + log::info!("Blocking IP {} after {} hits", peer_addr, hit_count); + state.blocked.insert(peer_addr); + BLOCKED_IPS.set(state.blocked.len() as f64); + state.save_to_disk(); + + // Try to add to firewall + let peer_addr_str = peer_addr.to_string(); + tokio::spawn(async move { + log::debug!("Adding IP {} to firewall blacklist", peer_addr_str); + match Command::new("nft") + .args([ + "add", + "element", + "inet", + "filter", + "eris_blacklist", + "{", + &peer_addr_str, + "}", + ]) + .output() + .await + { + Ok(output) => { + if !output.status.success() { + log::warn!( + "Failed to add IP {} to firewall: {}", + peer_addr_str, + String::from_utf8_lossy(&output.stderr) + ); + } + } + Err(e) => { + log::warn!("Failed to execute nft command: {}", e); + } + } + }); + } + } + + // Generate a deceptive response using Markov chains and Lua + let response = + generate_deceptive_response(path, &user_agent, &markov_generator, &script_manager) + .await; + + // Send the response with the tarpit delay strategy + tarpit_connection( + stream, + response, + peer_addr, + state.clone(), + config.min_delay, + config.max_delay, + config.max_tarpit_time, + ) + .await; + } else { + log::debug!("Proxying request: {} {} from {}", method, path, peer_addr); + + // Proxy non-matching requests to the actual backend + proxy_to_backend( + stream, + method, + path, + protocol, + &headers, + &config.backend_addr, + ) + .await; + } +} + +// Determine if a request should be tarpitted based on path and IP +async fn should_tarpit(path: &str, ip: &IpAddr, config: &Config) -> bool { + // Don't tarpit whitelisted IPs (internal networks, etc) + for network_str in &config.whitelist_networks { + if let Ok(network) = network_str.parse::() { + if network.contains(*ip) { + log::debug!("IP {} is in whitelist network {}", ip, network_str); + return false; + } + } + } + + // Check if the request path matches any of our trap patterns + for pattern in &config.trap_patterns { + if path.contains(pattern) { + log::debug!("Path '{}' matches trap pattern '{}'", path, pattern); + return true; + } + } + + // No trap patterns matched + false +} + +// Generate a deceptive HTTP response that appears legitimate +async fn generate_deceptive_response( + path: &str, + user_agent: &str, + markov: &MarkovGenerator, + script_manager: &ScriptManager, +) -> String { + // Choose response type based on path to seem more realistic + let response_type = if path.contains("phpunit") || path.contains("eval") { + "php_exploit" + } else if path.contains("wp-") { + "wordpress" + } else if path.contains("api") { + "api" + } else { + "generic" + }; + + log::debug!("Generating {} response for path: {}", response_type, path); + + // Generate tracking token for this interaction + let tracking_token = format!( + "BOT_{}_{}", + user_agent + .chars() + .filter(|c| c.is_alphanumeric()) + .collect::(), + chrono::Utc::now().timestamp() + ); + + // Generate base response using Markov chain text generator + let markov_text = markov.generate(response_type, 30); + + // Use Lua to enhance with honeytokens and other deceptive content + let enhanced = + script_manager.expand_response(&markov_text, response_type, path, &tracking_token); + + // Return full HTTP response with appropriate headers + format!( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nX-Powered-By: PHP/7.4.3\r\nConnection: keep-alive\r\n\r\n{enhanced}" + ) +} + +// Slowly feed a response to the client with random delays to waste attacker time +async fn tarpit_connection( + mut stream: TcpStream, + response: String, + peer_addr: IpAddr, + state: Arc>, + min_delay: u64, + max_delay: u64, + max_tarpit_time: u64, +) { + let start_time = Instant::now(); + let mut chars = response.chars().collect::>(); + + // Randomize the char order slightly to confuse automated tools + for i in (0..chars.len()).rev() { + if i > 0 && rand::random::() < 0.1 { + chars.swap(i, i - 1); + } + } + + log::debug!( + "Starting tarpit for {} with {} chars, min_delay={}ms, max_delay={}ms", + peer_addr, + chars.len(), + min_delay, + max_delay + ); + + let mut position = 0; + let mut chunks_sent = 0; + let mut total_delay = 0; + + // Send the response character by character with random delays + while position < chars.len() { + // Check if we've exceeded maximum tarpit time + let elapsed_secs = start_time.elapsed().as_secs(); + if elapsed_secs > max_tarpit_time { + log::info!( + "Tarpit maximum time ({} sec) reached for {}", + max_tarpit_time, + peer_addr + ); + break; + } + + // Decide how many chars to send in this chunk (usually 1, sometimes more) + let chunk_size = if rand::random::() < 0.9 { + 1 + } else { + (rand::random::() * 3.0).floor() as usize + 1 + }; + + let end = (position + chunk_size).min(chars.len()); + let chunk: String = chars[position..end].iter().collect(); + + // Try to write chunk + if stream.write_all(chunk.as_bytes()).await.is_err() { + log::debug!("Connection closed by client during tarpit: {}", peer_addr); + break; + } + + if stream.flush().await.is_err() { + log::debug!("Failed to flush stream during tarpit: {}", peer_addr); + break; + } + + position = end; + chunks_sent += 1; + + // Apply random delay between min and max configured values + let delay_ms = (rand::random::() * (max_delay - min_delay) as f32) as u64 + min_delay; + total_delay += delay_ms; + sleep(Duration::from_millis(delay_ms)).await; + } + + log::debug!( + "Tarpit stats for {}: sent {} chunks, {}% of data, total delay {}ms over {}s", + peer_addr, + chunks_sent, + position * 100 / chars.len(), + total_delay, + start_time.elapsed().as_secs() + ); + + // Remove from active connections + if let Ok(mut state) = state.try_write() { + state.active_connections.remove(&peer_addr); + ACTIVE_CONNECTIONS.set(state.active_connections.len() as f64); + } + + let _ = stream.shutdown().await; +} + +// Forward a legitimate request to the real backend server +async fn proxy_to_backend( + mut client_stream: TcpStream, + method: &str, + path: &str, + protocol: &str, + headers: &HashMap<&str, String>, + backend_addr: &str, +) { + // Connect to backend server + let server_stream = match TcpStream::connect(backend_addr).await { + Ok(stream) => stream, + Err(e) => { + log::warn!("Failed to connect to backend {}: {}", backend_addr, e); + let _ = client_stream.shutdown().await; + return; + } + }; + + log::debug!("Connected to backend server at {}", backend_addr); + + // Forward the original request + let mut request = format!("{method} {path} {protocol}\r\n"); + for (key, value) in headers { + request.push_str(&format!("{key}: {value}\r\n")); + } + request.push_str("\r\n"); + + let mut server_stream = server_stream; + if server_stream.write_all(request.as_bytes()).await.is_err() { + log::debug!("Failed to write request to backend server"); + let _ = client_stream.shutdown().await; + return; + } + + // Set up bidirectional forwarding between client and backend + let (mut client_read, mut client_write) = client_stream.split(); + let (mut server_read, mut server_write) = server_stream.split(); + + // Client -> Server + let client_to_server = async { + let mut buf = [0; 8192]; + let mut bytes_forwarded = 0; + + loop { + match client_read.read(&mut buf).await { + Ok(0) => break, + Ok(n) => { + bytes_forwarded += n; + if server_write.write_all(&buf[..n]).await.is_err() { + break; + } + } + Err(_) => break, + } + } + + log::debug!("Client -> Server: forwarded {} bytes", bytes_forwarded); + }; + + // Server -> Client + let server_to_client = async { + let mut buf = [0; 8192]; + let mut bytes_forwarded = 0; + + loop { + match server_read.read(&mut buf).await { + Ok(0) => break, + Ok(n) => { + bytes_forwarded += n; + if client_write.write_all(&buf[..n]).await.is_err() { + break; + } + } + Err(_) => break, + } + } + + log::debug!("Server -> Client: forwarded {} bytes", bytes_forwarded); + }; + + // Run both directions concurrently + tokio::select! { + () = client_to_server => {}, + () = server_to_client => {}, + } + + log::debug!("Proxy connection completed"); +} + +// Prometheus metrics endpoint +async fn metrics_handler(_req: HttpRequest) -> HttpResponse { + use prometheus::Encoder; + let encoder = prometheus::TextEncoder::new(); + let mut buffer = Vec::new(); + + match encoder.encode(&prometheus::gather(), &mut buffer) { + Ok(_) => { + log::debug!("Metrics requested, returned {} bytes", buffer.len()); + } + Err(e) => { + log::error!("Error encoding metrics: {}", e); + } + } + + HttpResponse::Ok().content_type("text/plain").body(buffer) +} + +// Status JSON endpoint +async fn status_handler(state: web::Data>>) -> HttpResponse { + let state = state.read().await; + + let info = serde_json::json!({ + "status": "running", + "version": env!("CARGO_PKG_VERSION"), + "blocked_ips": state.blocked.len(), + "active_connections": state.active_connections.len(), + "hit_count": state.hits.len(), + }); + + HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string_pretty(&info).unwrap()) +} + +// Set up nftables firewall rules for IP blocking +async fn setup_firewall() -> Result<(), String> { + log::info!("Setting up firewall rules"); + + // Check if nft command exists + let nft_exists = Command::new("which") + .arg("nft") + .output() + .await + .map(|output| output.status.success()) + .unwrap_or(false); + + if !nft_exists { + log::warn!("nft command not found. Firewall rules will not be set up."); + return Ok(()); + } + + // Create table if it doesn't exist + let output = Command::new("nft") + .args(["list", "table", "inet", "filter"]) + .output() + .await; + + match output { + Ok(output) => { + if !output.status.success() { + log::info!("Creating nftables table"); + let result = Command::new("nft") + .args(["create", "table", "inet", "filter"]) + .output() + .await; + + if let Err(e) = result { + return Err(format!("Failed to create nftables table: {}", e)); + } + } + } + Err(e) => { + log::warn!("Failed to check if nftables table exists: {}", e); + log::info!("Will try to create it anyway"); + let result = Command::new("nft") + .args(["create", "table", "inet", "filter"]) + .output() + .await; + + if let Err(e) = result { + return Err(format!("Failed to create nftables table: {}", e)); + } + } + } + + // Create blacklist set if it doesn't exist + let output = Command::new("nft") + .args(["list", "set", "inet", "filter", "eris_blacklist"]) + .output() + .await; + + match output { + Ok(output) => { + if !output.status.success() { + log::info!("Creating eris_blacklist set"); + let result = Command::new("nft") + .args([ + "create", + "set", + "inet", + "filter", + "eris_blacklist", + "{ type ipv4_addr; flags interval; }", + ]) + .output() + .await; + + if let Err(e) = result { + return Err(format!("Failed to create blacklist set: {}", e)); + } + } + } + Err(e) => { + log::warn!("Failed to check if blacklist set exists: {}", e); + return Err(format!("Failed to check if blacklist set exists: {}", e)); + } + } + + // Add rule to drop traffic from blacklisted IPs + let output = Command::new("nft") + .args(["list", "chain", "inet", "filter", "input"]) + .output() + .await; + + // Check if our rule already exists + match output { + Ok(output) => { + let rule_exists = String::from_utf8_lossy(&output.stdout) + .contains("ip saddr @eris_blacklist counter drop"); + + if !rule_exists { + log::info!("Adding drop rule for blacklisted IPs"); + let result = Command::new("nft") + .args([ + "add", + "rule", + "inet", + "filter", + "input", + "ip saddr @eris_blacklist", + "counter", + "drop", + ]) + .output() + .await; + + if let Err(e) = result { + return Err(format!("Failed to add firewall rule: {}", e)); + } + } + } + Err(e) => { + log::warn!("Failed to check if firewall rule exists: {}", e); + return Err(format!("Failed to check if firewall rule exists: {}", e)); + } + } + + log::info!("Firewall setup complete"); + Ok(()) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + // Parse command line arguments + let args = Args::parse(); + + // Initialize the logger + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(&args.log_level)) + .format_timestamp_millis() + .init(); + + log::info!("Starting eris tarpit system"); + + // Load configuration + let config = if let Some(config_path) = &args.config_file { + log::info!("Loading configuration from {:?}", config_path); + match Config::load_from_file(config_path) { + Ok(cfg) => { + log::info!("Configuration loaded successfully"); + cfg + } + Err(e) => { + log::warn!("Failed to load configuration file: {}", e); + log::info!("Using configuration from command-line arguments"); + Config::from_args(&args) + } + } + } else { + log::info!("Using configuration from command-line arguments"); + Config::from_args(&args) + }; + + // Ensure required directories exist + match config.ensure_dirs_exist() { + Ok(_) => log::info!("Directory setup completed"), + Err(e) => { + log::error!("Failed to create required directories: {}", e); + log::info!("Will continue with default in-memory configuration"); + } + } + + // Save config for reference if it was loaded from command line + if args.config_file.is_none() { + if let Err(e) = fs::create_dir_all(&config.config_dir) { + log::warn!("Failed to create config directory: {}", e); + } else { + let config_path = Path::new(&config.config_dir).join("config.json"); + if !config_path.exists() { + if let Err(e) = config.save_to_file(&config_path) { + log::warn!("Failed to save default configuration: {}", e); + } else { + log::info!("Saved default configuration to {:?}", config_path); + } + } + } + } + + log::info!("Using directories:"); + log::info!(" Config: {}", config.config_dir); + log::info!(" Corpora: {}", config.markov_corpora_dir); + log::info!(" Scripts: {}", config.lua_scripts_dir); + log::info!(" Data: {}", config.data_dir); + log::info!(" Cache: {}", config.cache_dir); + + let config = Arc::new(config); + + // Setup firewall rules for IP blocking + match setup_firewall().await { + Ok(_) => {} + Err(e) => { + log::warn!("Failed to set up firewall rules: {}", e); + log::info!("IP blocking will be managed in memory only"); + } + } + + // Initialize bot state for both servers + let tarpit_state = Arc::new(RwLock::new(BotState::load_from_disk( + &config.data_dir, + &config.cache_dir, + ))); + let metrics_state = tarpit_state.clone(); + + // Initialize Markov chain text generator + log::info!( + "Initializing Markov chain generator from {}", + config.markov_corpora_dir + ); + let markov_generator = Arc::new(MarkovGenerator::new(&config.markov_corpora_dir)); + + // Initialize Lua script manager + log::info!("Loading Lua scripts from {}", config.lua_scripts_dir); + let script_manager = Arc::new(ScriptManager::new(&config.lua_scripts_dir)); + + // Clone config for metrics server + let metrics_config = config.clone(); + + // Start the main tarpit server + let tarpit_server = tokio::spawn(async move { + log::info!("Starting tarpit server on {}", config.listen_addr); + + let listener = match TcpListener::bind(&config.listen_addr).await { + Ok(l) => l, + Err(e) => { + return Err(format!("Failed to bind to {}: {}", config.listen_addr, e)); + } + }; + + log::info!("Tarpit server listening on {}", config.listen_addr); + + loop { + match listener.accept().await { + Ok((stream, addr)) => { + log::debug!("Accepted connection from {}", addr); + + let state_clone = tarpit_state.clone(); + let markov_clone = markov_generator.clone(); + let script_manager_clone = script_manager.clone(); + let config_clone = config.clone(); + + tokio::spawn(async move { + handle_connection( + stream, + config_clone, + state_clone, + markov_clone, + script_manager_clone, + ) + .await; + }); + } + Err(e) => { + log::error!("Error accepting connection: {}", e); + } + } + } + + #[allow(unreachable_code)] + Ok::<(), String>(()) + }); + + // Start the metrics server with actix_web + let metrics_addr = format!("0.0.0.0:{}", metrics_config.metrics_port); + log::info!("Starting metrics server on {}", metrics_addr); + + let metrics_server = HttpServer::new(move || { + App::new() + .app_data(web::Data::new(metrics_state.clone())) + .route("/metrics", web::get().to(metrics_handler)) + .route("/status", web::get().to(|data: web::Data>>| async move { + status_handler(data).await + })) + .route("/", web::get().to(|| async { + HttpResponse::Ok().body("Botpot Server is running. Visit /metrics for metrics or /status for status.") + })) + }) + .bind(&metrics_addr); + + let metrics_server = match metrics_server { + Ok(server) => server.run(), + Err(e) => { + log::error!("Failed to bind metrics server to {}: {}", metrics_addr, e); + return Err(e); + } + }; + + log::info!("Metrics server listening on {}", metrics_addr); + + // Run both servers concurrently + tokio::select! { + result = tarpit_server => match result { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => { + log::error!("Tarpit server error: {}", e); + Err(std::io::Error::new(std::io::ErrorKind::Other, e)) + }, + Err(e) => { + log::error!("Tarpit server task error: {}", e); + Err(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) + }, + }, + result = metrics_server => { + if let Err(ref e) = result { + log::error!("Metrics server error: {}", e); + } + result + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{IpAddr, Ipv4Addr}; + use tokio::sync::RwLock; + + #[test] + fn test_config_from_args() { + let args = Args { + listen_addr: "127.0.0.1:8080".to_string(), + metrics_port: 9000, + backend_addr: "127.0.0.1:8081".to_string(), + min_delay: 500, + max_delay: 10000, + max_tarpit_time: 300, + block_threshold: 5, + base_dir: Some(PathBuf::from("/tmp/eris")), + config_file: None, + log_level: "debug".to_string(), + }; + + let config = Config::from_args(&args); + assert_eq!(config.listen_addr, "127.0.0.1:8080"); + assert_eq!(config.metrics_port, 9000); + assert_eq!(config.backend_addr, "127.0.0.1:8081"); + assert_eq!(config.min_delay, 500); + assert_eq!(config.max_delay, 10000); + assert_eq!(config.max_tarpit_time, 300); + assert_eq!(config.block_threshold, 5); + assert_eq!(config.markov_corpora_dir, "/tmp/eris/data/corpora"); + assert_eq!(config.lua_scripts_dir, "/tmp/eris/data/scripts"); + assert_eq!(config.data_dir, "/tmp/eris/data"); + assert_eq!(config.config_dir, "/tmp/eris/conf"); + assert_eq!(config.cache_dir, "/tmp/eris/cache"); + } + + #[tokio::test] + async fn test_should_tarpit() { + let config = Config::default(); + + // Test trap patterns + assert!( + should_tarpit( + "/vendor/phpunit/whatever", + &IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), + &config + ) + .await + ); + assert!( + should_tarpit( + "/wp-admin/login.php", + &IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), + &config + ) + .await + ); + assert!(should_tarpit("/.env", &IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), &config).await); + + // Test whitelist networks + assert!( + !should_tarpit( + "/wp-admin/login.php", + &IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + &config + ) + .await + ); + assert!( + !should_tarpit( + "/vendor/phpunit/whatever", + &IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), + &config + ) + .await + ); + + // Test legitimate paths + assert!( + !should_tarpit( + "/index.html", + &IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), + &config + ) + .await + ); + assert!( + !should_tarpit( + "/images/logo.png", + &IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), + &config + ) + .await + ); + } + + #[test] + fn test_script_manager_default_script() { + let script_manager = ScriptManager::new("/nonexistent_directory"); + assert!(script_manager.scripts_loaded); + assert!( + script_manager + .script_content + .contains("generate_honeytoken") + ); + assert!(script_manager.script_content.contains("enhance_response")); + } + + #[tokio::test] + async fn test_bot_state() { + let state = BotState::new("/tmp/eris_test", "/tmp/eris_test_cache"); + let ip1 = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)); + let ip2 = IpAddr::V4(Ipv4Addr::new(5, 6, 7, 8)); + + let state = Arc::new(RwLock::new(state)); + + // Test hit counter + { + let mut state = state.write().await; + *state.hits.entry(ip1).or_insert(0) += 1; + *state.hits.entry(ip1).or_insert(0) += 1; + *state.hits.entry(ip2).or_insert(0) += 1; + + assert_eq!(*state.hits.get(&ip1).unwrap(), 2); + assert_eq!(*state.hits.get(&ip2).unwrap(), 1); + } + + // Test blocking + { + let mut state = state.write().await; + state.blocked.insert(ip1); + assert!(state.blocked.contains(&ip1)); + assert!(!state.blocked.contains(&ip2)); + } + + // Test active connections + { + let mut state = state.write().await; + state.active_connections.insert(ip1); + state.active_connections.insert(ip2); + assert_eq!(state.active_connections.len(), 2); + + state.active_connections.remove(&ip1); + assert_eq!(state.active_connections.len(), 1); + assert!(!state.active_connections.contains(&ip1)); + assert!(state.active_connections.contains(&ip2)); + } + } + + #[tokio::test] + async fn test_generate_deceptive_response() { + // Create a simple markov generator for testing + let markov = MarkovGenerator::new("/nonexistent/path"); + let script_manager = ScriptManager::new("/nonexistent/path"); + + // Test different path types + let resp1 = generate_deceptive_response( + "/vendor/phpunit/exec", + "TestBot/1.0", + &markov, + &script_manager, + ) + .await; + assert!(resp1.contains("HTTP/1.1 200 OK")); + assert!(resp1.contains("X-Powered-By: PHP")); + + let resp2 = + generate_deceptive_response("/wp-admin/", "TestBot/1.0", &markov, &script_manager) + .await; + assert!(resp2.contains("HTTP/1.1 200 OK")); + + let resp3 = + generate_deceptive_response("/api/users", "TestBot/1.0", &markov, &script_manager) + .await; + assert!(resp3.contains("HTTP/1.1 200 OK")); + + // Verify tracking token is included + assert!(resp1.contains("BOT_TestBot")); + } +} diff --git a/src/markov.rs b/src/markov.rs new file mode 100644 index 0000000..b640dac --- /dev/null +++ b/src/markov.rs @@ -0,0 +1,216 @@ +use rand::prelude::*; +use std::collections::HashMap; +use std::fs; +use std::path::Path; +use std::sync::Mutex; + +const DEFAULT_ORDER: usize = 2; + +#[derive(Debug)] +struct Chain { + order: usize, + states: HashMap, Vec>, + start_states: Vec>, +} + +impl Chain { + fn new(order: usize) -> Self { + Self { + order, + states: HashMap::new(), + start_states: Vec::new(), + } + } + + fn add(&mut self, line: &str) { + // Tokenize and process the line + let words: Vec = line + .split_whitespace() + .map(std::string::ToString::to_string) + .collect(); + + if words.len() <= self.order { + return; + } + + // Add start state + let start = words[0..self.order].to_vec(); + self.start_states.push(start); + + // Add transitions + for i in 0..words.len() - self.order { + let state = words[i..i + self.order].to_vec(); + let next = words[i + self.order].clone(); + + self.states.entry(state).or_default().push(next); + } + } + + fn generate(&self, max_words: usize) -> String { + if self.start_states.is_empty() { + return String::new(); + } + + let mut rng = rand::rng(); + let mut result = Vec::new(); + + // Choose a random start state + let mut current = self + .start_states + .choose(&mut rng) + .cloned() + .unwrap_or_else(|| vec![String::new(); self.order]); + + result.extend(current.clone()); + + // Generate sequence + for _ in self.order..max_words { + if let Some(next_options) = self.states.get(¤t) { + if next_options.is_empty() { + break; + } + + let next = next_options.choose(&mut rng).unwrap().clone(); + result.push(next.clone()); + + // Slide the window + current = current[1..].to_vec(); + current.push(next); + } else { + break; + } + } + + result.join(" ") + } +} + +pub struct MarkovGenerator { + chains: Mutex>, +} + +impl MarkovGenerator { + pub fn new(corpus_dir: &str) -> Self { + let mut chains = HashMap::new(); + + // Initialize with default types + let types = ["php_exploit", "wordpress", "api", "generic"]; + for t in &types { + chains.insert((*t).to_string(), Chain::new(DEFAULT_ORDER)); + } + + // Load corpus files if they exist + let path = Path::new(corpus_dir); + if path.exists() && path.is_dir() { + if let Ok(entries) = fs::read_dir(path) { + for entry in entries { + if let Ok(entry) = entry { + let file_path = entry.path(); + if let Some(file_name) = file_path.file_stem() { + if let Some(file_name_str) = file_name.to_str() { + if types.contains(&file_name_str) { + if let Ok(content) = fs::read_to_string(&file_path) { + let mut chain = Chain::new(DEFAULT_ORDER); + for line in content.lines() { + chain.add(line); + } + chains.insert(file_name_str.to_string(), chain); + } + } + } + } + } + } + } + } + + // If corpus files didn't exist, initialize with some default content + if chains["php_exploit"].start_states.is_empty() { + let mut chain = Chain::new(DEFAULT_ORDER); + chain.add("PHP Fatal error: Uncaught Error: Call to undefined function"); + chain.add("PHP Warning: file_get_contents() expects parameter 1 to be string"); + chain.add("PHP Notice: Undefined variable: data in /var/www/html/index.php on line 26"); + chain.add("Warning: Invalid argument supplied for foreach() in /var/www/html/vendor/autoload.php"); + chains.insert("php_exploit".to_string(), chain); + } + + if chains["wordpress"].start_states.is_empty() { + let mut chain = Chain::new(DEFAULT_ORDER); + chain.add("WordPress database error: [Table 'wp_users' doesn't exist]"); + chain.add("Warning: Cannot modify header information - headers already sent by"); + chain.add("Fatal error: Allowed memory size of 41943040 bytes exhausted"); + chains.insert("wordpress".to_string(), chain); + } + + // Seed with common "interesting" terms that bots look for + let seed_words = [ + "username", + "password", + "token", + "secret", + "key", + "admin", + "root", + "shell", + "config", + "api_key", + "database", + "ssh", + "private", + "credential", + "system", + "vulnerability", + "exploit", + "access", + "error", + "warning", + "mysql", + "postgresql", + ]; + + // Add seed words to each chain + Self::add_seed_words_to_chains(&mut chains, &seed_words); + + Self { + chains: Mutex::new(chains), + } + } + + // Helper function to add seed words to chains + fn add_seed_words_to_chains(chains: &mut HashMap, seed_words: &[&str]) { + // Process each chain separately + for (_, chain) in chains.iter_mut() { + // Create new state if no states exist + if chain.states.is_empty() && !chain.start_states.is_empty() { + let state = chain.start_states[0].clone(); + let mut next_words = Vec::new(); + for word in seed_words { + next_words.push((*word).to_string()); + } + chain.states.insert(state, next_words); + } else { + // Find a state to add words to + if let Some((_state, next_words)) = chain.states.iter_mut().next() { + for word in seed_words { + next_words.push((*word).to_string()); + } + } + } + } + } + + pub fn generate(&self, chain_type: &str, max_words: usize) -> String { + let chains = self.chains.lock().unwrap(); + + match chains.get(chain_type) { + Some(chain) => chain.generate(max_words), + None => { + // Fall back to generic + chains + .get("generic") + .map(|c| c.generate(max_words)) + .unwrap_or_default() + } + } + } +}