diff --git a/.envrc b/.envrc index bad73b75..172993c4 100644 --- a/.envrc +++ b/.envrc @@ -2,6 +2,8 @@ dotenv_if_exists -# use flake ".#${DIRENV_DEVSHELL:-default}" +if [ -f /etc/os-release ] && grep -q '^ID=nixos' /etc/os-release; then + use flake ".#${DIRENV_DEVSHELL:-default}" +fi PATH_add bin diff --git a/.forgejo/actions/prefligit/action.yml b/.forgejo/actions/prefligit/action.yml deleted file mode 100644 index 8cbd4500..00000000 --- a/.forgejo/actions/prefligit/action.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: prefligit -description: | - Runs prefligit, pre-commit reimplemented in Rust. -inputs: - extra_args: - description: options to pass to pre-commit run - required: false - default: '--all-files' - -runs: - using: composite - steps: - - name: Install uv - uses: https://github.com/astral-sh/setup-uv@v6 - with: - enable-cache: true - ignore-nothing-to-cache: true - - name: Install Prefligit - shell: bash - run: | - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prefligit/releases/download/v0.0.10/prefligit-installer.sh | sh - - uses: actions/cache@v3 - with: - path: ~/.cache/prefligit - key: prefligit-0|${{ hashFiles('.pre-commit-config.yaml') }} - - run: prefligit run --show-diff-on-failure --color=always -v ${{ inputs.extra_args }} - shell: bash diff --git a/.forgejo/workflows/prefligit-checks.yml b/.forgejo/workflows/prefligit-checks.yml deleted file mode 100644 index cc512496..00000000 --- a/.forgejo/workflows/prefligit-checks.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Checks / Prefligit - -on: - push: - pull_request: -permissions: - contents: read - -jobs: - prefligit: - runs-on: ubuntu-latest - env: - FROM_REF: ${{ github.event.pull_request.base.sha || (!github.event.forced && ( github.event.before != '0000000000000000000000000000000000000000' && github.event.before || github.sha )) || format('{0}~', github.sha) }} - TO_REF: ${{ github.sha }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - - uses: ./.forgejo/actions/prefligit - with: - extra_args: --all-files --hook-stage manual diff --git a/.forgejo/workflows/prek-checks.yml b/.forgejo/workflows/prek-checks.yml new file mode 100644 index 00000000..ac330ca2 --- /dev/null +++ b/.forgejo/workflows/prek-checks.yml @@ -0,0 +1,34 @@ +name: Checks / Prek + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + fast-checks: + name: Pre-commit & Formatting + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install uv + uses: https://github.com/astral-sh/setup-uv@v5 + with: + enable-cache: true + ignore-nothing-to-cache: true + cache-dependency-glob: '' + + - name: Run prek + run: | + uvx prek run \ + --all-files \ + --hook-stage manual \ + --show-diff-on-failure \ + --color=always \ + -v diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml new file mode 100644 index 00000000..e8522bec --- /dev/null +++ b/.forgejo/workflows/renovate.yml @@ -0,0 +1,62 @@ +name: Maintenance / Renovate + +on: + schedule: + # Run at 5am UTC daily to avoid late-night dev + - cron: '0 5 * * *' + + workflow_dispatch: + inputs: + dryRun: + description: 'Dry run mode' + required: false + default: null + type: choice + options: + - null + - 'extract' + - 'lookup' + - 'full' + logLevel: + description: 'Log level' + required: false + default: 'info' + type: choice + options: + - 'info' + - 'warning' + - 'critical' + + push: + branches: + - main + paths: + # Re-run when config changes + - '.forgejo/workflows/renovate.yml' + - 'renovate.json' + +jobs: + renovate: + name: Renovate + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Self-hosted Renovate + uses: https://github.com/renovatebot/github-action@v40.1.0 + env: + LOG_LEVEL: ${{ inputs.logLevel || 'info' }} + RENOVATE_AUTODISCOVER: 'false' + RENOVATE_BINARY_SOURCE: 'install' + RENOVATE_DRY_RUN: ${{ inputs.dryRun || 'false' }} + RENOVATE_ENDPOINT: ${{ github.server_url }}/api/v1 + RENOVATE_GIT_TIMEOUT: 60000 + RENOVATE_GIT_URL: 'endpoint' + RENOVATE_GITHUB_TOKEN_WARN: 'false' + RENOVATE_ONBOARDING: 'false' + RENOVATE_PLATFORM: 'forgejo' + RENOVATE_PR_COMMITS_PER_RUN_LIMIT: 3 + RENOVATE_REPOSITORIES: '["${{ github.repository }}"]' + RENOVATE_REQUIRE_CONFIG: 'required' + RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} diff --git a/.forgejo/workflows/rust-checks.yml b/.forgejo/workflows/rust-checks.yml index 0595c053..c46363a0 100644 --- a/.forgejo/workflows/rust-checks.yml +++ b/.forgejo/workflows/rust-checks.yml @@ -73,7 +73,7 @@ jobs: run: | cargo clippy \ --workspace \ - --all-features \ + --features full \ --locked \ --no-deps \ --profile test \ @@ -133,7 +133,7 @@ jobs: run: | cargo test \ --workspace \ - --all-features \ + --features full \ --locked \ --profile test \ --all-targets \ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index fcfaade5..841427b7 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,5 +1,4 @@ -github: [JadedBlueEyes] -# Doesn't support an array, so we can only list nex -ko_fi: nexy7574 +github: [JadedBlueEyes, nexy7574] custom: + - https://ko-fi.com/nexy7574 - https://ko-fi.com/JadedBlueEyes diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68e3a982..da594310 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - - id: check-byte-order-marker + - id: fix-byte-order-marker - id: check-case-conflict - id: check-symlinks - id: destroyed-symlinks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e11cff4..b5ecf38a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,11 +65,11 @@ Tests, compilation, and linting can be run with standard Cargo commands: cargo test # Check compilation -cargo check --workspace --all-features +cargo check --workspace --features full # Run lints -cargo clippy --workspace --all-features -# Auto-fix: cargo clippy --workspace --all-features --fix --allow-staged; +cargo clippy --workspace --features full +# Auto-fix: cargo clippy --workspace --features full --fix --allow-staged; # Format code (must use nightly) cargo +nightly fmt diff --git a/Cargo.lock b/Cargo.lock index c85432c9..b38a0961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,9 +52,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -73,27 +73,27 @@ checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -126,7 +126,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -183,7 +183,7 @@ dependencies = [ "rustc-hash 2.1.1", "serde", "serde_derive", - "syn", + "syn 2.0.104", ] [[package]] @@ -198,6 +198,45 @@ dependencies = [ "winnow", ] +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "assign" version = "1.1.1" @@ -217,9 +256,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ "brotli", "flate2", @@ -250,7 +289,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -261,7 +300,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -301,18 +340,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" +checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42" dependencies = [ "arrayvec", ] [[package]] name = "aws-lc-rs" -version = "1.13.1" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" dependencies = [ "aws-lc-sys", "zeroize", @@ -320,9 +359,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ "bindgen 0.69.5", "cc", @@ -433,11 +472,11 @@ dependencies = [ "hyper", "hyper-util", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.23.29", + "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tower-service", ] @@ -452,9 +491,9 @@ dependencies = [ "http", "http-body-util", "pin-project", - "rustls", + "rustls 0.23.29", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tokio-util", "tower-layer", "tower-service", @@ -521,15 +560,15 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn", + "syn 2.0.104", "which", ] [[package]] name = "bindgen" -version = "0.71.1" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" dependencies = [ "bitflags 2.9.1", "cexpr", @@ -540,7 +579,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn", + "syn 2.0.104", ] [[package]] @@ -629,9 +668,9 @@ checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" @@ -685,9 +724,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.27" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -756,9 +795,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -775,9 +814,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -787,14 +826,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -805,9 +844,9 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "clap_mangen" -version = "0.2.26" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a" +checksum = "e2fb6d3f935bbb9819391528b0e7cf655e78a0bc7a7c3d227211a1d24fc11db1" dependencies = [ "clap", "roff", @@ -830,9 +869,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "concurrent-queue" @@ -845,7 +884,7 @@ dependencies = [ [[package]] name = "conduwuit" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "clap", "conduwuit_admin", @@ -875,7 +914,7 @@ dependencies = [ [[package]] name = "conduwuit_admin" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "clap", "conduwuit_api", @@ -896,7 +935,7 @@ dependencies = [ [[package]] name = "conduwuit_api" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "async-trait", "axum", @@ -928,14 +967,14 @@ dependencies = [ [[package]] name = "conduwuit_build_metadata" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "built 0.8.0", ] [[package]] name = "conduwuit_core" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "argon2", "arrayvec", @@ -972,7 +1011,7 @@ dependencies = [ "rand 0.8.5", "regex", "reqwest", - "ring", + "ring 0.17.14", "ruma", "sanitize-filename", "serde", @@ -996,7 +1035,7 @@ dependencies = [ [[package]] name = "conduwuit_database" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "async-channel", "conduwuit_core", @@ -1014,17 +1053,17 @@ dependencies = [ [[package]] name = "conduwuit_macros" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] name = "conduwuit_router" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "axum", "axum-client-ip", @@ -1044,7 +1083,7 @@ dependencies = [ "hyper-util", "log", "ruma", - "rustls", + "rustls 0.23.29", "sd-notify", "sentry", "sentry-tower", @@ -1058,7 +1097,7 @@ dependencies = [ [[package]] name = "conduwuit_service" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "async-trait", "base64 0.22.1", @@ -1074,6 +1113,7 @@ dependencies = [ "image", "ipaddress", "itertools 0.14.0", + "ldap3", "log", "loole", "lru-cache", @@ -1096,7 +1136,7 @@ dependencies = [ [[package]] name = "conduwuit_web" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "askama", "axum", @@ -1155,15 +1195,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-str" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e991226a70654b49d34de5ed064885f0bef0348a8e70018b8ff1ac80aa984a2" +checksum = "041fbfcf8e7054df725fb9985297e92422cdc80fcf313665f5ca3d761bb63f4c" [[package]] name = "const_panic" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" +checksum = "b98d1483e98c9d67f341ab4b3915cfdc54740bd6f5cccc9226ee0535d86aa8fb" [[package]] name = "convert_case" @@ -1183,6 +1223,16 @@ dependencies = [ "crossterm", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1220,9 +1270,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1256,7 +1306,7 @@ dependencies = [ "proc-macro2", "quote", "strict", - "syn", + "syn 2.0.104", ] [[package]] @@ -1328,7 +1378,7 @@ dependencies = [ "futures-core", "mio", "parking_lot", - "rustix 1.0.7", + "rustix 1.0.8", "signal-hook", "signal-hook-mio", "winapi", @@ -1345,9 +1395,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -1366,7 +1416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1393,7 +1443,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1434,6 +1484,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.4.0" @@ -1461,7 +1525,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1483,7 +1547,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1513,9 +1577,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -1544,7 +1608,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1564,7 +1628,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1734,6 +1798,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1781,7 +1846,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1867,9 +1932,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -1889,9 +1954,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -1899,7 +1964,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.9.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -2029,8 +2094,8 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.1", - "ring", + "rand 0.9.2", + "ring 0.17.14", "serde", "thiserror 2.0.12", "tinyvec", @@ -2073,7 +2138,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.1", + "rand 0.9.2", "resolv-conf", "serde", "smallvec", @@ -2122,7 +2187,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2216,13 +2281,13 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls", - "rustls-native-certs", + "rustls 0.23.29", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tower-service", - "webpki-roots 1.0.1", + "webpki-roots 1.0.2", ] [[package]] @@ -2415,9 +2480,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -2444,7 +2509,18 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", ] [[package]] @@ -2530,9 +2606,9 @@ dependencies = [ [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -2602,7 +2678,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 2.0.104", ] [[package]] @@ -2617,6 +2693,43 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lber" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a" +dependencies = [ + "bytes", + "nom", +] + +[[package]] +name = "ldap3" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166199a8207874a275144c8a94ff6eed5fcbf5c52303e4d9b4d53a0c7ac76554" +dependencies = [ + "async-trait", + "bytes", + "futures", + "futures-util", + "lazy_static", + "lber", + "log", + "nom", + "percent-encoding", + "ring 0.16.20", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.24.1", + "tokio-stream", + "tokio-util", + "url", + "x509-parser", +] + [[package]] name = "lebe" version = "0.5.2" @@ -2631,9 +2744,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libfuzzer-sys" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" dependencies = [ "arbitrary", "cc", @@ -2855,7 +2968,7 @@ checksum = "a9882ef5c56df184b8ffc107fc6c61e33ee3a654b021961d790a78571bb9d67a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3021,7 +3134,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3083,6 +3196,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -3113,7 +3235,7 @@ checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.9.0", + "indexmap 2.10.0", "js-sys", "once_cell", "pin-project-lite", @@ -3273,7 +3395,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3289,7 +3411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.9.0", + "indexmap 2.10.0", ] [[package]] @@ -3347,7 +3469,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3380,12 +3502,12 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.2" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64 0.22.1", - "indexmap 2.9.0", + "indexmap 2.10.0", "quick-xml", "serde", "time", @@ -3442,12 +3564,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.104", ] [[package]] @@ -3476,28 +3598,28 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "version_check", "yansi", ] [[package]] name = "profiling" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3520,7 +3642,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3567,9 +3689,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" dependencies = [ "memchr", ] @@ -3586,7 +3708,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls", + "rustls 0.23.29", "socket2", "thiserror 2.0.12", "tokio", @@ -3603,10 +3725,10 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand 0.9.1", - "ring", + "rand 0.9.2", + "ring 0.17.14", "rustc-hash 2.1.1", - "rustls", + "rustls 0.23.29", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -3657,9 +3779,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -3740,9 +3862,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.12" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" dependencies = [ "avif-serialize", "imgref", @@ -3786,9 +3908,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ "bitflags 2.9.1", ] @@ -3865,16 +3987,16 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", - "rustls-native-certs", - "rustls-pemfile", + "rustls 0.23.29", + "rustls-native-certs 0.8.1", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tokio-socks", "tokio-util", "tower 0.5.2", @@ -3894,9 +4016,24 @@ source = "git+https://forgejo.ellis.link/continuwuation/resolv-conf?rev=56251316 [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted 0.7.1", + "web-sys", + "winapi", +] [[package]] name = "ring" @@ -3908,7 +4045,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -3980,7 +4117,7 @@ dependencies = [ "form_urlencoded", "getrandom 0.2.16", "http", - "indexmap 2.9.0", + "indexmap 2.10.0", "js_int", "konst", "percent-encoding", @@ -4006,7 +4143,7 @@ name = "ruma-events" version = "0.28.1" dependencies = [ "as_variant", - "indexmap 2.9.0", + "indexmap 2.10.0", "js_int", "js_option", "percent-encoding", @@ -4073,7 +4210,7 @@ dependencies = [ "quote", "ruma-identifiers-validation", "serde", - "syn", + "syn 2.0.104", "toml", ] @@ -4105,10 +4242,10 @@ dependencies = [ [[package]] name = "rust-librocksdb-sys" -version = "0.33.0+9.11.1" -source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd#fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd" +version = "0.38.0+10.4.2" +source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=99b0319416b64830dd6f8943e1f65e15aeef18bc#99b0319416b64830dd6f8943e1f65e15aeef18bc" dependencies = [ - "bindgen 0.71.1", + "bindgen 0.72.0", "bzip2-sys", "cc", "glob", @@ -4122,8 +4259,8 @@ dependencies = [ [[package]] name = "rust-rocksdb" -version = "0.37.0" -source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd#fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd" +version = "0.42.1" +source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=99b0319416b64830dd6f8943e1f65e15aeef18bc#99b0319416b64830dd6f8943e1f65e15aeef18bc" dependencies = [ "libc", "rust-librocksdb-sys", @@ -4156,6 +4293,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.44" @@ -4171,33 +4317,57 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring", + "ring 0.17.14", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.4", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -4207,7 +4377,16 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", ] [[package]] @@ -4231,14 +4410,24 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "aws-lc-rs", - "ring", + "ring 0.17.14", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -4297,6 +4486,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + [[package]] name = "sd-notify" version = "0.4.5" @@ -4306,6 +4505,19 @@ dependencies = [ "libc", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -4313,7 +4525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.1", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -4343,7 +4555,7 @@ checksum = "255914a8e53822abd946e2ce8baa41d4cded6b8e938913b7f7b9da5b7ab44335" dependencies = [ "httpdate", "reqwest", - "rustls", + "rustls 0.23.29", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -4487,7 +4699,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4497,7 +4709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" dependencies = [ "form_urlencoded", - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "ryu", "serde", @@ -4505,9 +4717,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", @@ -4562,7 +4774,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "ryu", "serde", @@ -4701,6 +4913,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.7.3" @@ -4769,6 +4987,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.104" @@ -4789,6 +5018,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -4797,7 +5038,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4888,7 +5129,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4899,7 +5140,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5040,16 +5281,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "tracing", @@ -5064,14 +5307,14 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] name = "tokio-metrics" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7817b32d36c9b94744d7aa3f8fc13526aa0f5112009d7045f3c659413a6e44ac" +checksum = "23ff82f660c98e4ff60da5eb8fa864a4130f34b56d92d5cd23d6fdfcc14e95fa" dependencies = [ "futures-util", "pin-project-lite", @@ -5079,13 +5322,23 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls", + "rustls 0.23.29", "tokio", ] @@ -5152,7 +5405,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", @@ -5283,7 +5536,7 @@ source = "git+https://forgejo.ellis.link/continuwuation/tracing?rev=1e64095a8051 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5376,9 +5629,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "typewit" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" +checksum = "97e72ba082eeb9da9dc68ff5a2bf727ef6ce362556e8d29ec1aed3bd05e7d86a" dependencies = [ "typewit_proc_macros", ] @@ -5437,12 +5690,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -5458,7 +5723,7 @@ dependencies = [ "base64 0.22.1", "log", "once_cell", - "rustls", + "rustls 0.23.29", "rustls-pki-types", "url", "webpki-roots 0.26.11", @@ -5593,7 +5858,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -5628,7 +5893,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5690,14 +5955,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.1", + "webpki-roots 1.0.2", ] [[package]] name = "webpki-roots" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -5808,7 +6073,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5819,7 +6084,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -6108,9 +6373,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -6140,6 +6405,23 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xml5ever" version = "0.18.1" @@ -6153,7 +6435,7 @@ dependencies = [ [[package]] name = "xtask" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "clap", "serde", @@ -6162,7 +6444,7 @@ dependencies = [ [[package]] name = "xtask-generate-commands" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" dependencies = [ "clap-markdown", "clap_builder", @@ -6197,8 +6479,8 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.104", + "synstructure 0.13.2", ] [[package]] @@ -6218,7 +6500,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -6238,8 +6520,8 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.104", + "synstructure 0.13.2", ] [[package]] @@ -6278,7 +6560,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -6305,7 +6587,6 @@ version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ - "bindgen 0.71.1", "cc", "pkg-config", ] @@ -6327,9 +6608,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6fe2e33d02a98ee64423802e16df3de99c43e5cf5ff983767e1128b394c8ac" +checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 5b6c8143..4cf0bbca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ license = "Apache-2.0" readme = "README.md" repository = "https://forgejo.ellis.link/continuwuation/continuwuity" rust-version = "1.86.0" -version = "0.5.0-rc.6" +version = "0.5.0-rc.7" [workspace.metadata.crane] name = "conduwuit" @@ -392,7 +392,7 @@ features = [ [workspace.dependencies.rust-rocksdb] git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1" -rev = "fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd" +rev = "99b0319416b64830dd6f8943e1f65e15aeef18bc" default-features = false features = [ "multi-threaded-cf", @@ -547,6 +547,11 @@ features = ["std"] [workspace.dependencies.maplit] version = "1.0.2" +[workspace.dependencies.ldap3] +version = "0.11.5" +default-features = false +features = ["sync", "tls-rustls"] + # # Patches # @@ -868,7 +873,7 @@ unused-qualifications = "warn" #unused-results = "warn" # TODO ## some sadness -elided_named_lifetimes = "allow" # TODO! +mismatched_lifetime_syntaxes = "allow" # TODO! let_underscore_drop = "allow" missing_docs = "allow" # cfgs cannot be limited to expected cfgs or their de facto non-transitive/opt-in use-case e.g. @@ -1007,3 +1012,6 @@ literal_string_with_formatting_args = { level = "allow", priority = 1 } needless_raw_string_hashes = "allow" + +# TODO: Enable this lint & fix all instances +collapsible_if = "allow" diff --git a/README.md b/README.md index e63fe80f..a5f77a5b 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Continuwuity aims to: ### Can I try it out? -Check out the [documentation](introduction) for installation instructions. +Check out the [documentation](https://continuwuity.org) for installation instructions. There are currently no open registration Continuwuity instances available. diff --git a/arch/conduwuit.service b/arch/conduwuit.service index f7100179..34c3995e 100644 --- a/arch/conduwuit.service +++ b/arch/conduwuit.service @@ -64,7 +64,7 @@ StateDirectory=conduwuit RuntimeDirectory=conduwuit RuntimeDirectoryMode=0750 -Environment=CONTINUWUITY_CONFIG=${CREDENTIALS_DIRECTORY}/config.toml +Environment=CONTINUWUITY_CONFIG=%d/config.toml LoadCredential=config.toml:/etc/conduwuit/conduwuit.toml BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit diff --git a/conduwuit-example.toml b/conduwuit-example.toml index 541050b1..f0e510b4 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -1696,6 +1696,10 @@ # #config_reload_signal = true +# This item is undocumented. Please contribute documentation for it. +# +#ldap = false + [global.tls] # Path to a valid TLS certificate file. @@ -1774,3 +1778,91 @@ # is 33.55MB. Setting it to 0 disables blurhashing. # #blurhash_max_raw_size = 33554432 + +[global.ldap] + +# Whether to enable LDAP login. +# +# example: "true" +# +#enable = false + +# Whether to force LDAP authentication or authorize classical password +# login. +# +# example: "true" +# +#ldap_only = false + +# URI of the LDAP server. +# +# example: "ldap://ldap.example.com:389" +# +#uri = "" + +# Root of the searches. +# +# example: "ou=users,dc=example,dc=org" +# +#base_dn = "" + +# Bind DN if anonymous search is not enabled. +# +# You can use the variable `{username}` that will be replaced by the +# entered username. In such case, the password used to bind will be the +# one provided for the login and not the one given by +# `bind_password_file`. Beware: automatically granting admin rights will +# not work if you use this direct bind instead of a LDAP search. +# +# example: "cn=ldap-reader,dc=example,dc=org" or +# "cn={username},ou=users,dc=example,dc=org" +# +#bind_dn = "" + +# Path to a file on the system that contains the password for the +# `bind_dn`. +# +# The server must be able to access the file, and it must not be empty. +# +#bind_password_file = "" + +# Search filter to limit user searches. +# +# You can use the variable `{username}` that will be replaced by the +# entered username for more complex filters. +# +# example: "(&(objectClass=person)(memberOf=matrix))" +# +#filter = "(objectClass=*)" + +# Attribute to use to uniquely identify the user. +# +# example: "uid" or "cn" +# +#uid_attribute = "uid" + +# Attribute containing the display name of the user. +# +# example: "givenName" or "sn" +# +#name_attribute = "givenName" + +# Root of the searches for admin users. +# +# Defaults to `base_dn` if empty. +# +# example: "ou=admins,dc=example,dc=org" +# +#admin_base_dn = "" + +# The LDAP search filter to find administrative users for continuwuity. +# +# If left blank, administrative state must be configured manually for each +# user. +# +# You can use the variable `{username}` that will be replaced by the +# entered username for more complex filters. +# +# example: "(objectClass=conduwuitAdmin)" or "(uid={username})" +# +#admin_filter = "" diff --git a/docker/Dockerfile b/docker/Dockerfile index bd6e72d1..55150902 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -78,7 +78,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ WORKDIR /app COPY ./rust-toolchain.toml . RUN rustc --version \ - && rustup target add $(xx-cargo --print-target-triple) + && xx-cargo --setup-target-triple # Build binary # We disable incremental compilation to save disk space, as it only produces a minimal speedup for this case. @@ -87,8 +87,10 @@ RUN echo "CARGO_INCREMENTAL=0" >> /etc/environment # Configure pkg-config RUN <> /etc/environment - echo "PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /etc/environment + if command -v "$(xx-info)-pkg-config" >/dev/null 2>/dev/null; then + echo "PKG_CONFIG_LIBDIR=/usr/lib/$(xx-info)/pkgconfig" >> /etc/environment + echo "PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /etc/environment + fi echo "PKG_CONFIG_ALLOW_CROSS=true" >> /etc/environment EOF @@ -109,16 +111,17 @@ RUN <> /etc/environment - echo "CXXFLAGS='${CXXFLAGS} -march=${TARGET_CPU}'" >> /etc/environment - echo "RUSTFLAGS='${RUSTFLAGS} -C target-cpu=${TARGET_CPU}'" >> /etc/environment - fi + set -o allexport + set -o xtrace + . /etc/environment + if [ -n "${TARGET_CPU}" ]; then + echo "CFLAGS='${CFLAGS} -march=${TARGET_CPU}'" >> /etc/environment + echo "CXXFLAGS='${CXXFLAGS} -march=${TARGET_CPU}'" >> /etc/environment + echo "RUSTFLAGS='${RUSTFLAGS} -C target-cpu=${TARGET_CPU}'" >> /etc/environment + fi EOF # Prepare output directories @@ -136,12 +139,12 @@ ARG TARGETPLATFORM RUN xx-cargo --print-target-triple # Conduwuit version info -ARG GIT_COMMIT_HASH= -ARG GIT_COMMIT_HASH_SHORT= -ARG GIT_REMOTE_URL= -ARG GIT_REMOTE_COMMIT_URL= -ARG CONDUWUIT_VERSION_EXTRA= -ARG CONTINUWUITY_VERSION_EXTRA= +ARG GIT_COMMIT_HASH +ARG GIT_COMMIT_HASH_SHORT +ARG GIT_REMOTE_URL +ARG GIT_REMOTE_COMMIT_URL +ARG CONDUWUIT_VERSION_EXTRA +ARG CONTINUWUITY_VERSION_EXTRA ENV GIT_COMMIT_HASH=$GIT_COMMIT_HASH ENV GIT_COMMIT_HASH_SHORT=$GIT_COMMIT_HASH_SHORT ENV GIT_REMOTE_URL=$GIT_REMOTE_URL @@ -169,7 +172,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name")) for BINARY in "${BINARIES[@]}"; do echo $BINARY - xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY + xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY cp $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY /out/sbin/$BINARY done EOF diff --git a/docker/musl.Dockerfile b/docker/musl.Dockerfile new file mode 100644 index 00000000..2221525b --- /dev/null +++ b/docker/musl.Dockerfile @@ -0,0 +1,200 @@ +# Why does this exist? +# Debian doesn't provide prebuilt musl packages +# rocksdb requires a prebuilt liburing, and linking fails if a gnu one is provided + +ARG RUST_VERSION=1 +ARG ALPINE_VERSION=3.22 + +FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx +FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-alpine${ALPINE_VERSION} AS base +FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-alpine${ALPINE_VERSION} AS toolchain + +# Install repo tools and dependencies +RUN --mount=type=cache,target=/etc/apk/cache apk add \ + build-base pkgconfig make jq bash \ + curl git file \ + llvm-dev clang clang-static lld + + +# Developer tool versions +# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall +ENV BINSTALL_VERSION=1.13.0 +# renovate: datasource=github-releases depName=psastras/sbom-rs +ENV CARGO_SBOM_VERSION=0.9.1 +# renovate: datasource=crate depName=lddtree +ENV LDDTREE_VERSION=0.3.7 + +# Install unpackaged tools +RUN <> /etc/environment + +# Configure pkg-config +RUN </dev/null 2>/dev/null; then + echo "PKG_CONFIG_LIBDIR=/usr/lib/$(xx-info)/pkgconfig" >> /etc/environment + echo "PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /etc/environment + fi + echo "PKG_CONFIG_ALLOW_CROSS=true" >> /etc/environment +EOF + +# Configure cc to use clang version +RUN <> /etc/environment + echo "CXX=clang++" >> /etc/environment +EOF + +# Cross-language LTO +RUN <> /etc/environment + echo "CXXFLAGS=-flto" >> /etc/environment + # Linker is set to target-compatible clang by xx + echo "RUSTFLAGS='-Clinker-plugin-lto -Clink-arg=-fuse-ld=lld'" >> /etc/environment +EOF + +# Apply CPU-specific optimizations if TARGET_CPU is provided +ARG TARGET_CPU + +RUN <> /etc/environment + echo "CXXFLAGS='${CXXFLAGS} -march=${TARGET_CPU}'" >> /etc/environment + echo "RUSTFLAGS='${RUSTFLAGS} -C target-cpu=${TARGET_CPU}'" >> /etc/environment + fi +EOF + +# Prepare output directories +RUN mkdir /out + +FROM toolchain AS builder + + +# Get source +COPY . . + +ARG TARGETPLATFORM + +# Verify environment configuration +RUN xx-cargo --print-target-triple + +# Conduwuit version info +ARG GIT_COMMIT_HASH +ARG GIT_COMMIT_HASH_SHORT +ARG GIT_REMOTE_URL +ARG GIT_REMOTE_COMMIT_URL +ARG CONDUWUIT_VERSION_EXTRA +ARG CONTINUWUITY_VERSION_EXTRA +ENV GIT_COMMIT_HASH=$GIT_COMMIT_HASH +ENV GIT_COMMIT_HASH_SHORT=$GIT_COMMIT_HASH_SHORT +ENV GIT_REMOTE_URL=$GIT_REMOTE_URL +ENV GIT_REMOTE_COMMIT_URL=$GIT_REMOTE_COMMIT_URL +ENV CONDUWUIT_VERSION_EXTRA=$CONDUWUIT_VERSION_EXTRA +ENV CONTINUWUITY_VERSION_EXTRA=$CONTINUWUITY_VERSION_EXTRA + +ARG RUST_PROFILE=release + +# Build the binary +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/app/target,id=cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-musl-${RUST_PROFILE} \ + bash <<'EOF' + set -o allexport + set -o xtrace + . /etc/environment + TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \ + jq -r ".target_directory")) + mkdir /out/sbin + PACKAGE=conduwuit + xx-cargo build --locked --profile ${RUST_PROFILE} \ + -p $PACKAGE --no-default-features --features bindgen-static,release_max_log_level,standard; + BINARIES=($(cargo metadata --no-deps --format-version 1 | \ + jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name")) + for BINARY in "${BINARIES[@]}"; do + echo $BINARY + xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY + cp $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY /out/sbin/$BINARY + done +EOF + +# Generate Software Bill of Materials (SBOM) +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + bash <<'EOF' + set -o xtrace + mkdir /out/sbom + typeset -A PACKAGES + for BINARY in /out/sbin/*; do + BINARY_BASE=$(basename ${BINARY}) + package=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.targets[] | select( .kind | map(. == \"bin\") | any ) | .name == \"$BINARY_BASE\") | .name") + if [ -z "$package" ]; then + continue + fi + PACKAGES[$package]=1 + done + for PACKAGE in $(echo ${!PACKAGES[@]}); do + echo $PACKAGE + cargo sbom --cargo-package $PACKAGE > /out/sbom/$PACKAGE.spdx.json + done +EOF + +# Extract dynamically linked dependencies +RUN < return Err!("Couldn't reset the password for user {user_id}: {e}"), | Ok(()) => { diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml index 15ada812..9b4ea460 100644 --- a/src/api/Cargo.toml +++ b/src/api/Cargo.toml @@ -49,6 +49,9 @@ jemalloc_stats = [ "conduwuit-core/jemalloc_stats", "conduwuit-service/jemalloc_stats", ] +ldap = [ + "conduwuit-service/ldap" +] release_max_log_level = [ "conduwuit-core/release_max_log_level", "conduwuit-service/release_max_log_level", diff --git a/src/api/client/account.rs b/src/api/client/account.rs index 7b8464b5..67dc2a01 100644 --- a/src/api/client/account.rs +++ b/src/api/client/account.rs @@ -373,7 +373,7 @@ pub(crate) async fn register_route( let password = if is_guest { None } else { body.password.as_deref() }; // Create user - services.users.create(&user_id, password)?; + services.users.create(&user_id, password, None).await?; // Default to pretty displayname let mut displayname = user_id.localpart().to_owned(); @@ -659,7 +659,8 @@ pub(crate) async fn change_password_route( services .users - .set_password(sender_user, Some(&body.new_password))?; + .set_password(sender_user, Some(&body.new_password)) + .await?; if body.logout_devices { // Logout all devices except the current one diff --git a/src/api/client/message.rs b/src/api/client/message.rs index bd26473d..4f027847 100644 --- a/src/api/client/message.rs +++ b/src/api/client/message.rs @@ -8,7 +8,7 @@ use conduwuit::{ ref_at, utils::{ IterStream, ReadyExt, - result::{FlatOk, LogErr}, + result::LogErr, stream::{BroadbandExt, TryIgnore, WidebandExt}, }, }; @@ -35,6 +35,7 @@ use ruma::{ }; use tracing::warn; +use super::utils::{count_to_token, parse_pagination_token as parse_token}; use crate::Ruma; /// list of safe and common non-state events to ignore if the user is ignored @@ -84,14 +85,14 @@ pub(crate) async fn get_message_events_route( let from: PduCount = body .from .as_deref() - .map(str::parse) + .map(parse_token) .transpose()? .unwrap_or_else(|| match body.dir { | Direction::Forward => PduCount::min(), | Direction::Backward => PduCount::max(), }); - let to: Option = body.to.as_deref().map(str::parse).flat_ok(); + let to: Option = body.to.as_deref().map(parse_token).transpose()?; let limit: usize = body .limit @@ -180,8 +181,8 @@ pub(crate) async fn get_message_events_route( .collect(); Ok(get_message_events::v3::Response { - start: from.to_string(), - end: next_token.as_ref().map(ToString::to_string), + start: count_to_token(from), + end: next_token.map(count_to_token), chunk, state, }) diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs index be54e65f..e4be20b7 100644 --- a/src/api/client/mod.rs +++ b/src/api/client/mod.rs @@ -36,6 +36,7 @@ pub(super) mod typing; pub(super) mod unstable; pub(super) mod unversioned; pub(super) mod user_directory; +pub(super) mod utils; pub(super) mod voip; pub(super) mod well_known; diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index ff328dec..ab259eee 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -90,7 +90,7 @@ pub(crate) async fn get_displayname_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None)?; + services.users.create(&body.user_id, None, None).await?; } services @@ -189,7 +189,7 @@ pub(crate) async fn get_avatar_url_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None)?; + services.users.create(&body.user_id, None, None).await?; } services @@ -248,7 +248,7 @@ pub(crate) async fn get_profile_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None)?; + services.users.create(&body.user_id, None, None).await?; } services diff --git a/src/api/client/relations.rs b/src/api/client/relations.rs index 52f1747f..4e791cf4 100644 --- a/src/api/client/relations.rs +++ b/src/api/client/relations.rs @@ -18,6 +18,7 @@ use ruma::{ events::{TimelineEventType, relation::RelationType}, }; +use super::utils::{count_to_token, parse_pagination_token as parse_token}; use crate::Ruma; /// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}` @@ -110,14 +111,14 @@ async fn paginate_relations_with_filter( dir: Direction, ) -> Result { let start: PduCount = from - .map(str::parse) + .map(parse_token) .transpose()? .unwrap_or_else(|| match dir { | Direction::Forward => PduCount::min(), | Direction::Backward => PduCount::max(), }); - let to: Option = to.map(str::parse).flat_ok(); + let to: Option = to.map(parse_token).transpose()?; // Use limit or else 30, with maximum 100 let limit: usize = limit @@ -129,6 +130,11 @@ async fn paginate_relations_with_filter( // Spec (v1.10) recommends depth of at least 3 let depth: u8 = if recurse { 3 } else { 1 }; + // Check if this is a thread request + let is_thread = filter_rel_type + .as_ref() + .is_some_and(|rel| *rel == RelationType::Thread); + let events: Vec<_> = services .rooms .pdu_metadata @@ -152,23 +158,58 @@ async fn paginate_relations_with_filter( .collect() .await; - let next_batch = match dir { - | Direction::Forward => events.last(), - | Direction::Backward => events.first(), + // For threads, check if we should include the root event + let mut root_event = None; + if is_thread && dir == Direction::Backward { + // Check if we've reached the beginning of the thread + // (fewer events than requested means we've exhausted the thread) + if events.len() < limit { + // Try to get the thread root event + if let Ok(root_pdu) = services.rooms.timeline.get_pdu(target).await { + // Check visibility + if services + .rooms + .state_accessor + .user_can_see_event(sender_user, room_id, target) + .await + { + // Store the root event to add to the response + root_event = Some(root_pdu); + } + } + } } - .map(at!(0)) - .as_ref() - .map(ToString::to_string); + + // Determine if there are more events to fetch + let has_more = if root_event.is_some() { + false // We've included the root, no more events + } else { + // Check if we got a full page of results (might be more) + events.len() >= limit + }; + + let next_batch = if has_more { + match dir { + | Direction::Forward => events.last(), + | Direction::Backward => events.first(), + } + .map(|(count, _)| count_to_token(*count)) + } else { + None + }; + + // Build the response chunk with thread root if needed + let chunk: Vec<_> = root_event + .into_iter() + .map(Event::into_format) + .chain(events.into_iter().map(at!(1)).map(Event::into_format)) + .collect(); Ok(get_relating_events::v1::Response { next_batch, prev_batch: from.map(Into::into), recursion_depth: recurse.then_some(depth.into()), - chunk: events - .into_iter() - .map(at!(1)) - .map(Event::into_format) - .collect(), + chunk, }) } diff --git a/src/api/client/session.rs b/src/api/client/session.rs index 992073c6..da7bed2c 100644 --- a/src/api/client/session.rs +++ b/src/api/client/session.rs @@ -3,13 +3,14 @@ use std::time::Duration; use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduwuit::{ - Err, Error, Result, debug, err, info, utils, - utils::{ReadyExt, hash}, + Err, Error, Result, debug, err, info, + utils::{self, ReadyExt, hash}, }; -use conduwuit_service::uiaa::SESSION_ID_LENGTH; +use conduwuit_core::{debug_error, debug_warn}; +use conduwuit_service::{Services, uiaa::SESSION_ID_LENGTH}; use futures::StreamExt; use ruma::{ - UserId, + OwnedUserId, UserId, api::client::{ session::{ get_login_token, @@ -49,6 +50,154 @@ pub(crate) async fn get_login_types_route( ])) } +/// Authenticates the given user by its ID and its password. +/// +/// Returns the user ID if successful, and an error otherwise. +#[tracing::instrument(skip_all, fields(%user_id), name = "password")] +pub(crate) async fn password_login( + services: &Services, + user_id: &UserId, + lowercased_user_id: &UserId, + password: &str, +) -> Result { + // Restrict login to accounts only of type 'password', including untyped + // legacy accounts which are equivalent to 'password'. + if services + .users + .origin(user_id) + .await + .is_ok_and(|origin| origin != "password") + { + return Err!(Request(Forbidden("Account does not permit password login."))); + } + + let (hash, user_id) = match services.users.password_hash(user_id).await { + | Ok(hash) => (hash, user_id), + | Err(_) => services + .users + .password_hash(lowercased_user_id) + .await + .map(|hash| (hash, lowercased_user_id)) + .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?, + }; + + if hash.is_empty() { + return Err!(Request(UserDeactivated("The user has been deactivated"))); + } + + hash::verify_password(password, &hash) + .inspect_err(|e| debug_error!("{e}")) + .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; + + Ok(user_id.to_owned()) +} + +/// Authenticates the given user through the configured LDAP server. +/// +/// Creates the user if the user is found in the LDAP and do not already have an +/// account. +#[tracing::instrument(skip_all, fields(%user_id), name = "ldap")] +pub(super) async fn ldap_login( + services: &Services, + user_id: &UserId, + lowercased_user_id: &UserId, + password: &str, +) -> Result { + let (user_dn, is_ldap_admin) = match services.config.ldap.bind_dn.as_ref() { + | Some(bind_dn) if bind_dn.contains("{username}") => + (bind_dn.replace("{username}", lowercased_user_id.localpart()), false), + | _ => { + debug!("Searching user in LDAP"); + + let dns = services.users.search_ldap(user_id).await?; + if dns.len() >= 2 { + return Err!(Ldap("LDAP search returned two or more results")); + } + + let Some((user_dn, is_admin)) = dns.first() else { + return password_login(services, user_id, lowercased_user_id, password).await; + }; + + (user_dn.clone(), *is_admin) + }, + }; + + let user_id = services + .users + .auth_ldap(&user_dn, password) + .await + .map(|()| lowercased_user_id.to_owned())?; + + // LDAP users are automatically created on first login attempt. This is a very + // common feature that can be seen on many services using a LDAP provider for + // their users (synapse, Nextcloud, Jellyfin, ...). + // + // LDAP users are crated with a dummy password but non empty because an empty + // password is reserved for deactivated accounts. The conduwuit password field + // will never be read to login a LDAP user so it's not an issue. + if !services.users.exists(lowercased_user_id).await { + services + .users + .create(lowercased_user_id, Some("*"), Some("ldap")) + .await?; + } + + let is_conduwuit_admin = services.admin.user_is_admin(lowercased_user_id).await; + + if is_ldap_admin && !is_conduwuit_admin { + services.admin.make_user_admin(lowercased_user_id).await?; + } else if !is_ldap_admin && is_conduwuit_admin { + services.admin.revoke_admin(lowercased_user_id).await?; + } + + Ok(user_id) +} + +pub(crate) async fn handle_login( + services: &Services, + body: &Ruma, + identifier: Option<&uiaa::UserIdentifier>, + password: &str, + user: Option<&String>, +) -> Result { + debug!("Got password login type"); + let user_id = + if let Some(uiaa::UserIdentifier::UserIdOrLocalpart(user_id)) = identifier { + UserId::parse_with_server_name(user_id, &services.config.server_name) + } else if let Some(user) = user { + UserId::parse_with_server_name(user, &services.config.server_name) + } else { + return Err!(Request(Unknown( + debug_warn!(?body.login_info, "Valid identifier or username was not provided (invalid or unsupported login type?)") + ))); + } + .map_err(|e| err!(Request(InvalidUsername(warn!("Username is invalid: {e}")))))?; + + let lowercased_user_id = UserId::parse_with_server_name( + user_id.localpart().to_lowercase(), + &services.config.server_name, + )?; + + if !services.globals.user_is_local(&user_id) + || !services.globals.user_is_local(&lowercased_user_id) + { + return Err!(Request(Unknown("User ID does not belong to this homeserver"))); + } + + if cfg!(feature = "ldap") && services.config.ldap.enable { + match Box::pin(ldap_login(services, &user_id, &lowercased_user_id, password)).await { + | Ok(user_id) => Ok(user_id), + | Err(err) if services.config.ldap.ldap_only => Err(err), + | Err(err) => { + debug_warn!("{err}"); + password_login(services, &user_id, &lowercased_user_id, password).await + }, + } + } else { + password_login(services, &user_id, &lowercased_user_id, password).await + } +} + /// # `POST /_matrix/client/v3/login` /// /// Authenticates the user and returns an access token it can use in subsequent @@ -80,70 +229,7 @@ pub(crate) async fn login_route( password, user, .. - }) => { - debug!("Got password login type"); - let user_id = - if let Some(uiaa::UserIdentifier::UserIdOrLocalpart(user_id)) = identifier { - UserId::parse_with_server_name(user_id, &services.config.server_name) - } else if let Some(user) = user { - UserId::parse_with_server_name(user, &services.config.server_name) - } else { - return Err!(Request(Unknown( - debug_warn!(?body.login_info, "Valid identifier or username was not provided (invalid or unsupported login type?)") - ))); - } - .map_err(|e| err!(Request(InvalidUsername(warn!("Username is invalid: {e}")))))?; - - let lowercased_user_id = UserId::parse_with_server_name( - user_id.localpart().to_lowercase(), - &services.config.server_name, - )?; - - if !services.globals.user_is_local(&user_id) - || !services.globals.user_is_local(&lowercased_user_id) - { - return Err!(Request(Unknown("User ID does not belong to this homeserver"))); - } - - // first try the username as-is - let hash = services - .users - .password_hash(&user_id) - .await - .inspect_err(|e| debug!("{e}")); - - match hash { - | Ok(hash) => { - if hash.is_empty() { - return Err!(Request(UserDeactivated("The user has been deactivated"))); - } - - hash::verify_password(password, &hash) - .inspect_err(|e| debug!("{e}")) - .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; - - user_id - }, - | Err(_e) => { - let hash_lowercased_user_id = services - .users - .password_hash(&lowercased_user_id) - .await - .inspect_err(|e| debug!("{e}")) - .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; - - if hash_lowercased_user_id.is_empty() { - return Err!(Request(UserDeactivated("The user has been deactivated"))); - } - - hash::verify_password(password, &hash_lowercased_user_id) - .inspect_err(|e| debug!("{e}")) - .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; - - lowercased_user_id - }, - } - }, + }) => handle_login(&services, &body, identifier.as_ref(), password, user.as_ref()).await?, | login::v3::LoginInfo::Token(login::v3::Token { token }) => { debug!("Got token login type"); if !services.server.config.login_via_existing_session { @@ -198,8 +284,8 @@ pub(crate) async fn login_route( .clone() .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into()); - // Generate a new token for the device - let token = utils::random_string(TOKEN_LENGTH); + // Generate a new token for the device (ensuring no collisions) + let token = services.users.generate_unique_token().await; // Determine if device_id was provided and exists in the db for this user let device_exists = if body.device_id.is_some() { diff --git a/src/api/client/sync/v4.rs b/src/api/client/sync/v4.rs index 14cd50d8..a16e4526 100644 --- a/src/api/client/sync/v4.rs +++ b/src/api/client/sync/v4.rs @@ -45,6 +45,7 @@ use crate::{ type TodoRooms = BTreeMap, usize, u64)>; const SINGLE_CONNECTION_SYNC: &str = "single_connection_sync"; +#[allow(clippy::cognitive_complexity)] /// POST `/_matrix/client/unstable/org.matrix.msc3575/sync` /// /// Sliding Sync endpoint (future endpoint: `/_matrix/client/v4/sync`) diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index 08f70975..f8703ff3 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -292,7 +292,7 @@ pub(crate) async fn get_timezone_key_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None)?; + services.users.create(&body.user_id, None, None).await?; } services @@ -352,7 +352,7 @@ pub(crate) async fn get_profile_key_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None)?; + services.users.create(&body.user_id, None, None).await?; } services diff --git a/src/api/client/utils.rs b/src/api/client/utils.rs new file mode 100644 index 00000000..cc941b95 --- /dev/null +++ b/src/api/client/utils.rs @@ -0,0 +1,28 @@ +use conduwuit::{ + Result, err, + matrix::pdu::{PduCount, ShortEventId}, +}; + +/// Parse a pagination token, trying ShortEventId first, then falling back to +/// PduCount +pub(crate) fn parse_pagination_token(token: &str) -> Result { + // Try parsing as ShortEventId first + if let Ok(shorteventid) = token.parse::() { + // ShortEventId maps directly to a PduCount in our database + Ok(PduCount::Normal(shorteventid)) + } else if let Ok(count) = token.parse::() { + // Fallback to PduCount for backwards compatibility + Ok(PduCount::Normal(count)) + } else if let Ok(count) = token.parse::() { + // Also handle negative counts for backfilled events + Ok(PduCount::from_signed(count)) + } else { + Err(err!(Request(InvalidParam("Invalid pagination token")))) + } +} + +/// Convert a PduCount to a token string (using the underlying ShortEventId) +pub(crate) fn count_to_token(count: PduCount) -> String { + // The PduCount's unsigned value IS the ShortEventId + count.into_unsigned().to_string() +} diff --git a/src/api/router/auth.rs b/src/api/router/auth.rs index 01254c32..44afc3ef 100644 --- a/src/api/router/auth.rs +++ b/src/api/router/auth.rs @@ -5,6 +5,14 @@ use axum_extra::{ typed_header::TypedHeaderRejectionReason, }; use conduwuit::{Err, Error, Result, debug_error, err, warn}; +use futures::{ + TryFutureExt, + future::{ + Either::{Left, Right}, + select_ok, + }, + pin_mut, +}; use ruma::{ CanonicalJsonObject, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId, api::{ @@ -54,17 +62,7 @@ pub(super) async fn auth( | None => request.query.access_token.as_deref(), }; - let token = if let Some(token) = token { - match services.appservice.find_from_token(token).await { - | Some(reg_info) => Token::Appservice(Box::new(reg_info)), - | _ => match services.users.find_from_token(token).await { - | Ok((user_id, device_id)) => Token::User((user_id, device_id)), - | _ => Token::Invalid, - }, - } - } else { - Token::None - }; + let token = find_token(services, token).await?; if metadata.authentication == AuthScheme::None { match metadata { @@ -342,3 +340,25 @@ async fn parse_x_matrix(request: &mut Request) -> Result { Ok(x_matrix) } + +async fn find_token(services: &Services, token: Option<&str>) -> Result { + let Some(token) = token else { + return Ok(Token::None); + }; + + let user_token = services.users.find_from_token(token).map_ok(Token::User); + + let appservice_token = services + .appservice + .find_from_token(token) + .map_ok(Box::new) + .map_ok(Token::Appservice); + + pin_mut!(user_token, appservice_token); + // Returns Ok if either token type succeeds, Err only if both fail + match select_ok([Left(user_token), Right(appservice_token)]).await { + | Err(e) if !e.is_not_found() => Err(e), + | Ok((token, _)) => Ok(token), + | _ => Ok(Token::Invalid), + } +} diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index aa021be7..e8518ed4 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -1,3 +1,4 @@ +#![allow(clippy::doc_link_with_quotes)] pub mod check; pub mod manager; pub mod proxy; @@ -1947,6 +1948,10 @@ pub struct Config { pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure: bool, + // external structure; separate section + #[serde(default)] + pub ldap: LdapConfig, + // external structure; separate section #[serde(default)] pub blurhashing: BlurhashConfig, @@ -2041,6 +2046,114 @@ pub struct BlurhashConfig { pub blurhash_max_raw_size: u64, } +#[derive(Clone, Debug, Default, Deserialize)] +#[config_example_generator(filename = "conduwuit-example.toml", section = "global.ldap")] +pub struct LdapConfig { + /// Whether to enable LDAP login. + /// + /// example: "true" + #[serde(default)] + pub enable: bool, + + /// Whether to force LDAP authentication or authorize classical password + /// login. + /// + /// example: "true" + #[serde(default)] + pub ldap_only: bool, + + /// URI of the LDAP server. + /// + /// example: "ldap://ldap.example.com:389" + /// + /// default: "" + #[serde(default)] + pub uri: Option, + + /// Root of the searches. + /// + /// example: "ou=users,dc=example,dc=org" + /// + /// default: "" + #[serde(default)] + pub base_dn: String, + + /// Bind DN if anonymous search is not enabled. + /// + /// You can use the variable `{username}` that will be replaced by the + /// entered username. In such case, the password used to bind will be the + /// one provided for the login and not the one given by + /// `bind_password_file`. Beware: automatically granting admin rights will + /// not work if you use this direct bind instead of a LDAP search. + /// + /// example: "cn=ldap-reader,dc=example,dc=org" or + /// "cn={username},ou=users,dc=example,dc=org" + /// + /// default: "" + #[serde(default)] + pub bind_dn: Option, + + /// Path to a file on the system that contains the password for the + /// `bind_dn`. + /// + /// The server must be able to access the file, and it must not be empty. + /// + /// default: "" + #[serde(default)] + pub bind_password_file: Option, + + /// Search filter to limit user searches. + /// + /// You can use the variable `{username}` that will be replaced by the + /// entered username for more complex filters. + /// + /// example: "(&(objectClass=person)(memberOf=matrix))" + /// + /// default: "(objectClass=*)" + #[serde(default = "default_ldap_search_filter")] + pub filter: String, + + /// Attribute to use to uniquely identify the user. + /// + /// example: "uid" or "cn" + /// + /// default: "uid" + #[serde(default = "default_ldap_uid_attribute")] + pub uid_attribute: String, + + /// Attribute containing the display name of the user. + /// + /// example: "givenName" or "sn" + /// + /// default: "givenName" + #[serde(default = "default_ldap_name_attribute")] + pub name_attribute: String, + + /// Root of the searches for admin users. + /// + /// Defaults to `base_dn` if empty. + /// + /// example: "ou=admins,dc=example,dc=org" + /// + /// default: "" + #[serde(default)] + pub admin_base_dn: String, + + /// The LDAP search filter to find administrative users for continuwuity. + /// + /// If left blank, administrative state must be configured manually for each + /// user. + /// + /// You can use the variable `{username}` that will be replaced by the + /// entered username for more complex filters. + /// + /// example: "(objectClass=conduwuitAdmin)" or "(uid={username})" + /// + /// default: "" + #[serde(default)] + pub admin_filter: String, +} + #[derive(Deserialize, Clone, Debug)] #[serde(transparent)] struct ListeningPort { @@ -2430,3 +2543,9 @@ pub(super) fn default_blurhash_x_component() -> u32 { 4 } pub(super) fn default_blurhash_y_component() -> u32 { 3 } // end recommended & blurhashing defaults + +fn default_ldap_search_filter() -> String { "(objectClass=*)".to_owned() } + +fn default_ldap_uid_attribute() -> String { String::from("uid") } + +fn default_ldap_name_attribute() -> String { String::from("givenName") } diff --git a/src/core/debug.rs b/src/core/debug.rs index 21a5ada4..c728278d 100644 --- a/src/core/debug.rs +++ b/src/core/debug.rs @@ -100,7 +100,7 @@ pub fn trap() { #[must_use] pub fn panic_str(p: &Box) -> &'static str { - p.downcast_ref::<&str>().copied().unwrap_or_default() + (**p).downcast_ref::<&str>().copied().unwrap_or_default() } #[inline(always)] diff --git a/src/core/error/mod.rs b/src/core/error/mod.rs index e46edf09..541af793 100644 --- a/src/core/error/mod.rs +++ b/src/core/error/mod.rs @@ -110,6 +110,8 @@ pub enum Error { InconsistentRoomState(&'static str, ruma::OwnedRoomId), #[error(transparent)] IntoHttp(#[from] ruma::api::error::IntoHttpError), + #[error("{0}")] + Ldap(Cow<'static, str>), #[error(transparent)] Mxc(#[from] ruma::MxcUriError), #[error(transparent)] diff --git a/src/core/info/room_version.rs b/src/core/info/room_version.rs index 2036594f..ae16ab85 100644 --- a/src/core/info/room_version.rs +++ b/src/core/info/room_version.rs @@ -17,13 +17,8 @@ pub const STABLE_ROOM_VERSIONS: &[RoomVersionId] = &[ ]; /// Experimental, partially supported room versions -pub const UNSTABLE_ROOM_VERSIONS: &[RoomVersionId] = &[ - RoomVersionId::V2, - RoomVersionId::V3, - RoomVersionId::V4, - RoomVersionId::V5, - RoomVersionId::V12, -]; +pub const UNSTABLE_ROOM_VERSIONS: &[RoomVersionId] = + &[RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5, RoomVersionId::V12]; type RoomVersion = (RoomVersionId, RoomVersionStability); diff --git a/src/database/Cargo.toml b/src/database/Cargo.toml index 55d4793f..9f51f366 100644 --- a/src/database/Cargo.toml +++ b/src/database/Cargo.toml @@ -44,6 +44,14 @@ zstd_compression = [ "conduwuit-core/zstd_compression", "rust-rocksdb/zstd", ] +bindgen-static = [ + # "bindgen/static" + # "clang-sys/static" + "rust-rocksdb/bindgen-static" +] +bindgen-runtime = [ + "rust-rocksdb/bindgen-runtime" +] [dependencies] async-channel.workspace = true diff --git a/src/database/maps.rs b/src/database/maps.rs index 214dbf34..da97ef45 100644 --- a/src/database/maps.rs +++ b/src/database/maps.rs @@ -374,6 +374,10 @@ pub(super) static MAPS: &[Descriptor] = &[ name: "userid_masterkeyid", ..descriptor::RANDOM_SMALL }, + Descriptor { + name: "userid_origin", + ..descriptor::RANDOM + }, Descriptor { name: "userid_password", ..descriptor::RANDOM diff --git a/src/main/Cargo.toml b/src/main/Cargo.toml index cddf4156..eafa1e48 100644 --- a/src/main/Cargo.toml +++ b/src/main/Cargo.toml @@ -43,6 +43,11 @@ assets = [ [features] default = [ + "standard", + "release_max_log_level", + "bindgen-runtime", # replace with bindgen-static on alpine +] +standard = [ "blurhashing", "brotli_compression", "element_hacks", @@ -51,11 +56,19 @@ default = [ "jemalloc", "jemalloc_conf", "journald", + "ldap", "media_thumbnail", - "release_max_log_level", "systemd", "url_preview", - "zstd_compression", + "zstd_compression" +] +full = [ + "standard", + # "hardened_malloc", # Conflicts with jemalloc + "jemalloc_prof", + "perf_measurements", + "tokio_console" + # sentry_telemetry ] blurhashing = [ @@ -102,6 +115,9 @@ jemalloc_stats = [ jemalloc_conf = [ "conduwuit-core/jemalloc_conf", ] +ldap = [ + "conduwuit-api/ldap", +] media_thumbnail = [ "conduwuit-service/media_thumbnail", ] @@ -161,6 +177,18 @@ zstd_compression = [ conduwuit_mods = [ "conduwuit-core/conduwuit_mods", ] +bindgen-static = [ + # "bindgen/static" + # "clang-sys/static" + "conduwuit-database/bindgen-static" +] +bindgen-runtime = [ + "conduwuit-database/bindgen-runtime" +] + +[build-dependencies] +# bindgen = {version = "0.71.1", default-features = false} +# clang-sys = {version = "1", default-features = false} [dependencies] conduwuit-admin.workspace = true diff --git a/src/router/serve/unix.rs b/src/router/serve/unix.rs index 2af17274..9bb3dd6e 100644 --- a/src/router/serve/unix.rs +++ b/src/router/serve/unix.rs @@ -30,7 +30,7 @@ use tower::{Service, ServiceExt}; type MakeService = IntoMakeServiceWithConnectInfo; -const NULL_ADDR: net::SocketAddr = net::SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); +const NULL_ADDR: net::SocketAddr = net::SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); const FINI_POLL_INTERVAL: Duration = Duration::from_millis(750); #[tracing::instrument(skip_all, level = "debug")] diff --git a/src/service/Cargo.toml b/src/service/Cargo.toml index fdebd1d7..6e538f40 100644 --- a/src/service/Cargo.toml +++ b/src/service/Cargo.toml @@ -53,6 +53,9 @@ jemalloc_stats = [ "conduwuit-core/jemalloc_stats", "conduwuit-database/jemalloc_stats", ] +ldap = [ + "dep:ldap3" +] media_thumbnail = [ "dep:image", ] @@ -89,6 +92,8 @@ image.workspace = true image.optional = true ipaddress.workspace = true itertools.workspace = true +ldap3.workspace = true +ldap3.optional = true log.workspace = true loole.workspace = true lru-cache.workspace = true diff --git a/src/service/admin/create.rs b/src/service/admin/create.rs index 0b921749..913acc2d 100644 --- a/src/service/admin/create.rs +++ b/src/service/admin/create.rs @@ -38,7 +38,7 @@ pub async fn create_admin_room(services: &Services) -> Result { // Create a user for the server let server_user = services.globals.server_user.as_ref(); - services.users.create(server_user, None)?; + services.users.create(server_user, None, None).await?; let create_content = { use RoomVersionId::*; diff --git a/src/service/appservice/mod.rs b/src/service/appservice/mod.rs index 7be8a471..adbf3b6e 100644 --- a/src/service/appservice/mod.rs +++ b/src/service/appservice/mod.rs @@ -4,14 +4,14 @@ mod registration_info; use std::{collections::BTreeMap, iter::IntoIterator, sync::Arc}; use async_trait::async_trait; -use conduwuit::{Result, err, utils::stream::IterStream}; +use conduwuit::{Err, Result, err, utils::stream::IterStream}; use database::Map; use futures::{Future, FutureExt, Stream, TryStreamExt}; use ruma::{RoomAliasId, RoomId, UserId, api::appservice::Registration}; use tokio::sync::{RwLock, RwLockReadGuard}; pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo}; -use crate::{Dep, sending}; +use crate::{Dep, globals, sending, users}; pub struct Service { registration_info: RwLock, @@ -20,7 +20,9 @@ pub struct Service { } struct Services { + globals: Dep, sending: Dep, + users: Dep, } struct Data { @@ -35,7 +37,9 @@ impl crate::Service for Service { Ok(Arc::new(Self { registration_info: RwLock::new(BTreeMap::new()), services: Services { + globals: args.depend::("globals"), sending: args.depend::("sending"), + users: args.depend::("users"), }, db: Data { id_appserviceregistrations: args.db["id_appserviceregistrations"].clone(), @@ -44,23 +48,93 @@ impl crate::Service for Service { } async fn worker(self: Arc) -> Result { - // Inserting registrations into cache - self.iter_db_ids() - .try_for_each(async |appservice| { - self.registration_info - .write() - .await - .insert(appservice.0, appservice.1.try_into()?); + // First, collect all appservices to check for token conflicts + let appservices: Vec<(String, Registration)> = self.iter_db_ids().try_collect().await?; - Ok(()) - }) - .await + // Check for appservice-to-appservice token conflicts + for i in 0..appservices.len() { + for j in i.saturating_add(1)..appservices.len() { + if appservices[i].1.as_token == appservices[j].1.as_token { + return Err!(Database(error!( + "Token collision detected: Appservices '{}' and '{}' have the same token", + appservices[i].0, appservices[j].0 + ))); + } + } + } + + // Process each appservice + for (id, registration) in appservices { + // During startup, resolve any token collisions in favour of appservices + // by logging out conflicting user devices + if let Ok((user_id, device_id)) = self + .services + .users + .find_from_token(®istration.as_token) + .await + { + conduwuit::warn!( + "Token collision detected during startup: Appservice '{}' token was also \ + used by user '{}' device '{}'. Logging out the user device to resolve \ + conflict.", + id, + user_id.localpart(), + device_id + ); + + self.services + .users + .remove_device(&user_id, &device_id) + .await; + } + + self.start_appservice(id, registration).await?; + } + + Ok(()) } fn name(&self) -> &str { crate::service::make_name(std::module_path!()) } } impl Service { + /// Starts an appservice, ensuring its sender_localpart user exists and is + /// active. Creates the user if it doesn't exist, or reactivates it if it + /// was deactivated. Then registers the appservice in memory for request + /// handling. + async fn start_appservice(&self, id: String, registration: Registration) -> Result { + let appservice_user_id = UserId::parse_with_server_name( + registration.sender_localpart.as_str(), + self.services.globals.server_name(), + )?; + + if !self.services.users.exists(&appservice_user_id).await { + self.services + .users + .create(&appservice_user_id, None, None) + .await?; + } else if self + .services + .users + .is_deactivated(&appservice_user_id) + .await + .unwrap_or(false) + { + // Reactivate the appservice user if it was accidentally deactivated + self.services + .users + .set_password(&appservice_user_id, None) + .await?; + } + + self.registration_info + .write() + .await + .insert(id, registration.try_into()?); + + Ok(()) + } + /// Registers an appservice and returns the ID to the caller pub async fn register_appservice( &self, @@ -68,15 +142,40 @@ impl Service { appservice_config_body: &str, ) -> Result { //TODO: Check for collisions between exclusive appservice namespaces - self.registration_info - .write() + + // Check for token collision with other appservices (allow re-registration of + // same appservice) + if let Ok(existing) = self.find_from_token(®istration.as_token).await { + if existing.registration.id != registration.id { + return Err(err!(Request(InvalidParam( + "Cannot register appservice: Token is already used by appservice '{}'. \ + Please generate a different token.", + existing.registration.id + )))); + } + } + + // Prevent token collision with existing user tokens + if self + .services + .users + .find_from_token(®istration.as_token) .await - .insert(registration.id.clone(), registration.clone().try_into()?); + .is_ok() + { + return Err(err!(Request(InvalidParam( + "Cannot register appservice: The provided token is already in use by a user \ + device. Please generate a different token for the appservice." + )))); + } self.db .id_appserviceregistrations .insert(®istration.id, appservice_config_body); + self.start_appservice(registration.id.clone(), registration.clone()) + .await?; + Ok(()) } @@ -113,12 +212,14 @@ impl Service { .map(|info| info.registration) } - pub async fn find_from_token(&self, token: &str) -> Option { + /// Returns Result to match users::find_from_token for select_ok usage + pub async fn find_from_token(&self, token: &str) -> Result { self.read() .await .values() .find(|info| info.registration.as_token == token) .cloned() + .ok_or_else(|| err!(Request(NotFound("Appservice token not found")))) } /// Checks if a given user id matches any exclusive appservice regex diff --git a/src/service/emergency/mod.rs b/src/service/emergency/mod.rs index 3a61f710..f8ecbb3e 100644 --- a/src/service/emergency/mod.rs +++ b/src/service/emergency/mod.rs @@ -41,6 +41,11 @@ impl crate::Service for Service { return Ok(()); } + if self.services.config.ldap.enable { + warn!("emergency password feature not available with LDAP enabled."); + return Ok(()); + } + self.set_emergency_access().await.inspect_err(|e| { error!("Could not set the configured emergency password for the server user: {e}"); }) @@ -57,7 +62,8 @@ impl Service { self.services .users - .set_password(server_user, self.services.config.emergency_password.as_deref())?; + .set_password(server_user, self.services.config.emergency_password.as_deref()) + .await?; let (ruleset, pwd_set) = match self.services.config.emergency_password { | Some(_) => (Ruleset::server_default(server_user), true), diff --git a/src/service/rooms/pdu_metadata/data.rs b/src/service/rooms/pdu_metadata/data.rs index c1376cb0..a746b4cc 100644 --- a/src/service/rooms/pdu_metadata/data.rs +++ b/src/service/rooms/pdu_metadata/data.rs @@ -61,9 +61,12 @@ impl Data { from: PduCount, dir: Direction, ) -> impl Stream + Send + '_ { + // Query from exact position then filter excludes it (saturating_inc could skip + // events at min/max boundaries) + let from_unsigned = from.into_unsigned(); let mut current = ArrayVec::::new(); current.extend(target.to_be_bytes()); - current.extend(from.saturating_inc(dir).into_unsigned().to_be_bytes()); + current.extend(from_unsigned.to_be_bytes()); let current = current.as_slice(); match dir { | Direction::Forward => self.tofrom_relation.raw_keys_from(current).boxed(), @@ -73,6 +76,17 @@ impl Data { .ready_take_while(move |key| key.starts_with(&target.to_be_bytes())) .map(|to_from| u64_from_u8(&to_from[8..16])) .map(PduCount::from_unsigned) + .ready_filter(move |count| { + if from == PduCount::min() || from == PduCount::max() { + true + } else { + let count_unsigned = count.into_unsigned(); + match dir { + | Direction::Forward => count_unsigned > from_unsigned, + | Direction::Backward => count_unsigned < from_unsigned, + } + } + }) .wide_filter_map(move |shorteventid| async move { let pdu_id: RawPduId = PduId { shortroomid, shorteventid }.into(); diff --git a/src/service/rooms/state_cache/update.rs b/src/service/rooms/state_cache/update.rs index 32c67947..86c1afe7 100644 --- a/src/service/rooms/state_cache/update.rs +++ b/src/service/rooms/state_cache/update.rs @@ -49,7 +49,7 @@ pub async fn update_membership( #[allow(clippy::collapsible_if)] if !self.services.globals.user_is_local(user_id) { if !self.services.users.exists(user_id).await { - self.services.users.create(user_id, None)?; + self.services.users.create(user_id, None, None).await?; } } diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index d2dfccd9..fff1661c 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -1,11 +1,19 @@ +#[cfg(feature = "ldap")] +use std::collections::HashMap; use std::{collections::BTreeMap, mem, sync::Arc}; +#[cfg(feature = "ldap")] +use conduwuit::result::LogErr; use conduwuit::{ - Err, Error, Result, Server, at, debug_warn, err, trace, + Err, Error, Result, Server, at, debug_warn, err, is_equal_to, trace, utils::{self, ReadyExt, stream::TryIgnore, string::Unquoted}, }; +#[cfg(feature = "ldap")] +use conduwuit_core::{debug, error}; use database::{Deserialized, Ignore, Interfix, Json, Map}; use futures::{Stream, StreamExt, TryFutureExt}; +#[cfg(feature = "ldap")] +use ldap3::{LdapConnAsync, Scope, SearchEntry}; use ruma::{ DeviceId, KeyId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OneTimeKeyId, OneTimeKeyName, OwnedDeviceId, OwnedKeyId, OwnedMxcUri, OwnedUserId, RoomId, UInt, UserId, @@ -19,7 +27,7 @@ use ruma::{ use serde::{Deserialize, Serialize}; use serde_json::json; -use crate::{Dep, account_data, admin, globals, rooms}; +use crate::{Dep, account_data, admin, appservice, globals, rooms}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserSuspension { @@ -40,6 +48,7 @@ struct Services { server: Arc, account_data: Dep, admin: Dep, + appservice: Dep, globals: Dep, state_accessor: Dep, state_cache: Dep, @@ -62,6 +71,7 @@ struct Data { userid_displayname: Arc, userid_lastonetimekeyupdate: Arc, userid_masterkeyid: Arc, + userid_origin: Arc, userid_password: Arc, userid_suspension: Arc, userid_selfsigningkeyid: Arc, @@ -76,6 +86,7 @@ impl crate::Service for Service { server: args.server.clone(), account_data: args.depend::("account_data"), admin: args.depend::("admin"), + appservice: args.depend::("appservice"), globals: args.depend::("globals"), state_accessor: args .depend::("rooms::state_accessor"), @@ -98,6 +109,7 @@ impl crate::Service for Service { userid_displayname: args.db["userid_displayname"].clone(), userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(), userid_masterkeyid: args.db["userid_masterkeyid"].clone(), + userid_origin: args.db["userid_origin"].clone(), userid_password: args.db["userid_password"].clone(), userid_suspension: args.db["userid_suspension"].clone(), userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(), @@ -134,9 +146,21 @@ impl Service { } /// Create a new user account on this homeserver. + /// + /// User origin is by default "password" (meaning that it will login using + /// its user_id/password). Users with other origins (currently only "ldap" + /// is available) have special login processes. #[inline] - pub fn create(&self, user_id: &UserId, password: Option<&str>) -> Result<()> { - self.set_password(user_id, password) + pub async fn create( + &self, + user_id: &UserId, + password: Option<&str>, + origin: Option<&str>, + ) -> Result<()> { + self.db + .userid_origin + .insert(user_id, origin.unwrap_or("password")); + self.set_password(user_id, password).await } /// Deactivate account @@ -150,7 +174,7 @@ impl Service { // result in an empty string, so the user will not be able to log in again. // Systems like changing the password without logging in should check if the // account is deactivated. - self.set_password(user_id, None)?; + self.set_password(user_id, None).await?; // TODO: Unhook 3PID Ok(()) @@ -251,13 +275,34 @@ impl Service { .ready_filter_map(|(u, p): (&UserId, &[u8])| (!p.is_empty()).then_some(u)) } + /// Returns the origin of the user (password/LDAP/...). + pub async fn origin(&self, user_id: &UserId) -> Result { + self.db.userid_origin.get(user_id).await.deserialized() + } + /// Returns the password hash for the given user. pub async fn password_hash(&self, user_id: &UserId) -> Result { self.db.userid_password.get(user_id).await.deserialized() } /// Hash and set the user's password to the Argon2 hash - pub fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> { + pub async fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> { + // Cannot change the password of a LDAP user. There are two special cases : + // - a `None` password can be used to deactivate a LDAP user + // - a "*" password is used as the default password of an active LDAP user + if cfg!(feature = "ldap") + && password.is_some_and(|pwd| pwd != "*") + && self + .db + .userid_origin + .get(user_id) + .await + .deserialized::() + .is_ok_and(is_equal_to!("ldap")) + { + return Err!(Request(InvalidParam("Cannot change password of a LDAP user"))); + } + password .map(utils::hash::password) .transpose() @@ -391,6 +436,31 @@ impl Service { self.db.userdeviceid_token.qry(&key).await.deserialized() } + /// Generate a unique access token that doesn't collide with existing tokens + pub async fn generate_unique_token(&self) -> String { + loop { + let token = utils::random_string(32); + + // Check for collision with appservice tokens + if self + .services + .appservice + .find_from_token(&token) + .await + .is_ok() + { + continue; + } + + // Check for collision with user tokens + if self.db.token_userdeviceid.get(&token).await.is_ok() { + continue; + } + + return token; + } + } + /// Replaces the access token of one device. pub async fn set_token( &self, @@ -407,6 +477,19 @@ impl Service { ))); } + // Check for token collision with appservices + if self + .services + .appservice + .find_from_token(token) + .await + .is_ok() + { + return Err!(Request(InvalidParam( + "Token conflicts with an existing appservice token" + ))); + } + // Remove old token if let Ok(old_token) = self.db.userdeviceid_token.qry(&key).await { self.db.token_userdeviceid.remove(&old_token); @@ -1092,6 +1175,154 @@ impl Service { self.db.useridprofilekey_value.del(key); } } + + #[cfg(not(feature = "ldap"))] + pub async fn search_ldap(&self, _user_id: &UserId) -> Result> { + Err!(FeatureDisabled("ldap")) + } + + #[cfg(feature = "ldap")] + pub async fn search_ldap(&self, user_id: &UserId) -> Result> { + let localpart = user_id.localpart().to_owned(); + let lowercased_localpart = localpart.to_lowercase(); + + let config = &self.services.server.config.ldap; + let uri = config + .uri + .as_ref() + .ok_or_else(|| err!(Ldap(error!("LDAP URI is not configured."))))?; + + debug!(?uri, "LDAP creating connection..."); + let (conn, mut ldap) = LdapConnAsync::new(uri.as_str()) + .await + .map_err(|e| err!(Ldap(error!(?user_id, "LDAP connection setup error: {e}"))))?; + + let driver = self.services.server.runtime().spawn(async move { + match conn.drive().await { + | Err(e) => error!("LDAP connection error: {e}"), + | Ok(()) => debug!("LDAP connection completed."), + } + }); + + match (&config.bind_dn, &config.bind_password_file) { + | (Some(bind_dn), Some(bind_password_file)) => { + let bind_pw = String::from_utf8(std::fs::read(bind_password_file)?)?; + ldap.simple_bind(bind_dn, bind_pw.trim()) + .await + .and_then(ldap3::LdapResult::success) + .map_err(|e| err!(Ldap(error!("LDAP bind error: {e}"))))?; + }, + | (..) => {}, + } + + let attr = [&config.uid_attribute, &config.name_attribute]; + + let user_filter = &config.filter.replace("{username}", &lowercased_localpart); + + let (entries, _result) = ldap + .search(&config.base_dn, Scope::Subtree, user_filter, &attr) + .await + .and_then(ldap3::SearchResult::success) + .inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Search")) + .map_err(|e| err!(Ldap(error!(?attr, ?user_filter, "LDAP search error: {e}"))))?; + + let mut dns: HashMap = entries + .into_iter() + .filter_map(|entry| { + let search_entry = SearchEntry::construct(entry); + debug!(?search_entry, "LDAP search entry"); + search_entry + .attrs + .get(&config.uid_attribute) + .into_iter() + .chain(search_entry.attrs.get(&config.name_attribute)) + .any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart)) + .then_some((search_entry.dn, false)) + }) + .collect(); + + if !config.admin_filter.is_empty() { + let admin_base_dn = if config.admin_base_dn.is_empty() { + &config.base_dn + } else { + &config.admin_base_dn + }; + + let admin_filter = &config + .admin_filter + .replace("{username}", &lowercased_localpart); + + let (admin_entries, _result) = ldap + .search(admin_base_dn, Scope::Subtree, admin_filter, &attr) + .await + .and_then(ldap3::SearchResult::success) + .inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Admin Search")) + .map_err(|e| { + err!(Ldap(error!(?attr, ?admin_filter, "Ldap admin search error: {e}"))) + })?; + + dns.extend(admin_entries.into_iter().filter_map(|entry| { + let search_entry = SearchEntry::construct(entry); + debug!(?search_entry, "LDAP search entry"); + search_entry + .attrs + .get(&config.uid_attribute) + .into_iter() + .chain(search_entry.attrs.get(&config.name_attribute)) + .any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart)) + .then_some((search_entry.dn, true)) + })); + } + + ldap.unbind() + .await + .map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?; + + driver.await.log_err().ok(); + + Ok(dns.drain().collect()) + } + + #[cfg(not(feature = "ldap"))] + pub async fn auth_ldap(&self, _user_dn: &str, _password: &str) -> Result { + Err!(FeatureDisabled("ldap")) + } + + #[cfg(feature = "ldap")] + pub async fn auth_ldap(&self, user_dn: &str, password: &str) -> Result { + let config = &self.services.server.config.ldap; + let uri = config + .uri + .as_ref() + .ok_or_else(|| err!(Ldap(error!("LDAP URI is not configured."))))?; + + debug!(?uri, "LDAP creating connection..."); + let (conn, mut ldap) = LdapConnAsync::new(uri.as_str()) + .await + .map_err(|e| err!(Ldap(error!(?user_dn, "LDAP connection setup error: {e}"))))?; + + let driver = self.services.server.runtime().spawn(async move { + match conn.drive().await { + | Err(e) => error!("LDAP connection error: {e}"), + | Ok(()) => debug!("LDAP connection completed."), + } + }); + + ldap.simple_bind(user_dn, password) + .await + .and_then(ldap3::LdapResult::success) + .map_err(|e| { + err!(Request(Forbidden(debug_error!("LDAP authentication error: {e}")))) + })?; + + ldap.unbind() + .await + .map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?; + + driver.await.log_err().ok(); + + Ok(()) + } } pub fn parse_master_key( diff --git a/tests/cargo_smoke.sh b/tests/cargo_smoke.sh index 946790c3..e8bb5512 100755 --- a/tests/cargo_smoke.sh +++ b/tests/cargo_smoke.sh @@ -48,7 +48,7 @@ vector () { VECTOR_OPTS=$@ element "$TOOLCHAIN" $VECTOR_OPTS --no-default-features element "$TOOLCHAIN" $VECTOR_OPTS --features=default - element "$TOOLCHAIN" $VECTOR_OPTS --all-features + element "$TOOLCHAIN" $VECTOR_OPTS --features full } matrix () {