├── .dockerignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml ├── dependabot.yml └── workflows │ ├── docker.yml │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── build.Dockerfile ├── common ├── Cargo.toml ├── diesel.toml ├── migrations │ ├── .keep │ ├── 00000000000000_diesel_initial_setup │ │ ├── down.sql │ │ └── up.sql │ └── 2023-02-01-210714_init │ │ └── up.sql └── src │ ├── async_drop.rs │ ├── config.rs │ ├── db │ ├── mod.rs │ ├── schema.rs │ └── schema_auto.rs │ ├── lib.rs │ ├── md.rs │ ├── metrics.rs │ ├── rpc │ ├── de.rs │ ├── messages.rs │ ├── mod.rs │ ├── packing.rs │ └── ser.rs │ ├── web.rs │ └── web │ └── api.rs ├── config-example.toml ├── docker-compose.yml ├── docker-init.sh ├── lumen ├── Cargo.toml └── src │ ├── home.html │ ├── main.rs │ ├── server.rs │ └── web.rs └── rustfmt.toml /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | custom 3 | docker-compose.yml 4 | target/ 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://www.paypal.com/donate?hosted_button_id=MY6BP9XQ8UN2E&source=url"] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Server (please complete the following information):** 20 | - OS: [e.g. Debian 10] 21 | - Architecture: [e.g. x86_64] 22 | 23 | **Client (please complete the following information):** 24 | - OS: [e.g. Windows 10] 25 | - Architecture: [e.g. x86_64] 26 | - IDA Version: [e.g. 7.3sp3] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | Please add a panic backtrace if available. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Community support 4 | url: https://github.com/naim94a/lumen/discussions 5 | about: Please ask questions here 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docker image 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | IMAGE_NAME: ${{ github.repository }} 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | 18 | steps: 19 | - name: Checkout repo 20 | uses: actions/checkout@v3 21 | 22 | - name: Login to GitHub container registry 23 | uses: docker/login-action@v2 24 | with: 25 | registry: ${{ env.REGISTRY }} 26 | username: ${{ github.repository_owner }} 27 | password: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | - name: Extract metadata for Docker 30 | id: meta 31 | uses: docker/metadata-action@v4 32 | with: 33 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 34 | 35 | - name: Build and push Docker image 36 | uses: docker/build-push-action@v4 37 | with: 38 | context: . 39 | push: true 40 | tags: ${{ steps.meta.outputs.tags }} 41 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | clippy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Install latest Rust stable 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: stable 21 | override: true 22 | components: clippy 23 | - run: rustup component add clippy 24 | - uses: actions-rs/clippy-check@v1 25 | with: 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | args: --all-features 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: Install latest Rust stable 33 | uses: actions-rs/toolchain@v1 34 | with: 35 | toolchain: stable 36 | override: true 37 | profile: minimal 38 | - name: Code Formatting 39 | run: cargo fmt --check 40 | - name: Build 41 | run: cargo build 42 | - name: Run tests 43 | run: cargo test 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | private/ 3 | .env 4 | *.pem 5 | *.p12 6 | *.crt 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] - _TBD_ 4 | 5 | ## [v0.4.0] - 2024-03-19 6 | 7 | ### Added 8 | 9 | - Implemented the function histories command. 10 | - Configurable time limits. 11 | 12 | ### Fixed 13 | 14 | - RPC HelloResult will now report if deletes are enabled. 15 | 16 | ### Changes 17 | 18 | - Applied code formatting using `cargo fmt` 19 | 20 | ## [v0.3.0] - 2023-08-22 21 | 22 | ### Added 23 | 24 | - This changelog. 25 | - Support for IDA 8.1+ delete command. 26 | - Pooling for connections to database. 27 | - Attempt to cancel immutable database queries if client leaves. 28 | - Database migrations via Diesel ORM. 29 | - Support for IDA 8.3+ hello response. 30 | - Add Metrics for prometheus. 31 | 32 | ### Fixed 33 | 34 | - 8K stack size is too small for debug builds. 35 | 36 | ## [v0.2.0] - 2022-10-12 37 | 38 | ### Added 39 | 40 | - Protocol: support for IDA 8.1+ user authentication. 41 | - Client connection duration limitations. 42 | 43 | ### Changed 44 | 45 | - Tokio's thread size is reduced from 4M to 8K. 46 | 47 | ## [v0.1.0] - 2021-01-21 48 | 49 | This is Lumen's first tagged release. It contains a few fixes and dependency updates since the initial commit (2020-12-17). 50 | 51 | [Unreleased]: https://github.com/naim94a/lumen/compare/v0.4.0...HEAD 52 | [v0.4.0]: https://github.com/naim94a/lumen/compare/v0.3.0...v0.4.0 53 | [v0.3.0]: https://github.com/naim94a/lumen/compare/v0.2.0...v0.3.0 54 | [v0.2.0]: https://github.com/naim94a/lumen/compare/v0.1.0...v0.2.0 55 | [v0.1.0]: https://github.com/naim94a/lumen/releases/tag/v0.1.0 56 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.15" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.8" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 64 | dependencies = [ 65 | "windows-sys 0.52.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.4" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys 0.52.0", 76 | ] 77 | 78 | [[package]] 79 | name = "anyhow" 80 | version = "1.0.86" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 83 | 84 | [[package]] 85 | name = "async-trait" 86 | version = "0.1.81" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" 89 | dependencies = [ 90 | "proc-macro2", 91 | "quote", 92 | "syn", 93 | ] 94 | 95 | [[package]] 96 | name = "autocfg" 97 | version = "1.3.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 100 | 101 | [[package]] 102 | name = "backtrace" 103 | version = "0.3.73" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 106 | dependencies = [ 107 | "addr2line", 108 | "cc", 109 | "cfg-if", 110 | "libc", 111 | "miniz_oxide", 112 | "object", 113 | "rustc-demangle", 114 | ] 115 | 116 | [[package]] 117 | name = "base64" 118 | version = "0.21.7" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 121 | 122 | [[package]] 123 | name = "base64" 124 | version = "0.22.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 127 | 128 | [[package]] 129 | name = "bb8" 130 | version = "0.8.5" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "b10cf871f3ff2ce56432fddc2615ac7acc3aa22ca321f8fea800846fbb32f188" 133 | dependencies = [ 134 | "async-trait", 135 | "futures-util", 136 | "parking_lot", 137 | "tokio", 138 | ] 139 | 140 | [[package]] 141 | name = "binascii" 142 | version = "0.1.4" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" 145 | 146 | [[package]] 147 | name = "bitflags" 148 | version = "1.3.2" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 151 | 152 | [[package]] 153 | name = "bitflags" 154 | version = "2.6.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 157 | 158 | [[package]] 159 | name = "block-buffer" 160 | version = "0.10.4" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 163 | dependencies = [ 164 | "generic-array", 165 | ] 166 | 167 | [[package]] 168 | name = "bumpalo" 169 | version = "3.16.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 172 | 173 | [[package]] 174 | name = "byteorder" 175 | version = "1.5.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 178 | 179 | [[package]] 180 | name = "bytes" 181 | version = "1.7.1" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" 184 | 185 | [[package]] 186 | name = "cc" 187 | version = "1.1.10" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" 190 | 191 | [[package]] 192 | name = "cfg-if" 193 | version = "1.0.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 196 | 197 | [[package]] 198 | name = "clap" 199 | version = "4.5.15" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" 202 | dependencies = [ 203 | "clap_builder", 204 | ] 205 | 206 | [[package]] 207 | name = "clap_builder" 208 | version = "4.5.15" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" 211 | dependencies = [ 212 | "anstream", 213 | "anstyle", 214 | "clap_lex", 215 | "strsim", 216 | ] 217 | 218 | [[package]] 219 | name = "clap_lex" 220 | version = "0.7.2" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 223 | 224 | [[package]] 225 | name = "colorchoice" 226 | version = "1.0.2" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 229 | 230 | [[package]] 231 | name = "common" 232 | version = "0.2.0" 233 | dependencies = [ 234 | "anyhow", 235 | "binascii", 236 | "diesel", 237 | "diesel-async", 238 | "futures-util", 239 | "log", 240 | "native-tls", 241 | "postgres-native-tls", 242 | "prometheus-client", 243 | "serde", 244 | "time", 245 | "tokio", 246 | "tokio-postgres", 247 | "toml", 248 | "warp", 249 | ] 250 | 251 | [[package]] 252 | name = "core-foundation" 253 | version = "0.9.4" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 256 | dependencies = [ 257 | "core-foundation-sys", 258 | "libc", 259 | ] 260 | 261 | [[package]] 262 | name = "core-foundation-sys" 263 | version = "0.8.7" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 266 | 267 | [[package]] 268 | name = "cpufeatures" 269 | version = "0.2.13" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" 272 | dependencies = [ 273 | "libc", 274 | ] 275 | 276 | [[package]] 277 | name = "crypto-common" 278 | version = "0.1.6" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 281 | dependencies = [ 282 | "generic-array", 283 | "typenum", 284 | ] 285 | 286 | [[package]] 287 | name = "darling" 288 | version = "0.20.10" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 291 | dependencies = [ 292 | "darling_core", 293 | "darling_macro", 294 | ] 295 | 296 | [[package]] 297 | name = "darling_core" 298 | version = "0.20.10" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 301 | dependencies = [ 302 | "fnv", 303 | "ident_case", 304 | "proc-macro2", 305 | "quote", 306 | "strsim", 307 | "syn", 308 | ] 309 | 310 | [[package]] 311 | name = "darling_macro" 312 | version = "0.20.10" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 315 | dependencies = [ 316 | "darling_core", 317 | "quote", 318 | "syn", 319 | ] 320 | 321 | [[package]] 322 | name = "data-encoding" 323 | version = "2.6.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" 326 | 327 | [[package]] 328 | name = "deranged" 329 | version = "0.3.11" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 332 | dependencies = [ 333 | "powerfmt", 334 | ] 335 | 336 | [[package]] 337 | name = "diesel" 338 | version = "2.2.3" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "65e13bab2796f412722112327f3e575601a3e9cdcbe426f0d30dbf43f3f5dc71" 341 | dependencies = [ 342 | "bitflags 2.6.0", 343 | "byteorder", 344 | "diesel_derives", 345 | "itoa", 346 | "time", 347 | ] 348 | 349 | [[package]] 350 | name = "diesel-async" 351 | version = "0.5.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "fcb799bb6f8ca6a794462125d7b8983b0c86e6c93a33a9c55934a4a5de4409d3" 354 | dependencies = [ 355 | "async-trait", 356 | "bb8", 357 | "diesel", 358 | "futures-util", 359 | "scoped-futures", 360 | "tokio", 361 | "tokio-postgres", 362 | ] 363 | 364 | [[package]] 365 | name = "diesel_derives" 366 | version = "2.2.2" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "d6ff2be1e7312c858b2ef974f5c7089833ae57b5311b334b30923af58e5718d8" 369 | dependencies = [ 370 | "diesel_table_macro_syntax", 371 | "dsl_auto_type", 372 | "proc-macro2", 373 | "quote", 374 | "syn", 375 | ] 376 | 377 | [[package]] 378 | name = "diesel_table_macro_syntax" 379 | version = "0.2.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" 382 | dependencies = [ 383 | "syn", 384 | ] 385 | 386 | [[package]] 387 | name = "digest" 388 | version = "0.10.7" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 391 | dependencies = [ 392 | "block-buffer", 393 | "crypto-common", 394 | "subtle", 395 | ] 396 | 397 | [[package]] 398 | name = "dsl_auto_type" 399 | version = "0.1.2" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" 402 | dependencies = [ 403 | "darling", 404 | "either", 405 | "heck", 406 | "proc-macro2", 407 | "quote", 408 | "syn", 409 | ] 410 | 411 | [[package]] 412 | name = "dtoa" 413 | version = "1.0.9" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" 416 | 417 | [[package]] 418 | name = "either" 419 | version = "1.13.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 422 | 423 | [[package]] 424 | name = "encoding_rs" 425 | version = "0.8.34" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 428 | dependencies = [ 429 | "cfg-if", 430 | ] 431 | 432 | [[package]] 433 | name = "env_logger" 434 | version = "0.10.2" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 437 | dependencies = [ 438 | "humantime", 439 | "is-terminal", 440 | "log", 441 | "regex", 442 | "termcolor", 443 | ] 444 | 445 | [[package]] 446 | name = "equivalent" 447 | version = "1.0.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 450 | 451 | [[package]] 452 | name = "errno" 453 | version = "0.3.9" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 456 | dependencies = [ 457 | "libc", 458 | "windows-sys 0.52.0", 459 | ] 460 | 461 | [[package]] 462 | name = "fallible-iterator" 463 | version = "0.2.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 466 | 467 | [[package]] 468 | name = "fastrand" 469 | version = "2.1.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 472 | 473 | [[package]] 474 | name = "fnv" 475 | version = "1.0.7" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 478 | 479 | [[package]] 480 | name = "foreign-types" 481 | version = "0.3.2" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 484 | dependencies = [ 485 | "foreign-types-shared", 486 | ] 487 | 488 | [[package]] 489 | name = "foreign-types-shared" 490 | version = "0.1.1" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 493 | 494 | [[package]] 495 | name = "form_urlencoded" 496 | version = "1.2.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 499 | dependencies = [ 500 | "percent-encoding", 501 | ] 502 | 503 | [[package]] 504 | name = "futures" 505 | version = "0.3.30" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 508 | dependencies = [ 509 | "futures-channel", 510 | "futures-core", 511 | "futures-executor", 512 | "futures-io", 513 | "futures-sink", 514 | "futures-task", 515 | "futures-util", 516 | ] 517 | 518 | [[package]] 519 | name = "futures-channel" 520 | version = "0.3.30" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 523 | dependencies = [ 524 | "futures-core", 525 | "futures-sink", 526 | ] 527 | 528 | [[package]] 529 | name = "futures-core" 530 | version = "0.3.30" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 533 | 534 | [[package]] 535 | name = "futures-executor" 536 | version = "0.3.30" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 539 | dependencies = [ 540 | "futures-core", 541 | "futures-task", 542 | "futures-util", 543 | ] 544 | 545 | [[package]] 546 | name = "futures-io" 547 | version = "0.3.30" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 550 | 551 | [[package]] 552 | name = "futures-macro" 553 | version = "0.3.30" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 556 | dependencies = [ 557 | "proc-macro2", 558 | "quote", 559 | "syn", 560 | ] 561 | 562 | [[package]] 563 | name = "futures-sink" 564 | version = "0.3.30" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 567 | 568 | [[package]] 569 | name = "futures-task" 570 | version = "0.3.30" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 573 | 574 | [[package]] 575 | name = "futures-util" 576 | version = "0.3.30" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 579 | dependencies = [ 580 | "futures-channel", 581 | "futures-core", 582 | "futures-io", 583 | "futures-macro", 584 | "futures-sink", 585 | "futures-task", 586 | "memchr", 587 | "pin-project-lite", 588 | "pin-utils", 589 | "slab", 590 | ] 591 | 592 | [[package]] 593 | name = "generic-array" 594 | version = "0.14.7" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 597 | dependencies = [ 598 | "typenum", 599 | "version_check", 600 | ] 601 | 602 | [[package]] 603 | name = "getrandom" 604 | version = "0.2.15" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 607 | dependencies = [ 608 | "cfg-if", 609 | "libc", 610 | "wasi", 611 | ] 612 | 613 | [[package]] 614 | name = "gimli" 615 | version = "0.29.0" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 618 | 619 | [[package]] 620 | name = "h2" 621 | version = "0.3.26" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 624 | dependencies = [ 625 | "bytes", 626 | "fnv", 627 | "futures-core", 628 | "futures-sink", 629 | "futures-util", 630 | "http 0.2.12", 631 | "indexmap", 632 | "slab", 633 | "tokio", 634 | "tokio-util", 635 | "tracing", 636 | ] 637 | 638 | [[package]] 639 | name = "hashbrown" 640 | version = "0.14.5" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 643 | 644 | [[package]] 645 | name = "headers" 646 | version = "0.3.9" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" 649 | dependencies = [ 650 | "base64 0.21.7", 651 | "bytes", 652 | "headers-core", 653 | "http 0.2.12", 654 | "httpdate", 655 | "mime", 656 | "sha1", 657 | ] 658 | 659 | [[package]] 660 | name = "headers-core" 661 | version = "0.2.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" 664 | dependencies = [ 665 | "http 0.2.12", 666 | ] 667 | 668 | [[package]] 669 | name = "heck" 670 | version = "0.5.0" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 673 | 674 | [[package]] 675 | name = "hermit-abi" 676 | version = "0.3.9" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 679 | 680 | [[package]] 681 | name = "hmac" 682 | version = "0.12.1" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 685 | dependencies = [ 686 | "digest", 687 | ] 688 | 689 | [[package]] 690 | name = "http" 691 | version = "0.2.12" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 694 | dependencies = [ 695 | "bytes", 696 | "fnv", 697 | "itoa", 698 | ] 699 | 700 | [[package]] 701 | name = "http" 702 | version = "1.1.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 705 | dependencies = [ 706 | "bytes", 707 | "fnv", 708 | "itoa", 709 | ] 710 | 711 | [[package]] 712 | name = "http-body" 713 | version = "0.4.6" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 716 | dependencies = [ 717 | "bytes", 718 | "http 0.2.12", 719 | "pin-project-lite", 720 | ] 721 | 722 | [[package]] 723 | name = "httparse" 724 | version = "1.9.4" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" 727 | 728 | [[package]] 729 | name = "httpdate" 730 | version = "1.0.3" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 733 | 734 | [[package]] 735 | name = "humantime" 736 | version = "2.1.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 739 | 740 | [[package]] 741 | name = "hyper" 742 | version = "0.14.30" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" 745 | dependencies = [ 746 | "bytes", 747 | "futures-channel", 748 | "futures-core", 749 | "futures-util", 750 | "h2", 751 | "http 0.2.12", 752 | "http-body", 753 | "httparse", 754 | "httpdate", 755 | "itoa", 756 | "pin-project-lite", 757 | "socket2", 758 | "tokio", 759 | "tower-service", 760 | "tracing", 761 | "want", 762 | ] 763 | 764 | [[package]] 765 | name = "ident_case" 766 | version = "1.0.1" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 769 | 770 | [[package]] 771 | name = "idna" 772 | version = "0.5.0" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 775 | dependencies = [ 776 | "unicode-bidi", 777 | "unicode-normalization", 778 | ] 779 | 780 | [[package]] 781 | name = "indexmap" 782 | version = "2.3.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" 785 | dependencies = [ 786 | "equivalent", 787 | "hashbrown", 788 | ] 789 | 790 | [[package]] 791 | name = "is-terminal" 792 | version = "0.4.12" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 795 | dependencies = [ 796 | "hermit-abi", 797 | "libc", 798 | "windows-sys 0.52.0", 799 | ] 800 | 801 | [[package]] 802 | name = "is_terminal_polyfill" 803 | version = "1.70.1" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 806 | 807 | [[package]] 808 | name = "itoa" 809 | version = "1.0.11" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 812 | 813 | [[package]] 814 | name = "js-sys" 815 | version = "0.3.70" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 818 | dependencies = [ 819 | "wasm-bindgen", 820 | ] 821 | 822 | [[package]] 823 | name = "libc" 824 | version = "0.2.155" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 827 | 828 | [[package]] 829 | name = "linux-raw-sys" 830 | version = "0.4.14" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 833 | 834 | [[package]] 835 | name = "lock_api" 836 | version = "0.4.12" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 839 | dependencies = [ 840 | "autocfg", 841 | "scopeguard", 842 | ] 843 | 844 | [[package]] 845 | name = "log" 846 | version = "0.4.22" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 849 | 850 | [[package]] 851 | name = "lumen" 852 | version = "0.2.0" 853 | dependencies = [ 854 | "clap", 855 | "common", 856 | "log", 857 | "native-tls", 858 | "pretty_env_logger", 859 | "prometheus-client", 860 | "tokio", 861 | "tokio-native-tls", 862 | "warp", 863 | ] 864 | 865 | [[package]] 866 | name = "md-5" 867 | version = "0.10.6" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 870 | dependencies = [ 871 | "cfg-if", 872 | "digest", 873 | ] 874 | 875 | [[package]] 876 | name = "memchr" 877 | version = "2.7.4" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 880 | 881 | [[package]] 882 | name = "mime" 883 | version = "0.3.17" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 886 | 887 | [[package]] 888 | name = "mime_guess" 889 | version = "2.0.5" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 892 | dependencies = [ 893 | "mime", 894 | "unicase", 895 | ] 896 | 897 | [[package]] 898 | name = "miniz_oxide" 899 | version = "0.7.4" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 902 | dependencies = [ 903 | "adler", 904 | ] 905 | 906 | [[package]] 907 | name = "mio" 908 | version = "1.0.2" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 911 | dependencies = [ 912 | "hermit-abi", 913 | "libc", 914 | "wasi", 915 | "windows-sys 0.52.0", 916 | ] 917 | 918 | [[package]] 919 | name = "multer" 920 | version = "2.1.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" 923 | dependencies = [ 924 | "bytes", 925 | "encoding_rs", 926 | "futures-util", 927 | "http 0.2.12", 928 | "httparse", 929 | "log", 930 | "memchr", 931 | "mime", 932 | "spin", 933 | "version_check", 934 | ] 935 | 936 | [[package]] 937 | name = "native-tls" 938 | version = "0.2.12" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 941 | dependencies = [ 942 | "libc", 943 | "log", 944 | "openssl", 945 | "openssl-probe", 946 | "openssl-sys", 947 | "schannel", 948 | "security-framework", 949 | "security-framework-sys", 950 | "tempfile", 951 | ] 952 | 953 | [[package]] 954 | name = "num-conv" 955 | version = "0.1.0" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 958 | 959 | [[package]] 960 | name = "object" 961 | version = "0.36.3" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" 964 | dependencies = [ 965 | "memchr", 966 | ] 967 | 968 | [[package]] 969 | name = "once_cell" 970 | version = "1.19.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 973 | 974 | [[package]] 975 | name = "openssl" 976 | version = "0.10.66" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 979 | dependencies = [ 980 | "bitflags 2.6.0", 981 | "cfg-if", 982 | "foreign-types", 983 | "libc", 984 | "once_cell", 985 | "openssl-macros", 986 | "openssl-sys", 987 | ] 988 | 989 | [[package]] 990 | name = "openssl-macros" 991 | version = "0.1.1" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 994 | dependencies = [ 995 | "proc-macro2", 996 | "quote", 997 | "syn", 998 | ] 999 | 1000 | [[package]] 1001 | name = "openssl-probe" 1002 | version = "0.1.5" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1005 | 1006 | [[package]] 1007 | name = "openssl-sys" 1008 | version = "0.9.103" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" 1011 | dependencies = [ 1012 | "cc", 1013 | "libc", 1014 | "pkg-config", 1015 | "vcpkg", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "parking_lot" 1020 | version = "0.12.3" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1023 | dependencies = [ 1024 | "lock_api", 1025 | "parking_lot_core", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "parking_lot_core" 1030 | version = "0.9.10" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1033 | dependencies = [ 1034 | "cfg-if", 1035 | "libc", 1036 | "redox_syscall 0.5.3", 1037 | "smallvec", 1038 | "windows-targets", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "percent-encoding" 1043 | version = "2.3.1" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1046 | 1047 | [[package]] 1048 | name = "phf" 1049 | version = "0.11.2" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 1052 | dependencies = [ 1053 | "phf_shared", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "phf_shared" 1058 | version = "0.11.2" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 1061 | dependencies = [ 1062 | "siphasher", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "pin-project" 1067 | version = "1.1.5" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1070 | dependencies = [ 1071 | "pin-project-internal", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "pin-project-internal" 1076 | version = "1.1.5" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1079 | dependencies = [ 1080 | "proc-macro2", 1081 | "quote", 1082 | "syn", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "pin-project-lite" 1087 | version = "0.2.14" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1090 | 1091 | [[package]] 1092 | name = "pin-utils" 1093 | version = "0.1.0" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1096 | 1097 | [[package]] 1098 | name = "pkg-config" 1099 | version = "0.3.30" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 1102 | 1103 | [[package]] 1104 | name = "postgres-native-tls" 1105 | version = "0.5.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "2d442770e2b1e244bb5eb03b31c79b65bb2568f413b899eaba850fa945a65954" 1108 | dependencies = [ 1109 | "futures", 1110 | "native-tls", 1111 | "tokio", 1112 | "tokio-native-tls", 1113 | "tokio-postgres", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "postgres-protocol" 1118 | version = "0.6.7" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" 1121 | dependencies = [ 1122 | "base64 0.22.1", 1123 | "byteorder", 1124 | "bytes", 1125 | "fallible-iterator", 1126 | "hmac", 1127 | "md-5", 1128 | "memchr", 1129 | "rand", 1130 | "sha2", 1131 | "stringprep", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "postgres-types" 1136 | version = "0.2.7" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9" 1139 | dependencies = [ 1140 | "bytes", 1141 | "fallible-iterator", 1142 | "postgres-protocol", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "powerfmt" 1147 | version = "0.2.0" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1150 | 1151 | [[package]] 1152 | name = "ppv-lite86" 1153 | version = "0.2.20" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1156 | dependencies = [ 1157 | "zerocopy", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "pretty_env_logger" 1162 | version = "0.5.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" 1165 | dependencies = [ 1166 | "env_logger", 1167 | "log", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "proc-macro2" 1172 | version = "1.0.86" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1175 | dependencies = [ 1176 | "unicode-ident", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "prometheus-client" 1181 | version = "0.22.3" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" 1184 | dependencies = [ 1185 | "dtoa", 1186 | "itoa", 1187 | "parking_lot", 1188 | "prometheus-client-derive-encode", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "prometheus-client-derive-encode" 1193 | version = "0.4.2" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" 1196 | dependencies = [ 1197 | "proc-macro2", 1198 | "quote", 1199 | "syn", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "quote" 1204 | version = "1.0.36" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1207 | dependencies = [ 1208 | "proc-macro2", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "rand" 1213 | version = "0.8.5" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1216 | dependencies = [ 1217 | "libc", 1218 | "rand_chacha", 1219 | "rand_core", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "rand_chacha" 1224 | version = "0.3.1" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1227 | dependencies = [ 1228 | "ppv-lite86", 1229 | "rand_core", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "rand_core" 1234 | version = "0.6.4" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1237 | dependencies = [ 1238 | "getrandom", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "redox_syscall" 1243 | version = "0.4.1" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1246 | dependencies = [ 1247 | "bitflags 1.3.2", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "redox_syscall" 1252 | version = "0.5.3" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" 1255 | dependencies = [ 1256 | "bitflags 2.6.0", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "regex" 1261 | version = "1.10.6" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 1264 | dependencies = [ 1265 | "aho-corasick", 1266 | "memchr", 1267 | "regex-automata", 1268 | "regex-syntax", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "regex-automata" 1273 | version = "0.4.7" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1276 | dependencies = [ 1277 | "aho-corasick", 1278 | "memchr", 1279 | "regex-syntax", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "regex-syntax" 1284 | version = "0.8.4" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1287 | 1288 | [[package]] 1289 | name = "rustc-demangle" 1290 | version = "0.1.24" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1293 | 1294 | [[package]] 1295 | name = "rustix" 1296 | version = "0.38.34" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1299 | dependencies = [ 1300 | "bitflags 2.6.0", 1301 | "errno", 1302 | "libc", 1303 | "linux-raw-sys", 1304 | "windows-sys 0.52.0", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "ryu" 1309 | version = "1.0.18" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1312 | 1313 | [[package]] 1314 | name = "schannel" 1315 | version = "0.1.23" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 1318 | dependencies = [ 1319 | "windows-sys 0.52.0", 1320 | ] 1321 | 1322 | [[package]] 1323 | name = "scoped-futures" 1324 | version = "0.1.3" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467" 1327 | dependencies = [ 1328 | "cfg-if", 1329 | "pin-utils", 1330 | ] 1331 | 1332 | [[package]] 1333 | name = "scoped-tls" 1334 | version = "1.0.1" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1337 | 1338 | [[package]] 1339 | name = "scopeguard" 1340 | version = "1.2.0" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1343 | 1344 | [[package]] 1345 | name = "security-framework" 1346 | version = "2.11.1" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1349 | dependencies = [ 1350 | "bitflags 2.6.0", 1351 | "core-foundation", 1352 | "core-foundation-sys", 1353 | "libc", 1354 | "security-framework-sys", 1355 | ] 1356 | 1357 | [[package]] 1358 | name = "security-framework-sys" 1359 | version = "2.11.1" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" 1362 | dependencies = [ 1363 | "core-foundation-sys", 1364 | "libc", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "serde" 1369 | version = "1.0.207" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" 1372 | dependencies = [ 1373 | "serde_derive", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "serde_derive" 1378 | version = "1.0.207" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" 1381 | dependencies = [ 1382 | "proc-macro2", 1383 | "quote", 1384 | "syn", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "serde_json" 1389 | version = "1.0.124" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" 1392 | dependencies = [ 1393 | "itoa", 1394 | "memchr", 1395 | "ryu", 1396 | "serde", 1397 | ] 1398 | 1399 | [[package]] 1400 | name = "serde_spanned" 1401 | version = "0.6.7" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" 1404 | dependencies = [ 1405 | "serde", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "serde_urlencoded" 1410 | version = "0.7.1" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1413 | dependencies = [ 1414 | "form_urlencoded", 1415 | "itoa", 1416 | "ryu", 1417 | "serde", 1418 | ] 1419 | 1420 | [[package]] 1421 | name = "sha1" 1422 | version = "0.10.6" 1423 | source = "registry+https://github.com/rust-lang/crates.io-index" 1424 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1425 | dependencies = [ 1426 | "cfg-if", 1427 | "cpufeatures", 1428 | "digest", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "sha2" 1433 | version = "0.10.8" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1436 | dependencies = [ 1437 | "cfg-if", 1438 | "cpufeatures", 1439 | "digest", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "signal-hook-registry" 1444 | version = "1.4.2" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1447 | dependencies = [ 1448 | "libc", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "siphasher" 1453 | version = "0.3.11" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1456 | 1457 | [[package]] 1458 | name = "slab" 1459 | version = "0.4.9" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1462 | dependencies = [ 1463 | "autocfg", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "smallvec" 1468 | version = "1.13.2" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1471 | 1472 | [[package]] 1473 | name = "socket2" 1474 | version = "0.5.7" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1477 | dependencies = [ 1478 | "libc", 1479 | "windows-sys 0.52.0", 1480 | ] 1481 | 1482 | [[package]] 1483 | name = "spin" 1484 | version = "0.9.8" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1487 | 1488 | [[package]] 1489 | name = "stringprep" 1490 | version = "0.1.5" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 1493 | dependencies = [ 1494 | "unicode-bidi", 1495 | "unicode-normalization", 1496 | "unicode-properties", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "strsim" 1501 | version = "0.11.1" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1504 | 1505 | [[package]] 1506 | name = "subtle" 1507 | version = "2.6.1" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1510 | 1511 | [[package]] 1512 | name = "syn" 1513 | version = "2.0.74" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" 1516 | dependencies = [ 1517 | "proc-macro2", 1518 | "quote", 1519 | "unicode-ident", 1520 | ] 1521 | 1522 | [[package]] 1523 | name = "tempfile" 1524 | version = "3.12.0" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 1527 | dependencies = [ 1528 | "cfg-if", 1529 | "fastrand", 1530 | "once_cell", 1531 | "rustix", 1532 | "windows-sys 0.59.0", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "termcolor" 1537 | version = "1.4.1" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1540 | dependencies = [ 1541 | "winapi-util", 1542 | ] 1543 | 1544 | [[package]] 1545 | name = "thiserror" 1546 | version = "1.0.63" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 1549 | dependencies = [ 1550 | "thiserror-impl", 1551 | ] 1552 | 1553 | [[package]] 1554 | name = "thiserror-impl" 1555 | version = "1.0.63" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 1558 | dependencies = [ 1559 | "proc-macro2", 1560 | "quote", 1561 | "syn", 1562 | ] 1563 | 1564 | [[package]] 1565 | name = "time" 1566 | version = "0.3.36" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1569 | dependencies = [ 1570 | "deranged", 1571 | "num-conv", 1572 | "powerfmt", 1573 | "serde", 1574 | "time-core", 1575 | "time-macros", 1576 | ] 1577 | 1578 | [[package]] 1579 | name = "time-core" 1580 | version = "0.1.2" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1583 | 1584 | [[package]] 1585 | name = "time-macros" 1586 | version = "0.2.18" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1589 | dependencies = [ 1590 | "num-conv", 1591 | "time-core", 1592 | ] 1593 | 1594 | [[package]] 1595 | name = "tinyvec" 1596 | version = "1.8.0" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1599 | dependencies = [ 1600 | "tinyvec_macros", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "tinyvec_macros" 1605 | version = "0.1.1" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1608 | 1609 | [[package]] 1610 | name = "tokio" 1611 | version = "1.39.2" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" 1614 | dependencies = [ 1615 | "backtrace", 1616 | "bytes", 1617 | "libc", 1618 | "mio", 1619 | "parking_lot", 1620 | "pin-project-lite", 1621 | "signal-hook-registry", 1622 | "socket2", 1623 | "tokio-macros", 1624 | "windows-sys 0.52.0", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "tokio-macros" 1629 | version = "2.4.0" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1632 | dependencies = [ 1633 | "proc-macro2", 1634 | "quote", 1635 | "syn", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "tokio-native-tls" 1640 | version = "0.3.1" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1643 | dependencies = [ 1644 | "native-tls", 1645 | "tokio", 1646 | ] 1647 | 1648 | [[package]] 1649 | name = "tokio-postgres" 1650 | version = "0.7.11" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3" 1653 | dependencies = [ 1654 | "async-trait", 1655 | "byteorder", 1656 | "bytes", 1657 | "fallible-iterator", 1658 | "futures-channel", 1659 | "futures-util", 1660 | "log", 1661 | "parking_lot", 1662 | "percent-encoding", 1663 | "phf", 1664 | "pin-project-lite", 1665 | "postgres-protocol", 1666 | "postgres-types", 1667 | "rand", 1668 | "socket2", 1669 | "tokio", 1670 | "tokio-util", 1671 | "whoami", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "tokio-tungstenite" 1676 | version = "0.21.0" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" 1679 | dependencies = [ 1680 | "futures-util", 1681 | "log", 1682 | "tokio", 1683 | "tungstenite", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "tokio-util" 1688 | version = "0.7.11" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" 1691 | dependencies = [ 1692 | "bytes", 1693 | "futures-core", 1694 | "futures-sink", 1695 | "pin-project-lite", 1696 | "tokio", 1697 | ] 1698 | 1699 | [[package]] 1700 | name = "toml" 1701 | version = "0.8.19" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 1704 | dependencies = [ 1705 | "serde", 1706 | "serde_spanned", 1707 | "toml_datetime", 1708 | "toml_edit", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "toml_datetime" 1713 | version = "0.6.8" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1716 | dependencies = [ 1717 | "serde", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "toml_edit" 1722 | version = "0.22.20" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" 1725 | dependencies = [ 1726 | "indexmap", 1727 | "serde", 1728 | "serde_spanned", 1729 | "toml_datetime", 1730 | "winnow", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "tower-service" 1735 | version = "0.3.2" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1738 | 1739 | [[package]] 1740 | name = "tracing" 1741 | version = "0.1.40" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1744 | dependencies = [ 1745 | "log", 1746 | "pin-project-lite", 1747 | "tracing-core", 1748 | ] 1749 | 1750 | [[package]] 1751 | name = "tracing-core" 1752 | version = "0.1.32" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1755 | dependencies = [ 1756 | "once_cell", 1757 | ] 1758 | 1759 | [[package]] 1760 | name = "try-lock" 1761 | version = "0.2.5" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1764 | 1765 | [[package]] 1766 | name = "tungstenite" 1767 | version = "0.21.0" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" 1770 | dependencies = [ 1771 | "byteorder", 1772 | "bytes", 1773 | "data-encoding", 1774 | "http 1.1.0", 1775 | "httparse", 1776 | "log", 1777 | "rand", 1778 | "sha1", 1779 | "thiserror", 1780 | "url", 1781 | "utf-8", 1782 | ] 1783 | 1784 | [[package]] 1785 | name = "typenum" 1786 | version = "1.17.0" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1789 | 1790 | [[package]] 1791 | name = "unicase" 1792 | version = "2.7.0" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 1795 | dependencies = [ 1796 | "version_check", 1797 | ] 1798 | 1799 | [[package]] 1800 | name = "unicode-bidi" 1801 | version = "0.3.15" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1804 | 1805 | [[package]] 1806 | name = "unicode-ident" 1807 | version = "1.0.12" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1810 | 1811 | [[package]] 1812 | name = "unicode-normalization" 1813 | version = "0.1.23" 1814 | source = "registry+https://github.com/rust-lang/crates.io-index" 1815 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1816 | dependencies = [ 1817 | "tinyvec", 1818 | ] 1819 | 1820 | [[package]] 1821 | name = "unicode-properties" 1822 | version = "0.1.1" 1823 | source = "registry+https://github.com/rust-lang/crates.io-index" 1824 | checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" 1825 | 1826 | [[package]] 1827 | name = "url" 1828 | version = "2.5.2" 1829 | source = "registry+https://github.com/rust-lang/crates.io-index" 1830 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1831 | dependencies = [ 1832 | "form_urlencoded", 1833 | "idna", 1834 | "percent-encoding", 1835 | ] 1836 | 1837 | [[package]] 1838 | name = "utf-8" 1839 | version = "0.7.6" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1842 | 1843 | [[package]] 1844 | name = "utf8parse" 1845 | version = "0.2.2" 1846 | source = "registry+https://github.com/rust-lang/crates.io-index" 1847 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1848 | 1849 | [[package]] 1850 | name = "vcpkg" 1851 | version = "0.2.15" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1854 | 1855 | [[package]] 1856 | name = "version_check" 1857 | version = "0.9.5" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1860 | 1861 | [[package]] 1862 | name = "want" 1863 | version = "0.3.1" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1866 | dependencies = [ 1867 | "try-lock", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "warp" 1872 | version = "0.3.7" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" 1875 | dependencies = [ 1876 | "bytes", 1877 | "futures-channel", 1878 | "futures-util", 1879 | "headers", 1880 | "http 0.2.12", 1881 | "hyper", 1882 | "log", 1883 | "mime", 1884 | "mime_guess", 1885 | "multer", 1886 | "percent-encoding", 1887 | "pin-project", 1888 | "scoped-tls", 1889 | "serde", 1890 | "serde_json", 1891 | "serde_urlencoded", 1892 | "tokio", 1893 | "tokio-tungstenite", 1894 | "tokio-util", 1895 | "tower-service", 1896 | "tracing", 1897 | ] 1898 | 1899 | [[package]] 1900 | name = "wasi" 1901 | version = "0.11.0+wasi-snapshot-preview1" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1904 | 1905 | [[package]] 1906 | name = "wasite" 1907 | version = "0.1.0" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 1910 | 1911 | [[package]] 1912 | name = "wasm-bindgen" 1913 | version = "0.2.93" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 1916 | dependencies = [ 1917 | "cfg-if", 1918 | "once_cell", 1919 | "wasm-bindgen-macro", 1920 | ] 1921 | 1922 | [[package]] 1923 | name = "wasm-bindgen-backend" 1924 | version = "0.2.93" 1925 | source = "registry+https://github.com/rust-lang/crates.io-index" 1926 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1927 | dependencies = [ 1928 | "bumpalo", 1929 | "log", 1930 | "once_cell", 1931 | "proc-macro2", 1932 | "quote", 1933 | "syn", 1934 | "wasm-bindgen-shared", 1935 | ] 1936 | 1937 | [[package]] 1938 | name = "wasm-bindgen-macro" 1939 | version = "0.2.93" 1940 | source = "registry+https://github.com/rust-lang/crates.io-index" 1941 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1942 | dependencies = [ 1943 | "quote", 1944 | "wasm-bindgen-macro-support", 1945 | ] 1946 | 1947 | [[package]] 1948 | name = "wasm-bindgen-macro-support" 1949 | version = "0.2.93" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1952 | dependencies = [ 1953 | "proc-macro2", 1954 | "quote", 1955 | "syn", 1956 | "wasm-bindgen-backend", 1957 | "wasm-bindgen-shared", 1958 | ] 1959 | 1960 | [[package]] 1961 | name = "wasm-bindgen-shared" 1962 | version = "0.2.93" 1963 | source = "registry+https://github.com/rust-lang/crates.io-index" 1964 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1965 | 1966 | [[package]] 1967 | name = "web-sys" 1968 | version = "0.3.70" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 1971 | dependencies = [ 1972 | "js-sys", 1973 | "wasm-bindgen", 1974 | ] 1975 | 1976 | [[package]] 1977 | name = "whoami" 1978 | version = "1.5.1" 1979 | source = "registry+https://github.com/rust-lang/crates.io-index" 1980 | checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" 1981 | dependencies = [ 1982 | "redox_syscall 0.4.1", 1983 | "wasite", 1984 | "web-sys", 1985 | ] 1986 | 1987 | [[package]] 1988 | name = "winapi-util" 1989 | version = "0.1.9" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1992 | dependencies = [ 1993 | "windows-sys 0.59.0", 1994 | ] 1995 | 1996 | [[package]] 1997 | name = "windows-sys" 1998 | version = "0.52.0" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2001 | dependencies = [ 2002 | "windows-targets", 2003 | ] 2004 | 2005 | [[package]] 2006 | name = "windows-sys" 2007 | version = "0.59.0" 2008 | source = "registry+https://github.com/rust-lang/crates.io-index" 2009 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2010 | dependencies = [ 2011 | "windows-targets", 2012 | ] 2013 | 2014 | [[package]] 2015 | name = "windows-targets" 2016 | version = "0.52.6" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2019 | dependencies = [ 2020 | "windows_aarch64_gnullvm", 2021 | "windows_aarch64_msvc", 2022 | "windows_i686_gnu", 2023 | "windows_i686_gnullvm", 2024 | "windows_i686_msvc", 2025 | "windows_x86_64_gnu", 2026 | "windows_x86_64_gnullvm", 2027 | "windows_x86_64_msvc", 2028 | ] 2029 | 2030 | [[package]] 2031 | name = "windows_aarch64_gnullvm" 2032 | version = "0.52.6" 2033 | source = "registry+https://github.com/rust-lang/crates.io-index" 2034 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2035 | 2036 | [[package]] 2037 | name = "windows_aarch64_msvc" 2038 | version = "0.52.6" 2039 | source = "registry+https://github.com/rust-lang/crates.io-index" 2040 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2041 | 2042 | [[package]] 2043 | name = "windows_i686_gnu" 2044 | version = "0.52.6" 2045 | source = "registry+https://github.com/rust-lang/crates.io-index" 2046 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2047 | 2048 | [[package]] 2049 | name = "windows_i686_gnullvm" 2050 | version = "0.52.6" 2051 | source = "registry+https://github.com/rust-lang/crates.io-index" 2052 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2053 | 2054 | [[package]] 2055 | name = "windows_i686_msvc" 2056 | version = "0.52.6" 2057 | source = "registry+https://github.com/rust-lang/crates.io-index" 2058 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2059 | 2060 | [[package]] 2061 | name = "windows_x86_64_gnu" 2062 | version = "0.52.6" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2065 | 2066 | [[package]] 2067 | name = "windows_x86_64_gnullvm" 2068 | version = "0.52.6" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2071 | 2072 | [[package]] 2073 | name = "windows_x86_64_msvc" 2074 | version = "0.52.6" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2077 | 2078 | [[package]] 2079 | name = "winnow" 2080 | version = "0.6.18" 2081 | source = "registry+https://github.com/rust-lang/crates.io-index" 2082 | checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" 2083 | dependencies = [ 2084 | "memchr", 2085 | ] 2086 | 2087 | [[package]] 2088 | name = "zerocopy" 2089 | version = "0.7.35" 2090 | source = "registry+https://github.com/rust-lang/crates.io-index" 2091 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2092 | dependencies = [ 2093 | "byteorder", 2094 | "zerocopy-derive", 2095 | ] 2096 | 2097 | [[package]] 2098 | name = "zerocopy-derive" 2099 | version = "0.7.35" 2100 | source = "registry+https://github.com/rust-lang/crates.io-index" 2101 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2102 | dependencies = [ 2103 | "proc-macro2", 2104 | "quote", 2105 | "syn", 2106 | ] 2107 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "common", 5 | "lumen", 6 | ] 7 | 8 | [profile.release] 9 | lto = "fat" 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.80.1-slim-bookworm 2 | ARG DEBIAN_FRONTEND=noninteractive 3 | RUN apt-get update && apt-get install -y --no-install-recommends --no-install-suggests ca-certificates pkg-config libssl-dev libpq-dev 4 | ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse 5 | 6 | RUN --mount=type=cache,target=$CARGO_HOME/registry \ 7 | cargo install diesel_cli --version 2.2.1 --no-default-features --features postgres 8 | 9 | COPY common /lumen/common 10 | COPY lumen /lumen/lumen 11 | COPY Cargo.toml /lumen/ 12 | RUN --mount=type=cache,target=$CARGO_HOME/registry,target=/lumen/target \ 13 | cd /lumen && cargo build --release && cp /lumen/target/release/lumen /root/ 14 | 15 | FROM debian:bookworm-slim 16 | ARG DEBIAN_FRONTEND=noninteractive 17 | RUN apt-get update && apt-get install -y --no-install-recommends --no-install-suggests openssl libpq5 && \ 18 | sed -i -e 's,\[ v3_req \],\[ v3_req \]\nextendedKeyUsage = serverAuth,' /etc/ssl/openssl.cnf 19 | RUN mkdir /usr/lib/lumen/ 20 | 21 | COPY --from=0 /usr/local/cargo/bin/diesel /usr/bin/diesel 22 | COPY --from=0 /lumen/common/migrations /usr/lib/lumen/migrations 23 | COPY --from=0 /lumen/common/diesel.toml /usr/lib/lumen/ 24 | COPY --from=0 /root/lumen /usr/bin/lumen 25 | 26 | COPY config-example.toml docker-init.sh /lumen/ 27 | RUN chmod a+x /lumen/docker-init.sh && chmod a+x /usr/bin/lumen 28 | WORKDIR /lumen 29 | CMD /lumen/docker-init.sh 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Naim A. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lumen 2 | 3 | A private Lumina server that can be used with IDA Pro 7.2+. 4 | 5 | [lumen.abda.nl](https://lumen.abda.nl/) runs this server. 6 | 7 | You can read about the protocol research [here](https://abda.nl/posts/introducing-lumen/). 8 | 9 | ## Features 10 | 11 | - Stores function signatures so you (and your team) can quickly identify functions that you found in the past using IDA's built-in Lumina features. 12 | - Backed by PostgreSQL 13 | - Experimental HTTP API that allows querying the database for comments by file or function hash. 14 | 15 | ## Getting Started 16 | 17 | ### Docker Method (Recommended) 18 | 19 | In this method precompiled docker images will be downloaded, All you need is [docker-compose.yml](./docker-compose.yml). 20 | 21 | 1. Install `docker-engine` and `docker-compose`. 22 | 2. If using a custom TLS certificate, copy the private key (`.p12`/`.pfx` extension) to `./dockershare` and set the key password in `.env` as `PKCSPASSWD`. 23 | 3. If using a custom Lumen config, copy it to `./dockershare/config.toml`. 24 | 4. Otherwise, or if you have finished these steps, just run `docker-compose up`. 25 | 5. Regardless, if TLS is enabled in the `config.toml`, a `hexrays.crt` will be generated in `./dockershare` to be copied to the IDA install directory. 26 | 27 | ### Building from source with Rust 28 | 29 | 1. `git clone https://github.com/naim94a/lumen.git` 30 | 2. Get a rust toolchain: https://rustup.rs/ 31 | 3. `cd lumen` 32 | 4. Setup a the database 33 | 34 | - install postgres 35 | - install diesel-cli and run migrations: 36 | 37 | ```bash 38 | cargo install diesel_cli --no-default-features -Fpostgres 39 | diesel --config-file common/diesel.toml \ 40 | --database-url postgres://postgres:password@localhost/lumen \ 41 | migration run 42 | ``` 43 | 44 | 5. `cargo build --release` 45 | 46 | ### Usage 47 | 48 | ```bash 49 | ./lumen -c config.toml 50 | ``` 51 | 52 | ### Configuring IDA 53 | 54 | #### IDA Pro >= 8.1 55 | 56 | If you used LUMEN in the past, remove the LUMINA settings in the ida.cfg or idauser.cfg files, otherwise you will get a warning about 57 | bad config parameters. 58 | 59 | ##### Setup under Linux : 60 | 61 | ```bash 62 | #!/bin/sh 63 | export LUMINA_TLS=false 64 | $1 65 | ``` 66 | 67 | - save as ida_lumen.sh, "chmod +x ida_lumen.sh", now you can run IDA using "./ida_lumen.sh ./ida" or "./ida_lumen ./ida64" 68 | 69 | ##### Setup under Windows : 70 | 71 | ```batch 72 | set LUMINA_TLS=false 73 | %1 74 | ``` 75 | 76 | - save as ida_lumen.bat, now you can run IDA using "./ida_lumen.bat ida.exe" or "./ida_lumen.bat ida64.exe" 77 | 78 | ##### Setup IDA 79 | 80 | - Go to Options, General, Lumina. Select "Use a private server", then set your host and port and "guest" as username and password. Click on ok. 81 | 82 | #### IDA Pro < 8.1 83 | 84 | You will need IDA Pro 7.2 or above in order to use _lumen_. 85 | 86 | > The following information may get sent to _lumen_ server: IDA key, Hostname, IDB path, original file path, file MD5, function signature, stack frames & comments. 87 | 88 | - In your IDA's installation directory open "cfg\ida.cfg" with your favorite text editor _(Example: C:\Program Files\IDA Pro 7.5\cfg\ida.cfg)_ 89 | - Locate the commented out `LUMINA_HOST`, `LUMINA_PORT`, and change their values to the address of your _lumen_ server. 90 | - If you didn't configure TLS, Add "LUMINA_TLS = NO" after the line with `LUMINA_PORT`. 91 | 92 | Example: 93 | 94 | ```C 95 | LUMINA_HOST = "192.168.1.1"; 96 | LUMINA_PORT = 1234 97 | 98 | // Only if TLS isn't used: 99 | LUMINA_TLS = NO 100 | ``` 101 | 102 | ### Configuring TLS 103 | 104 | IDA Pro uses a pinned certificate for Lumina's communcation, so adding a self-signed certificate to your root certificates won't work. 105 | Luckily, we can override the hard-coded public key by writing a DER-base64 encoded certificate to "hexrays.crt" in IDA's install directory. 106 | 107 | You may find the following commands useful: 108 | 109 | ```bash 110 | # create a certificate 111 | openssl req -x509 -newkey rsa:4096 -keyout lumen_key.pem -out lumen_crt.pem -days 365 -nodes 112 | 113 | # convert to pkcs12 for lumen; used for `lumen.tls` in config 114 | openssl pkcs12 -export -out lumen.p12 -inkey lumen_key.pem -in lumen_crt.pem 115 | 116 | # export public-key for IDA; Copy hexrays.crt to IDA installation folder 117 | openssl x509 -in lumen_crt.pem -out hexrays.crt 118 | ``` 119 | 120 | No attempt is made to merge function data - this may cause a situation where metadata is inconsistent. 121 | Instead, the metadata with the highest calculated score is returned to the user. 122 | 123 | --- 124 | 125 | Developed by [Naim A.](https://github.com/naim94a); License: MIT. 126 | -------------------------------------------------------------------------------- /build.Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | # docker build -t lumen-builer -f build.Dockerfile . 3 | # docker run --rm -v `pwd`/out:/out -it lumen-builder 4 | 5 | FROM rust:latest 6 | RUN apt -y update && apt install -y mingw-w64 zip jq 7 | RUN rustup target add x86_64-pc-windows-gnu 8 | 9 | COPY Cargo.toml /usr/src/lumen/Cargo.toml 10 | COPY Cargo.lock /usr/src/lumen/Cargo.lock 11 | COPY common /usr/src/lumen/common 12 | COPY lumen /usr/src/lumen/lumen 13 | WORKDIR /usr/src/lumen 14 | RUN cargo fetch 15 | 16 | RUN cargo build --release --target x86_64-unknown-linux-gnu && \ 17 | cargo build --release --target x86_64-pc-windows-gnu 18 | 19 | COPY README.md /usr/src/lumen/ 20 | COPY LICENSE /usr/src/lumen/ 21 | COPY config-example.toml /usr/src/lumen/ 22 | 23 | VOLUME [ "/out" ] 24 | CMD mkdir /tmp/out/ && \ 25 | cp README.md LICENSE config-example.toml /tmp/out/ && \ 26 | cp target/x86_64-unknown-linux-gnu/release/lumen /tmp/out/ && \ 27 | cp target/x86_64-pc-windows-gnu/release/lumen.exe /tmp/out/ && \ 28 | cd /tmp/out/ && \ 29 | tar czvf /out/lumen-x86_64-unknown-linux-gnu.tar.gz README.md LICENSE config-example.toml lumen && \ 30 | zip -9 /out/lumen-x86_64-pc-windows-gnu.zip README.md LICENSE config-example.toml lumen.exe 31 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | description = "common utilities for lumen" 4 | version = "0.2.0" 5 | authors = ["Naim A. "] 6 | edition = "2021" 7 | publish = false 8 | 9 | [dependencies] 10 | tokio = { version = "1.39", features = ["full"], optional = true } 11 | log = { version = "0.4", features = ["release_max_level_debug"] } 12 | serde = { version = "1.0", features = ["derive"] } 13 | postgres-native-tls = { version = "0.5", optional = true } 14 | native-tls = { version = "0.2", optional = true } 15 | futures-util = "0.3" 16 | toml = "0.8" 17 | warp = { version = "0.3", optional = true } 18 | binascii = "0.1" 19 | 20 | tokio-postgres = { version = "0.7", default-features = false, optional = true } 21 | diesel = { version = "2.2", optional = true, default-features = false, features = [ 22 | "postgres_backend", 23 | "time", 24 | ] } 25 | time = { version = "0.3.36", optional = true } 26 | diesel-async = { version = "0.5", optional = true, features = [ 27 | "postgres", 28 | "bb8", 29 | ] } 30 | anyhow = "1.0" 31 | prometheus-client = "0.22" 32 | 33 | [features] 34 | default = ["web", "db"] 35 | web = ["warp"] 36 | db = [ 37 | "tokio", 38 | "postgres-native-tls", 39 | "native-tls", 40 | "diesel", 41 | "diesel-async", 42 | "tokio-postgres", 43 | "time", 44 | ] 45 | -------------------------------------------------------------------------------- /common/diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see https://diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "src/db/schema_auto.rs" 6 | 7 | [migrations_directory] 8 | dir = "migrations" 9 | -------------------------------------------------------------------------------- /common/migrations/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naim94a/lumen/1fe940563888bebe49bf1437db479473617eed45/common/migrations/.keep -------------------------------------------------------------------------------- /common/migrations/00000000000000_diesel_initial_setup/down.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); 6 | DROP FUNCTION IF EXISTS diesel_set_updated_at(); 7 | -------------------------------------------------------------------------------- /common/migrations/00000000000000_diesel_initial_setup/up.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | 6 | 7 | 8 | -- Sets up a trigger for the given table to automatically set a column called 9 | -- `updated_at` whenever the row is modified (unless `updated_at` was included 10 | -- in the modified columns) 11 | -- 12 | -- # Example 13 | -- 14 | -- ```sql 15 | -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); 16 | -- 17 | -- SELECT diesel_manage_updated_at('users'); 18 | -- ``` 19 | CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ 20 | BEGIN 21 | EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s 22 | FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); 23 | END; 24 | $$ LANGUAGE plpgsql; 25 | 26 | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ 27 | BEGIN 28 | IF ( 29 | NEW IS DISTINCT FROM OLD AND 30 | NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at 31 | ) THEN 32 | NEW.updated_at := current_timestamp; 33 | END IF; 34 | RETURN NEW; 35 | END; 36 | $$ LANGUAGE plpgsql; 37 | -------------------------------------------------------------------------------- /common/migrations/2023-02-01-210714_init/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS users ( 2 | id SERIAL PRIMARY KEY, 3 | lic_id bytea, 4 | lic_data bytea, 5 | hostname VARCHAR(260), 6 | first_seen TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP 7 | ); 8 | CREATE UNIQUE INDEX IF NOT EXISTS user_rec ON users(lic_id,lic_data,hostname); 9 | CREATE UNIQUE INDEX IF NOT EXISTS user_hn_null ON users (lic_id,lic_data, (hostname IS NULL)) WHERE hostname is NULL; 10 | 11 | CREATE TABLE IF NOT EXISTS files ( 12 | id SERIAL PRIMARY KEY, 13 | chksum bytea UNIQUE /* file chksum */ 14 | ); 15 | 16 | CREATE TABLE IF NOT EXISTS dbs ( 17 | id SERIAL PRIMARY KEY, 18 | file_path VARCHAR(260), 19 | idb_path VARCHAR(260), 20 | file_id INTEGER REFERENCES files(id), 21 | user_id INTEGER REFERENCES users(id) 22 | ); 23 | CREATE UNIQUE INDEX IF NOT EXISTS db_paths ON dbs(file_id, user_id, idb_path); 24 | 25 | CREATE TABLE IF NOT EXISTS funcs ( 26 | id SERIAL PRIMARY KEY, 27 | name TEXT NOT NULL, 28 | len INTEGER NOT NULL, 29 | db_id INTEGER REFERENCES dbs(id) NOT NULL, 30 | chksum bytea, /* function chksum */ 31 | metadata bytea, 32 | rank INTEGER, 33 | 34 | push_dt TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, 35 | update_dt TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP 36 | ); 37 | CREATE UNIQUE INDEX IF NOT EXISTS funcs_db ON funcs(chksum, db_id); 38 | CREATE INDEX IF NOT EXISTS funcs_ranking ON funcs(chksum,rank); 39 | CREATE INDEX IF NOT EXISTS func_chksum ON funcs(chksum); 40 | -------------------------------------------------------------------------------- /common/src/async_drop.rs: -------------------------------------------------------------------------------- 1 | use futures_util::{future::BoxFuture, Future}; 2 | use log::trace; 3 | use tokio::sync::mpsc::{unbounded_channel, UnboundedSender, WeakUnboundedSender}; 4 | 5 | enum AsyncDropperMsg { 6 | Future(BoxFuture<'static, ()>), 7 | Termination, 8 | } 9 | 10 | /// Assists with executing code in a future when the future is cancelled. 11 | pub struct AsyncDropper { 12 | tx: UnboundedSender, 13 | } 14 | impl AsyncDropper { 15 | /// Creates a new `AsyncDropper` 16 | /// Returns a tuple containing the AsyncDropper struct and a future that will perform tasks when a future is dropped. 17 | #[track_caller] 18 | pub fn new() -> (AsyncDropper, impl Future + 'static) { 19 | let orig = format!("{}", std::panic::Location::caller()); 20 | trace!("new dropper '{orig}'"); 21 | 22 | let (tx, mut rx) = unbounded_channel(); 23 | let fut = async move { 24 | while let Some(msg) = rx.recv().await { 25 | match msg { 26 | AsyncDropperMsg::Future(fut) => { 27 | fut.await; 28 | }, 29 | AsyncDropperMsg::Termination => { 30 | trace!("term received for '{orig}'..."); 31 | break; 32 | }, 33 | } 34 | } 35 | trace!("dropper '{orig}' exited."); 36 | }; 37 | 38 | (Self { tx }, fut) 39 | } 40 | 41 | /// Defers execution of a future to when the returned `AsyncDropGuard` is dropped 42 | pub fn defer + Send + 'static>(&self, fut: F) -> AsyncDropGuard { 43 | let tx = self.tx.downgrade(); 44 | AsyncDropGuard { tx, run: Some(Box::pin(fut)) } 45 | } 46 | } 47 | impl Drop for AsyncDropper { 48 | fn drop(&mut self) { 49 | if let Err(err) = self.tx.send(AsyncDropperMsg::Termination) { 50 | // If this ever panics, we're not cleaning up resources properly. 51 | panic!("failed to send termination: {}", err); 52 | } 53 | } 54 | } 55 | 56 | /// A Guard struct. When dropped executes the defered future. 57 | pub struct AsyncDropGuard { 58 | tx: WeakUnboundedSender, 59 | run: Option>, 60 | } 61 | 62 | impl AsyncDropGuard { 63 | /// This consumes the guard, causing the internal future not to execute. 64 | pub fn consume(mut self) { 65 | self.run.take(); 66 | } 67 | } 68 | 69 | impl Drop for AsyncDropGuard { 70 | fn drop(&mut self) { 71 | if let Some(fun) = self.run.take() { 72 | if let Some(tx) = self.tx.upgrade() { 73 | let _ = tx.send(AsyncDropperMsg::Future(fun)); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /common/src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::time::Duration; 3 | use std::{net::SocketAddr, path::PathBuf}; 4 | use toml::from_str; 5 | 6 | #[derive(Deserialize)] 7 | pub struct TlsIdentity { 8 | pub server_cert: PathBuf, 9 | } 10 | 11 | #[derive(Deserialize)] 12 | pub struct LuminaServer { 13 | pub bind_addr: SocketAddr, 14 | pub use_tls: Option, 15 | pub tls: Option, 16 | pub server_name: Option, 17 | pub allow_deletes: Option, 18 | 19 | /// limit of function histories to return per function. 20 | /// `None`, or `Some(0)` will disable the feature on the server. 21 | pub get_history_limit: Option, 22 | } 23 | 24 | #[derive(Deserialize)] 25 | pub struct WebServer { 26 | pub bind_addr: SocketAddr, 27 | } 28 | 29 | #[derive(Deserialize)] 30 | pub struct Database { 31 | pub connection_info: String, 32 | 33 | pub use_tls: bool, 34 | pub server_ca: Option, 35 | pub client_id: Option, 36 | } 37 | 38 | #[derive(Deserialize, Debug)] 39 | #[serde(default)] 40 | pub struct Limits { 41 | /// Maximum time to wait on an idle connection between commands. 42 | pub command_timeout: Duration, 43 | 44 | /// Maximum time to all `PULL_MD` queries. 45 | pub pull_md_timeout: Duration, 46 | 47 | /// Maximum time to wait for `HELO` message. 48 | pub hello_timeout: Duration, 49 | 50 | /// Maximum time allowed until TLS handshake completes. 51 | pub tls_handshake_timeout: Duration, 52 | } 53 | 54 | impl Default for Limits { 55 | fn default() -> Self { 56 | Self { 57 | command_timeout: Duration::from_secs(3600), 58 | pull_md_timeout: Duration::from_secs(4 * 60), 59 | hello_timeout: Duration::from_secs(15), 60 | tls_handshake_timeout: Duration::from_secs(10), 61 | } 62 | } 63 | } 64 | 65 | #[derive(Deserialize)] 66 | pub struct Config { 67 | pub lumina: LuminaServer, 68 | pub api_server: Option, 69 | pub database: Database, 70 | 71 | #[serde(default)] 72 | pub limits: Limits, 73 | } 74 | 75 | pub trait HasConfig { 76 | fn get_config(&self) -> &Config; 77 | } 78 | 79 | impl HasConfig for Config { 80 | fn get_config(&self) -> &Config { 81 | self 82 | } 83 | } 84 | 85 | pub fn load_config(mut fd: R) -> Config { 86 | let mut buf = vec![]; 87 | fd.read_to_end(&mut buf).expect("failed to read config"); 88 | 89 | let buf = std::str::from_utf8(&buf).expect("file contains invalid utf-8"); 90 | 91 | from_str(buf).expect("failed to parse configuration") 92 | } 93 | -------------------------------------------------------------------------------- /common/src/db/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::async_drop::{AsyncDropGuard, AsyncDropper}; 2 | use log::*; 3 | use postgres_native_tls::MakeTlsConnector; 4 | use serde::Serialize; 5 | use std::collections::HashMap; 6 | use time::OffsetDateTime; 7 | use tokio_postgres::{tls::MakeTlsConnect, NoTls, Socket}; 8 | pub mod schema; 9 | mod schema_auto; 10 | 11 | use diesel::{ 12 | query_builder::{Query, QueryFragment}, 13 | sql_types::{Array, Binary, Integer, VarChar}, 14 | upsert::excluded, 15 | ExpressionMethods, NullableExpressionMethods, QueryDsl, 16 | }; 17 | use diesel_async::{pooled_connection::ManagerConfig, RunQueryDsl}; 18 | 19 | pub type DynConfig = dyn crate::config::HasConfig + Send + Sync; 20 | 21 | pub struct Database { 22 | tls_connector: Option, 23 | diesel: diesel_async::pooled_connection::bb8::Pool, 24 | dropper: AsyncDropper, 25 | } 26 | 27 | pub struct FunctionInfo { 28 | pub name: String, 29 | pub len: u32, 30 | pub data: Vec, 31 | pub popularity: u32, 32 | } 33 | 34 | #[derive(Debug, Serialize)] 35 | pub struct DbStats { 36 | unique_lics: i32, 37 | unique_hosts_per_lic: i32, 38 | 39 | unique_funcs: i32, 40 | total_funcs: i32, 41 | 42 | dbs: i32, 43 | unique_files: i32, 44 | } 45 | 46 | impl Database { 47 | pub async fn open(config: &crate::config::Database) -> Result { 48 | let connection_string = config.connection_info.as_str(); 49 | let tls_connector = if config.use_tls { Some(Self::make_tls(config).await) } else { None }; 50 | 51 | let (dropper, worker) = AsyncDropper::new(); 52 | tokio::task::spawn(worker); 53 | 54 | let diesel = Self::make_bb8_pool(connection_string, tls_connector.clone()).await?; 55 | 56 | Ok(Database { tls_connector, dropper, diesel }) 57 | } 58 | 59 | async fn make_pg_client( 60 | db_url: &str, tls: T, 61 | ) -> diesel::result::ConnectionResult 62 | where 63 | T: MakeTlsConnect, 64 | T::Stream: Send + 'static, 65 | { 66 | let (cli, conn) = tokio_postgres::connect(db_url, tls).await.map_err(|e| { 67 | error!("failed to connect db: {e}"); 68 | diesel::result::ConnectionError::BadConnection(format!("{e}")) 69 | })?; 70 | 71 | tokio::spawn(async move { 72 | if let Err(e) = conn.await { 73 | error!("connection task error: {e}"); 74 | } 75 | }); 76 | 77 | diesel_async::AsyncPgConnection::try_from(cli).await 78 | } 79 | 80 | async fn make_bb8_pool( 81 | db_url: &str, tls: Option, 82 | ) -> Result< 83 | diesel_async::pooled_connection::bb8::Pool, 84 | anyhow::Error, 85 | > { 86 | let mut config = ManagerConfig::default(); 87 | config.custom_setup = Box::new(move |db_url| { 88 | let tls = tls.clone(); 89 | Box::pin(async move { 90 | if let Some(tls) = tls { 91 | Self::make_pg_client(db_url, tls).await 92 | } else { 93 | Self::make_pg_client(db_url, NoTls).await 94 | } 95 | }) 96 | }); 97 | let cfg = diesel_async::pooled_connection::AsyncDieselConnectionManager::< 98 | diesel_async::AsyncPgConnection, 99 | >::new_with_config(db_url, config); 100 | 101 | let pool = diesel_async::pooled_connection::bb8::Pool::builder() 102 | .min_idle(Some(1)) 103 | .build(cfg) 104 | .await?; 105 | Ok(pool) 106 | } 107 | 108 | async fn make_tls(database: &crate::config::Database) -> MakeTlsConnector { 109 | use native_tls::{Certificate, Identity, TlsConnector}; 110 | 111 | let mut tls_connector = TlsConnector::builder(); 112 | 113 | if let Some(ref client_identity) = database.client_id { 114 | let client_identity = 115 | tokio::fs::read(client_identity).await.expect("failed to read db's client id"); 116 | let client_identity = Identity::from_pkcs12(&client_identity, "") 117 | .expect("failed to load db's client identity (PKCS12)"); 118 | tls_connector.identity(client_identity); 119 | } 120 | 121 | if let Some(ref server_ca) = database.server_ca { 122 | let server_ca = 123 | tokio::fs::read(server_ca).await.expect("failed to read db's server ca"); 124 | let server_ca = 125 | Certificate::from_pem(&server_ca).expect("failed to load db's server ca (PEM)"); 126 | tls_connector.add_root_certificate(server_ca); 127 | } 128 | 129 | let tls_connector = tls_connector 130 | .danger_accept_invalid_hostnames(true) 131 | .build() 132 | .expect("failed to build TlsConnector"); 133 | 134 | MakeTlsConnector::new(tls_connector) 135 | } 136 | 137 | pub async fn get_funcs( 138 | &self, funcs: &[crate::rpc::PullMetadataFunc<'_>], 139 | ) -> Result>, anyhow::Error> { 140 | let chksums: Vec<&[u8]> = funcs.iter().map(|v| v.mb_hash).collect(); 141 | 142 | let rows: Vec<(String, i32, Vec, Vec)> = { 143 | let conn = &mut self.diesel.get().await?; 144 | 145 | let ct = self.cancel_guard(&*conn); 146 | 147 | let res: Vec<_> = BestMds(chksums.as_slice()).get_results::<_>(conn).await?; 148 | ct.consume(); 149 | res 150 | }; 151 | 152 | let mut partial: HashMap, FunctionInfo> = rows 153 | .into_iter() 154 | .map(|row| { 155 | let v = FunctionInfo { name: row.0, len: row.1 as u32, data: row.2, popularity: 0 }; 156 | 157 | (row.3, v) 158 | }) 159 | .collect(); 160 | 161 | let results = partial.len(); 162 | 163 | let res: Vec> = 164 | chksums.iter().map(|&chksum| partial.remove(chksum)).collect(); 165 | 166 | trace!("found {}/{} results", results, chksums.len()); 167 | debug_assert_eq!(chksums.len(), res.len()); 168 | Ok(res) 169 | } 170 | 171 | pub async fn get_or_create_user<'a>( 172 | &self, user: &'a crate::rpc::RpcHello<'a>, hostname: &str, 173 | ) -> Result { 174 | use schema::users; 175 | 176 | let conn = &mut self.diesel.get().await?; 177 | 178 | let lic_id = &user.lic_number[..]; 179 | let lic_data = user.license_data; 180 | 181 | let get_user = || { 182 | users::table 183 | .select(users::id) 184 | .filter(users::lic_data.eq(lic_data)) 185 | .filter(users::lic_id.eq(lic_id)) 186 | .filter(users::hostname.eq(hostname)) 187 | }; 188 | 189 | match get_user().get_result::(conn).await { 190 | Ok(v) => return Ok(v), 191 | Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 192 | _ => {}, 193 | }; 194 | 195 | match diesel::insert_into(users::table) 196 | .values(vec![( 197 | users::lic_id.eq(lic_id), 198 | users::lic_data.eq(lic_data), 199 | users::hostname.eq(hostname), 200 | )]) 201 | .returning(users::id) // xmax = 0 if the row is new 202 | .get_result::(conn) 203 | .await 204 | { 205 | Ok(v) => return Ok(v), 206 | Err(diesel::result::Error::DatabaseError( 207 | diesel::result::DatabaseErrorKind::UniqueViolation, 208 | _, 209 | )) => {}, 210 | Err(e) => return Err(e.into()), 211 | } 212 | 213 | Ok(get_user().get_result::(conn).await?) 214 | } 215 | 216 | async fn get_or_create_file<'a>( 217 | &self, funcs: &'a crate::rpc::PushMetadata<'a>, 218 | ) -> Result { 219 | use schema::files::{chksum, id, table as files}; 220 | 221 | let hash = &funcs.md5[..]; 222 | 223 | let conn = &mut self.diesel.get().await?; 224 | 225 | let get_file = || files.filter(chksum.eq(hash)).select(id); 226 | 227 | match get_file().get_result::(conn).await { 228 | Ok(v) => return Ok(v), 229 | Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 230 | _ => {}, 231 | } 232 | 233 | match diesel::insert_into(files) 234 | .values(vec![(chksum.eq(hash),)]) 235 | .returning(id) 236 | .get_result::(conn) 237 | .await 238 | { 239 | Ok(v) => return Ok(v), 240 | Err(diesel::result::Error::DatabaseError( 241 | diesel::result::DatabaseErrorKind::UniqueViolation, 242 | _, 243 | )) => {}, 244 | Err(e) => return Err(e.into()), 245 | } 246 | Ok(get_file().get_result::(conn).await?) 247 | } 248 | 249 | async fn get_or_create_db<'a>( 250 | &self, user: &'a crate::rpc::RpcHello<'a>, funcs: &'a crate::rpc::PushMetadata<'a>, 251 | ) -> Result { 252 | use schema::dbs::{ 253 | file_id as db_file_id, file_path, id as db_id, idb_path, table as dbs, 254 | user_id as db_user, 255 | }; 256 | 257 | let file_id = self.get_or_create_file(funcs); 258 | let user_id = self.get_or_create_user(user, funcs.hostname); 259 | 260 | let (file_id, user_id): (i32, i32) = futures_util::try_join!(file_id, user_id)?; 261 | 262 | let conn = &mut self.diesel.get().await?; 263 | 264 | let get_db = || { 265 | dbs.select(db_id) 266 | .filter(db_user.eq(user_id)) 267 | .filter(db_file_id.eq(file_id)) 268 | .filter(file_path.eq(funcs.file_path)) 269 | .filter(idb_path.eq(funcs.idb_path)) 270 | }; 271 | 272 | match get_db().get_result::(conn).await { 273 | Ok(v) => return Ok(v), 274 | Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 275 | _ => {}, 276 | }; 277 | 278 | match diesel::insert_into(dbs) 279 | .values(vec![( 280 | db_user.eq(user_id), 281 | db_file_id.eq(file_id), 282 | file_path.eq(funcs.file_path), 283 | idb_path.eq(funcs.idb_path), 284 | )]) 285 | .returning(db_id) 286 | .get_result::(conn) 287 | .await 288 | { 289 | Ok(id) => return Ok(id), 290 | Err(diesel::result::Error::DatabaseError( 291 | diesel::result::DatabaseErrorKind::UniqueViolation, 292 | _, 293 | )) => {}, 294 | Err(e) => return Err(e.into()), 295 | }; 296 | Ok(get_db().get_result::(conn).await?) 297 | } 298 | 299 | pub async fn push_funcs<'a, 'b>( 300 | &'b self, user: &'a crate::rpc::RpcHello<'a>, funcs: &'a crate::rpc::PushMetadata<'a>, 301 | scores: &[u32], 302 | ) -> Result, anyhow::Error> { 303 | use futures_util::TryStreamExt; 304 | 305 | // postgres has a limitation of binding per statement (i16::MAX). Split large push requests into smaller chunks. 306 | const PUSH_FUNC_CHUNK_SIZE: usize = 3000; 307 | 308 | let db_id = self.get_or_create_db(user, funcs).await?; 309 | 310 | let mut rows = Vec::with_capacity(funcs.funcs.len().min(PUSH_FUNC_CHUNK_SIZE)); 311 | let mut is_new = Vec::with_capacity(funcs.funcs.len()); 312 | let conn = &mut self.diesel.get().await?; 313 | let f2 = diesel::alias!(schema::funcs as f2); 314 | 315 | for (idx, (func, &score)) in funcs.funcs.iter().zip(scores.iter()).enumerate() { 316 | let name = func.name; 317 | let len = func.func_len as i32; 318 | let chksum = func.hash; 319 | let md = func.func_data; 320 | let score = score as i32; 321 | 322 | rows.push(( 323 | schema::funcs::name.eq(name), 324 | schema::funcs::len.eq(len), 325 | schema::funcs::chksum.eq(chksum), 326 | schema::funcs::metadata.eq(md), 327 | schema::funcs::rank.eq(score), 328 | schema::funcs::db_id.eq(db_id), 329 | )); 330 | 331 | if rows.len() < PUSH_FUNC_CHUNK_SIZE && idx < funcs.funcs.len() - 1 { 332 | continue; 333 | } 334 | 335 | let mut current_rows = 336 | Vec::with_capacity((funcs.funcs.len() - (idx + 1)).max(PUSH_FUNC_CHUNK_SIZE)); 337 | std::mem::swap(&mut current_rows, &mut rows); 338 | 339 | diesel::insert_into(schema::funcs::table) 340 | .values(current_rows) 341 | .on_conflict((schema::funcs::chksum, schema::funcs::db_id)) 342 | .do_update() 343 | .set(( 344 | schema::funcs::name.eq(excluded(schema::funcs::name)), 345 | schema::funcs::metadata.eq(excluded(schema::funcs::metadata)), 346 | schema::funcs::rank.eq(excluded(schema::funcs::rank)), 347 | schema::funcs::update_dt.eq(diesel::dsl::now), 348 | )) 349 | .returning(diesel::dsl::not(diesel::dsl::exists( 350 | f2.filter(f2.field(schema::funcs::chksum).eq(schema::funcs::chksum)), 351 | ))) // xmax=0 when a new row is created. 352 | .load_stream::(conn) 353 | .await? 354 | .try_fold(&mut is_new, |acc, item: bool| { 355 | acc.push(item); 356 | futures_util::future::ready(Ok(acc)) 357 | }) 358 | .await?; 359 | } 360 | 361 | Ok(is_new) 362 | } 363 | 364 | pub async fn get_file_funcs( 365 | &self, md5: &[u8], offset: i64, limit: i64, 366 | ) -> Result)>, anyhow::Error> { 367 | let conn = &mut self.diesel.get().await?; 368 | let results = schema::funcs::table 369 | .left_join(schema::dbs::table.left_join(schema::files::table)) 370 | .select(( 371 | schema::funcs::name.assume_not_null(), 372 | schema::funcs::len.assume_not_null(), 373 | schema::funcs::chksum.assume_not_null(), 374 | )) 375 | .filter(schema::files::chksum.eq(md5)) 376 | .offset(offset) 377 | .limit(limit) 378 | .get_results::<(String, i32, Vec)>(conn) 379 | .await?; 380 | Ok(results) 381 | } 382 | 383 | pub async fn get_files_with_func(&self, func: &[u8]) -> Result>, anyhow::Error> { 384 | let conn = &mut self.diesel.get().await?; 385 | 386 | let res = schema::files::table 387 | .left_join(schema::dbs::table.left_join(schema::funcs::table)) 388 | .select(schema::files::chksum.assume_not_null()) 389 | .distinct() 390 | .filter(schema::funcs::chksum.eq(func)) 391 | .get_results::>(conn) 392 | .await?; 393 | Ok(res) 394 | } 395 | 396 | fn cancel_guard( 397 | &self, 398 | conn: &diesel_async::pooled_connection::bb8::PooledConnection< 399 | '_, 400 | diesel_async::AsyncPgConnection, 401 | >, 402 | ) -> AsyncDropGuard { 403 | let token = conn.cancel_token(); 404 | let tls_connector = self.tls_connector.clone(); 405 | self.dropper.defer(async move { 406 | debug!("cancelling query..."); 407 | 408 | if let Some(tls) = tls_connector { 409 | let _ = token.cancel_query(tls).await; 410 | } else { 411 | let _ = token.cancel_query(NoTls).await; 412 | } 413 | }) 414 | } 415 | 416 | pub async fn delete_metadata( 417 | &self, req: &crate::rpc::DelHistory<'_>, 418 | ) -> Result<(), anyhow::Error> { 419 | use schema::funcs::{chksum, table as funcs}; 420 | 421 | let chksums = req.funcs.iter().map(|v| v.as_slice()).collect::>(); 422 | 423 | let conn = &mut self.diesel.get().await?; 424 | let rows_modified = 425 | diesel::delete(funcs.filter(chksum.eq_any(&chksums))).execute(conn).await?; 426 | 427 | debug!("deleted {rows_modified} rows"); 428 | 429 | Ok(()) 430 | } 431 | 432 | pub async fn get_func_histories( 433 | &self, chksum: &[u8], limit: u32, 434 | ) -> Result)>, anyhow::Error> { 435 | let conn = &mut self.diesel.get().await?; 436 | let rows = &schema::funcs::table.select(( 437 | schema::funcs::update_dt.assume_not_null(), 438 | schema::funcs::name, 439 | schema::funcs::metadata.assume_not_null(), 440 | )); 441 | let rows = rows 442 | .limit(limit as i64) 443 | .order_by(schema::funcs::update_dt.desc()) 444 | .filter(schema::funcs::chksum.eq(chksum)) 445 | .get_results::<(time::OffsetDateTime, String, Vec)>(conn) 446 | .await?; 447 | Ok(rows) 448 | } 449 | } 450 | 451 | // This is eww, but it's the fastest. 452 | struct BestMds<'a>(&'a [&'a [u8]]); 453 | impl<'a> QueryFragment for BestMds<'a> { 454 | fn walk_ast<'b>( 455 | &'b self, mut pass: diesel::query_builder::AstPass<'_, 'b, diesel::pg::Pg>, 456 | ) -> diesel::QueryResult<()> { 457 | pass.push_sql( 458 | r#"WITH best AS ( 459 | select chksum,MAX(rank) as maxrank from funcs f1 460 | WHERE chksum = ANY("#, 461 | ); 462 | pass.push_bind_param::, _>(&self.0)?; 463 | pass.push_sql( 464 | r#") 465 | GROUP BY chksum 466 | ) 467 | SELECT f2.name,f2.len,f2.metadata,f2.chksum FROM best 468 | LEFT JOIN funcs f2 ON (best.chksum=f2.chksum AND best.maxrank=f2.rank)"#, 469 | ); 470 | Ok(()) 471 | } 472 | } 473 | impl<'a> diesel::query_builder::QueryId for BestMds<'a> { 474 | type QueryId = BestMds<'static>; 475 | } 476 | impl<'a> Query for BestMds<'a> { 477 | type SqlType = (VarChar, Integer, Binary, Binary); 478 | } 479 | -------------------------------------------------------------------------------- /common/src/db/schema.rs: -------------------------------------------------------------------------------- 1 | pub use super::schema_auto::*; 2 | 3 | diesel::table! { 4 | func_ranks { 5 | id -> Int4, 6 | name -> Text, 7 | len -> Int4, 8 | db_id -> Int4, 9 | chksum -> Nullable, 10 | metadata -> Nullable, 11 | rank -> Nullable, 12 | push_dt -> Nullable, 13 | update_dt -> Nullable, 14 | } 15 | } 16 | 17 | diesel::joinable!(func_ranks -> dbs (id)); 18 | -------------------------------------------------------------------------------- /common/src/db/schema_auto.rs: -------------------------------------------------------------------------------- 1 | // @generated automatically by Diesel CLI. 2 | 3 | diesel::table! { 4 | dbs (id) { 5 | id -> Int4, 6 | file_path -> Nullable, 7 | idb_path -> Nullable, 8 | file_id -> Nullable, 9 | user_id -> Nullable, 10 | } 11 | } 12 | 13 | diesel::table! { 14 | files (id) { 15 | id -> Int4, 16 | chksum -> Nullable, 17 | } 18 | } 19 | 20 | diesel::table! { 21 | funcs (id) { 22 | id -> Int4, 23 | name -> Text, 24 | len -> Int4, 25 | db_id -> Int4, 26 | chksum -> Nullable, 27 | metadata -> Nullable, 28 | rank -> Nullable, 29 | push_dt -> Nullable, 30 | update_dt -> Nullable, 31 | } 32 | } 33 | 34 | diesel::table! { 35 | users (id) { 36 | id -> Int4, 37 | lic_id -> Nullable, 38 | lic_data -> Nullable, 39 | hostname -> Nullable, 40 | first_seen -> Nullable, 41 | } 42 | } 43 | 44 | diesel::joinable!(dbs -> files (file_id)); 45 | diesel::joinable!(dbs -> users (user_id)); 46 | diesel::joinable!(funcs -> dbs (db_id)); 47 | 48 | diesel::allow_tables_to_appear_in_same_query!(dbs, files, funcs, users,); 49 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | #![warn(unused_crate_dependencies)] 3 | 4 | use std::fmt::Write; 5 | 6 | pub mod async_drop; 7 | pub mod config; 8 | pub mod db; 9 | pub mod md; 10 | pub mod metrics; 11 | pub mod rpc; 12 | pub mod web; 13 | 14 | pub struct SharedState_ { 15 | pub db: db::Database, 16 | pub config: std::sync::Arc, 17 | pub server_name: String, 18 | pub metrics: metrics::Metrics, 19 | } 20 | 21 | pub type SharedState = std::sync::Arc; 22 | 23 | pub fn make_pretty_hex(data: &[u8]) -> String { 24 | let mut output = String::new(); 25 | const CHUNK_SIZE: usize = 32; 26 | data.chunks(CHUNK_SIZE).for_each(|chunk| { 27 | for &ch in chunk { 28 | let _ = write!(&mut output, "{ch:02x} "); 29 | } 30 | let padding = CHUNK_SIZE - chunk.len(); 31 | for _ in 0..padding { 32 | let _ = write!(&mut output, ".. "); 33 | } 34 | 35 | let _ = write!(&mut output, " | "); 36 | for ch in chunk 37 | .iter() 38 | .chain(std::iter::repeat(&b' ').take(padding)) 39 | .map(|&v| std::char::from_u32(v as u32).unwrap_or('.')) 40 | { 41 | if !ch.is_ascii_graphic() { 42 | output.push('.'); 43 | } else { 44 | output.push(ch); 45 | } 46 | } 47 | output.push_str(" |\n"); 48 | }); 49 | 50 | output 51 | } 52 | -------------------------------------------------------------------------------- /common/src/md.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use crate::rpc::de::from_slice; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | pub struct MetadataChunk<'a> { 8 | code: u32, 9 | data: &'a [u8], 10 | } 11 | 12 | #[derive(Debug)] 13 | pub enum FunctionMetadata<'a> { 14 | FunctionComment(FunctionComment<'a>), 15 | ByteComment(ByteComment<'a>), 16 | ExtraComment(ExtraComment<'a>), 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct FunctionComment<'a> { 21 | pub is_repeatable: bool, 22 | pub comment: &'a str, 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct ByteComment<'a> { 27 | pub is_repeatable: bool, 28 | pub offset: u32, 29 | pub comment: &'a str, 30 | } 31 | 32 | #[derive(Debug)] 33 | pub struct ExtraComment<'a> { 34 | pub offset: u32, 35 | pub anterior: &'a str, 36 | pub posterior: &'a str, 37 | } 38 | 39 | impl<'a> FunctionMetadata<'a> { 40 | fn is_useful(&self) -> bool { 41 | // TODO: rewrite using regex with configurable library names 42 | match self { 43 | FunctionMetadata::ExtraComment(cmt) => { 44 | if cmt.anterior.starts_with("; Exported entry ") 45 | // offset=0 46 | { 47 | return false; 48 | } 49 | if cmt.anterior.is_empty() && cmt.posterior.is_empty() { 50 | return false; 51 | } 52 | }, 53 | FunctionMetadata::FunctionComment(cmt) => { 54 | if cmt.comment == "Microsoft VisualC v14 64bit runtime" 55 | || cmt.comment == "Microsoft VisualC 64bit universal runtime" 56 | { 57 | return false; 58 | } 59 | if cmt.comment.is_empty() { 60 | return false; 61 | } 62 | }, 63 | FunctionMetadata::ByteComment(cmt) => { 64 | if cmt.comment == "Trap to Debugger" 65 | || (cmt.comment.starts_with("jumptable ") && cmt.comment.contains(" case")) // repeatable=true 66 | || cmt.comment == "switch jump" 67 | || (cmt.comment.starts_with("switch ") && cmt.comment.ends_with(" cases ")) 68 | || cmt.comment == "jump table for switch statement" 69 | || cmt.comment == "indirect table for switch statement" 70 | || cmt.comment == "Microsoft VisualC v7/14 64bit runtime" 71 | || cmt.comment == "Microsoft VisualC v7/14 64bit runtime\nMicrosoft VisualC v14 64bit runtime" 72 | || cmt.comment == "Microsoft VisualC v14 64bit runtime" { 73 | return false; 74 | } 75 | if cmt.comment.is_empty() { 76 | return false; 77 | } 78 | }, 79 | } 80 | true 81 | } 82 | } 83 | 84 | fn deserialize_seq<'de, T: Deserialize<'de>>( 85 | mut data: &'de [u8], 86 | ) -> Result, crate::rpc::Error> { 87 | let mut res = vec![]; 88 | let mut reset = true; 89 | let (mut offset, used): (u32, usize) = from_slice(data)?; 90 | data = &data[used..]; 91 | if data.is_empty() { 92 | return Err(crate::rpc::Error::UnexpectedEof); 93 | } 94 | 95 | loop { 96 | let (offset_diff, used): (u32, usize) = from_slice(data)?; 97 | data = &data[used..]; 98 | if data.is_empty() { 99 | return Err(crate::rpc::Error::UnexpectedEof); 100 | } 101 | 102 | if (offset_diff > 0) || reset { 103 | offset += offset_diff; 104 | let (e, used): (T, usize) = from_slice(data)?; 105 | data = &data[used..]; 106 | 107 | res.push((offset, e)); 108 | 109 | reset = false; 110 | } else { 111 | let (offset_diff, used): (u32, usize) = from_slice(data)?; 112 | data = &data[used..]; 113 | 114 | offset = offset_diff; 115 | reset = true; 116 | } 117 | 118 | if data.is_empty() { 119 | break; 120 | } 121 | } 122 | 123 | Ok(res) 124 | } 125 | 126 | pub fn parse_metadata(mut data: &[u8]) -> Result>, crate::rpc::Error> { 127 | let mut res = vec![]; 128 | let mut bad_codes = HashSet::new(); 129 | 130 | while !data.is_empty() { 131 | let (chunk, used): (MetadataChunk, _) = from_slice(data)?; 132 | data = &data[used..]; 133 | 134 | let data = chunk.data; 135 | 136 | if data.is_empty() { 137 | continue; 138 | } 139 | 140 | match chunk.code { 141 | 1 => {}, // TODO: parse typeinfo 142 | 2 => {}, // nop 143 | 3 | 4 => { 144 | // function comments 145 | let is_repeatable = chunk.code == 4; 146 | let cmt = std::str::from_utf8(data)?; 147 | res.push(FunctionMetadata::FunctionComment(FunctionComment { 148 | is_repeatable, 149 | comment: cmt, 150 | })); 151 | }, 152 | 5 | 6 => { 153 | // comments 154 | let is_repeatable = chunk.code == 6; 155 | let byte_comments: Vec<(_, &[u8])> = match deserialize_seq(data) { 156 | Ok(v) => v, 157 | Err(err) => { 158 | log::error!("err: {}\n{}", err, super::make_pretty_hex(data)); 159 | return Err(err); 160 | }, 161 | }; 162 | 163 | for comment in byte_comments { 164 | let cmt = std::str::from_utf8(comment.1)?; 165 | res.push(FunctionMetadata::ByteComment(ByteComment { 166 | is_repeatable, 167 | offset: comment.0, 168 | comment: cmt, 169 | })); 170 | } 171 | }, 172 | 7 => { 173 | // extra comments 174 | let byte_comments: Vec<(_, (&[u8], &[u8]))> = match deserialize_seq(data) { 175 | Ok(v) => v, 176 | Err(err) => { 177 | log::error!("err: {}\n{}", err, super::make_pretty_hex(data)); 178 | return Err(err); 179 | }, 180 | }; 181 | 182 | for comment in byte_comments { 183 | res.push(FunctionMetadata::ExtraComment(ExtraComment { 184 | offset: comment.0, 185 | anterior: std::str::from_utf8(comment.1 .0)?, 186 | posterior: std::str::from_utf8(comment.1 .1)?, 187 | })); 188 | } 189 | }, 190 | 9 | 10 => { /* TODO! */ }, 191 | _ => { 192 | bad_codes.insert(chunk.code); 193 | }, 194 | } 195 | } 196 | 197 | Ok(res) 198 | } 199 | 200 | pub fn get_score(md: &crate::rpc::PushMetadataFunc) -> u32 { 201 | let mut score = 0; 202 | 203 | let md = match parse_metadata(md.func_data) { 204 | Ok(v) => v, 205 | Err(e) => { 206 | log::warn!("failed to parse metadata: {}", e); 207 | return 0; 208 | }, 209 | }; 210 | 211 | for md in md { 212 | if md.is_useful() { 213 | score += 10; 214 | } 215 | } 216 | 217 | score 218 | } 219 | -------------------------------------------------------------------------------- /common/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicI64; 2 | 3 | use prometheus_client::{ 4 | encoding::EncodeLabelSet, 5 | metrics::{counter::Counter, family::Family, gauge::Gauge}, 6 | registry::Registry, 7 | }; 8 | 9 | pub struct Metrics { 10 | pub registry: Registry, 11 | 12 | /// Count active lumina connections 13 | pub active_connections: Gauge, 14 | 15 | /// Record connected client versions 16 | pub lumina_version: Family, 17 | 18 | /// Count new functions pushes 19 | pub new_funcs: Counter, 20 | 21 | /// Count pushed functions 22 | pub pushes: Counter, 23 | 24 | /// Count pulled functions (only found) 25 | pub pulls: Counter, 26 | 27 | /// Queried functions 28 | pub queried_funcs: Counter, 29 | } 30 | 31 | #[derive(EncodeLabelSet, Debug, Hash, Eq, PartialEq, Clone)] 32 | pub struct LuminaVersion { 33 | pub protocol_version: u32, 34 | } 35 | 36 | impl Default for Metrics { 37 | fn default() -> Self { 38 | let mut registry = Registry::default(); 39 | 40 | let active_connections = Gauge::default(); 41 | registry.register( 42 | "lumen_active_connections", 43 | "Active Lumina connections", 44 | active_connections.clone(), 45 | ); 46 | 47 | let lumina_version = Family::::default(); 48 | registry.register( 49 | "lumen_protocol_version", 50 | "Version of Lumina protocol being used", 51 | lumina_version.clone(), 52 | ); 53 | 54 | let new_funcs = Counter::default(); 55 | registry.register( 56 | "lumen_new_funcs", 57 | "Pushes previously unknown functions", 58 | new_funcs.clone(), 59 | ); 60 | 61 | let pushes = Counter::default(); 62 | registry.register("lumen_pushes_total", "Total pushes functions", pushes.clone()); 63 | 64 | let pulls = Counter::default(); 65 | registry.register("lumen_pulls_total", "Total pulled functions", pulls.clone()); 66 | 67 | let queried_funcs = Counter::default(); 68 | registry.register("lumen_queried_total", "Total Queried functions", queried_funcs.clone()); 69 | 70 | Metrics { 71 | registry, 72 | active_connections, 73 | lumina_version, 74 | new_funcs, 75 | pushes, 76 | pulls, 77 | queried_funcs, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /common/src/rpc/de.rs: -------------------------------------------------------------------------------- 1 | use super::Error; 2 | use serde::de::{self, DeserializeSeed, SeqAccess, Visitor}; 3 | use serde::Deserialize; 4 | 5 | struct Deserializer<'de> { 6 | input: &'de [u8], 7 | } 8 | 9 | impl<'de> Deserializer<'de> { 10 | fn from_bytes(b: &'de [u8]) -> Self { 11 | Self { input: b } 12 | } 13 | 14 | fn unpack_dd(&mut self) -> Result { 15 | let (v, len) = super::packing::unpack_dd(self.input); 16 | if len == 0 { 17 | Err(Error::UnexpectedEof) 18 | } else { 19 | self.input = &self.input[len..]; 20 | Ok(v) 21 | } 22 | } 23 | 24 | fn unpack_dq(&mut self) -> Result { 25 | let a = self.unpack_dd()? as u64; 26 | let b = self.unpack_dd()? as u64; 27 | Ok((a << 32) | b) 28 | } 29 | 30 | fn unpack_var_bytes(&mut self) -> Result<&'de [u8], Error> { 31 | let bytes = self.unpack_dd()? as usize; 32 | if bytes > self.input.len() { 33 | return Err(Error::UnexpectedEof); 34 | } 35 | 36 | let payload = &self.input[..bytes]; 37 | self.input = &self.input[bytes..]; 38 | assert_eq!(payload.len(), bytes); 39 | 40 | Ok(payload) 41 | } 42 | 43 | fn unpack_cstr(&mut self) -> Result<&'de str, Error> { 44 | let len = self 45 | .input 46 | .iter() 47 | .enumerate() 48 | .find_map(|(idx, &v)| if v == 0 { Some(idx) } else { None }); 49 | let len = match len { 50 | Some(v) => v, 51 | None => return Err(Error::UnexpectedEof), 52 | }; 53 | let res = match std::str::from_utf8(&self.input[..len]) { 54 | Ok(v) => v, 55 | Err(err) => { 56 | return Err(err.into()); 57 | }, 58 | }; 59 | self.input = &self.input[len + 1..]; 60 | Ok(res) 61 | } 62 | 63 | fn take_byte(&mut self) -> Result { 64 | if self.input.is_empty() { 65 | return Err(Error::UnexpectedEof); 66 | } 67 | 68 | let v = self.input[0]; 69 | self.input = &self.input[1..]; 70 | Ok(v) 71 | } 72 | } 73 | 74 | /// Returns: a tuple containing the deserialized struct and the bytes used 75 | pub fn from_slice<'a, T: Deserialize<'a>>(b: &'a [u8]) -> Result<(T, usize), Error> { 76 | let mut de = Deserializer::from_bytes(b); 77 | let v = T::deserialize(&mut de)?; 78 | Ok((v, b.len() - de.input.len())) 79 | } 80 | 81 | impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { 82 | type Error = Error; 83 | 84 | fn deserialize_any>(self, _: V) -> Result { 85 | unimplemented!() 86 | } 87 | 88 | fn deserialize_u8>(self, visitor: V) -> Result { 89 | visitor.visit_u8(self.take_byte()?) 90 | } 91 | 92 | fn deserialize_u32>(self, visitor: V) -> Result { 93 | visitor.visit_u32(self.unpack_dd()?) 94 | } 95 | 96 | fn deserialize_u64>(self, visitor: V) -> Result { 97 | visitor.visit_u64(self.unpack_dq()?) 98 | } 99 | 100 | fn deserialize_seq>(self, visitor: V) -> Result { 101 | let len = self.unpack_dd()?; 102 | 103 | visitor.visit_seq(Access { len: len as usize, de: &mut *self }) 104 | } 105 | 106 | fn deserialize_str>(self, visitor: V) -> Result { 107 | visitor.visit_borrowed_str(self.unpack_cstr()?) 108 | } 109 | 110 | fn deserialize_bytes>(self, visitor: V) -> Result { 111 | let v = self.unpack_var_bytes()?; 112 | visitor.visit_borrowed_bytes(v) 113 | } 114 | 115 | fn deserialize_tuple>( 116 | self, len: usize, visitor: V, 117 | ) -> Result { 118 | visitor.visit_seq(Access { len, de: &mut *self }) 119 | } 120 | 121 | fn deserialize_tuple_struct>( 122 | self, _name: &'static str, len: usize, visitor: V, 123 | ) -> Result { 124 | self.deserialize_tuple(len, visitor) 125 | } 126 | 127 | fn deserialize_struct>( 128 | self, name: &'static str, fields: &'static [&'static str], visitor: V, 129 | ) -> Result { 130 | self.deserialize_tuple_struct(name, fields.len(), visitor) 131 | } 132 | 133 | serde::forward_to_deserialize_any! { 134 | i8 i16 i32 i64 char u16 bool 135 | f32 f64 string byte_buf option unit unit_struct newtype_struct map enum identifier ignored_any 136 | } 137 | } 138 | 139 | struct Access<'a, 'de> { 140 | de: &'a mut Deserializer<'de>, 141 | len: usize, 142 | } 143 | 144 | impl<'de, 'a> SeqAccess<'a> for Access<'de, 'a> { 145 | type Error = Error; 146 | 147 | fn next_element_seed>( 148 | &mut self, seed: T, 149 | ) -> Result, Self::Error> { 150 | if self.len > 0 { 151 | self.len -= 1; 152 | 153 | let v = serde::de::DeserializeSeed::deserialize(seed, &mut *self.de)?; 154 | Ok(Some(v)) 155 | } else { 156 | Ok(None) 157 | } 158 | } 159 | 160 | fn size_hint(&self) -> Option { 161 | Some(self.len) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /common/src/rpc/messages.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::borrow::Cow; 3 | 4 | #[derive(Deserialize, Serialize)] 5 | pub struct RpcFail<'a> { 6 | pub code: u32, 7 | pub message: &'a str, 8 | } 9 | 10 | #[derive(Serialize, Deserialize)] 11 | pub struct RpcNotify<'a> { 12 | pub code: u32, 13 | pub msg: &'a str, 14 | } 15 | 16 | #[derive(Serialize, Deserialize, Debug)] 17 | pub struct Creds<'a> { 18 | pub username: &'a str, 19 | pub password: &'a str, 20 | } 21 | 22 | #[derive(Serialize, Deserialize)] 23 | pub struct RpcHello<'a> { 24 | pub protocol_version: u32, 25 | pub license_data: &'a [u8], 26 | pub lic_number: [u8; 6], 27 | pub unk2: u32, 28 | } 29 | 30 | #[derive(Debug, Deserialize, Serialize, Clone)] 31 | pub struct PullMetadataFunc<'a> { 32 | pub unk0: u32, 33 | pub mb_hash: &'a [u8], 34 | } 35 | 36 | #[derive(Deserialize, Serialize)] 37 | pub struct PullMetadata<'a> { 38 | pub unk0: u32, 39 | pub unk1: Cow<'a, [u32]>, 40 | 41 | #[serde(borrow)] 42 | pub funcs: Cow<'a, [PullMetadataFunc<'a>]>, 43 | } 44 | 45 | #[derive(Deserialize, Serialize, Clone)] 46 | pub struct PullMetadataResultFunc<'a> { 47 | pub name: Cow<'a, str>, 48 | pub len: u32, 49 | pub mb_data: Cow<'a, [u8]>, 50 | pub popularity: u32, 51 | } 52 | 53 | #[derive(Deserialize, Serialize)] 54 | pub struct PullMetadataResult<'a> { 55 | pub unk0: Cow<'a, [u32]>, 56 | #[serde(borrow)] 57 | pub funcs: Cow<'a, [PullMetadataResultFunc<'a>]>, 58 | } 59 | 60 | #[derive(Clone, Deserialize, Serialize)] 61 | pub struct PushMetadataFunc<'a> { 62 | pub name: &'a str, 63 | pub func_len: u32, 64 | pub func_data: &'a [u8], 65 | 66 | // PullMetadata's fields (tuple 'unk2') are similar to these two 67 | pub unk2: u32, 68 | pub hash: &'a [u8], 69 | } 70 | 71 | #[derive(Deserialize, Serialize)] 72 | pub struct PushMetadata<'a> { 73 | pub unk0: u32, 74 | pub idb_path: &'a str, 75 | pub file_path: &'a str, 76 | pub md5: [u8; 16], 77 | pub hostname: &'a str, 78 | pub funcs: Cow<'a, [PushMetadataFunc<'a>]>, 79 | pub unk1: Cow<'a, [u64]>, 80 | } 81 | 82 | #[derive(Deserialize, Serialize)] 83 | pub struct PushMetadataResult<'a> { 84 | // array of 0=exists, 1=NEW 85 | pub status: Cow<'a, [u32]>, 86 | } 87 | 88 | #[derive(Debug, Deserialize, Serialize)] 89 | pub struct DelHistory<'a> { 90 | pub unk0: u32, // =0x08 91 | pub unk1: Cow<'a, [Cow<'a, str>]>, 92 | pub unk2: Cow<'a, [[u64; 2]]>, 93 | pub unk3: Cow<'a, [[u64; 2]]>, 94 | pub unk4: Cow<'a, [Cow<'a, str>]>, 95 | pub unk5: Cow<'a, [Cow<'a, str>]>, 96 | pub unk6: Cow<'a, [Cow<'a, str>]>, 97 | pub unk7: Cow<'a, [Cow<'a, str>]>, 98 | pub unk8: Cow<'a, [Cow<'a, [u8; 16]>]>, 99 | pub funcs: Cow<'a, [Cow<'a, [u8; 16]>]>, 100 | pub unk10: Cow<'a, [[u64; 2]]>, 101 | pub unk11: u64, 102 | } 103 | 104 | #[derive(Deserialize, Serialize)] 105 | pub struct DelHistoryResult { 106 | pub deleted_mds: u32, 107 | } 108 | 109 | #[derive(Debug, Deserialize, Serialize, Default)] 110 | pub struct LicenseInfo<'a> { 111 | pub id: Cow<'a, str>, 112 | pub name: Cow<'a, str>, 113 | pub email: Cow<'a, str>, 114 | } 115 | 116 | #[derive(Debug, Deserialize, Serialize, Default)] 117 | pub struct HelloResult<'a> { 118 | pub license_info: LicenseInfo<'a>, 119 | pub username: Cow<'a, str>, 120 | pub karma: u32, 121 | pub last_active: u64, 122 | pub features: u32, 123 | } 124 | 125 | #[derive(Debug, Deserialize, Serialize)] 126 | pub struct GetFuncHistories<'a> { 127 | #[serde(borrow)] 128 | pub funcs: Cow<'a, [PullMetadataFunc<'a>]>, 129 | pub unk0: u32, 130 | } 131 | 132 | #[derive(Debug, Deserialize, Serialize, Clone)] 133 | pub struct FunctionHistory<'a> { 134 | pub unk0: u64, 135 | pub unk1: u64, 136 | pub name: Cow<'a, str>, 137 | pub metadata: Cow<'a, [u8]>, 138 | pub timestamp: u64, 139 | pub author_idx: u32, 140 | pub idb_path_idx: u32, 141 | } 142 | 143 | #[derive(Debug, Deserialize, Serialize, Clone)] 144 | pub struct FunctionHistories<'a> { 145 | #[serde(borrow)] 146 | pub log: Cow<'a, [FunctionHistory<'a>]>, 147 | } 148 | 149 | #[derive(Debug, Deserialize, Serialize)] 150 | pub struct GetFuncHistoriesResult<'a> { 151 | pub status: Cow<'a, [u32]>, 152 | #[serde(borrow)] 153 | pub funcs: Cow<'a, [FunctionHistories<'a>]>, 154 | pub users: Cow<'a, [Cow<'a, str>]>, 155 | pub dbs: Cow<'a, [Cow<'a, str>]>, 156 | } 157 | -------------------------------------------------------------------------------- /common/src/rpc/mod.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 3 | pub(crate) mod de; 4 | mod messages; 5 | mod packing; 6 | mod ser; 7 | use serde::Serializer; 8 | 9 | pub use messages::*; 10 | 11 | #[derive(Debug)] 12 | pub enum Error { 13 | UnexpectedEof, 14 | Utf8Error(std::str::Utf8Error), 15 | IOError(std::io::Error), 16 | Serde(String), 17 | InvalidData, 18 | OutOfMemory, 19 | Todo, 20 | Timeout, 21 | Eof, 22 | } 23 | 24 | impl std::fmt::Display for Error { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | std::fmt::Debug::fmt(self, f) 27 | } 28 | } 29 | 30 | impl std::error::Error for Error {} 31 | impl serde::ser::Error for Error { 32 | fn custom(msg: T) -> Self { 33 | Error::Serde(msg.to_string()) 34 | } 35 | } 36 | impl serde::de::Error for Error { 37 | fn custom(msg: T) -> Self { 38 | Error::Serde(msg.to_string()) 39 | } 40 | } 41 | impl From for Error { 42 | fn from(v: std::io::Error) -> Self { 43 | Error::IOError(v) 44 | } 45 | } 46 | impl From for Error { 47 | fn from(v: std::str::Utf8Error) -> Self { 48 | Error::Utf8Error(v) 49 | } 50 | } 51 | impl From for Error { 52 | fn from(v: std::collections::TryReserveError) -> Self { 53 | error!("failed to allocate {} bytes", v); 54 | Error::OutOfMemory 55 | } 56 | } 57 | 58 | fn get_code_maxlen(code: u8) -> usize { 59 | match code { 60 | 0x0e => 50 * 1024 * 1024, // PullMD: 50 MiB 61 | 0x10 => 200 * 1024 * 1024, // PushMD: 200 MiB 62 | _ => 1024 * 50, // otherwise 50K 63 | } 64 | } 65 | 66 | pub async fn read_packet(mut reader: R) -> Result, Error> { 67 | let mut head = [0u8; 5]; 68 | match reader.read_exact(&mut head).await { 69 | Ok(_) => {}, 70 | Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Err(Error::Eof), // client decided to disconnect... 71 | Err(e) => return Err(e.into()), 72 | } 73 | let code = head[4]; 74 | let mut buf_len = [0u8; 4]; 75 | buf_len.copy_from_slice(&head[..4]); 76 | 77 | let buf_len = u32::from_be_bytes(buf_len) as usize; 78 | if buf_len < 4 { 79 | return Err(std::io::Error::new( 80 | std::io::ErrorKind::InvalidData, 81 | "payload size is too small", 82 | ) 83 | .into()); 84 | } 85 | 86 | let max_len = get_code_maxlen(code); 87 | 88 | if buf_len > max_len { 89 | info!("maxium size exceeded: code={}: max={}; req={}", code, max_len, buf_len); 90 | return Err(std::io::Error::new( 91 | std::io::ErrorKind::InvalidData, 92 | "request length exceeded maximum limit", 93 | ) 94 | .into()); 95 | } 96 | 97 | // the additional byte is for the RPC code 98 | trace!("expecting {} bytes...", buf_len); 99 | let buf_len = buf_len + 1; 100 | 101 | let mut data = Vec::new(); 102 | data.try_reserve_exact(buf_len)?; 103 | data.resize(buf_len, 0); 104 | data[0] = code; 105 | reader.read_exact(&mut data[1..]).await?; 106 | 107 | Ok(data) 108 | } 109 | 110 | async fn write_packet(mut w: W, data: &[u8]) -> Result<(), std::io::Error> { 111 | let buf_len: u32 = (data.len() - 1) as u32; 112 | let buf_len = buf_len.to_be_bytes(); 113 | w.write_all(&buf_len).await?; 114 | w.write_all(data).await?; 115 | Ok(()) 116 | } 117 | 118 | pub enum RpcMessage<'a> { 119 | Ok(()), 120 | Fail(RpcFail<'a>), 121 | Notify(RpcNotify<'a>), 122 | Hello(RpcHello<'a>, Option>), 123 | PullMetadata(PullMetadata<'a>), 124 | PullMetadataResult(PullMetadataResult<'a>), 125 | PushMetadata(PushMetadata<'a>), 126 | PushMetadataResult(PushMetadataResult<'a>), 127 | DelHistory(DelHistory<'a>), 128 | DelHistoryResult(DelHistoryResult), 129 | GetFuncHistories(GetFuncHistories<'a>), 130 | GetFuncHistoriesResult(GetFuncHistoriesResult<'a>), 131 | HelloResult(HelloResult<'a>), 132 | } 133 | 134 | impl<'a> serde::Serialize for RpcMessage<'a> { 135 | fn serialize(&self, serializer: S) -> Result { 136 | use serde::ser::SerializeTuple; 137 | 138 | let code = self.get_code(); 139 | let mut tuple = serializer.serialize_tuple(2)?; 140 | 141 | // u8 is pushed without further encoding... 142 | tuple.serialize_element(&code)?; 143 | 144 | match self { 145 | RpcMessage::Ok(msg) => tuple.serialize_element(msg)?, 146 | RpcMessage::Fail(msg) => tuple.serialize_element(msg)?, 147 | RpcMessage::Notify(msg) => tuple.serialize_element(msg)?, 148 | RpcMessage::Hello(msg, _) => tuple.serialize_element(msg)?, 149 | RpcMessage::PullMetadata(msg) => tuple.serialize_element(msg)?, 150 | RpcMessage::PullMetadataResult(msg) => tuple.serialize_element(msg)?, 151 | RpcMessage::PushMetadata(msg) => tuple.serialize_element(msg)?, 152 | RpcMessage::PushMetadataResult(msg) => tuple.serialize_element(msg)?, 153 | RpcMessage::DelHistory(msg) => tuple.serialize_element(msg)?, 154 | RpcMessage::DelHistoryResult(msg) => tuple.serialize_element(msg)?, 155 | RpcMessage::GetFuncHistories(msg) => tuple.serialize_element(msg)?, 156 | RpcMessage::GetFuncHistoriesResult(msg) => tuple.serialize_element(msg)?, 157 | RpcMessage::HelloResult(msg) => tuple.serialize_element(msg)?, 158 | } 159 | 160 | tuple.end() 161 | } 162 | } 163 | 164 | impl<'a> RpcMessage<'a> { 165 | fn deserialize_check>(payload: &'a [u8]) -> Result { 166 | let v = de::from_slice(payload)?; 167 | if v.1 != payload.len() { 168 | let bytes_remaining = crate::make_pretty_hex(&payload[v.1..]); 169 | trace!( 170 | "{} remaining bytes after deserializing {}\n{bytes_remaining}", 171 | payload.len() - v.1, 172 | std::any::type_name::() 173 | ); 174 | } 175 | Ok(v.0) 176 | } 177 | 178 | pub fn deserialize(payload: &'a [u8]) -> Result, Error> { 179 | let msg_type = payload[0]; 180 | let payload = &payload[1..]; 181 | 182 | let res = match msg_type { 183 | 0x0a => { 184 | if !payload.is_empty() { 185 | trace!( 186 | "Ok message with additional data: {} bytes: {payload:02x?}", 187 | payload.len() 188 | ); 189 | } 190 | RpcMessage::Ok(()) 191 | }, 192 | 0x0b => RpcMessage::Fail(Self::deserialize_check(payload)?), 193 | 0x0c => RpcMessage::Notify(Self::deserialize_check(payload)?), 194 | 0x0d => { 195 | let (hello, consumed) = de::from_slice::(payload)?; 196 | let creds = if payload.len() > consumed && hello.protocol_version > 2 { 197 | let payload = &payload[consumed..]; 198 | let (creds, consumed) = de::from_slice::(payload)?; 199 | if payload.len() != consumed { 200 | trace!("bytes remaining after HelloV2: {payload:02x?}"); 201 | } 202 | Some(creds) 203 | } else { 204 | if hello.protocol_version > 2 || payload.len() != consumed { 205 | trace!("Unexpected Hello msg: {payload:02x?}"); 206 | } 207 | None 208 | }; 209 | RpcMessage::Hello(hello, creds) 210 | }, 211 | 0x0e => RpcMessage::PullMetadata(Self::deserialize_check(payload)?), 212 | 0x0f => RpcMessage::PullMetadataResult(Self::deserialize_check(payload)?), 213 | 0x10 => RpcMessage::PushMetadata(Self::deserialize_check(payload)?), 214 | 0x11 => RpcMessage::PushMetadataResult(Self::deserialize_check(payload)?), 215 | 0x18 => RpcMessage::DelHistory(Self::deserialize_check(payload)?), 216 | 0x19 => RpcMessage::DelHistoryResult(Self::deserialize_check(payload)?), 217 | 0x2f => RpcMessage::GetFuncHistories(Self::deserialize_check(payload)?), 218 | 0x30 => RpcMessage::GetFuncHistoriesResult(Self::deserialize_check(payload)?), 219 | 0x31 => RpcMessage::HelloResult(Self::deserialize_check(payload)?), 220 | _ => { 221 | trace!("got invalid message type '{:02x}'", msg_type); 222 | return Err(Error::InvalidData); 223 | }, 224 | }; 225 | 226 | Ok(res) 227 | } 228 | 229 | pub async fn async_write(&self, w: W) -> Result<(), Error> { 230 | let mut output = Vec::with_capacity(32); 231 | ser::to_writer(self, &mut output)?; 232 | 233 | write_packet(w, &output).await?; 234 | 235 | Ok(()) 236 | } 237 | 238 | fn get_code(&self) -> u8 { 239 | use RpcMessage::*; 240 | 241 | match self { 242 | Ok(_) => 0x0a, 243 | Fail(_) => 0x0b, 244 | Notify(_) => 0x0c, 245 | Hello(..) => 0x0d, 246 | PullMetadata(_) => 0x0e, 247 | PullMetadataResult(_) => 0x0f, 248 | PushMetadata(_) => 0x10, 249 | PushMetadataResult(_) => 0x11, 250 | DelHistory(_) => 0x18, 251 | DelHistoryResult(_) => 0x19, 252 | GetFuncHistories(_) => 0x2f, 253 | GetFuncHistoriesResult(_) => 0x30, 254 | HelloResult(_) => 0x31, 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /common/src/rpc/packing.rs: -------------------------------------------------------------------------------- 1 | /// packs a dd into `buf` returning amout of bytes written. 2 | /// Returns 0 if buffer is too small 3 | pub fn pack_dd(v: u32, buf: &mut [u8]) -> usize { 4 | let bytes = v.to_le_bytes(); 5 | match v { 6 | 0..=0x7f => { 7 | // 0..0XXXXXXX (7 bits) 8 | if buf.is_empty() { 9 | return 0; 10 | } 11 | buf[0] = bytes[0]; 12 | 1 13 | }, 14 | 0x80..=0x3fff => { 15 | // 10AAAAAA..BBBBBBBB (14 bits) 16 | if buf.len() < 2 { 17 | return 0; 18 | } 19 | buf[0] = 0x80 | bytes[1]; 20 | buf[1] = bytes[0]; 21 | 2 22 | }, 23 | 0x4000..=0x1fffff => { 24 | // 11000000_AAAAAAAA_BBBBBBBB_CCCCCCCC (24 bits) 25 | if buf.len() < 3 { 26 | return 0; 27 | } 28 | buf[0] = 0xc0; 29 | buf[1] = bytes[2]; 30 | buf[2] = bytes[1]; 31 | buf[3] = bytes[0]; 32 | 4 33 | }, 34 | 0x200000..=u32::MAX => { 35 | // 11111111_AAAAAAAA_BBBBBBBB_CCCCCCCC_DDDDDDDD (32 bits) 36 | if buf.len() < 5 { 37 | return 0; 38 | } 39 | buf[0] = 0xff; 40 | buf[1] = bytes[3]; 41 | buf[2] = bytes[2]; 42 | buf[3] = bytes[1]; 43 | buf[4] = bytes[0]; 44 | 5 45 | }, 46 | } 47 | } 48 | 49 | /// unpacks a dd from `buf`, returning the amount (value, byte consumed) 50 | pub fn unpack_dd(buf: &[u8]) -> (u32, usize) { 51 | if buf.is_empty() { 52 | return (0, 0); 53 | } 54 | 55 | let msb = buf[0]; 56 | let mut val = [0u8; 4]; 57 | 58 | if msb & 0x80 == 0 { 59 | // 0...... 60 | val[0] = msb; 61 | return (u32::from_le_bytes(val), 1); 62 | } 63 | if msb & 0x40 == 0 { 64 | // 10....../0x80 65 | if buf.len() < 2 { 66 | return (0, 0); 67 | } 68 | val[1] = msb & 0x3f; 69 | val[0] = buf[1]; 70 | return (u32::from_le_bytes(val), 2); 71 | } 72 | if msb & 0x20 == 0 { 73 | // 110...../0xC0 74 | if buf.len() < 4 { 75 | return (0, 0); 76 | } 77 | val[3] = msb & 0x1f; 78 | val[2] = buf[1]; 79 | val[1] = buf[2]; 80 | val[0] = buf[3]; 81 | return (u32::from_le_bytes(val), 4); 82 | } 83 | 84 | if buf.len() < 5 { 85 | return (0, 0); 86 | } 87 | 88 | val[3] = buf[1]; 89 | val[2] = buf[2]; 90 | val[1] = buf[3]; 91 | val[0] = buf[4]; 92 | 93 | (u32::from_le_bytes(val), 5) 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | #[test] 99 | #[ignore = "this is a very time consuming test, it should be run in release/profiling mode"] 100 | fn pack_all_nums() { 101 | for num in 0..=u32::MAX { 102 | let mut buf = [0u8; 5]; 103 | let rlen = super::pack_dd(num, &mut buf); 104 | assert!(rlen > 0); 105 | 106 | let unpacked = super::unpack_dd(&buf[..rlen]); 107 | assert_eq!(unpacked.1, rlen, "bad unpack size"); 108 | assert_eq!(unpacked.0, num, "values don't match"); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /common/src/rpc/ser.rs: -------------------------------------------------------------------------------- 1 | use super::Error; 2 | use serde::{ser, ser::Impossible, Serialize}; 3 | use std::io::Write; 4 | 5 | struct Serializer { 6 | output: W, 7 | } 8 | 9 | pub fn to_writer(v: &T, w: W) -> Result<(), Error> { 10 | let mut serializer = Serializer { output: w }; 11 | v.serialize(&mut serializer)?; 12 | Ok(()) 13 | } 14 | 15 | #[allow(dead_code)] 16 | pub fn to_vec(v: &T) -> Result, Error> { 17 | let mut buf = vec![]; 18 | to_writer(v, &mut buf)?; 19 | Ok(buf) 20 | } 21 | 22 | impl Serializer { 23 | fn pack_dd(&mut self, num: u32) -> Result<(), Error> { 24 | let mut buf = [0u8; 5]; 25 | let bytes = super::packing::pack_dd(num, &mut buf); 26 | self.output.write_all(&buf[..bytes])?; 27 | Ok(()) 28 | } 29 | 30 | fn pack_str(&mut self, s: &str) -> Result<(), Error> { 31 | self.output.write_all(s.as_bytes())?; 32 | self.output.write_all(&[0])?; 33 | Ok(()) 34 | } 35 | 36 | fn pack_bytes(&mut self, b: &[u8]) -> Result<(), Error> { 37 | self.pack_dd(b.len() as u32)?; 38 | self.output.write_all(b)?; 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl<'a, W: Write> ser::Serializer for &'a mut Serializer { 44 | type Ok = (); 45 | type Error = Error; 46 | 47 | type SerializeSeq = Self; 48 | type SerializeTuple = Self; 49 | type SerializeTupleStruct = Impossible<(), Self::Error>; 50 | type SerializeTupleVariant = Impossible<(), Self::Error>; 51 | type SerializeMap = Impossible<(), Self::Error>; 52 | type SerializeStruct = Self; 53 | type SerializeStructVariant = Impossible<(), Self::Error>; 54 | 55 | fn serialize_bool(self, _v: bool) -> Result { 56 | unreachable!() 57 | } 58 | 59 | fn serialize_i8(self, _v: i8) -> Result { 60 | unreachable!() 61 | } 62 | 63 | fn serialize_i16(self, _v: i16) -> Result { 64 | unreachable!() 65 | } 66 | 67 | fn serialize_i32(self, _v: i32) -> Result { 68 | unreachable!() 69 | } 70 | 71 | fn serialize_i64(self, _v: i64) -> Result { 72 | unreachable!() 73 | } 74 | 75 | fn serialize_u8(self, v: u8) -> Result { 76 | self.output.write_all(&[v])?; 77 | Ok(()) 78 | } 79 | 80 | fn serialize_u16(self, _v: u16) -> Result { 81 | unreachable!() 82 | } 83 | 84 | fn serialize_u32(self, v: u32) -> Result { 85 | self.pack_dd(v) 86 | } 87 | 88 | fn serialize_u64(self, v: u64) -> Result { 89 | let high = (v >> 32) & 0xffffffff; 90 | let low = v & 0xffffffff; 91 | self.pack_dd(high as u32)?; 92 | self.pack_dd(low as u32)?; 93 | Ok(()) 94 | } 95 | 96 | fn serialize_f32(self, _v: f32) -> Result { 97 | unreachable!() 98 | } 99 | 100 | fn serialize_f64(self, _v: f64) -> Result { 101 | unreachable!() 102 | } 103 | 104 | fn serialize_char(self, _v: char) -> Result { 105 | unreachable!() 106 | } 107 | 108 | fn serialize_str(self, v: &str) -> Result { 109 | self.pack_str(v) 110 | } 111 | 112 | fn serialize_bytes(self, v: &[u8]) -> Result { 113 | self.pack_bytes(v) 114 | } 115 | 116 | fn serialize_none(self) -> Result { 117 | unreachable!() 118 | } 119 | 120 | fn serialize_some(self, _value: &T) -> Result { 121 | unreachable!() 122 | } 123 | 124 | fn serialize_unit(self) -> Result { 125 | // unit contains no information... 126 | Ok(()) 127 | } 128 | 129 | fn serialize_unit_struct(self, _name: &'static str) -> Result { 130 | unreachable!() 131 | } 132 | 133 | fn serialize_unit_variant( 134 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, 135 | ) -> Result { 136 | unreachable!() 137 | } 138 | 139 | fn serialize_newtype_struct( 140 | self, _name: &'static str, _value: &T, 141 | ) -> Result { 142 | unreachable!() 143 | } 144 | 145 | fn serialize_newtype_variant( 146 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, _value: &T, 147 | ) -> Result { 148 | unreachable!() 149 | } 150 | 151 | fn serialize_seq(self, len: Option) -> Result { 152 | let len = len.unwrap(); 153 | self.pack_dd(len as u32)?; 154 | Ok(self) 155 | } 156 | 157 | fn serialize_tuple(self, _len: usize) -> Result { 158 | Ok(self) 159 | } 160 | 161 | fn serialize_tuple_struct( 162 | self, _name: &'static str, _len: usize, 163 | ) -> Result { 164 | unreachable!(); 165 | } 166 | 167 | fn serialize_tuple_variant( 168 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, 169 | ) -> Result { 170 | unreachable!() 171 | } 172 | 173 | fn serialize_map(self, _len: Option) -> Result { 174 | unreachable!() 175 | } 176 | 177 | fn serialize_struct( 178 | self, _name: &'static str, _len: usize, 179 | ) -> Result { 180 | // structs will simply be flattened 181 | Ok(self) 182 | } 183 | 184 | fn serialize_struct_variant( 185 | self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, 186 | ) -> Result { 187 | unreachable!() 188 | } 189 | } 190 | 191 | impl<'a, W: Write> ser::SerializeSeq for &'a mut Serializer { 192 | type Ok = (); 193 | type Error = Error; 194 | 195 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { 196 | value.serialize(&mut **self) 197 | } 198 | 199 | fn end(self) -> Result { 200 | Ok(()) 201 | } 202 | } 203 | 204 | impl<'a, W: Write> ser::SerializeTuple for &'a mut Serializer { 205 | type Ok = (); 206 | type Error = Error; 207 | 208 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { 209 | value.serialize(&mut **self) 210 | } 211 | 212 | fn end(self) -> Result { 213 | Ok(()) 214 | } 215 | } 216 | 217 | impl<'a, W: Write> ser::SerializeStruct for &'a mut Serializer { 218 | type Ok = (); 219 | type Error = Error; 220 | 221 | fn serialize_field( 222 | &mut self, _key: &'static str, value: &T, 223 | ) -> Result<(), Self::Error> { 224 | // struct names have no meaning 225 | value.serialize(&mut **self) 226 | } 227 | 228 | fn end(self) -> Result { 229 | Ok(()) 230 | } 231 | } 232 | 233 | #[cfg(test)] 234 | mod tests { 235 | #[test] 236 | fn ser_hello() { 237 | #[derive(serde::Serialize)] 238 | struct Test<'a> { 239 | arr: [u8; 16], 240 | s: &'a str, 241 | b: &'a [u8], 242 | i: u32, 243 | q: u64, 244 | } 245 | let v = Test { 246 | arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 247 | s: "somestring", 248 | b: b"bytes", 249 | i: 0x20, 250 | q: 0x20, 251 | }; 252 | let v = super::to_vec(&v).expect("failed to serialize dummy"); 253 | assert_eq!(v, b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10somestring\x00\x05bytes\x20\x00\x20"); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /common/src/web.rs: -------------------------------------------------------------------------------- 1 | use crate::SharedState; 2 | use warp::Filter; 3 | 4 | pub mod api; 5 | 6 | pub fn with_state( 7 | state: SharedState, 8 | ) -> impl Filter + Clone { 9 | warp::any().map(move || state.clone()) 10 | } 11 | -------------------------------------------------------------------------------- /common/src/web/api.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | use serde::Serialize; 3 | use std::borrow::Cow; 4 | use warp::{Filter, Rejection, Reply}; 5 | 6 | use super::SharedState; 7 | 8 | struct Md5([u8; 16]); 9 | impl std::str::FromStr for Md5 { 10 | type Err = &'static str; 11 | fn from_str(s: &str) -> Result { 12 | let mut res = [0u8; 16]; 13 | let s = s.trim(); 14 | if s.len() != 32 { 15 | return Err("bad md5 length"); 16 | } 17 | binascii::hex2bin(s.as_bytes(), &mut res).map_err(|_| "bad md5")?; 18 | Ok(Md5(res)) 19 | } 20 | } 21 | 22 | #[derive(Serialize)] 23 | struct Error<'a> { 24 | error: &'a str, 25 | } 26 | 27 | impl std::fmt::Display for Md5 { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | let mut out = [0u8; 32]; 30 | binascii::bin2hex(&self.0, &mut out).unwrap(); 31 | let out = std::str::from_utf8(&out).unwrap(); 32 | write!(f, "{}", &out) 33 | } 34 | } 35 | 36 | impl Serialize for Md5 { 37 | fn serialize(&self, serializer: S) -> Result { 38 | serializer.serialize_str(&format!("{}", &self)) 39 | } 40 | } 41 | 42 | pub fn api_root( 43 | state: SharedState, 44 | ) -> impl Filter + Clone { 45 | let view_file = warp::get() 46 | .and(warp::path("files")) 47 | .and(super::with_state(state.clone())) 48 | .and(warp::filters::path::param::()) 49 | .and_then(view_file_by_hash); 50 | let view_func = warp::get() 51 | .and(warp::path("funcs")) 52 | .and(super::with_state(state)) 53 | .and(warp::filters::path::param::()) 54 | .and_then(view_func_by_hash); 55 | 56 | view_file.or(view_func) 57 | } 58 | 59 | // GET server/api/files/:md5 60 | async fn view_file_by_hash(state: SharedState, md5: Md5) -> Result { 61 | #[derive(Serialize)] 62 | struct FileFunc { 63 | hash: Md5, 64 | len: u32, 65 | name: String, 66 | } 67 | 68 | let v = match state.db.get_file_funcs(&md5.0[..], 0, 10_000).await { 69 | Ok(v) => v, 70 | Err(err) => { 71 | error!("failed to get file's funcs {}: {}", &md5, err); 72 | return Ok(warp::reply::json(&Error { error: "internal server error" })); 73 | }, 74 | }; 75 | let v: Vec<_> = v 76 | .into_iter() 77 | .map(|v| { 78 | let mut hash = [0u8; 16]; 79 | hash.copy_from_slice(&v.2); 80 | FileFunc { name: v.0, len: v.1 as u32, hash: Md5(hash) } 81 | }) 82 | .collect(); 83 | 84 | Result::<_, Rejection>::Ok(warp::reply::json(&v)) 85 | } 86 | 87 | // GET server/api/funcs/:md5 88 | async fn view_func_by_hash(state: SharedState, md5: Md5) -> Result { 89 | #[derive(Serialize)] 90 | enum CommentType { 91 | Posterior, 92 | Anterior, 93 | Function { repeatable: bool }, 94 | Byte { repeatable: bool }, 95 | } 96 | 97 | #[derive(Serialize)] 98 | struct Comment<'a> { 99 | #[serde(skip_serializing_if = "Option::is_none")] 100 | offset: Option, 101 | #[serde(rename = "type")] 102 | type_: CommentType, 103 | comment: Cow<'a, str>, 104 | } 105 | 106 | #[derive(Serialize)] 107 | struct FuncInfo<'a> { 108 | name: &'a str, 109 | comments: Vec>, 110 | length: u32, 111 | in_files: &'a [Md5], 112 | } 113 | 114 | let funcs = [crate::rpc::PullMetadataFunc { unk0: 1, mb_hash: &md5.0 }]; 115 | 116 | let files_with = state.db.get_files_with_func(&md5.0[..]); 117 | let files_info = state.db.get_funcs(&funcs); 118 | 119 | let (files_with, files_info) = match futures_util::try_join!(files_with, files_info) { 120 | Ok(v) => v, 121 | Err(err) => { 122 | error!("failed to execute db queries: {}", err); 123 | return Ok(warp::reply::json(&Error { error: "internal server error" })); 124 | }, 125 | }; 126 | 127 | let files_with: Vec = files_with 128 | .into_iter() 129 | .map(|v| { 130 | let mut md5 = [0u8; 16]; 131 | md5.copy_from_slice(&v); 132 | Md5(md5) 133 | }) 134 | .collect(); 135 | 136 | let v = files_info; 137 | let v: Vec = v 138 | .iter() 139 | .take(1) 140 | .filter_map(|v| v.as_ref()) 141 | .filter_map(|v| { 142 | let md = match crate::md::parse_metadata(&v.data) { 143 | Ok(v) => v, 144 | Err(e) => { 145 | error!("error parsing metadata for {}: {}", &md5, e); 146 | return None; 147 | }, 148 | }; 149 | let comments: Vec = md 150 | .into_iter() 151 | .filter_map(|md| match md { 152 | crate::md::FunctionMetadata::ByteComment(c) => Some(vec![Comment { 153 | offset: Some(c.offset), 154 | type_: CommentType::Byte { repeatable: c.is_repeatable }, 155 | comment: c.comment.into(), 156 | }]), 157 | crate::md::FunctionMetadata::FunctionComment(c) => Some(vec![Comment { 158 | offset: None, 159 | type_: CommentType::Function { repeatable: c.is_repeatable }, 160 | comment: c.comment.into(), 161 | }]), 162 | crate::md::FunctionMetadata::ExtraComment(c) => { 163 | let mut res = vec![]; 164 | if !c.anterior.is_empty() { 165 | res.push(Comment { 166 | offset: Some(c.offset), 167 | type_: CommentType::Anterior, 168 | comment: c.anterior.into(), 169 | }); 170 | } 171 | if !c.posterior.is_empty() { 172 | res.push(Comment { 173 | offset: Some(c.offset), 174 | type_: CommentType::Posterior, 175 | comment: c.posterior.into(), 176 | }); 177 | } 178 | if !res.is_empty() { 179 | Some(res) 180 | } else { 181 | None 182 | } 183 | }, 184 | }) 185 | .flatten() 186 | .collect(); 187 | Some(FuncInfo { name: &v.name, length: v.len, comments, in_files: &files_with }) 188 | }) 189 | .collect(); 190 | 191 | Result::<_, Rejection>::Ok(warp::reply::json(&v)) 192 | } 193 | -------------------------------------------------------------------------------- /config-example.toml: -------------------------------------------------------------------------------- 1 | [lumina] 2 | # address that lumen will listen on for IDA to connect to 3 | bind_addr = "0.0.0.0:1234" 4 | # indicates if TLS should be used for connections, if true the `lumina.tls` section is required. 5 | use_tls = false 6 | # server display name; appears in IDA output window 7 | server_name = "lumen" 8 | 9 | # Allow clients to delete metadata from the database? 10 | allow_deletes = false 11 | # How many function histories should we return? 0=Disabled. 12 | get_history_limit = 50 13 | 14 | # only required when `use_tls` is set to true. 15 | [lumina.tls] 16 | # Specify the server's certificate. 17 | # Clients connecting to the server must match this certificate. 18 | # If the certificate is password protected, the password can be specified in the `PKCSPASSWD` environment variable. 19 | server_cert = "path/to/server_crt" 20 | 21 | [database] 22 | # Specifies a postgresql connection string. All variables can be found here: https://docs.rs/tokio-postgres/0.6.0/tokio_postgres/config/struct.Config.html 23 | connection_info = "postgres://postgres:1@127.0.0.1/postgres" 24 | # Sets if the database connection should be made using TLS. 25 | use_tls = false 26 | # If the database requires a secure connection, paths to server-ca and client-id certificates can be set here: 27 | server_ca = "db_ca.pem" 28 | client_id = "db_id.p12" 29 | 30 | # comment out this section to disable api server 31 | # api server allows to query the database for comments by file or function hash. 32 | [api_server] 33 | bind_addr = "0.0.0.0:8082" 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: lumen 2 | volumes: 3 | postgres_data: 4 | 5 | services: 6 | db: 7 | image: postgres:15.1-alpine 8 | container_name: lumina-postgres 9 | healthcheck: 10 | test: ["CMD", "pg_isready", "-U", "lumina"] 11 | interval: 5s 12 | retries: 10 13 | timeout: 5s 14 | environment: 15 | POSTGRES_USER: lumina 16 | POSTGRES_DB: lumina 17 | POSTGRES_PASSWORD: 1 18 | expose: 19 | - "5432" 20 | volumes: 21 | - postgres_data:/var/lib/postgresql 22 | mem_swappiness: 0 23 | 24 | lumina: 25 | build: . 26 | image: ghcr.io/naim94a/lumen:master 27 | depends_on: 28 | db: 29 | condition: service_healthy 30 | ports: 31 | - 1234:1234 32 | - 8082:8082 33 | environment: 34 | PKCSPASSWD: $PKCSPASSWD 35 | DATABASE_URL: postgres://lumina:1@db/lumina 36 | volumes: 37 | - ./dockershare:/dockershare 38 | links: 39 | - db 40 | -------------------------------------------------------------------------------- /docker-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CFGPATH="/dockershare" 3 | KEYPATH="/lumen/lumen.p12" 4 | die(){ 5 | echo "Exiting due to error: $@" && exit 1 6 | } 7 | do_config_fixup(){ 8 | sed -i -e "s,connection_info.*,connection_info = \"${DATABASE_URL}\"," \ 9 | /lumen/config.toml 10 | } 11 | use_default_config(){ 12 | echo "No custom config.toml found, creating secure default." 13 | tee /lumen/config.toml <<- EOF > /dev/null 14 | [lumina] 15 | bind_addr = "0.0.0.0:1234" 16 | use_tls = true 17 | server_name = "lumen" 18 | [lumina.tls] 19 | server_cert = "${KEYPATH}" 20 | [database] 21 | connection_info = "host=db port=5432 user=lumina password=1" 22 | use_tls = false 23 | [api_server] 24 | bind_addr = "0.0.0.0:8082" 25 | EOF 26 | } 27 | use_default_key(){ 28 | openssl req -x509 -newkey rsa:4096 -keyout /lumen/lumen_key.pem -out /lumen/lumen_crt.pem -days 365 -nodes \ 29 | --subj "/C=US/ST=Texas/L=Austin/O=Lumina/OU=Naimd/CN=lumen" -passout "pass:" -extensions v3_req || die "Generating key" 30 | openssl pkcs12 -export -out /lumen/lumen.p12 -inkey /lumen/lumen_key.pem -in /lumen/lumen_crt.pem \ 31 | -passin "pass:" -passout "pass:" || die "Exporting key" 32 | openssl x509 -in /lumen/lumen_crt.pem -out $CFGPATH/hexrays.crt -passin "pass:" || die "Exporting hexrays.crt" 33 | echo "hexrays.crt added to mounted volume. Copy this to your IDA install dir." ; 34 | sed -i -e "s,server_cert.*,server_cert = \"${KEYPATH}\"," /lumen/config.toml ; 35 | } 36 | setup_tls_key(){ 37 | PRIVKEY=$(find $CFGPATH -type f \( -name '*.p12' -o -name '*.pfx' \) | head -1) 38 | PASSIN="-passin pass:$PKCSPASSWD" 39 | if [ ! -z "${PRIVKEY}" ] ; then 40 | KEYNAME=$(basename "${PRIVKEY}") 41 | KEYPATH="/lumen/${KEYNAME}" 42 | echo "Starting lumen with custom TLS certificate ${KEYNAME}" ; 43 | cp "${PRIVKEY}" $KEYPATH ; 44 | openssl pkcs12 -in $KEYPATH ${PASSIN} -clcerts -nokeys -out $CFGPATH/hexrays.crt || die "Exporting hexrays.crt from private key. If there's a password, add it in .env as PKCSPASSWD=..."; 45 | echo "hexrays.crt added to mounted volume. Copy this to your IDA install dir." ; 46 | sed -i -e "s,server_cert.*,server_cert = \"${KEYPATH}\"," /lumen/config.toml 47 | else 48 | echo "No custom TLS key with p12/pfx extension in the custom mount directory." ; 49 | use_default_key ; 50 | fi ; 51 | } 52 | setup_config(){ 53 | if [ -e $CFGPATH/config.toml ] ; then 54 | echo "Detected custom config.toml" 55 | cp $CFGPATH/config.toml /lumen/config.toml ; 56 | if grep use_tls /lumen/config.toml | head -1 | grep -q false ; then 57 | echo "Starting lumen without TLS. Make sure to set LUMINA_TLS = NO in ida.cfg" ; 58 | else 59 | setup_tls_key ; 60 | fi ; 61 | else 62 | use_default_config ; 63 | setup_tls_key ; 64 | fi 65 | } 66 | 67 | setup_config ; 68 | do_config_fixup ; 69 | echo Running DB migrations... 70 | diesel --config-file /usr/lib/lumen/diesel.toml migration run 71 | echo Migrations done. 72 | exec lumen -c /lumen/config.toml 73 | -------------------------------------------------------------------------------- /lumen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lumen" 3 | description = "lumen server" 4 | version = "0.2.0" 5 | authors = ["Naim A. "] 6 | edition = "2021" 7 | publish = false 8 | 9 | [dependencies] 10 | common = { path = "../common" } 11 | tokio = { version = "1.39", features = ["full"] } 12 | log = { version = "0.4", features = ["release_max_level_debug"] } 13 | pretty_env_logger = "0.5" 14 | clap = "4.5" 15 | tokio-native-tls = "0.3" 16 | native-tls = { version = "0.2" } 17 | warp = "0.3" 18 | prometheus-client = "0.22" 19 | -------------------------------------------------------------------------------- /lumen/src/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lumen 5 | 6 | 7 |

Lumen

8 | Lumen is an open source Lumina server for IDA Pro. 9 | 10 | 11 | -------------------------------------------------------------------------------- /lumen/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Naim A. 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn(unused_crate_dependencies)] 5 | #![deny(clippy::all)] 6 | 7 | use clap::Arg; 8 | use log::*; 9 | use server::do_lumen; 10 | use std::sync::Arc; 11 | 12 | mod server; 13 | mod web; 14 | 15 | use common::config; 16 | 17 | fn setup_logger() { 18 | if std::env::var("RUST_LOG").is_err() { 19 | std::env::set_var("RUST_LOG", concat!(env!("CARGO_PKG_NAME"), "=info")); 20 | } 21 | pretty_env_logger::init_timed(); 22 | } 23 | 24 | #[tokio::main] 25 | async fn main() { 26 | setup_logger(); 27 | let matches = clap::Command::new("lumen") 28 | .version(env!("CARGO_PKG_VERSION")) 29 | .about("lumen is a private Lumina server for IDA.\nVisit https://github.com/naim94a/lumen/ for updates.") 30 | .author("Naim A. ") 31 | .arg( 32 | Arg::new("config") 33 | .short('c') 34 | .default_value("config.toml") 35 | .help("Configuration file path") 36 | ) 37 | .get_matches(); 38 | 39 | let config = { 40 | config::load_config( 41 | std::fs::File::open(matches.get_one::("config").unwrap()) 42 | .expect("failed to read config"), 43 | ) 44 | }; 45 | let config = Arc::new(config); 46 | 47 | do_lumen(config).await; 48 | } 49 | -------------------------------------------------------------------------------- /lumen/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, collections::HashMap, mem::discriminant, process::exit, sync::Arc, time::Instant, 3 | }; 4 | 5 | use common::{ 6 | async_drop::AsyncDropper, 7 | config::Config, 8 | db::Database, 9 | make_pretty_hex, md, 10 | metrics::LuminaVersion, 11 | rpc::{self, Error, HelloResult, RpcFail, RpcHello, RpcMessage}, 12 | SharedState, SharedState_, 13 | }; 14 | use log::{debug, error, info, trace, warn}; 15 | use native_tls::Identity; 16 | use tokio::{ 17 | io::{AsyncRead, AsyncWrite}, 18 | net::TcpListener, 19 | time::timeout, 20 | }; 21 | 22 | use crate::web; 23 | 24 | async fn handle_transaction<'a, S: AsyncRead + AsyncWrite + Unpin>( 25 | state: &SharedState, user: &'a RpcHello<'a>, mut stream: S, 26 | ) -> Result<(), Error> { 27 | let db = &state.db; 28 | let server_name = state.server_name.as_str(); 29 | 30 | trace!("waiting for command.."); 31 | let req = 32 | match timeout(state.config.limits.command_timeout, rpc::read_packet(&mut stream)).await { 33 | Ok(res) => match res { 34 | Ok(v) => v, 35 | Err(e) => return Err(e), 36 | }, 37 | Err(_) => { 38 | _ = RpcMessage::Fail(RpcFail { 39 | code: 0, 40 | message: &format!("{server_name} client idle for too long.\n"), 41 | }) 42 | .async_write(&mut stream) 43 | .await; 44 | return Err(Error::Timeout); 45 | }, 46 | }; 47 | trace!("got command!"); 48 | let req = match RpcMessage::deserialize(&req) { 49 | Ok(v) => v, 50 | Err(err) => { 51 | warn!("bad message: \n{}\n", make_pretty_hex(&req)); 52 | error!("failed to process rpc message: {}", err); 53 | let resp = rpc::RpcFail { 54 | code: 0, 55 | message: &format!("{server_name}: error: invalid data.\n"), 56 | }; 57 | let resp = RpcMessage::Fail(resp); 58 | resp.async_write(&mut stream).await?; 59 | 60 | return Ok(()); 61 | }, 62 | }; 63 | match req { 64 | RpcMessage::PullMetadata(md) => { 65 | let start = Instant::now(); 66 | let funcs = 67 | match timeout(state.config.limits.pull_md_timeout, db.get_funcs(&md.funcs)).await { 68 | Ok(r) => match r { 69 | Ok(v) => v, 70 | Err(e) => { 71 | error!("pull failed, db: {}", e); 72 | rpc::RpcMessage::Fail(rpc::RpcFail { 73 | code: 0, 74 | message: &format!( 75 | "{server_name}: db error; please try again later..\n" 76 | ), 77 | }) 78 | .async_write(&mut stream) 79 | .await?; 80 | return Ok(()); 81 | }, 82 | }, 83 | Err(_) => { 84 | RpcMessage::Fail(RpcFail { 85 | code: 0, 86 | message: &format!("{server_name}: query took too long to execute.\n"), 87 | }) 88 | .async_write(&mut stream) 89 | .await?; 90 | debug!("pull query timeout"); 91 | return Err(Error::Timeout); 92 | }, 93 | }; 94 | let pulled_funcs = funcs.iter().filter(|v| v.is_some()).count(); 95 | state.metrics.pulls.inc_by(pulled_funcs as _); 96 | state.metrics.queried_funcs.inc_by(md.funcs.len() as _); 97 | debug!( 98 | "pull {pulled_funcs}/{} funcs ended after {:?}", 99 | md.funcs.len(), 100 | start.elapsed() 101 | ); 102 | 103 | let statuses: Vec = funcs.iter().map(|v| u32::from(v.is_none())).collect(); 104 | let found = funcs 105 | .into_iter() 106 | .flatten() 107 | .map(|v| rpc::PullMetadataResultFunc { 108 | popularity: v.popularity, 109 | len: v.len, 110 | name: Cow::Owned(v.name), 111 | mb_data: Cow::Owned(v.data), 112 | }) 113 | .collect(); 114 | 115 | RpcMessage::PullMetadataResult(rpc::PullMetadataResult { 116 | unk0: Cow::Owned(statuses), 117 | funcs: Cow::Owned(found), 118 | }) 119 | .async_write(&mut stream) 120 | .await?; 121 | }, 122 | RpcMessage::PushMetadata(mds) => { 123 | // parse the function's metadata 124 | let start = Instant::now(); 125 | let scores: Vec = mds.funcs.iter().map(md::get_score).collect(); 126 | 127 | let status = match db.push_funcs(user, &mds, &scores).await { 128 | Ok(v) => v.into_iter().map(u32::from).collect::>(), 129 | Err(err) => { 130 | log::error!("push failed, db: {}", err); 131 | rpc::RpcMessage::Fail(rpc::RpcFail { 132 | code: 0, 133 | message: &format!("{server_name}: db error; please try again later.\n"), 134 | }) 135 | .async_write(&mut stream) 136 | .await?; 137 | return Ok(()); 138 | }, 139 | }; 140 | state.metrics.pushes.inc_by(status.len() as _); 141 | let new_funcs = 142 | status.iter().fold(0u64, |counter, &v| if v > 0 { counter + 1 } else { counter }); 143 | state.metrics.new_funcs.inc_by(new_funcs); 144 | debug!( 145 | "push {} funcs ended after {:?} ({new_funcs} new)", 146 | status.len(), 147 | start.elapsed() 148 | ); 149 | 150 | RpcMessage::PushMetadataResult(rpc::PushMetadataResult { status: Cow::Owned(status) }) 151 | .async_write(&mut stream) 152 | .await?; 153 | }, 154 | RpcMessage::DelHistory(req) => { 155 | let is_delete_allowed = state.config.lumina.allow_deletes.unwrap_or(false); 156 | if !is_delete_allowed { 157 | RpcMessage::Fail(rpc::RpcFail { 158 | code: 2, 159 | message: &format!("{server_name}: Delete command is disabled on this server."), 160 | }) 161 | .async_write(&mut stream) 162 | .await?; 163 | } else { 164 | if let Err(err) = db.delete_metadata(&req).await { 165 | error!("delete failed. db: {err}"); 166 | RpcMessage::Fail(rpc::RpcFail { 167 | code: 3, 168 | message: &format!("{server_name}: db error, please try again later."), 169 | }) 170 | .async_write(&mut stream) 171 | .await?; 172 | return Ok(()); 173 | } 174 | RpcMessage::DelHistoryResult(rpc::DelHistoryResult { 175 | deleted_mds: req.funcs.len() as u32, 176 | }) 177 | .async_write(&mut stream) 178 | .await?; 179 | } 180 | }, 181 | RpcMessage::GetFuncHistories(req) => { 182 | let limit = state.config.lumina.get_history_limit.unwrap_or(0); 183 | 184 | if limit == 0 { 185 | RpcMessage::Fail(rpc::RpcFail { 186 | code: 4, 187 | message: &format!( 188 | "{server_name}: function histories are disabled on this server." 189 | ), 190 | }) 191 | .async_write(&mut stream) 192 | .await?; 193 | return Ok(()); 194 | } 195 | 196 | let mut statuses = vec![]; 197 | let mut res = vec![]; 198 | for chksum in req.funcs.iter().map(|v| v.mb_hash) { 199 | let history = match db.get_func_histories(chksum, limit).await { 200 | Ok(v) => v, 201 | Err(err) => { 202 | error!("failed to get function histories: {err:?}"); 203 | RpcMessage::Fail(rpc::RpcFail { 204 | code: 3, 205 | message: &format!("{server_name}: db error, please try again later."), 206 | }) 207 | .async_write(&mut stream) 208 | .await?; 209 | return Ok(()); 210 | }, 211 | }; 212 | let status = !history.is_empty() as u32; 213 | statuses.push(status); 214 | if history.is_empty() { 215 | continue; 216 | } 217 | let log = history 218 | .into_iter() 219 | .map(|(updated, name, metadata)| rpc::FunctionHistory { 220 | unk0: 0, 221 | unk1: 0, 222 | name: Cow::Owned(name), 223 | metadata: Cow::Owned(metadata), 224 | timestamp: updated.unix_timestamp() as u64, 225 | author_idx: 0, 226 | idb_path_idx: 0, 227 | }) 228 | .collect::>(); 229 | res.push(rpc::FunctionHistories { log: Cow::Owned(log) }); 230 | } 231 | 232 | trace!("returning {} histories", res.len()); 233 | 234 | RpcMessage::GetFuncHistoriesResult(rpc::GetFuncHistoriesResult { 235 | status: statuses.into(), 236 | funcs: Cow::Owned(res), 237 | users: vec![].into(), 238 | dbs: vec![].into(), 239 | }) 240 | .async_write(&mut stream) 241 | .await?; 242 | }, 243 | _ => { 244 | RpcMessage::Fail(rpc::RpcFail { 245 | code: 0, 246 | message: &format!("{server_name}: invalid data.\n"), 247 | }) 248 | .async_write(&mut stream) 249 | .await?; 250 | }, 251 | } 252 | Ok(()) 253 | } 254 | 255 | async fn handle_client( 256 | state: &SharedState, mut stream: S, 257 | ) -> Result<(), rpc::Error> { 258 | let server_name = &state.server_name; 259 | let hello = 260 | match timeout(state.config.limits.hello_timeout, rpc::read_packet(&mut stream)).await { 261 | Ok(v) => v?, 262 | Err(_) => { 263 | debug!("didn't get hello in time."); 264 | return Ok(()); 265 | }, 266 | }; 267 | 268 | let (hello, creds) = match RpcMessage::deserialize(&hello) { 269 | Ok(RpcMessage::Hello(v, creds)) => { 270 | debug!("hello protocol={}, login creds: {creds:?}", v.protocol_version); 271 | (v, creds) 272 | }, 273 | _ => { 274 | // send error 275 | error!("got bad hello message"); 276 | 277 | let resp = rpc::RpcFail { code: 0, message: &format!("{server_name}: bad sequence.") }; 278 | let resp = rpc::RpcMessage::Fail(resp); 279 | resp.async_write(&mut stream).await?; 280 | 281 | return Ok(()); 282 | }, 283 | }; 284 | state 285 | .metrics 286 | .lumina_version 287 | .get_or_create(&LuminaVersion { protocol_version: hello.protocol_version }) 288 | .inc(); 289 | 290 | if let Some(ref creds) = creds { 291 | if creds.username != "guest" { 292 | // Only allow "guest" to connect for now. 293 | rpc::RpcMessage::Fail(rpc::RpcFail { 294 | code: 1, 295 | message: &format!("{server_name}: invalid username or password. Try logging in with `guest` instead."), 296 | }).async_write(&mut stream).await?; 297 | return Ok(()); 298 | } 299 | } 300 | 301 | let resp = match hello.protocol_version { 302 | 0..=4 => rpc::RpcMessage::Ok(()), 303 | 304 | // starting IDA 8.3 305 | 5.. => { 306 | let mut features = 0; 307 | 308 | if state.config.lumina.allow_deletes.unwrap_or(false) { 309 | features |= 0x02; 310 | } 311 | 312 | rpc::RpcMessage::HelloResult(HelloResult { features, ..Default::default() }) 313 | }, 314 | }; 315 | resp.async_write(&mut stream).await?; 316 | 317 | loop { 318 | handle_transaction(state, &hello, &mut stream).await?; 319 | } 320 | } 321 | 322 | async fn handle_connection(state: &SharedState, s: S) { 323 | if let Err(err) = handle_client(state, s).await { 324 | if discriminant(&err) != discriminant(&Error::Eof) { 325 | warn!("err: {}", err); 326 | } 327 | } 328 | } 329 | 330 | async fn serve( 331 | listener: TcpListener, accpt: Option, state: SharedState, 332 | mut shutdown_signal: tokio::sync::oneshot::Receiver<()>, 333 | ) { 334 | let accpt = accpt.map(Arc::new); 335 | 336 | let (async_drop, worker) = AsyncDropper::new(); 337 | tokio::task::spawn(worker); 338 | 339 | let connections = Arc::new(tokio::sync::Mutex::new(HashMap::< 340 | std::net::SocketAddr, 341 | tokio::task::JoinHandle<()>, 342 | >::new())); 343 | 344 | loop { 345 | let (client, addr) = tokio::select! { 346 | _ = &mut shutdown_signal => { 347 | drop(state); 348 | info!("shutting down..."); 349 | let m = connections.lock().await; 350 | m.iter().for_each(|(k, v)| { 351 | debug!("aborting task for {k}..."); 352 | v.abort(); 353 | }); 354 | return; 355 | }, 356 | res = listener.accept() => match res { 357 | Ok(v) => v, 358 | Err(err) => { 359 | warn!("failed to accept(): {}", err); 360 | continue; 361 | } 362 | }, 363 | }; 364 | 365 | let start = Instant::now(); 366 | 367 | let state = state.clone(); 368 | let accpt = accpt.clone(); 369 | 370 | let conns2 = connections.clone(); 371 | let counter = state.metrics.active_connections.clone(); 372 | let guard = async_drop.defer(async move { 373 | let count = counter.dec() - 1; 374 | debug!( 375 | "connection with {:?} ended after {:?}; {} active connections", 376 | addr, 377 | start.elapsed(), 378 | count 379 | ); 380 | 381 | let mut guard = conns2.lock().await; 382 | if guard.remove(&addr).is_none() { 383 | error!("Couldn't remove connection from set {addr}"); 384 | } 385 | }); 386 | 387 | let counter = state.metrics.active_connections.clone(); 388 | let handle = tokio::spawn(async move { 389 | let _guard = guard; 390 | let count = { counter.inc() + 1 }; 391 | let protocol = if accpt.is_some() { " [TLS]" } else { "" }; 392 | debug!("Connection from {:?}{}: {} active connections", &addr, protocol, count); 393 | match accpt { 394 | Some(accpt) => { 395 | match timeout(state.config.limits.tls_handshake_timeout, accpt.accept(client)) 396 | .await 397 | { 398 | Ok(r) => match r { 399 | Ok(s) => { 400 | handle_connection(&state, s).await; 401 | }, 402 | Err(err) => debug!("tls accept ({}): {}", &addr, err), 403 | }, 404 | Err(_) => { 405 | debug!("client {} didn't complete ssl handshake in time.", &addr); 406 | }, 407 | }; 408 | }, 409 | None => handle_connection(&state, client).await, 410 | } 411 | }); 412 | 413 | let mut guard = connections.lock().await; 414 | guard.insert(addr, handle); 415 | } 416 | } 417 | 418 | pub(crate) async fn do_lumen(config: Arc) { 419 | info!("starting private lumen server..."); 420 | 421 | let db = match Database::open(&config.database).await { 422 | Ok(v) => v, 423 | Err(err) => { 424 | error!("failed to open database: {}", err); 425 | exit(1); 426 | }, 427 | }; 428 | 429 | let server_name = config.lumina.server_name.clone().unwrap_or_else(|| String::from("lumen")); 430 | 431 | let state = Arc::new(SharedState_ { 432 | db, 433 | config, 434 | server_name, 435 | metrics: common::metrics::Metrics::default(), 436 | }); 437 | 438 | let tls_acceptor; 439 | 440 | if state.config.lumina.use_tls.unwrap_or_default() { 441 | let cert_path = 442 | &state.config.lumina.tls.as_ref().expect("tls section is missing").server_cert; 443 | let mut crt = match std::fs::read(cert_path) { 444 | Ok(v) => v, 445 | Err(err) => { 446 | error!("failed to read certificate file: {}", err); 447 | exit(1); 448 | }, 449 | }; 450 | let pkcs_passwd = std::env::var("PKCSPASSWD").unwrap_or_default(); 451 | let id = match Identity::from_pkcs12(&crt, &pkcs_passwd) { 452 | Ok(v) => v, 453 | Err(err) => { 454 | error!("failed to parse tls certificate: {}", err); 455 | exit(1); 456 | }, 457 | }; 458 | let _ = pkcs_passwd; 459 | crt.iter_mut().for_each(|v| *v = 0); 460 | let _ = crt; 461 | let mut accpt = native_tls::TlsAcceptor::builder(id); 462 | accpt.min_protocol_version(Some(native_tls::Protocol::Sslv3)); 463 | let accpt = match accpt.build() { 464 | Ok(v) => v, 465 | Err(err) => { 466 | error!("failed to build tls acceptor: {}", err); 467 | exit(1); 468 | }, 469 | }; 470 | let accpt = tokio_native_tls::TlsAcceptor::from(accpt); 471 | tls_acceptor = Some(accpt); 472 | } else { 473 | tls_acceptor = None; 474 | } 475 | 476 | let web_handle = if let Some(ref webcfg) = state.config.api_server { 477 | let bind_addr = webcfg.bind_addr; 478 | let state = state.clone(); 479 | info!("starting http api server on {:?}", &bind_addr); 480 | Some(tokio::spawn(async move { 481 | web::start_webserver(bind_addr, state).await; 482 | })) 483 | } else { 484 | None 485 | }; 486 | 487 | let (exit_signal_tx, exit_signal_rx) = tokio::sync::oneshot::channel::<()>(); 488 | 489 | let async_server = async move { 490 | let server = match TcpListener::bind(state.config.lumina.bind_addr).await { 491 | Ok(v) => v, 492 | Err(err) => { 493 | error!("failed to bind server port: {}", err); 494 | exit(1); 495 | }, 496 | }; 497 | 498 | info!("listening on {:?} secure={}", server.local_addr().unwrap(), tls_acceptor.is_some()); 499 | 500 | serve(server, tls_acceptor, state, exit_signal_rx).await; 501 | }; 502 | 503 | let server_handle = tokio::task::spawn(async_server); 504 | tokio::signal::ctrl_c().await.unwrap(); 505 | debug!("CTRL-C; exiting..."); 506 | if let Some(handle) = web_handle { 507 | handle.abort(); 508 | } 509 | exit_signal_tx.send(()).unwrap(); 510 | server_handle.await.unwrap(); 511 | 512 | info!("Goodbye."); 513 | } 514 | -------------------------------------------------------------------------------- /lumen/src/web.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use common::{web::api::api_root, SharedState}; 4 | use log::error; 5 | use warp::{hyper::StatusCode, reply::Response, Filter}; 6 | 7 | pub async fn start_webserver + 'static>( 8 | bind_addr: A, shared_state: SharedState, 9 | ) { 10 | let root = 11 | warp::get().and(warp::path::end()).map(|| warp::reply::html(include_str!("home.html"))); 12 | 13 | let shared_state1 = shared_state.clone(); 14 | let api = warp::path("api").and(api_root(shared_state1)); 15 | 16 | let metrics = warp::get().and(warp::path("metrics")).and(warp::path::end()).map(move || { 17 | let mut res = String::new(); 18 | if let Err(err) = 19 | prometheus_client::encoding::text::encode(&mut res, &shared_state.metrics.registry) 20 | { 21 | error!("failed to encode metrics: {err}"); 22 | let mut r = Response::default(); 23 | *r.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; 24 | r 25 | } else { 26 | warp::reply::Response::new(res.into()) 27 | } 28 | }); 29 | 30 | let routes = root.or(api).or(metrics); 31 | 32 | warp::serve(routes).run(bind_addr).await; 33 | } 34 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | fn_params_layout = "Compressed" 3 | match_block_trailing_comma = true 4 | newline_style = "Unix" 5 | use_small_heuristics = "Max" 6 | --------------------------------------------------------------------------------