├── .dockerignore ├── .github └── workflows │ └── docker.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── config.rs ├── error.rs └── main.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | /Dockerfile 2 | /LICENSE* 3 | /README.md 4 | 5 | /target/ 6 | **/*.rs.bk 7 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v3 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v3 20 | 21 | - name: Docker meta 22 | id: meta 23 | uses: docker/metadata-action@v5 24 | with: 25 | images: ${{ secrets.DOCKERHUB_USERNAME }}/koblas 26 | tags: type=semver,pattern={{version}} 27 | 28 | - name: Login to Docker Hub 29 | uses: docker/login-action@v3 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USERNAME }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | 34 | - name: Build and push 35 | uses: docker/build-push-action@v6 36 | with: 37 | context: . 38 | platforms: linux/amd64,linux/arm64,linux/arm/v7 39 | push: true 40 | tags: ${{ steps.meta.outputs.tags }} 41 | labels: ${{ steps.meta.outputs.labels }} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /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.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" 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 = "anstream" 22 | version = "0.6.7" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "utf8parse", 32 | ] 33 | 34 | [[package]] 35 | name = "anstyle" 36 | version = "1.0.8" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 39 | 40 | [[package]] 41 | name = "anstyle-parse" 42 | version = "0.2.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" 45 | dependencies = [ 46 | "utf8parse", 47 | ] 48 | 49 | [[package]] 50 | name = "anstyle-query" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 54 | dependencies = [ 55 | "windows-sys 0.48.0", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-wincon" 60 | version = "3.0.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" 63 | dependencies = [ 64 | "anstyle", 65 | "windows-sys 0.48.0", 66 | ] 67 | 68 | [[package]] 69 | name = "argon2" 70 | version = "0.5.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" 73 | dependencies = [ 74 | "base64ct", 75 | "blake2", 76 | "cpufeatures", 77 | "password-hash", 78 | ] 79 | 80 | [[package]] 81 | name = "backtrace" 82 | version = "0.3.66" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" 85 | dependencies = [ 86 | "addr2line", 87 | "cc", 88 | "cfg-if", 89 | "libc", 90 | "miniz_oxide", 91 | "object", 92 | "rustc-demangle", 93 | ] 94 | 95 | [[package]] 96 | name = "base64ct" 97 | version = "1.5.2" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" 100 | 101 | [[package]] 102 | name = "blake2" 103 | version = "0.10.6" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 106 | dependencies = [ 107 | "digest", 108 | ] 109 | 110 | [[package]] 111 | name = "block-buffer" 112 | version = "0.10.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 115 | dependencies = [ 116 | "generic-array", 117 | ] 118 | 119 | [[package]] 120 | name = "bytes" 121 | version = "1.2.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 124 | 125 | [[package]] 126 | name = "cc" 127 | version = "1.0.73" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 130 | 131 | [[package]] 132 | name = "cfg-if" 133 | version = "1.0.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 136 | 137 | [[package]] 138 | name = "clap" 139 | version = "4.5.17" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" 142 | dependencies = [ 143 | "clap_builder", 144 | "clap_derive", 145 | ] 146 | 147 | [[package]] 148 | name = "clap_builder" 149 | version = "4.5.17" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" 152 | dependencies = [ 153 | "anstream", 154 | "anstyle", 155 | "clap_lex", 156 | "strsim", 157 | ] 158 | 159 | [[package]] 160 | name = "clap_derive" 161 | version = "4.5.13" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" 164 | dependencies = [ 165 | "heck", 166 | "proc-macro2", 167 | "quote", 168 | "syn", 169 | ] 170 | 171 | [[package]] 172 | name = "clap_lex" 173 | version = "0.7.2" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 176 | 177 | [[package]] 178 | name = "colorchoice" 179 | version = "1.0.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 182 | 183 | [[package]] 184 | name = "cpufeatures" 185 | version = "0.2.14" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 188 | dependencies = [ 189 | "libc", 190 | ] 191 | 192 | [[package]] 193 | name = "crypto-common" 194 | version = "0.1.6" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 197 | dependencies = [ 198 | "generic-array", 199 | "typenum", 200 | ] 201 | 202 | [[package]] 203 | name = "digest" 204 | version = "0.10.5" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" 207 | dependencies = [ 208 | "block-buffer", 209 | "crypto-common", 210 | "subtle", 211 | ] 212 | 213 | [[package]] 214 | name = "equivalent" 215 | version = "1.0.1" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 218 | 219 | [[package]] 220 | name = "generic-array" 221 | version = "0.14.6" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 224 | dependencies = [ 225 | "typenum", 226 | "version_check", 227 | ] 228 | 229 | [[package]] 230 | name = "getrandom" 231 | version = "0.2.7" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 234 | dependencies = [ 235 | "cfg-if", 236 | "libc", 237 | "wasi", 238 | ] 239 | 240 | [[package]] 241 | name = "gimli" 242 | version = "0.26.2" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" 245 | 246 | [[package]] 247 | name = "hashbrown" 248 | version = "0.14.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 251 | 252 | [[package]] 253 | name = "heck" 254 | version = "0.5.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 257 | 258 | [[package]] 259 | name = "hermit-abi" 260 | version = "0.3.9" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 263 | 264 | [[package]] 265 | name = "indexmap" 266 | version = "2.0.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" 269 | dependencies = [ 270 | "equivalent", 271 | "hashbrown", 272 | ] 273 | 274 | [[package]] 275 | name = "ipnet" 276 | version = "2.10.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" 279 | dependencies = [ 280 | "serde", 281 | ] 282 | 283 | [[package]] 284 | name = "koblas" 285 | version = "1.1.4" 286 | dependencies = [ 287 | "argon2", 288 | "clap", 289 | "ipnet", 290 | "rand_core", 291 | "serde", 292 | "tokio", 293 | "toml", 294 | "tracing", 295 | "tracing-error", 296 | "tracing-subscriber", 297 | ] 298 | 299 | [[package]] 300 | name = "lazy_static" 301 | version = "1.4.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 304 | 305 | [[package]] 306 | name = "libc" 307 | version = "0.2.158" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 310 | 311 | [[package]] 312 | name = "log" 313 | version = "0.4.17" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 316 | dependencies = [ 317 | "cfg-if", 318 | ] 319 | 320 | [[package]] 321 | name = "matchers" 322 | version = "0.1.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 325 | dependencies = [ 326 | "regex-automata", 327 | ] 328 | 329 | [[package]] 330 | name = "memchr" 331 | version = "2.5.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 334 | 335 | [[package]] 336 | name = "miniz_oxide" 337 | version = "0.5.4" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" 340 | dependencies = [ 341 | "adler", 342 | ] 343 | 344 | [[package]] 345 | name = "mio" 346 | version = "1.0.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 349 | dependencies = [ 350 | "hermit-abi", 351 | "libc", 352 | "wasi", 353 | "windows-sys 0.52.0", 354 | ] 355 | 356 | [[package]] 357 | name = "nu-ansi-term" 358 | version = "0.46.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 361 | dependencies = [ 362 | "overload", 363 | "winapi", 364 | ] 365 | 366 | [[package]] 367 | name = "object" 368 | version = "0.29.0" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" 371 | dependencies = [ 372 | "memchr", 373 | ] 374 | 375 | [[package]] 376 | name = "once_cell" 377 | version = "1.15.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" 380 | 381 | [[package]] 382 | name = "overload" 383 | version = "0.1.1" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 386 | 387 | [[package]] 388 | name = "password-hash" 389 | version = "0.5.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" 392 | dependencies = [ 393 | "base64ct", 394 | "rand_core", 395 | "subtle", 396 | ] 397 | 398 | [[package]] 399 | name = "pin-project-lite" 400 | version = "0.2.13" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 403 | 404 | [[package]] 405 | name = "proc-macro2" 406 | version = "1.0.76" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" 409 | dependencies = [ 410 | "unicode-ident", 411 | ] 412 | 413 | [[package]] 414 | name = "quote" 415 | version = "1.0.35" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 418 | dependencies = [ 419 | "proc-macro2", 420 | ] 421 | 422 | [[package]] 423 | name = "rand_core" 424 | version = "0.6.4" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 427 | dependencies = [ 428 | "getrandom", 429 | ] 430 | 431 | [[package]] 432 | name = "regex" 433 | version = "1.7.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 436 | dependencies = [ 437 | "regex-syntax", 438 | ] 439 | 440 | [[package]] 441 | name = "regex-automata" 442 | version = "0.1.10" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 445 | dependencies = [ 446 | "regex-syntax", 447 | ] 448 | 449 | [[package]] 450 | name = "regex-syntax" 451 | version = "0.6.28" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 454 | 455 | [[package]] 456 | name = "rustc-demangle" 457 | version = "0.1.21" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 460 | 461 | [[package]] 462 | name = "serde" 463 | version = "1.0.210" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 466 | dependencies = [ 467 | "serde_derive", 468 | ] 469 | 470 | [[package]] 471 | name = "serde_derive" 472 | version = "1.0.210" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 475 | dependencies = [ 476 | "proc-macro2", 477 | "quote", 478 | "syn", 479 | ] 480 | 481 | [[package]] 482 | name = "serde_spanned" 483 | version = "0.6.7" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" 486 | dependencies = [ 487 | "serde", 488 | ] 489 | 490 | [[package]] 491 | name = "sharded-slab" 492 | version = "0.1.4" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 495 | dependencies = [ 496 | "lazy_static", 497 | ] 498 | 499 | [[package]] 500 | name = "smallvec" 501 | version = "1.10.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 504 | 505 | [[package]] 506 | name = "socket2" 507 | version = "0.5.5" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 510 | dependencies = [ 511 | "libc", 512 | "windows-sys 0.48.0", 513 | ] 514 | 515 | [[package]] 516 | name = "strsim" 517 | version = "0.11.1" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 520 | 521 | [[package]] 522 | name = "subtle" 523 | version = "2.4.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 526 | 527 | [[package]] 528 | name = "syn" 529 | version = "2.0.48" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 532 | dependencies = [ 533 | "proc-macro2", 534 | "quote", 535 | "unicode-ident", 536 | ] 537 | 538 | [[package]] 539 | name = "thread_local" 540 | version = "1.1.4" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 543 | dependencies = [ 544 | "once_cell", 545 | ] 546 | 547 | [[package]] 548 | name = "tokio" 549 | version = "1.40.0" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 552 | dependencies = [ 553 | "backtrace", 554 | "bytes", 555 | "libc", 556 | "mio", 557 | "pin-project-lite", 558 | "socket2", 559 | "windows-sys 0.52.0", 560 | ] 561 | 562 | [[package]] 563 | name = "toml" 564 | version = "0.8.19" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 567 | dependencies = [ 568 | "serde", 569 | "serde_spanned", 570 | "toml_datetime", 571 | "toml_edit", 572 | ] 573 | 574 | [[package]] 575 | name = "toml_datetime" 576 | version = "0.6.8" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 579 | dependencies = [ 580 | "serde", 581 | ] 582 | 583 | [[package]] 584 | name = "toml_edit" 585 | version = "0.22.20" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" 588 | dependencies = [ 589 | "indexmap", 590 | "serde", 591 | "serde_spanned", 592 | "toml_datetime", 593 | "winnow", 594 | ] 595 | 596 | [[package]] 597 | name = "tracing" 598 | version = "0.1.40" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 601 | dependencies = [ 602 | "pin-project-lite", 603 | "tracing-attributes", 604 | "tracing-core", 605 | ] 606 | 607 | [[package]] 608 | name = "tracing-attributes" 609 | version = "0.1.27" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 612 | dependencies = [ 613 | "proc-macro2", 614 | "quote", 615 | "syn", 616 | ] 617 | 618 | [[package]] 619 | name = "tracing-core" 620 | version = "0.1.32" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 623 | dependencies = [ 624 | "once_cell", 625 | "valuable", 626 | ] 627 | 628 | [[package]] 629 | name = "tracing-error" 630 | version = "0.2.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 633 | dependencies = [ 634 | "tracing", 635 | "tracing-subscriber", 636 | ] 637 | 638 | [[package]] 639 | name = "tracing-log" 640 | version = "0.2.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 643 | dependencies = [ 644 | "log", 645 | "once_cell", 646 | "tracing-core", 647 | ] 648 | 649 | [[package]] 650 | name = "tracing-subscriber" 651 | version = "0.3.18" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 654 | dependencies = [ 655 | "matchers", 656 | "nu-ansi-term", 657 | "once_cell", 658 | "regex", 659 | "sharded-slab", 660 | "smallvec", 661 | "thread_local", 662 | "tracing", 663 | "tracing-core", 664 | "tracing-log", 665 | ] 666 | 667 | [[package]] 668 | name = "typenum" 669 | version = "1.15.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 672 | 673 | [[package]] 674 | name = "unicode-ident" 675 | version = "1.0.4" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" 678 | 679 | [[package]] 680 | name = "utf8parse" 681 | version = "0.2.1" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 684 | 685 | [[package]] 686 | name = "valuable" 687 | version = "0.1.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 690 | 691 | [[package]] 692 | name = "version_check" 693 | version = "0.9.4" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 696 | 697 | [[package]] 698 | name = "wasi" 699 | version = "0.11.0+wasi-snapshot-preview1" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 702 | 703 | [[package]] 704 | name = "winapi" 705 | version = "0.3.9" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 708 | dependencies = [ 709 | "winapi-i686-pc-windows-gnu", 710 | "winapi-x86_64-pc-windows-gnu", 711 | ] 712 | 713 | [[package]] 714 | name = "winapi-i686-pc-windows-gnu" 715 | version = "0.4.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 718 | 719 | [[package]] 720 | name = "winapi-x86_64-pc-windows-gnu" 721 | version = "0.4.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 724 | 725 | [[package]] 726 | name = "windows-sys" 727 | version = "0.48.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 730 | dependencies = [ 731 | "windows-targets 0.48.0", 732 | ] 733 | 734 | [[package]] 735 | name = "windows-sys" 736 | version = "0.52.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 739 | dependencies = [ 740 | "windows-targets 0.52.6", 741 | ] 742 | 743 | [[package]] 744 | name = "windows-targets" 745 | version = "0.48.0" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 748 | dependencies = [ 749 | "windows_aarch64_gnullvm 0.48.0", 750 | "windows_aarch64_msvc 0.48.0", 751 | "windows_i686_gnu 0.48.0", 752 | "windows_i686_msvc 0.48.0", 753 | "windows_x86_64_gnu 0.48.0", 754 | "windows_x86_64_gnullvm 0.48.0", 755 | "windows_x86_64_msvc 0.48.0", 756 | ] 757 | 758 | [[package]] 759 | name = "windows-targets" 760 | version = "0.52.6" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 763 | dependencies = [ 764 | "windows_aarch64_gnullvm 0.52.6", 765 | "windows_aarch64_msvc 0.52.6", 766 | "windows_i686_gnu 0.52.6", 767 | "windows_i686_gnullvm", 768 | "windows_i686_msvc 0.52.6", 769 | "windows_x86_64_gnu 0.52.6", 770 | "windows_x86_64_gnullvm 0.52.6", 771 | "windows_x86_64_msvc 0.52.6", 772 | ] 773 | 774 | [[package]] 775 | name = "windows_aarch64_gnullvm" 776 | version = "0.48.0" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 779 | 780 | [[package]] 781 | name = "windows_aarch64_gnullvm" 782 | version = "0.52.6" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 785 | 786 | [[package]] 787 | name = "windows_aarch64_msvc" 788 | version = "0.48.0" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 791 | 792 | [[package]] 793 | name = "windows_aarch64_msvc" 794 | version = "0.52.6" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 797 | 798 | [[package]] 799 | name = "windows_i686_gnu" 800 | version = "0.48.0" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 803 | 804 | [[package]] 805 | name = "windows_i686_gnu" 806 | version = "0.52.6" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 809 | 810 | [[package]] 811 | name = "windows_i686_gnullvm" 812 | version = "0.52.6" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 815 | 816 | [[package]] 817 | name = "windows_i686_msvc" 818 | version = "0.48.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 821 | 822 | [[package]] 823 | name = "windows_i686_msvc" 824 | version = "0.52.6" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 827 | 828 | [[package]] 829 | name = "windows_x86_64_gnu" 830 | version = "0.48.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 833 | 834 | [[package]] 835 | name = "windows_x86_64_gnu" 836 | version = "0.52.6" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 839 | 840 | [[package]] 841 | name = "windows_x86_64_gnullvm" 842 | version = "0.48.0" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 845 | 846 | [[package]] 847 | name = "windows_x86_64_gnullvm" 848 | version = "0.52.6" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 851 | 852 | [[package]] 853 | name = "windows_x86_64_msvc" 854 | version = "0.48.0" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 857 | 858 | [[package]] 859 | name = "windows_x86_64_msvc" 860 | version = "0.52.6" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 863 | 864 | [[package]] 865 | name = "winnow" 866 | version = "0.6.18" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" 869 | dependencies = [ 870 | "memchr", 871 | ] 872 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "koblas" 3 | version = "1.1.4" 4 | edition = "2021" 5 | description = "Lightweight SOCKS5 proxy server" 6 | readme = "README.md" 7 | homepage = "https://github.com/ynuwenhof/koblas" 8 | repository = "https://github.com/ynuwenhof/koblas" 9 | license = "MIT OR Apache-2.0" 10 | keywords = ["proxy", "socks", "server"] 11 | 12 | [dependencies] 13 | toml = "0.8.19" 14 | tracing = "0.1.40" 15 | tracing-error = "0.2.0" 16 | 17 | [dependencies.argon2] 18 | version = "0.5.3" 19 | features = ["std"] 20 | 21 | [dependencies.clap] 22 | version = "4.5.17" 23 | features = ["derive", "env"] 24 | 25 | [dependencies.ipnet] 26 | version = "2.10.0" 27 | features = ["serde"] 28 | 29 | [dependencies.rand_core] 30 | version = "0.6.4" 31 | features = ["std"] 32 | 33 | [dependencies.serde] 34 | version = "1.0.210" 35 | features = ["derive"] 36 | 37 | [dependencies.tokio] 38 | version = "1.40.0" 39 | features = ["fs", "net", "io-util", "rt-multi-thread"] 40 | 41 | [dependencies.tracing-subscriber] 42 | version = "0.3.18" 43 | features = ["env-filter"] 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM rust:alpine AS builder 2 | RUN apk add --no-cache musl-dev 3 | WORKDIR /usr/src/koblas 4 | COPY . . 5 | RUN cargo install --path . 6 | 7 | RUN addgroup -g 1000 -S koblas && \ 8 | adduser -u 1000 -G koblas -S koblas 9 | 10 | FROM scratch 11 | COPY --from=builder /usr/local/cargo/bin/koblas /koblas 12 | COPY --from=builder /etc/passwd /etc/passwd 13 | 14 | USER koblas 15 | 16 | ENV KOBLAS_ADDRESS=0.0.0.0 \ 17 | KOBLAS_PORT=1080 \ 18 | KOBLAS_CONFIG_PATH=/etc/koblas/config.toml 19 | 20 | EXPOSE 1080 21 | 22 | ENTRYPOINT ["/koblas"] 23 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2022 Yannic Nuwenhof 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Koblas 2 | 3 | A lightweight [SOCKS5](https://datatracker.ietf.org/doc/html/rfc1928) proxy server, written in [Rust](https://rust-lang.org). 4 | 5 | * Multi-User 6 | * Configurable 7 | * IPv4 & IPv6 8 | * Blacklist & Whitelist 9 | * No Authentication 10 | * [Username/Password](https://datatracker.ietf.org/doc/html/rfc1929) Authentication 11 | 12 | ## Installation 13 | 14 | ### Cargo 15 | 16 | Make sure the current stable release of [Rust](https://rust-lang.org/tools/install) is installed. 17 | 18 | #### Registry 19 | 20 | ```bash 21 | cargo install koblas 22 | ``` 23 | 24 | #### Manual 25 | 26 | ```bash 27 | git clone https://github.com/ynuwenhof/koblas.git 28 | cd koblas 29 | cargo install --path . 30 | ``` 31 | 32 | Hash passwords by running the following command: 33 | 34 | ```bash 35 | koblas hash "correct-horse-battery-staple" 36 | ``` 37 | 38 | this will return an [Argon2id](https://en.wikipedia.org/wiki/Argon2) password hash. 39 | 40 | After installing, you can run the server with: 41 | 42 | ```bash 43 | koblas -a 0.0.0.0 --auth -c /path/to/config.toml 44 | ``` 45 | 46 | this will bind the server to `0.0.0.0:1080`. 47 | 48 | ### Docker 49 | 50 | Make sure the [Docker Engine](https://docs.docker.com/engine/install) is installed. 51 | 52 | Pull the latest image from the [Docker Hub](https://hub.docker.com) registry with: 53 | 54 | ```bash 55 | docker pull ynuwenhof/koblas:latest 56 | ``` 57 | 58 | Run the following commands to build the image yourself instead: 59 | 60 | ```bash 61 | git clone https://github.com/ynuwenhof/koblas.git 62 | cd koblas 63 | docker build -t ynuwenhof/koblas:latest . 64 | ``` 65 | 66 | Hash passwords by running the following command: 67 | 68 | ```bash 69 | docker run -it --rm ynuwenhof/koblas:latest hash "correct-horse-battery-staple" 70 | ``` 71 | 72 | this will return an [Argon2id](https://en.wikipedia.org/wiki/Argon2) password hash. 73 | 74 | After pulling or building the image, you can run the server with: 75 | 76 | ```bash 77 | docker run -d -p 1080:1080 \ 78 | -v /path/to/config.toml:/etc/koblas/config.toml \ 79 | -e RUST_LOG=debug \ 80 | -e KOBLAS_NO_AUTHENTICATION=false \ 81 | -e KOBLAS_ANONYMIZE=false \ 82 | --name koblas ynuwenhof/koblas:latest 83 | ``` 84 | 85 | this will bind the server to `0.0.0.0:1080`. 86 | 87 | Deploy the server with Docker Compose: 88 | 89 | ```yaml 90 | version: "3.8" 91 | services: 92 | koblas: 93 | image: ynuwenhof/koblas:latest 94 | container_name: koblas 95 | restart: unless-stopped 96 | ports: 97 | - 1080:1080 98 | environment: 99 | RUST_LOG: debug 100 | KOBLAS_LIMIT: 256 101 | KOBLAS_NO_AUTHENTICATION: false 102 | KOBLAS_ANONYMIZATION: true 103 | volumes: 104 | - /path/to/config.toml:/etc/koblas/config.toml 105 | ``` 106 | 107 | ## Configuration 108 | 109 | Koblas can be configured via environment variables or command line arguments. 110 | 111 | Missing keys will fall back to their default value. 112 | 113 | | Key | Description | Default | 114 | |----------------------------|-----------------------------------------------------------------------------|-------------| 115 | | `KOBLAS_ADDRESS` | Address on which to listen for incoming TCP connections | `127.0.0.1` | 116 | | `KOBLAS_PORT` | Port on which to listen for incoming TCP connections | `1080` | 117 | | `KOBLAS_LIMIT` | Maximum amount of clients to handle at once | `255` | 118 | | `KOBLAS_NO_AUTHENTICATION` | Don't require clients to authenticate using a username/password combination | `false` | 119 | | `KOBLAS_ANONYMIZATION` | Exclude sensitive information from the logs | `false` | 120 | | `KOBLAS_CONFIG_PATH` | File path to the config file | `None` | 121 | 122 | > :warning: The default configuration requires everyone to connect with a pre-existing username/password combination. 123 | 124 | Koblas doesn't have a default config file location, but we recommend the following locations: 125 | 126 | * Linux: `/etc/koblas/config.toml` 127 | * MacOS: `/etc/koblas/config.toml` 128 | * Windows: `%ProgramData%\koblas\config.toml` 129 | 130 | ### Example 131 | 132 | ```toml 133 | # All matching IPs will be automatically blocked 134 | blacklist = [ 135 | # Blacklist all IPs between 136 | # 192.168.2.0 and 192.168.2.255 137 | "192.168.2.0/24", 138 | # Blacklist a single IP 139 | "192.168.3.0/32", 140 | ] 141 | 142 | # All non matching IPs will be automatically blocked 143 | # Keep empty to disable the whitelist 144 | whitelist = [ 145 | # Whitelist all IPs between 146 | # 192.168.0.0 and 192.168.0.255 147 | "192.168.0.0/24", 148 | # Whitelist a single IP 149 | "192.168.1.0/32", 150 | ] 151 | 152 | [users] 153 | # Username = "alice", password = "QDuMGlxdhpZt" 154 | alice = "$argon2id$v=19$m=8,t=2,p=1$bWUwSXl2M2pYNU9xcVBocw$f4gFaE7p0qWRKw" 155 | # Username = "bob", password = "ceQvWaDGVeTv" 156 | bob = "$argon2id$v=19$m=8,t=2,p=1$ZExzaTM3aks1WjU1a3g4UA$J+EiueHYuR/dlA" 157 | ``` 158 | 159 | ## License 160 | 161 | This project is licensed under either of the following licenses, at your option: 162 | 163 | * [Apache License, Version 2.0](https://apache.org/licenses/LICENSE-2.0) 164 | ([LICENSE-APACHE](https://github.com/ynuwenhof/koblas/blob/main/LICENSE-APACHE)) 165 | * [MIT License](https://opensource.org/licenses/MIT) 166 | ([LICENSE-MIT](https://github.com/ynuwenhof/koblas/blob/main/LICENSE-MIT)) 167 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use ipnet::IpNet; 3 | use serde::Deserialize; 4 | use std::collections::BTreeMap; 5 | use std::net::IpAddr; 6 | use std::path::Path; 7 | use std::str::FromStr; 8 | use std::{fs, str}; 9 | use toml::de; 10 | 11 | #[derive(Default, Deserialize)] 12 | #[serde(deny_unknown_fields)] 13 | pub struct Config { 14 | #[serde(default)] 15 | pub users: BTreeMap, 16 | #[serde(default)] 17 | pub whitelist: Vec, 18 | #[serde(default)] 19 | pub blacklist: Vec, 20 | } 21 | 22 | impl Config { 23 | pub fn from_path(path: impl AsRef) -> error::Result { 24 | let path = path.as_ref(); 25 | 26 | let buf = fs::read(path)?; 27 | let str = str::from_utf8(&buf)?; 28 | 29 | Ok(Self::from_str(str)?) 30 | } 31 | 32 | pub fn is_blacklisted(&self, ip: &IpAddr) -> bool { 33 | self.blacklist.iter().any(|n| n.contains(ip)) 34 | } 35 | 36 | pub fn is_whitelisted(&self, ip: &IpAddr) -> bool { 37 | self.whitelist.is_empty() || self.whitelist.iter().any(|n| n.contains(ip)) 38 | } 39 | } 40 | 41 | impl FromStr for Config { 42 | type Err = de::Error; 43 | 44 | fn from_str(s: &str) -> Result { 45 | toml::from_str(s) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use argon2::password_hash; 2 | use std::fmt::{Display, Formatter}; 3 | use std::str::Utf8Error; 4 | use std::string::FromUtf8Error; 5 | use std::{error, fmt, io, result}; 6 | use toml::de; 7 | 8 | pub type Result = result::Result; 9 | 10 | #[derive(Debug)] 11 | pub enum Error { 12 | InvalidVersion, 13 | NoAcceptableMethod, 14 | AddrUnsupported, 15 | CommandUnsupported, 16 | UsernameNotFound, 17 | Io(io::Error), 18 | De(Box), 19 | Password(Box), 20 | Utf8(Utf8Error), 21 | } 22 | 23 | impl error::Error for Error {} 24 | 25 | impl Display for Error { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 27 | match self { 28 | Self::InvalidVersion => write!(f, "invalid protocol version"), 29 | Self::NoAcceptableMethod => write!(f, "no acceptable method"), 30 | Self::AddrUnsupported => write!(f, "address type unsupported"), 31 | Self::CommandUnsupported => write!(f, "command unsupported"), 32 | Self::UsernameNotFound => write!(f, "username not found"), 33 | Self::Io(e) => e.fmt(f), 34 | Self::De(e) => e.fmt(f), 35 | Self::Password(e) => e.fmt(f), 36 | Self::Utf8(e) => e.fmt(f), 37 | } 38 | } 39 | } 40 | 41 | impl From for Error { 42 | fn from(e: io::Error) -> Self { 43 | Self::Io(e) 44 | } 45 | } 46 | 47 | impl From for Error { 48 | fn from(e: de::Error) -> Self { 49 | Self::De(Box::new(e)) 50 | } 51 | } 52 | 53 | impl From for Error { 54 | fn from(e: password_hash::Error) -> Self { 55 | Self::Password(Box::new(e)) 56 | } 57 | } 58 | 59 | impl From for Error { 60 | fn from(e: Utf8Error) -> Self { 61 | Self::Utf8(e) 62 | } 63 | } 64 | 65 | impl From for Error { 66 | fn from(e: FromUtf8Error) -> Self { 67 | Self::Utf8(e.utf8_error()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod error; 3 | 4 | use crate::config::Config; 5 | use crate::error::Error; 6 | use argon2::password_hash::SaltString; 7 | use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; 8 | use clap::{Parser, Subcommand}; 9 | use rand_core::OsRng; 10 | use std::io::ErrorKind; 11 | use std::net::{IpAddr, SocketAddr}; 12 | use std::path::PathBuf; 13 | use std::sync::atomic::{AtomicI32, Ordering}; 14 | use std::sync::Arc; 15 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 16 | use tokio::net::{TcpListener, TcpStream}; 17 | use tokio::runtime::Builder; 18 | use tokio::{io, net}; 19 | use tracing::{debug, error, error_span, field, info, warn, Instrument, Span}; 20 | 21 | #[derive(Debug, Parser)] 22 | struct Cli { 23 | #[arg(short, long, env = "KOBLAS_ADDRESS", default_value_t = IpAddr::from([127, 0, 0, 1]))] 24 | addr: IpAddr, 25 | #[arg(short, long, env = "KOBLAS_PORT", default_value_t = 1080)] 26 | port: u16, 27 | #[arg(short, long, env = "KOBLAS_LIMIT", default_value_t = 255)] 28 | limit: i32, 29 | #[arg(short, long, env = "KOBLAS_NO_AUTHENTICATION")] 30 | no_auth: bool, 31 | #[arg(long, env = "KOBLAS_ANONYMIZATION")] 32 | anon: bool, 33 | #[arg(short, long, env = "KOBLAS_CONFIG_PATH", value_name = "FILE")] 34 | config: Option, 35 | #[command(subcommand)] 36 | command: Option, 37 | } 38 | 39 | #[derive(Debug, Subcommand)] 40 | enum Command { 41 | Hash { password: String }, 42 | } 43 | 44 | fn install_tracing() { 45 | use tracing_error::ErrorLayer; 46 | use tracing_subscriber::prelude::*; 47 | use tracing_subscriber::{fmt, EnvFilter}; 48 | 49 | tracing_subscriber::registry() 50 | .with(fmt::layer().with_target(false)) 51 | .with(EnvFilter::from_default_env()) 52 | .with(ErrorLayer::default()) 53 | .init(); 54 | } 55 | 56 | fn main() -> error::Result<()> { 57 | let cli = Cli::parse(); 58 | 59 | install_tracing(); 60 | 61 | debug!("{cli:?}"); 62 | 63 | if let Some(Command::Hash { password }) = cli.command { 64 | let salt = SaltString::generate(&mut OsRng); 65 | let hash = Argon2::default().hash_password(password.as_bytes(), &salt)?; 66 | println!("{hash}"); 67 | 68 | return Ok(()); 69 | } 70 | 71 | let config = cli.config.as_ref().map_or_else( 72 | || { 73 | warn!("config file path not set"); 74 | Ok(Config::default()) 75 | }, 76 | |path| { 77 | if path.exists() { 78 | Config::from_path(path) 79 | } else { 80 | warn!("config file doesn't exist"); 81 | Ok(Config::default()) 82 | } 83 | }, 84 | )?; 85 | 86 | debug!("loaded {} users", config.users.len()); 87 | 88 | Builder::new_multi_thread() 89 | .enable_all() 90 | .build() 91 | .expect("Failed building the Runtime") 92 | .block_on(run(cli, config)) 93 | } 94 | 95 | async fn run(cli: Cli, config: Config) -> error::Result<()> { 96 | let listener = TcpListener::bind((cli.addr, cli.port)).await?; 97 | 98 | info!( 99 | "listening on {}:{} for incoming connections", 100 | cli.addr, cli.port 101 | ); 102 | 103 | let cli = Arc::new(cli); 104 | let config = Arc::new(config); 105 | let clients = Arc::new(AtomicI32::new(0)); 106 | 107 | loop { 108 | let (mut stream, addr) = match listener.accept().await { 109 | Ok(s) => s, 110 | Err(e) => { 111 | error!("{e}"); 112 | continue; 113 | } 114 | }; 115 | 116 | let cli = cli.clone(); 117 | let config = config.clone(); 118 | let clients = clients.clone(); 119 | 120 | tokio::spawn(async move { 121 | let span = if cli.anon { 122 | Span::none() 123 | } else { 124 | error_span!( 125 | "client", 126 | %addr, 127 | peer = field::Empty, 128 | user = field::Empty 129 | ) 130 | }; 131 | 132 | async { 133 | let ip = addr.ip(); 134 | if clients.load(Ordering::SeqCst) >= cli.limit 135 | || config.is_blacklisted(&ip) 136 | || !config.is_whitelisted(&ip) 137 | { 138 | warn!("connection denied"); 139 | return; 140 | } 141 | 142 | clients.fetch_add(1, Ordering::SeqCst); 143 | 144 | info!("connected"); 145 | 146 | if let Err(e) = handle(&mut stream, cli, config).await { 147 | error!("{e}"); 148 | } 149 | 150 | clients.fetch_sub(1, Ordering::SeqCst); 151 | 152 | info!("disconnected"); 153 | } 154 | .instrument(span) 155 | .await; 156 | 157 | stream.shutdown().await 158 | }); 159 | } 160 | } 161 | 162 | const AUTH_VERSION: u8 = 0x1; 163 | const AUTH_METHOD: u8 = 0x2; 164 | const NO_AUTH_METHOD: u8 = 0x0; 165 | const NO_METHOD: u8 = 0xff; 166 | const SOCKS_VERSION: u8 = 0x5; 167 | const SUCCESS_REPLY: u8 = 0x0; 168 | 169 | async fn handle(stream: &mut TcpStream, cli: Arc, config: Arc) -> error::Result<()> { 170 | let mut buf = [0u8; 2]; 171 | stream.read_exact(&mut buf).await?; 172 | 173 | let ver = buf[0]; 174 | if ver != SOCKS_VERSION { 175 | return Err(Error::InvalidVersion); 176 | } 177 | 178 | let len = buf[1] as usize; 179 | let mut buf = vec![0u8; len]; 180 | stream.read_exact(&mut buf).await?; 181 | 182 | let method = *buf 183 | .iter() 184 | .find(|&&m| { 185 | m == NO_AUTH_METHOD && cli.no_auth 186 | || m == AUTH_METHOD && (!cli.no_auth || !config.users.is_empty()) 187 | }) 188 | .unwrap_or(&NO_METHOD); 189 | 190 | let buf = [SOCKS_VERSION, method]; 191 | stream.write_all(&buf).await?; 192 | 193 | match method { 194 | AUTH_METHOD => { 195 | let res = auth(stream, config).await; 196 | let reply = res.is_err() as u8; 197 | let buf = [AUTH_VERSION, reply]; 198 | stream.write_all(&buf).await?; 199 | res?; 200 | } 201 | NO_METHOD => return Err(Error::NoAcceptableMethod), 202 | _ => {} 203 | } 204 | 205 | let mut buf = [0u8; 4]; 206 | stream.read_exact(&mut buf).await?; 207 | 208 | let ver = buf[0]; 209 | if ver != SOCKS_VERSION { 210 | return Err(Error::InvalidVersion); 211 | } 212 | 213 | let (mut peer, local_addr) = match socks(stream, buf).await { 214 | Ok(t) => t, 215 | Err(e) => { 216 | let reply = match &e { 217 | Error::AddrUnsupported => 0x8, 218 | Error::CommandUnsupported => 0x7, 219 | Error::Io(e) => { 220 | // TODO: https://github.com/rust-lang/rust/issues/86442 221 | match e.kind() { 222 | ErrorKind::ConnectionRefused => 0x5, 223 | _ => 0x1, 224 | } 225 | } 226 | _ => 0x1, 227 | }; 228 | 229 | let buf = [SOCKS_VERSION, reply, 0, IPV4_TYPE, 0, 0, 0, 0, 0, 0]; 230 | stream.write_all(&buf).await?; 231 | 232 | return Err(e); 233 | } 234 | }; 235 | 236 | let span = Span::current(); 237 | span.record("peer", field::display(local_addr)); 238 | 239 | let mut buf = Vec::with_capacity(22); 240 | buf.extend([SOCKS_VERSION, SUCCESS_REPLY, 0]); 241 | 242 | match local_addr.ip() { 243 | IpAddr::V4(i) => { 244 | buf.push(IPV4_TYPE); 245 | buf.extend(i.octets()); 246 | } 247 | IpAddr::V6(i) => { 248 | buf.push(IPV6_TYPE); 249 | buf.extend(i.octets()); 250 | } 251 | } 252 | 253 | let port = local_addr.port().to_le_bytes(); 254 | buf.extend(port); 255 | stream.write_all(&buf).await?; 256 | 257 | let (sent, received) = io::copy_bidirectional(stream, &mut peer).await?; 258 | info!("sent {sent} bytes and received {received} bytes"); 259 | 260 | Ok(()) 261 | } 262 | 263 | async fn auth(stream: &mut TcpStream, config: Arc) -> error::Result<()> { 264 | let mut buf = [0u8; 2]; 265 | stream.read_exact(&mut buf).await?; 266 | 267 | let ver = buf[0]; 268 | if ver != AUTH_VERSION { 269 | return Err(Error::InvalidVersion); 270 | } 271 | 272 | let len = buf[1] as usize; 273 | let mut buf = vec![0u8; len]; 274 | stream.read_exact(&mut buf).await?; 275 | let username = String::from_utf8(buf)?; 276 | 277 | let len = stream.read_u8().await? as usize; 278 | let mut buf = vec![0u8; len]; 279 | stream.read_exact(&mut buf).await?; 280 | let password = String::from_utf8(buf)?; 281 | 282 | let pass = config.users.get(&username).ok_or(Error::UsernameNotFound)?; 283 | 284 | let span = Span::current(); 285 | span.record("user", field::display(username)); 286 | 287 | let hash = PasswordHash::new(pass)?; 288 | Argon2::default().verify_password(password.as_bytes(), &hash)?; 289 | 290 | Ok(()) 291 | } 292 | 293 | const IPV4_TYPE: u8 = 0x1; 294 | const IPV6_TYPE: u8 = 0x4; 295 | const DOMAIN_TYPE: u8 = 0x3; 296 | const CONNECT_COMMAND: u8 = 0x1; 297 | 298 | async fn socks(stream: &mut TcpStream, buf: [u8; 4]) -> error::Result<(TcpStream, SocketAddr)> { 299 | let cmd = buf[1]; 300 | if cmd != CONNECT_COMMAND { 301 | return Err(Error::CommandUnsupported); 302 | } 303 | 304 | let addr_type = buf[3]; 305 | let dest = match addr_type { 306 | IPV4_TYPE => { 307 | let mut octets = [0u8; 4]; 308 | stream.read_exact(&mut octets).await?; 309 | 310 | let port = stream.read_u16().await?; 311 | vec![SocketAddr::new(IpAddr::from(octets), port)] 312 | } 313 | DOMAIN_TYPE => { 314 | let len = stream.read_u8().await? as usize; 315 | let mut buf = vec![0u8; len]; 316 | stream.read_exact(&mut buf).await?; 317 | 318 | let domain = String::from_utf8(buf)?; 319 | let port = stream.read_u16().await?; 320 | 321 | net::lookup_host(format!("{domain}:{port}")) 322 | .await? 323 | .collect() 324 | } 325 | IPV6_TYPE => { 326 | let mut octets = [0u8; 16]; 327 | stream.read_exact(&mut octets).await?; 328 | 329 | let port = stream.read_u16().await?; 330 | vec![SocketAddr::new(IpAddr::from(octets), port)] 331 | } 332 | _ => return Err(Error::AddrUnsupported), 333 | }; 334 | 335 | let stream = TcpStream::connect(&dest[..]).await?; 336 | let addr = stream.local_addr()?; 337 | Ok((stream, addr)) 338 | } 339 | --------------------------------------------------------------------------------