Compare commits

..

63 Commits

Author SHA1 Message Date
strawberry
fe637f481d ci: fix incorrect startsWith syntax
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-11 14:29:00 -04:00
strawberry
18e43e1d35 Reapply "bump various deps"
This reverts commit 6b918966d4.
2024-05-10 22:56:44 -04:00
strawberry
09fca89ac5 Revert "rocksdb: enable async_io if using io_uring feature"
This reverts commit 6266e0ab5e.
2024-05-10 22:56:44 -04:00
morguldir
9f19a2025d Revert "feat(membership): check if user already has the membership that is requested to be set"
This reverts commit 321a6ca0fe.

These checks were not working as intended, resulting in the unban button not working

The join check gets kept since it slightly reduces the amount of sent joins in some cases
This check will probably be replaced soon for a more universal solution to the "made no change" issue

Signed-off-by: morguldir <morguldir@protonmail.com>
2024-05-10 22:52:44 -04:00
strawberry
6b918966d4 Revert "bump various deps"
This reverts commit 653ec3799e.
2024-05-09 22:38:05 -04:00
strawberry
328502c1cd dont send avatar url or display name for ban membership events
the display name or avatar may be offensive

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 17:44:15 -04:00
strawberry
d15e461303 config option to auto-remediate bad users joining bad rooms or servers
also forgets all rooms upon leave_all_rooms

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 17:44:15 -04:00
strawberry
6946eead28 pin rust-rocksdb to before snappy update
it seems to break nix builds

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 17:42:05 -04:00
strawberry
09d3240365 bump conduwuit version to 0.3.3
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 17:42:05 -04:00
strawberry
653ec3799e bump various deps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 17:42:05 -04:00
strawberry
6de9f52d5a docs: update differences.md
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 12:34:50 -04:00
strawberry
484e7d1d2a docs: add my selfhosted forgejo mirror
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 12:34:50 -04:00
strawberry
dfa01541b3 docs: transfem.dev has rules
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 12:34:50 -04:00
strawberry
adbe9268ce docs: add troubleshooting, maintenance, various improvements and fixes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 12:34:50 -04:00
strawberry
3504e6e724 fix broken reports
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 12:33:46 -04:00
strawberry
154b2ab490 media: additional sanitisation on the Content-Disposition filename
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 09:53:04 -04:00
strawberry
2231ccf118 return inline Content-Disposition based on the detected file type (e.g. image/video)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 09:53:04 -04:00
strawberry
d4d9f92ade add security response HTTP headers if not present
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-09 09:53:04 -04:00
renovate[bot]
e4e1636da8 chore(deps): update aquasecurity/trivy-action action to v0.20.0 2024-05-08 15:06:45 -04:00
strawberry
e99aac9550 ci: fix gitlab container registry destination
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-08 15:04:59 -04:00
strawberry
ddb87168ed update gitlab repo link
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-08 15:04:59 -04:00
strawberry
245c34e659 ci: dont run docker publishing if none of the usernames are set
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-07 23:59:05 -04:00
strawberry
43b07be3fc ci: use PR author instead of branch name for docker image publishing
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-07 02:44:55 -04:00
strawberry
99d98efeb1 ci: fix docker publishing typo
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 13:05:00 -04:00
strawberry
7b25ef2e6c make next_batch token a variable in search, revert threads_until change
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
1f8a7a707c nix: cache complement outputs using nix-build-and-cache
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
86ec20e787 docs: remove last dev branch mention
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
Charles Hall
8c21388f01 fix nix-build-and-cache
Now it actually caches everything.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
d657fa32e9 ci: format string
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
321e197d8c correct arithmetic adjustments
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
16a98b0683 ci: push docker images for PRs in the merge-PR_NUMBER-HEAD_REF format, fix main pushes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
9e1bbc1650 ci: run on new tag pushes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
91ff6a36a4 ci: abort workflow if latest repo tag does not match with running tag ref
protects against a maintainer creating a downgrading version tag, and
uploading artifacts with that version

this check is only ran via workflow dispatch on the tag

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
56f1d8be1f ci(docker): publish latest only if ref starts with our tag format
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
ed60f189cc docs: remove dev docker images
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
cabf4362be docs: direct all PRs to main
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
2472c7c47a ci: don't run on dev anymore, run on main and non-draft PRs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
Xiretza
136cb038cf auth_chain: add useful debug logging 2024-05-06 03:45:10 -04:00
Xiretza
8f89be0fbd utils: add helper for adding unbounded slices to tracing spans 2024-05-06 03:45:10 -04:00
Xiretza
bbdced9c90 Fix appservice namespace check for room aliases
Only normal users should be prevented from creating an alias within an
exclusive namespace, not the appservice itself. This mirrors the
behaviour in api/client_server/room.rs on room creation.
2024-05-06 03:45:10 -04:00
strawberry
a6f4dc2b74 engage(lychee): check all markdown files too, enable verbose mode
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
df203fa244 add a contributing guide
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
c6e6eb0af3 ignore empty CONDUWUIT_VERSION_EXTRA for server version
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
29babebc4d adminroom: add count to list-joined-rooms user command
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
Matt Moriarity
2f3194840c fix extra version when using flake-compat 2024-05-06 03:45:10 -04:00
strawberry
0ebb323490 resolve almost all as_conversions lints
may need further opinion from others on these

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
f8e1255994 presence: set empty string status msg to None
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
b5c0c30a5e resolve half of the integer_arithmetic lints, couple misc changes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
ac4590952b set io_uring for rocksdb a default feature
this was already enabled by default by rocksdb technically, but
it wasn't building with it properly.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
67569cb9c8 nix: switch to fork of rocksdb input
db6df0b185
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
11ec0dff4f add PATCH to list of allowed HTTP methods in CORS (MSC4138)
https://github.com/matrix-org/matrix-spec-proposals/pull/4138

we already had HEAD

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
a198f0481a nix: add liburing to devshell
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
6266e0ab5e rocksdb: enable async_io if using io_uring feature
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
9ee1485960 enable overflow-checks for dev/debug profile
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
05314ec46c nix: set hardcoded NIX_OUTPATH_USED_AS_RANDOM_SEED for bindgen
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
b66d2d44d0 chore: bump MSRV to 1.77.0 as 1.78.0 came out
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
3b2db9027a envrc: allow loading env vars from .env if it exists
from ffd479d66f

This is primarily useful for replicating the environment from CI so that
the `nix-build-and-cache` script is easier to invoke.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
97e81885db use dep: syntax in cargo.toml features
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
706c1c993b nix: don't run cargo test for crane buildpackage
CI does this already

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-06 03:45:10 -04:00
strawberry
cb70d51e2b bump conduwuit version to 0.3.2
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-05 16:52:51 -04:00
strawberry
bfb827a418 send Cache-Control and CORS header for remote thumbnail responses
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-05 16:28:21 -04:00
strawberry
e2fb588a8c sent attachment content-disposition on thumbnails too
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-05 16:28:21 -04:00
strawberry
43c4dfc5df set content-disposition to attachment instead of inline
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-05 16:28:21 -04:00
88 changed files with 1404 additions and 861 deletions

2
.envrc
View File

@@ -3,3 +3,5 @@
use flake
PATH_add bin
dotenv_if_exists

View File

@@ -14,10 +14,10 @@ on:
- 'docs/**'
- 'debian/**'
- 'docker/**'
- 'test_results/**'
branches:
- main
- dev
tags:
- '*'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -48,6 +48,18 @@ jobs:
- name: Sync repository
uses: actions/checkout@v4
- name: Tag comparison check
if: startsWith(github.ref, 'refs/tags/v')
run: |
# Tag mismatch with latest repo tag check to prevent potential downgrades
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
if [ $LATEST_TAG != ${{ github.ref_name }} ]; then
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
@@ -138,7 +150,7 @@ jobs:
name: Build
runs-on: ubuntu-latest
needs: tests
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false)
strategy:
matrix:
include:
@@ -223,20 +235,21 @@ jobs:
name: Docker publish
runs-on: ubuntu-latest
needs: build
if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && github.event_name != 'pull_request'
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '')
env:
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
DOCKER_TAG: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
GLCR_ARM64: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
GLCR_AMD64: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
GLCR_TAG: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
GLCR_BRANCH: registry.gitlab.com/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
DOCKER_TAG: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GLCR_ARM64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GLCR_AMD64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GLCR_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
steps:

View File

@@ -5,6 +5,8 @@ on:
push:
branches:
- main
tags:
- '*'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

View File

@@ -5,6 +5,8 @@ on:
push:
branches:
- main
tags:
- '*'
schedule:
- cron: '00 12 * * *'
@@ -24,7 +26,7 @@ jobs:
uses: actions/checkout@v4
- name: Run Trivy code and vulnerability scanner on repo
uses: aquasecurity/trivy-action@0.19.0
uses: aquasecurity/trivy-action@0.20.0
with:
scan-type: repo
format: sarif
@@ -32,9 +34,9 @@ jobs:
severity: CRITICAL,HIGH,MEDIUM,LOW
- name: Run Trivy code and vulnerability scanner on filesystem
uses: aquasecurity/trivy-action@0.19.0
uses: aquasecurity/trivy-action@0.20.0
with:
scan-type: fs
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH,MEDIUM,LOW
severity: CRITICAL,HIGH,MEDIUM,LOW

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
# Local environment overrides
/.env
# CMake
cmake-build-*/

92
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,92 @@
# Contributing guide
This page is for about contributing to conduwuit. The [development](docs/development.md) page may be of interest for you as well.
If you would like to work on an [issue][issues] that is not assigned, preferably ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix], and comment on it.
### Linting and Formatting
It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc) and your code is formatted via the **nightly** `cargo fmt`. A lot of the `rustfmt.toml` features depend on nightly toolchain. It would be ideal if they weren't nightly-exclusive features, but they currently still are. CI's rustfmt uses nightly.
If you need to allow a lint, please make sure it's either obvious as to why (e.g. clippy saying redundant clone but it's actually required) or it has a comment saying why. Do not write inefficient code for the sake of satisfying lints. If a lint is wrong and provides a more inefficient solution or suggestion, allow the lint and mention that in a comment.
### Running CI tests locally
conduwuit's CI for tests, linting, formatting, audit, etc use [`engage`][engage]. engage can be installed from nixpkgs or `cargo install engage`. conduwuit's Nix flake devshell has the nixpkgs engage with `direnv`. Use `engage --help` for more usage details.
To test, format, lint, etc that CI would do, install engage, allow the `.envrc` file using `direnv allow`, and run `engage`.
All of the tasks are defined at the [engage.toml][engage.toml] file. You can view all of them neatly by running `engage list`
If you would like to run only a specific engage task group, use `just`:
- `engage just <group>`
- Example: `engage just lints`
If you would like to run a specific engage task in a specific group, use `just <GROUP> [TASK]`: `engage just lints cargo-fmt`
The following binaries are used in [`engage.toml`][engage.toml]:
- [`engage`][engage]
- `nix`
- [`direnv`][direnv]
- `rustc`
- `cargo`
- `cargo-fmt`
- `rustdoc`
- `cargo-clippy`
- [`cargo-audit`][cargo-audit]
- [`cargo-deb`][cargo-deb]
- [`lychee`][lychee]
### Matrix tests
CI runs [Complement][complement], but currently does not fail if results from the checked-in results differ with the new results. If your changes are done to fix Matrix tests, note that in your pull request. If more Complement tests start failing from your changes, please review the logs (they are uploaded as artifacts) and determine if they're intended or not.
If you'd like to run Complement locally using Nix, see the [testing](docs/development/testing.md) page.
[Sytest][sytest] support will come soon.
### Writing documentation
conduwuit's website uses [`mdbook`][mdbook] and deployed via CI using GitHub Pages in the [`documentation.yml`][documentation.yml] workflow file with Nix's mdbook in the devshell. All documentation is in the `docs/` directory at the top level. The compiled mdbook website is also uploaded as an artifact.
To build the documentation using Nix, run: `bin/nix-build-and-cache just .#book`
The output of the mdbook generation is in `result/`. mdbooks can be opened in your browser from the individual HTML files without any web server needed.
### Inclusivity and Diversity
All **MUST** code and write with inclusivity and diversity in mind. See the [following page by Google on writing inclusive code and documentation](https://developers.google.com/style/inclusive-documentation).
This **EXPLICITLY** forbids usage of terms like "blacklist"/"whitelist" and "master"/"slave", [forbids gender-specific words and phrases](https://developers.google.com/style/pronouns#gender-neutral-pronouns), forbids ableist language like "sanity-check", "cripple", or "insane", and forbids culture-specific language (e.g. US-only holidays or cultures).
No exceptions are allowed. Dependencies that may use these terms are allowed but [do not replicate the name in your functions or variables](https://developers.google.com/style/inclusive-documentation#write-around).
In addition to language, write and code with the user experience in mind. This is software that intends to be used by everyone, so make it easy and comfortable for everyone to use. 🏳️‍⚧️
### Variable, comment, function, etc standards
Rust's default style and standards with regards to [function names, variable names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc applies here.
### Creating pull requests
Please try to keep contributions to the GitHub. While the mirrors of conduwuit allow for pull/merge requests, there is no guarantee I will see them in a timely manner. Additionally, please mark WIP or unfinished or incomplete PRs as drafts. This prevents me from having to ping once in a while to double check the status of it, especially when the CI completed successfully and everything so it *looks* done.
If you open a pull request on one of the mirrors, it is your responsibility to inform me about its existence. In the future I may try to solve this with more repo bots in the conduwuit Matrix room. There is no mailing list or email-patch support on the sr.ht mirror, but if you'd like to email me a git patch you can do so at `strawberry@puppygock.gay`.
Direct all PRs/MRs to the `main` branch.
By sending a pull request or patch, you are agreeing that your changes are allowed to be licenced under the Apache-2.0 licence and all of your conduct is in line with the Contributor's Covenant.
[issues]: https://github.com/girlbossceo/conduwuit/issues
[conduwuit-matrix]: https://matrix.to/#/#conduwuit:puppygock.gay
[complement]: https://github.com/matrix-org/complement/
[engage.toml]: https://github.com/girlbossceo/conduwuit/blob/main/engage.toml
[engage]: https://charles.page.computer.surgery/engage/
[sytest]: https://github.com/matrix-org/sytest/
[cargo-deb]: https://github.com/kornelski/cargo-deb
[lychee]: https://github.com/lycheeverse/lychee
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
[direnv]: https://direnv.net/
[mdbook]: https://rust-lang.github.io/mdBook/
[documentation.yml]: https://github.com/girlbossceo/conduwuit/blob/main/.github/workflows/documentation.yml

312
Cargo.lock generated
View File

@@ -61,15 +61,15 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "anstyle"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anyhow"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
[[package]]
name = "arc-swap"
@@ -103,9 +103,9 @@ checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002"
[[package]]
name = "async-compression"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693"
checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498"
dependencies = [
"brotli",
"flate2",
@@ -136,7 +136,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -147,7 +147,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -161,9 +161,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "axum"
@@ -375,7 +375,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -410,9 +410,9 @@ dependencies = [
[[package]]
name = "brotli"
version = "5.0.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67"
checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -472,9 +472,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.96"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
dependencies = [
"jobserver",
"libc",
@@ -551,7 +551,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -568,7 +568,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "conduit"
version = "0.3.1"
version = "0.3.3"
dependencies = [
"argon2",
"async-trait",
@@ -594,6 +594,7 @@ dependencies = [
"hyper 1.3.1",
"hyper-util",
"image",
"infer",
"ipaddress",
"itertools",
"jsonwebtoken",
@@ -614,6 +615,7 @@ dependencies = [
"ruma-identifiers-validation",
"rusqlite",
"rust-rocksdb",
"sanitize-filename",
"sd-notify",
"sentry",
"sentry-tower",
@@ -774,7 +776,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -878,7 +880,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -1046,7 +1048,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -1090,9 +1092,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
@@ -1357,16 +1359,16 @@ dependencies = [
[[package]]
name = "html5ever"
version = "0.26.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
dependencies = [
"log",
"mac",
"markup5ever",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.61",
]
[[package]]
@@ -1605,6 +1607,12 @@ dependencies = [
"serde",
]
[[package]]
name = "infer"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
[[package]]
name = "inlinable_string"
version = "0.1.15"
@@ -1885,9 +1893,9 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "markup5ever"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
dependencies = [
"log",
"phf",
@@ -1899,9 +1907,9 @@ dependencies = [
[[package]]
name = "markup5ever_rcdom"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2"
checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18"
dependencies = [
"html5ever",
"markup5ever",
@@ -2043,9 +2051,9 @@ dependencies = [
[[package]]
name = "num"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
@@ -2057,20 +2065,19 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
@@ -2092,9 +2099,9 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.44"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
@@ -2103,11 +2110,10 @@ dependencies = [
[[package]]
name = "num-rational"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
@@ -2115,9 +2121,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
@@ -2287,9 +2293,9 @@ dependencies = [
[[package]]
name = "paste"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pear"
@@ -2311,7 +2317,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -2332,21 +2338,21 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
version = "0.10.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared",
"phf_shared 0.11.2",
]
[[package]]
name = "phf_codegen"
version = "0.10.0"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator",
"phf_shared",
"phf_generator 0.11.2",
"phf_shared 0.11.2",
]
[[package]]
@@ -2355,7 +2361,17 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [
"phf_shared",
"phf_shared 0.10.0",
"rand",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared 0.11.2",
"rand",
]
@@ -2368,6 +2384,15 @@ dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.5"
@@ -2385,7 +2410,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -2464,9 +2489,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.81"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
@@ -2479,7 +2504,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
"version_check",
"yansi",
]
@@ -2496,15 +2521,15 @@ dependencies = [
[[package]]
name = "prost-derive"
version = "0.12.4"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48"
checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -2697,8 +2722,8 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.9.4"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.10.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"assign",
"js_int",
@@ -2717,8 +2742,8 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.9.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.10.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"js_int",
"ruma-common",
@@ -2729,8 +2754,8 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.17.4"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.18.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"as_variant",
"assign",
@@ -2751,8 +2776,8 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.12.1"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.13.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -2781,8 +2806,8 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.27.11"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.28.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"as_variant",
"indexmap 2.2.6",
@@ -2803,8 +2828,8 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.8.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.9.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"js_int",
"ruma-common",
@@ -2815,8 +2840,8 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.3"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.9.5"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"js_int",
"thiserror",
@@ -2824,8 +2849,8 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.8.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.9.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"js_int",
"ruma-common",
@@ -2834,8 +2859,8 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.12.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.13.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"once_cell",
"proc-macro-crate",
@@ -2843,14 +2868,14 @@ dependencies = [
"quote",
"ruma-identifiers-validation",
"serde",
"syn 2.0.60",
"syn 2.0.61",
"toml",
]
[[package]]
name = "ruma-push-gateway-api"
version = "0.8.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.9.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"js_int",
"ruma-common",
@@ -2861,8 +2886,8 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.14.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.15.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -2877,8 +2902,8 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.10.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#1c291e18efd0559c2dd76b27d555c86471cbb0fd"
version = "0.11.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#9e29e07ae1561fa7e6ed1897192f9c43c111b026"
dependencies = [
"itertools",
"js_int",
@@ -2906,7 +2931,7 @@ dependencies = [
[[package]]
name = "rust-librocksdb-sys"
version = "0.21.0+9.1.1"
source = "git+https://github.com/zaidoon1/rust-rocksdb?branch=master#c5cd6bd25152ef1f8a488761351da0c3d29ed93a"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=c5cd6bd25152ef1f8a488761351da0c3d29ed93a#c5cd6bd25152ef1f8a488761351da0c3d29ed93a"
dependencies = [
"bindgen",
"bzip2-sys",
@@ -2923,7 +2948,7 @@ dependencies = [
[[package]]
name = "rust-rocksdb"
version = "0.25.0"
source = "git+https://github.com/zaidoon1/rust-rocksdb?branch=master#c5cd6bd25152ef1f8a488761351da0c3d29ed93a"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=c5cd6bd25152ef1f8a488761351da0c3d29ed93a#c5cd6bd25152ef1f8a488761351da0c3d29ed93a"
dependencies = [
"libc",
"rust-librocksdb-sys",
@@ -2931,9 +2956,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
@@ -3001,9 +3026,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.5.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54"
checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
[[package]]
name = "rustls-webpki"
@@ -3028,15 +3053,15 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.15"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0"
[[package]]
name = "ryu"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "same-file"
@@ -3047,6 +3072,16 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "sanitize-filename"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "schannel"
version = "0.1.23"
@@ -3080,11 +3115,11 @@ checksum = "621e3680f3e07db4c9c2c3fb07c6223ab2fab2e54bd3c04c3ae037990f428c32"
[[package]]
name = "security-framework"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.5.0",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -3093,9 +3128,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
dependencies = [
"core-foundation-sys",
"libc",
@@ -3103,9 +3138,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "sentry"
@@ -3244,22 +3279,22 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.200"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.200"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -3277,9 +3312,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.116"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"itoa",
"ryu",
@@ -3480,7 +3515,7 @@ dependencies = [
"new_debug_unreachable",
"once_cell",
"parking_lot",
"phf_shared",
"phf_shared 0.10.0",
"precomputed-hash",
"serde",
]
@@ -3491,8 +3526,8 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
dependencies = [
"phf_generator",
"phf_shared",
"phf_generator 0.10.0",
"phf_shared 0.10.0",
"proc-macro2",
"quote",
]
@@ -3525,9 +3560,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.60"
version = "2.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
dependencies = [
"proc-macro2",
"quote",
@@ -3559,22 +3594,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.59"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.59"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -3723,7 +3758,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -3772,16 +3807,15 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.10"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
@@ -3826,7 +3860,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.6.7",
"winnow 0.6.8",
]
[[package]]
@@ -3929,7 +3963,7 @@ source = "git+https://github.com/girlbossceo/tracing?branch=tracing-subscriber/e
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]
@@ -4200,7 +4234,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
"wasm-bindgen-shared",
]
@@ -4234,7 +4268,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4277,9 +4311,9 @@ dependencies = [
[[package]]
name = "webpage"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb86b12e58d490a99867f561ce8466ffa7b73e24d015a8e7f5bc111d4424ba2"
checksum = "70862efc041d46e6bbaa82bb9c34ae0596d090e86cbd14bd9e93b36ee6802eac"
dependencies = [
"html5ever",
"markup5ever_rcdom",
@@ -4520,9 +4554,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.6.7"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578"
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
dependencies = [
"memchr",
]
@@ -4549,9 +4583,9 @@ dependencies = [
[[package]]
name = "xml5ever"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650"
checksum = "7c376f76ed09df711203e20c3ef5ce556f0166fa03d39590016c0fd625437fad"
dependencies = [
"log",
"mac",
@@ -4566,22 +4600,22 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.7.32"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.61",
]
[[package]]

View File

@@ -10,15 +10,17 @@ authors = [
homepage = "https://conduwuit.puppyirl.gay/"
repository = "https://github.com/girlbossceo/conduwuit"
readme = "README.md"
version = "0.3.1"
version = "0.3.3"
edition = "2021"
# See also `rust-toolchain.toml`
rust-version = "1.76.0"
rust-version = "1.77.0"
[dependencies]
console-subscriber = { version = "0.2", optional = true }
infer = { version = "0.15", default-features = false }
# for hot lib reload
hot-lib-reloader = { version = "^0.7", optional = true }
@@ -26,7 +28,7 @@ hot-lib-reloader = { version = "^0.7", optional = true }
rand = "0.8.5"
# Used for conduit::Error type
thiserror = "1.0.59"
thiserror = "1.0.60"
# Used to encode server public key
base64 = "0.22.1"
@@ -77,6 +79,7 @@ url = { version = "2.5.0", features = ["serde"] }
async-trait = "0.1.80"
lru-cache = "0.1.2"
sanitize-filename = "0.5.0"
# standard date and time tools
[dependencies.chrono]
@@ -109,6 +112,7 @@ features = [
"add-extension",
"cors",
"sensitive-headers",
"set-header",
"trace",
"util",
"catch-panic",
@@ -129,14 +133,14 @@ features = ["rustls-tls-native-roots", "socks", "hickory-dns"]
# all the serde stuff
# Used for pdu definition
[dependencies.serde]
version = "1.0.200"
version = "1.0.201"
features = ["rc"]
# Used for appservice registration files
[dependencies.serde_yaml]
version = "0.9.34"
# Used for ruma wrapper
[dependencies.serde_json]
version = "1.0.116"
version = "1.0.117"
features = ["raw_value"]
@@ -230,7 +234,7 @@ features = ["use_std"]
# for URL previews
[dependencies.webpage]
version = "2.0"
version = "2.0.1"
default-features = false
# to support multiple variations of setting a config option
@@ -295,7 +299,8 @@ default-features = false
[dependencies.rust-rocksdb]
git = "https://github.com/zaidoon1/rust-rocksdb"
branch = "master"
rev = "c5cd6bd25152ef1f8a488761351da0c3d29ed93a"
#branch = "master"
optional = true
default-features = true
features = ["multi-threaded-cf", "zstd"]
@@ -362,44 +367,45 @@ default = [
"brotli_compression",
"zstd_compression",
"release_max_log_level",
"io_uring",
]
backend_sqlite = ["sqlite"]
backend_rocksdb = ["rocksdb"]
rocksdb = ["rust-rocksdb"]
rocksdb = ["dep:rust-rocksdb"]
jemalloc = [
"tikv-jemalloc-sys",
"tikv-jemalloc-ctl",
"tikv-jemallocator",
"dep:tikv-jemalloc-sys",
"dep:tikv-jemalloc-ctl",
"dep:tikv-jemallocator",
"rust-rocksdb/jemalloc",
]
jemalloc_prof = ["tikv-jemalloc-sys/profiling"]
sqlite = ["rusqlite", "parking_lot", "thread_local"]
systemd = ["sd-notify"]
sentry_telemetry = ["sentry", "sentry-tracing", "sentry-tower"]
sqlite = ["dep:rusqlite", "dep:parking_lot", "dep:thread_local"]
systemd = ["dep:sd-notify"]
sentry_telemetry = ["dep:sentry", "dep:sentry-tracing", "dep:sentry-tower"]
gzip_compression = ["tower-http/compression-gzip", "reqwest/gzip"]
zstd_compression = ["tower-http/compression-zstd"]
brotli_compression = ["tower-http/compression-br", "reqwest/brotli"]
sha256_media = ["sha2"]
sha256_media = ["dep:sha2"]
io_uring = ["rust-rocksdb/io-uring"]
axum_dual_protocol = ["axum-server-dual-protocol"]
axum_dual_protocol = ["dep:axum-server-dual-protocol"]
perf_measurements = [
"opentelemetry",
"tracing-flame",
"tracing-opentelemetry",
"opentelemetry_sdk",
"opentelemetry-jaeger",
"dep:opentelemetry",
"dep:tracing-flame",
"dep:tracing-opentelemetry",
"dep:opentelemetry_sdk",
"dep:opentelemetry-jaeger",
]
# enable the tokio_console server
# incompatible with release_max_log_level
tokio_console = ["console-subscriber", "tokio/tracing"]
tokio_console = ["dep:console-subscriber", "tokio/tracing"]
hot_reload = ["dep:hot-lib-reloader"]
hardened_malloc = ["hardened_malloc-rs"]
hardened_malloc = ["dep:hardened_malloc-rs"]
# increases performance, reduces build times, and reduces binary size by not compiling or
# genreating code for log level filters that users will generally not use (debug and trace) only in release builds
@@ -459,9 +465,11 @@ systemd-units = { unit-name = "conduwuit" }
[profile.dev]
#debug = 0
lto = 'off'
codegen-units = 512
incremental = true
overflow-checks = true
#panic = "abort"
# seems to speed up continuous debug compilations
@@ -622,6 +630,8 @@ manual_let_else = "warn"
trivially_copy_pass_by_ref = "warn"
wildcard_imports = "warn"
checked_conversions = "warn"
#integer_arithmetic = "warn"
#as_conversions = "warn"
# some sadness
missing_errors_doc = "allow"
@@ -642,7 +652,6 @@ map_err_ignore = "allow"
missing_docs_in_private_items = "allow"
multiple_inherent_impl = "allow"
error_impl_error = "allow"
as_conversions = "allow"
string_add = "allow"
string_slice = "allow"
ref_patterns = "allow"

View File

@@ -2,8 +2,6 @@ # conduwuit
`main` / stable: [![CI and Artifacts](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
`dev`: [![CI and Artifacts](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
<!-- ANCHOR: catchphrase -->
### a very cool, featureful fork of [Conduit](https://conduit.rs/)
<!-- ANCHOR_END: catchphrase -->
@@ -28,6 +26,10 @@ #### Can I try it out?
An official conduwuit server ran by me is available at transfem.dev ([element.transfem.dev](https://element.transfem.dev) / [cinny.transfem.dev](https://cinny.transfem.dev))
transfem.dev is a public homeserver that can be used, it is not a "test only homeserver". This means there are rules, so please read the rules: [https://transfem.dev/homeserver_rules.txt](https://transfem.dev/homeserver_rules.txt)
transfem.dev is also listed at [servers.joinmatrix.org](https://servers.joinmatrix.org/)
#### What is the current status?
conduwuit is a hard fork of Conduit which is in beta, meaning you can join and participate in most
@@ -37,14 +39,6 @@ #### What is the current status?
<!-- ANCHOR_END: body -->
<!-- ANCHOR: footer -->
#### How can I contribute?
1. Look for an issue you would like to work on and make sure it's not assigned
to other users
2. Ask someone to assign the issue to you (comment on the issue or chat in
[#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay))
3. Fork the repo and work on the issue.
4. Submit a PR (please keep contributions to the GitHub repo, main development is done here, not the GitLab repo which exists just as a mirror. If you are avoiding GitHub, feel free to join our Matrix chat to get your patch in.)
#### Contact
@@ -69,7 +63,8 @@ #### Is it conduwuit or Conduwuit?
#### Mirrors of conduwuit
- GitHub: <https://github.com/girlbossceo/conduwuit>
- GitLab: <https://gitlab.com/girlbossceo/conduwuit>
- GitLab: <https://gitlab.com/conduwuit/conduwuit>
- git.girlcock.ceo: <https://git.girlcock.ceo/strawberry/conduwuit>
- git.gay: <https://git.gay/june/conduwuit>
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>

View File

@@ -20,12 +20,9 @@ OCI_IMAGE="complement-conduit:dev"
toplevel="$(git rev-parse --show-toplevel)"
pushd "$toplevel" > /dev/null
# uses nix-output-monitor (nom) if available
if command -v nom &> /dev/null; then
nom build .#complement
else
nix build -L .#complement
fi
bin/nix-build-and-cache just .#complement
docker load < result
popd > /dev/null

View File

@@ -13,41 +13,55 @@ just() {
nix build -L "$@"
fi
if [ ! -z "$ATTIC_TOKEN" ]; then
# historical "conduit" store for compatibility purposes, same as conduwuit
nix run --inputs-from "$toplevel" attic -- \
login \
conduit \
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduit}" \
"$ATTIC_TOKEN"
readarray -t outputs < <(nix path-info "$@")
readarray -t derivations < <(nix path-info "$@" --derivation)
# Push the target installable and its build dependencies
nix run --inputs-from "$toplevel" attic -- \
push \
conduit \
"${outputs[@]}" \
"${derivations[@]}"
# main "conduwuit" store
nix run --inputs-from "$toplevel" attic -- \
login \
conduwuit \
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduwuit}" \
"$ATTIC_TOKEN"
# Push the target installable and its build dependencies
nix run --inputs-from "$toplevel" attic -- \
push \
conduwuit \
"${outputs[@]}" \
"${derivations[@]}"
else
if [ -z "$ATTIC_TOKEN" ]; then
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
return
fi
# historical "conduit" store for compatibility purposes, same as conduwuit
nix run --inputs-from "$toplevel" attic -- \
login \
conduit \
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduit}" \
"$ATTIC_TOKEN"
# Find all output paths of the installables and their build dependencies
readarray -t derivations < <(nix path-info --derivation "$@")
cache=()
for derivation in "${derivations[@]}"; do
cache+=(
"$(nix-store --query --requisites --include-outputs "$derivation")"
)
done
# Upload them to Attic (conduit store)
#
# Use `xargs` and a here-string because something would probably explode if
# several thousand arguments got passed to a command at once. Hopefully no
# store paths include a newline in them.
(
IFS=$'\n'
nix shell --inputs-from "$toplevel" attic -c xargs \
attic push conduit <<< "${cache[*]}"
)
# main "conduwuit" store
nix run --inputs-from "$toplevel" attic -- \
login \
conduwuit \
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduwuit}" \
"$ATTIC_TOKEN"
# Upload them to Attic (conduwuit store)
#
# Use `xargs` and a here-string because something would probably explode if
# several thousand arguments got passed to a command at once. Hopefully no
# store paths include a newline in them.
(
IFS=$'\n'
nix shell --inputs-from "$toplevel" attic -c xargs \
attic push conduwuit <<< "${cache[*]}"
)
}
# Build and cache things needed for CI
@@ -56,7 +70,7 @@ ci() {
--inputs-from "$toplevel"
# Keep sorted
"$toplevel#devShells.x86_64-linux.default.inputDerivation"
"$toplevel#devShells.x86_64-linux.default"
attic#default
nixpkgs#direnv
nixpkgs#jq

View File

@@ -269,6 +269,19 @@ url_preview_check_root_domain = false
# Defaults to true
allow_profile_lookup_federation_requests = true
# Config option to automatically deactivate the account of any user who attempts to join a:
# - banned room
# - forbidden room alias
# - room alias or ID with a forbidden server name
#
# This may be useful if all your banned lists consist of toxic rooms or servers that no good faith user would ever attempt to join, and
# to automatically remediate the problem without any admin user intervention.
#
# This will also make the user leave all rooms. Federation (e.g. remote room invites) are ignored here.
#
# Defaults to false as rooms can be banned for non-moderation-related reasons
#auto_deactivate_banned_room_attempts = false
### Misc

3
debian/README.md vendored
View File

@@ -1,5 +1,4 @@
conduwuit for Debian
==================
# conduwuit for Debian
Installation
------------

View File

@@ -1,13 +1,18 @@
[Unit]
Description=conduwuit Matrix homeserver
Documentation=https://conduwuit.puppyirl.gay/
After=network-online.target
[Service]
DynamicUser=yes
User=_conduwuit
Group=_conduwuit
User=conduwuit
Group=conduwuit
Type=notify
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
ExecStart=/usr/sbin/conduwuit
AmbientCapabilities=
CapabilityBoundingSet=
@@ -39,14 +44,11 @@ SystemCallArchitectures=native
SystemCallFilter=@system-service @resources
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
SystemCallErrorNumber=EPERM
StateDirectory=matrix-conduit
StateDirectory=conduwuit
RuntimeDirectory=conduit
RuntimeDirectoryMode=0750
Environment="CONDUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
ExecStart=/usr/sbin/conduwuit
Restart=on-failure
RestartSec=5

8
debian/postinst vendored
View File

@@ -7,21 +7,21 @@ CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit/
case "$1" in
configure)
# Create the `_conduwuit` user if it does not exist yet.
if ! getent passwd _conduwuit > /dev/null ; then
# Create the `conduwuit` user if it does not exist yet.
if ! getent passwd conduwuit > /dev/null ; then
echo 'Adding system user for the conduwuit Matrix homeserver' 1>&2
adduser --system --group --quiet \
--home "$CONDUWUIT_DATABASE_PATH" \
--disabled-login \
--shell "/usr/sbin/nologin" \
--force-badname \
_conduwuit
conduwuit
fi
# Create the database path if it does not exist yet and fix up ownership
# and permissions.
mkdir -p "$CONDUWUIT_DATABASE_PATH"
chown _conduwuit:_conduwuit -R "$CONDUWUIT_DATABASE_PATH"
chown conduwuit:conduwuit -R "$CONDUWUIT_DATABASE_PATH"
chmod 700 "$CONDUWUIT_DATABASE_PATH"
;;
esac

View File

@@ -2,14 +2,17 @@ # Summary
- [Introduction](introduction.md)
- [Differences from upstream Conduit](differences.md)
- [Example configuration](configuration.md)
- [Deploying](deploying.md)
- [Generic](deploying/generic.md)
- [Debian](deploying/debian.md)
- [Docker](deploying/docker.md)
- [NixOS](deploying/nixos.md)
- [Docker](deploying/docker.md)
- [Arch Linux](deploying/arch-linux.md)
- [Debian](deploying/debian.md)
- [TURN](turn.md)
- [Appservices](appservices.md)
- [Maintenance](maintenance.md)
- [Troubleshooting](troubleshooting.md)
- [Development](development.md)
- [Contributing](contributing.md)
- [Testing](development/testing.md)

1
docs/contributing.md Normal file
View File

@@ -0,0 +1 @@
{{#include ../CONTRIBUTING.md}}

View File

@@ -0,0 +1,8 @@
# conduwuit for Arch Linux
Currently conduwuit is only on the Arch User Repository (AUR).
The conduwuit AUR packages are community maintained and are not maintained by conduwuit development team, but the AUR package maintainers are in the Matrix room. Please attempt to verify your AUR package's PKGBUILD file looks fine before asking for support.
- [conduwuit](https://aur.archlinux.org/packages/conduwuit) - latest tagged conduwuit
- [conduwuit-git](https://aur.archlinux.org/packages/conduwuit-git) - latest git conduwuit from `main` branch

View File

@@ -12,22 +12,17 @@ ### Use a registry
| Registry | Image | Size | Notes |
| --------------- | --------------------------------------------------------------- | ----------------------------- | ---------------------- |
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:latest][gh] | ![Image Size][shield-latest] | Stable tagged image. |
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:latest][gl] | ![Image Size][shield-latest] | Stable tagged image. |
| GitLab Registry | [registry.gitlab.com/conduwuit/conduwuit:latest][gl] | ![Image Size][shield-latest] | Stable tagged image. |
| Docker Hub | [docker.io/girlbossceo/conduwuit:latest][dh] | ![Image Size][shield-latest] | Stable tagged image. |
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:main][gh] | ![Image Size][shield-main] | Stable main branch. |
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:main][gl] | ![Image Size][shield-main] | Stable main branch. |
| GitLab Registry | [registry.gitlab.com/conduwuit/conduwuit:main][gl] | ![Image Size][shield-main] | Stable main branch. |
| Docker Hub | [docker.io/girlbossceo/conduwuit:main][dh] | ![Image Size][shield-main] | Stable main branch. |
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:dev][gh] | ![Image Size][shield-dev] | Development version/branch. |
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:dev][gl] | ![Image Size][shield-dev] | Development version/branch. |
| Docker Hub | [docker.io/girlbossceo/conduwuit:dev][dh] | ![Image Size][shield-dev] | Development version/branch. |
[dh]: https://hub.docker.com/repository/docker/girlbossceo/conduwuit
[gh]: https://github.com/girlbossceo/conduwuit/pkgs/container/conduwuit
[gl]: https://gitlab.com/girlbossceo/conduwuit/container_registry/6351657
[gl]: https://gitlab.com/conduwuit/conduwuit/container_registry/6351657
[shield-latest]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/latest
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
[shield-dev]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/dev
Use
@@ -56,9 +51,9 @@ ### Run
or you can use [docker compose](#docker-compose).
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, an example can be found [here](../configuration.md).
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, the example config can be found [here](../configuration.md).
You can pass in different env vars to change config values on the fly. You can even configure conduwuit completely by using env vars. For an overview of possible
values, please take a look at the `docker-compose.yml` file.
values, please take a look at the [`docker-compose.yml`](docker-compose.yml) file.
If you just want to test conduwuit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it.
@@ -112,92 +107,7 @@ ### Use Traefik as Proxy
With the service `well-known` we use a single `nginx` container that will serve those two files.
So...step by step:
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and [`docker-compose.override.yml`](docker-compose.override.yml) from the repository and remove `.for-traefik` (or `.with-traefik`) from the filename.
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
3. Create the `conduwuit.toml` config file, an example can be found [here](../configuration.md), or set `CONDUIT_CONFIG=""` and configure conduwuit per env vars.
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
5. Create the files needed by the `well-known` service.
- `./nginx/matrix.conf` (relative to the compose file, you can change this, but then also need to change the volume mapping)
```nginx
server {
server_name <SUBDOMAIN>.<DOMAIN>;
listen 80 default_server;
location /.well-known/matrix/server {
return 200 '{"m.server": "<SUBDOMAIN>.<DOMAIN>:443"}';
types { } default_type "application/json; charset=utf-8";
}
location /.well-known/matrix/client {
return 200 '{"m.homeserver": {"base_url": "https://<SUBDOMAIN>.<DOMAIN>"}}';
types { } default_type "application/json; charset=utf-8";
add_header "Access-Control-Allow-Origin" *;
}
location / {
return 404;
}
}
```
6. Run `docker compose up -d`
7. Connect to your homeserver with your preferred client and create a user. You should do this immediately after starting Conduit, because the first created user is the admin.
## Voice communication
In order to make or receive calls, a TURN server is required. conduwuit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image. Before proceeding with the software installation, it is essential to have the necessary configurations in place.
### Configuration
Create a configuration file called `coturn.conf` containing:
```conf
use-auth-secret
static-auth-secret=<a secret key>
realm=<your server domain>
```
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
These same values need to be set in conduwuit. You can either modify conduwuit.toml to include these lines:
```
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
turn_secret = "<secret key from coturn configuration>"
```
or append the following to the docker environment variables dependig on which configuration method you used earlier:
```yml
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
```
Restart Conduit to apply these changes.
### Run
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
```bash
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
```
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
and run `docker compose up -d` in the same directory.
```yml
version: 3
services:
turn:
container_name: coturn-server
image: docker.io/coturn/coturn
restart: unless-stopped
network_mode: "host"
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf
```
To understand why the host networking mode is used and explore alternative configuration options, please visit the following link: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md.
For security recommendations see Synapse's [Coturn documentation](https://github.com/matrix-org/synapse/blob/develop/docs/setup/turn/coturn.md#configuration).
See the [TURN](../turn.md) page.

View File

@@ -1,7 +1,5 @@
# Generic deployment documentation
### Please note that this documentation is not fully representative of conduwuit at the moment. Assume majority of it is outdated.
> ## Getting help
>
> If you run into any problems while setting up conduwuit, ask us
@@ -13,23 +11,16 @@ ## Installing conduwuit
Prebuilt binaries can be downloaded from the latest tagged release [here](https://github.com/girlbossceo/conduwuit/releases/latest).
Alternatively, you may compile the binary yourself. First, install any dependencies:
The latest tagged release also includes the Debian packages.
```bash
# Debian
$ sudo apt install libclang-dev build-essential
Alternatively, you may compile the binary yourself. We recommend using [Lix](https://lix.systems) to build conduwuit as this has the most guaranteed
reproducibiltiy and easiest to get a build environment and output going.
# RHEL
$ sudo dnf install clang
```
Then, `cd` into the source tree of conduwuit and run:
```bash
$ cargo build --release
```
Otherwise, follow standard Rust project build guides (installing git and cloning the repo, getting the Rust toolchain via rustup, installing LLVM toolchain + libclang, installing liburing for io_uring and RocksDB, etc).
## Adding a conduwuit user
While conduwuit can run as any user it is usually better to use dedicated users for different services. This also allows
While conduwuit can run as any user it is better to use dedicated users for different services. This also allows
you to make sure that the file permissions are correctly set up.
In Debian or RHEL, you can use this command to create a conduwuit user:
@@ -38,6 +29,12 @@ ## Adding a conduwuit user
sudo adduser --system conduwuit --group --disabled-login --no-create-home
```
For distros without `adduser`:
```bash
sudo useradd -r --shell /usr/bin/nologin --no-create-home conduwuit
```
## Forwarding ports in the firewall or the router
conduwuit uses the ports 443 and 8448 both of which need to be open in the firewall.
@@ -46,45 +43,18 @@ ## Forwarding ports in the firewall or the router
## Setting up a systemd service
Now we'll set up a systemd service for conduwuit, so it's easy to start/stop conduwuit and set it to autostart when your
server reboots. Simply paste the default systemd service you can find below into
`/etc/systemd/system/conduwuit.service`.
```systemd
[Unit]
Description=conduwuit Matrix Server
After=network.target
[Service]
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
User=conduwuit
Group=conduwuit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
Restart=always
ExecStart=/usr/local/bin/conduwuit
[Install]
WantedBy=multi-user.target
```
Finally, run
```bash
$ sudo systemctl daemon-reload
```
The systemd unit for conduwuit can be found [here](../../debian/conduwuit.service). You may need to change the `ExecStart=` path to where you placed the conduwuit binary.
## Creating the conduwuit configuration file
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. Paste this in **and take a moment
to read it. You need to change at least the server name.**
RocksDB (`rocksdb`) is the only supported database backend. SQLite only exists for historical reasons and is not recommended. Any performance issues, storage issues, database issues, etc will not be assisted if using SQLite and you will be asked to migrate to RocksDB first.
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. The example config can be found at [conduwuit-example.toml](../configuration.md).**Please take a moment to read it. You need to change at least the server name.**
See the following example config at [conduwuit-example.toml](../configuration.md)
RocksDB (`rocksdb`) is the only supported database backend. SQLite only exists for historical reasons and is not recommended. Any performance issues, storage issues, database issues, etc will not be assisted if using SQLite and you will be asked to migrate to RocksDB first.
## Setting the correct file permissions
As we are using a conduwuit specific user we need to allow it to read the config. To do that you can run this command on
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this command on
Debian or RHEL:
```bash
@@ -102,7 +72,7 @@ ## Setting the correct file permissions
## Setting up the Reverse Proxy
Refer to the documentation or various guides online of your chosen reverse proxy software. A Caddy example will be provided as this is the recommended reverse proxy for new users and is very trivial.
Refer to the documentation or various guides online of your chosen reverse proxy software. A [Caddy](https://caddyserver.com/) example will be provided as this is the recommended reverse proxy for new users and is very trivial to use (handles TLS, reverse proxy headers, etc transparently with proper defaults).
### Caddy
@@ -118,10 +88,10 @@ ### Caddy
}
```
That's it! Just start or enable the service and you're set.
That's it! Just start and enable the service and you're set.
```bash
$ sudo systemctl enable caddy
$ sudo systemctl enable --now caddy
```
## You're done!

View File

@@ -1,7 +1,8 @@
# Development
Information about developing the project. If you are only interested in using
it, you can safely ignore this section.
it, you can safely ignore this section. If you plan on contributing, see the
[contributor's guide](contributing.md).
## Debugging with `tokio-console`

View File

@@ -6,7 +6,7 @@ ### list of features, bug fixes, etc that conduwuit does that Conduit does not:
## Performance:
- Concurrency support for key fetching for faster remote room joins and room joins that will error less frequently
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests (download and thumbnail) to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
- Add feature flags and config options to enable/build with zstd, brotli, and/or gzip HTTP body compression (response and request)
- Eliminate all usage of the thread-blocking `getaddrinfo(3)` call upon DNS queries, significantly improving federation latency/ping and cache DNS results (NXDOMAINs, successful queries, etc) using hickory-dns / hickory-resolver
- Vastly improve RocksDB default settings to use new features that help with performance significantly, uses settings tailored to SSDs, various ways to tweak RocksDB, and a conduwuit setting to tell RocksDB to use settings that are tailored to HDDs or slow spinning rust storage or buggy filesystems.
@@ -22,6 +22,7 @@ ## Performance:
- Add config options for RocksDB compression and bottommost compression, including choosing the algorithm and compression level
- Use [loole](https://github.com/mahdi-shojaee/loole) MPSC channels instead of tokio MPSC channels for huge performance boosts in sending channels (mainly relevant for federation) and presence channels
- Use `tracing`/`log`'s `release_max_level_info` feature to improve performance, build speeds, binary size, and CPU usage in release builds by avoid compiling debug/trace log level macros that users will generally never use (can be disabled with a build-time feature flag)
- Enable RocksDB async read I/O via `io_uring` by default
## General Fixes:
@@ -35,7 +36,7 @@ ## General Fixes:
- Increased graceful shutdown timeout from a low 60 seconds to 180 seconds to avoid killing connections and let the remaining ones finish processing
- Return joined member count of rooms for push rules/conditions instead of a hardcoded value of 10
- Make `CONDUIT_CONFIG` optional, relevant for container users that configure only by environment variables and no longer need to set `CONDUIT_CONFIG` to an empty string.
- Allow HEAD HTTP requests in CORS for clients (despite not being explicity mentioned in Matrix spec, HTTP spec says all HEAD requests need to behave the same as GET requests, Synapse supports HEAD requests)
- Allow HEAD and PATCH (MSC4138) HTTP requests in CORS for clients (despite not being explicity mentioned in Matrix spec, HTTP spec says all HEAD requests need to behave the same as GET requests, Synapse supports HEAD requests)
- Resolve and remove some "features" from upstream that result in concurrency hazards, exponential backoff issues, or arbitrary performance limiters
- Find more servers for outbound federation `/hierarchy` requests instead of just the room ID server name
- Support for suggesting servers to join through at `/_matrix/client/v3/directory/room/{roomAlias}`
@@ -55,6 +56,7 @@ ## Moderation:
- On new public room creations, only allow moderators to send `m.call.invite`, `org.matrix.msc3401.call`, and `org.matrix.msc3401.call.member` events
- Add support for a "global ACLs" feature (`forbidden_remote_server_names`) that blocks inbound remote room invites, room joins by room ID on server name, room joins by room alias on server name, incoming federated joins, and incoming federated room directory requests. This is very helpful for blocking servers that are purely toxic/bad and serve no value in allowing our users to suffer from things like room invite spam or such. Please note that this is not a substitute for room ACLs.
- Add support for a config option to forbid our local users from sending federated room directory requests for (`forbidden_remote_room_directory_server_names`). Similar to above, useful for blocking servers that help prevent our users from wandering into bad areas of Matrix via room directories of those malicious servers.
- Add config option for auto remediating/deactivating local non-admin users who attempt to join bad/forbidden rooms (`auto_deactivate_banned_room_attempts`)
## Privacy/Security:
@@ -68,6 +70,9 @@ ## Privacy/Security:
- Config option to disable incoming and/or outgoing remote read receipts
- Config option to disable incoming and/or outgoing remote typing indicators
- Config option to disable incoming, outgoing, and/or local presence
- Sanitise file names for the `Content-Disposition` header for all media requests (thumbnails, downloads, uploads)
- Return `inline` or `attachment` based on the detected file MIME type for the `Content-Disposition` and only allow images/videos/text/audio to be `inline`
- Send secure default HTTP headers such as a strong restrictive CSP, deny iframes, disable `X-XSS-Protection`, disable interest cohort in `Permission-Policy`, etc to mitigate any potential attack surface such as from untrusted media
## Administration/Logging:
@@ -81,7 +86,7 @@ ## Administration/Logging:
- Warn on unknown config options specified
- Add `/_conduwuit/server_version` route to return the version of conduwuit without relying on the federation API `/_matrix/federation/v1/version`
- Add configurable RocksDB recovery modes to aid in recovering corrupted RocksDB databases
- Support config options via `CONDUWUIT_` prefix
- Support config options via `CONDUWUIT_` prefix and accessing non-global struct config options with the `__` split (e.g. `CONDUWUIT_WELL_KNOWN__SERVER`)
- Add support for listening on multiple TCP ports
- Disable update check by default as it's not useful for conduwuit
- **Opt-in** Sentry.io telemetry and metrics, mainly used for crash reporting
@@ -89,7 +94,8 @@ ## Administration/Logging:
## Maintenance/Stability:
- GitLab CI ported to GitHub Actions
- Repo is mirrored to GitHub, GitLab, git.gay, sourcehut, and Codeberg (see README.md for their links)
- Repo is mirrored to GitHub, GitLab, git.gay, git.girlcock.ceo, sourcehut, and Codeberg (see README.md for their links)
- Docker container images published to GitLab Container Registry, GitHub Container Registry, and Dockerhub
- Extensively revamp the example config to be extremely helpful and useful to both new users and power users
- Fixed every single clippy (default lints) and rustc warnings, including some that were performance related or potential safety issues / unsoundness
- Add a **lot** of other clippy and rustc lints and a rustfmt.toml file
@@ -146,6 +152,7 @@ ## Misc:
- Implement running and diff'ing Complement results in CI
- Interest in supporting other operating systems such as macOS, BSDs, and Windows, and getting them added into CI and doing builds for them
- Add config option for disabling RocksDB Direct IO if needed
- Add various documentation on maintaining conduwuit, using RocksDB online backups, some troubleshooting, using admin commands, etc
- (Developers): Add support for tokio-console
- (Developers): Add support for tracing flame graphs
- Add `release-debuginfo` Cargo build profile

View File

@@ -6,7 +6,7 @@ # Conduwuit
#### What's different about your fork than upstream Conduit?
See [differences.md](differences.md)
See the [differences](differences.md) page
#### How can I deploy my own?
@@ -14,4 +14,8 @@ #### How can I deploy my own?
If you want to connect an Appservice to Conduwuit, take a look at the [appservices documentation](appservices.md).
#### How can I contribute?
See the [contributor's guide](contributing.md)
{{#include ../README.md:footer}}

63
docs/maintenance.md Normal file
View File

@@ -0,0 +1,63 @@
# Maintaining your conduwuit setup
## Moderation
conduwuit has moderation through admin room commands. "binary commands" (medium priority) and an admin API (low priority) is planned. Some moderation-related config options are available in the example config such as "global ACLs" and blocking media requests to certain servers. See the example config for the moderation config options under the "Moderation / Privacy / Security" section.
conduwuit has moderation admin commands for:
- managing room aliases (`!admin rooms alias`)
- managing room directory (`!admin rooms directory`)
- managing room banning/blocking and user removal (`!admin rooms moderation`)
- managing user accounts (`!admin users`)
- fetching `/.well-known/matrix/support` from servers (`!admin federation`)
- blocking incoming federation for certain rooms (not the same as room banning) (`!admin federation`)
- deleting media (see [the media section](#media))
Any commands with `-list` in them will require a codeblock in the message with each object being newline delimited. An example of doing this is:
````
!admin rooms moderation ban-list-of-rooms
```
!roomid1:server.name
!roomid2:server.name
!roomid3:server.name
```
````
## Database
If using RocksDB, there's very little you need to do. Compaction is ran automatically based on various defined thresholds tuned for conduwuit to be high performance with the least I/O amplifcation or overhead. Manually running compaction is not recommended, or compaction via a timer. RocksDB is built with io_uring support via liburing for async read I/O.
Some RocksDB settings can be adjusted such as the compression method chosen. See the RocksDB section in the [example config](configuration.md). btrfs users may benefit from disabling compression on RocksDB if CoW is in use.
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
## Backups
Currently only RocksDB supports online backups. If you'd like to backup your database online without any downtime, see the `!admin server` command for the backup commands and the `database_backup_path` config options in the example config. Please note that the format of the database backup is not the exact same. This is unfortunately a bad design choice by Facebook as we are using the database backup engine API from RocksDB, however the data is still there and can still be joined together.
To restore a backup from an online RocksDB backup:
- shutdown conduwuit
- create a new directory for merging together the data
- in the online backup created, copy all `.sst` files in `$DATABASE_BACKUP_PATH/shared_checksum` to your new directory
- trim all the strings so instead of `######_sxxxxxxxxx.sst`, it reads `######.sst`. A way of doing this with sed and bash is `for file in *.sst; do mv "$file" "$(echo "$file" | sed 's/_s.*/.sst/')"; done`
- copy all the files in `$DATABASE_BACKUP_PATH/1` to your new directory
- set your `database_path` config option to your new directory, or replace your old one with the new one you crafted
- start up conduwuit again and it should open as normal
If you'd like to do an offline backup, shutdown conduwuit and copy your `database_path` directory elsewhere. This can be restored with no modifications needed.
Backing up media is also just copying the `media/` directory from your database directory.
## Media
Media still needs various work, however conduwuit implements media deletion via:
- MXC URI
- Delete list of MXC URIs
- Delete remote media in the past `N` seconds/minutes
See the `!admin media` command for further information. All media in conduwuit is stored at `$DATABASE_DIR/media`. This will be configurable soon.
If you are finding yourself needing extensive granular control over media, we recommend looking into [Matrix Media Repo](https://github.com/t2bot/matrix-media-repo). conduwuit intends to implement various utilities for media, but MMR is dedicated to extensive media management.
Built-in S3 support is also planned, but for now using a "S3 filesystem" on `media/` works. conduwuit also sends a `Cache-Control` header of 1 year and immutable for all media requests (download and thumbnail) to reduce unnecessary media requests from browsers.

62
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,62 @@
# Troubleshooting conduwuit
> ## Docker users ⚠️
>
> Docker is extremely UX unfriendly. Because of this, a ton of issues or support is actually Docker support, not conduwuit support. We also cannot document the ever-growing list of Docker issues here.
>
> If you intend on asking for support and you are using Docker, **PLEASE** triple validate your issues are **NOT** because you have a misconfiguration in your Docker setup.
>
> If there are things like Compose file issues or Dockerhub image issues, those can still be mentioned as long as they're something we can fix.
## Rocksdb / database issues
#### Direct IO
Some filesystems may not like RocksDB using [Direct IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for non-buffered I/O which improves conduwuit performance, but at least FUSE is a filesystem potentially known to not like this. See the [example config](configuration.md) for disabling it if needed. Issues from Direct IO on unsupported filesystems are usually shown as startup errors.
#### Database corruption
If your database is corrupted and is failing to start (e.g. checksum mismatch), it may be recoverable but careful steps must be taken, and there is no guarantee it may be recoverable.
RocksDB has the following recovery modes:
- `TolerateCorruptedTailRecords`
- `AbsoluteConsistency`
- `PointInTime`
- `SkipAnyCorruptedRecord`
By default, conduwuit uses `TolerateCorruptedTailRecords` as generally these may be due to bad federation and we can re-fetch the correct data over federation. The RocksDB default is `PointInTime` which will attempt to restore a "snapshot" of the data when it was last known to be good. This data can be either a few seconds old, or multiple minutes prior. `PointInTime` may not be suitable for default usage due to clients and servers possibly not being able to handle sudden "backwards time travels", and `AbsoluteConsistency` may be too strict.
`AbsoluteConsistency` will fail to start the database if any sign of corruption is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless it forbids the database from opening (e.g. too severe). Usage of `SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or leave your database in a permanently inconsistent state, but it may do something if `PointInTime` does not work as a last ditch effort.
With this in mind:
- First start conduwuit with the `PointInTime` recovery method. See the [example config](configuration.md) for how to do this using `rocksdb_recovery_mode`
- If your database successfully opens, clients are recommended to clear their client cache to account for the rollback
- Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as much possible corruption is restored
- If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database
## Media
#### "File name too long"
If you are running into the "file name is too long" OS error for media requests, your filesystem cannot handle file name lengths >=255 characters. This is unfortuntely due to Conduit (upstream) using base64 for file name keys which is very problematic for some filesystems as the base64 input is untrusted and long file names or specific inputs can cause this. If you would like to avoid this, you may build conduwuit yourself with the `sha256_media` feature. **This will lose database compatibility with upstream**.
## Debugging
Note that users should not really be debugging things. If you find yourself debugging and find the issue, please let us know and/or how we can fix it. Various debug commands can be found in `!admin debug`.
#### Debug/Trace log level
conduwuit builds without debug or trace log levels by default for at least performance reasons. This may change in the future and/or binaries providing such configurations may be provided. If you need to access debug/trace log levels, you will need to build without the `release_max_log_level` feature.
#### Changing log level dynamically
conduwuit supports changing the tracing log environment filter on-the-fly using the admin command `!admin debug change-log-level`. This accepts a string **without quotes** the same format as the `log` config option.
#### Pinging servers
conduwuit can ping other servers using `!admin debug ping`. This takes a server name and goes through the server discovery process and queries `/_matrix/federation/v1/version`. Errors are outputted.
#### Allocator memory stats
If using jemalloc (for now) and built with jemallocator's `stats` feature, you can see conduwuit's jemalloc memory stats by using `!admin debug memory-stats`

View File

@@ -1,25 +1,55 @@
# Setting up TURN/STURN
## General instructions
In order to make or receive calls, a TURN server is required. conduwuit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image.
* It is assumed you have a [Coturn server](https://github.com/coturn/coturn) up and running. See [Synapse reference implementation](https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md).
### Configuration
## Edit/Add a few settings to your existing conduit.toml
Create a configuration file called `coturn.conf` containing:
```conf
use-auth-secret
static-auth-secret=<a secret key>
realm=<your server domain>
```
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
These same values need to be set in conduwuit. You can either modify conduwuit.toml to include these lines:
```
# Refer to your Coturn settings.
# `your.turn.url` has to match the REALM setting of your Coturn as well as `transport`.
turn_uris = ["turn:your.turn.url?transport=udp", "turn:your.turn.url?transport=tcp"]
# static-auth-secret of your turnserver
turn_secret = "ADD SECRET HERE"
# If you have your TURN server configured to use a username and password
# you can provide these information too. In this case comment out `turn_secret above`!
#turn_username = ""
#turn_password = ""
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
turn_secret = "<secret key from coturn configuration>"
```
## Apply settings
or append the following to the docker environment variables dependig on which configuration method you used earlier:
Restart Conduit.
```yml
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
```
Restart conduwuit to apply these changes.
### Run
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
```bash
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
```
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
and run `docker compose up -d` in the same directory.
```yml
version: 3
services:
turn:
container_name: coturn-server
image: docker.io/coturn/coturn
restart: unless-stopped
network_mode: "host"
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf
```
To understand why the host networking mode is used and explore alternative configuration options, please visit [Coturn's Docker documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md).
For security recommendations see Synapse's [Coturn documentation](https://element-hq.github.io/synapse/latest/turn-howto.html).

View File

@@ -131,7 +131,7 @@ cargo clippy \
[[task]]
name = "lychee"
group = "lints"
script = "lychee --offline docs"
script = "lychee --verbose --offline docs *.md"
[[task]]
name = "cargo"

10
flake.lock generated
View File

@@ -238,15 +238,15 @@
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1713810944,
"narHash": "sha256-/Xf0bzNJPclH9IP80QNaABfhj4IAR5LycYET18VFCXc=",
"owner": "facebook",
"lastModified": 1714770052,
"narHash": "sha256-NCPYF2wYBsB9OHEkZSOYoPlxjC9BBMhJp8EM5M1o3Mc=",
"owner": "girlbossceo",
"repo": "rocksdb",
"rev": "6f7cabeac80a3a6150be2c8a8369fcecb107bf43",
"rev": "db6df0b185774778457dabfcbd822cb81760cade",
"type": "github"
},
"original": {
"owner": "facebook",
"owner": "girlbossceo",
"ref": "v9.1.1",
"repo": "rocksdb",
"type": "github"

View File

@@ -8,7 +8,8 @@
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
rocksdb = { url = "github:facebook/rocksdb?ref=v9.1.1"; flake = false; };
# https://github.com/girlbossceo/rocksdb/commit/db6df0b185774778457dabfcbd822cb81760cade
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.1.1"; flake = false; };
};
outputs = inputs:
@@ -21,7 +22,7 @@
file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml`
sha256 = "sha256-e4mlaJehWBymYxJGgnbuCObVlqMlQSilZ8FljG9zPHY=";
sha256 = "sha256-+syqAd2kX8KVa8/U2gz3blIQTTsYYt3U63xBWaGOSc8";
};
scope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
@@ -179,6 +180,10 @@
# Useful for editing the book locally
mdbook
])
++ (if !pkgsHost.stdenv.isDarwin then [
# Needed for building with io_uring
pkgsHost.liburing
] else [])
++
scopeHost.main.nativeBuildInputs;
};

View File

@@ -14,6 +14,7 @@ stdenv.mkDerivation {
include = [
"book.toml"
"conduwuit-example.toml"
"CONTRIBUTING.md"
"README.md"
"debian/README.md"
"docs"

View File

@@ -37,7 +37,7 @@ buildDepsOnlyEnv =
});
buildPackageEnv = {
CONDUWUIT_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev;
CONDUWUIT_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
} // buildDepsOnlyEnv;
commonAttrs = {
@@ -83,9 +83,15 @@ craneLib.buildPackage ( commonAttrs // {
(features != [])
"--features " + (builtins.concatStringsSep "," features);
# This is redundant with CI
cargoTestCommand = "";
# This is redundant with CI
doCheck = false;
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
env = buildPackageEnv;
passthru = {

View File

@@ -11,7 +11,7 @@
# If you're having trouble making the relevant changes, bug a maintainer.
[toolchain]
channel = "1.76.0"
channel = "1.77.0"
components = [
# For rust-analyzer
"rust-src",

View File

@@ -1,3 +1,5 @@
use std::fmt::Write as _;
use register::RegistrationKind;
use ruma::{
api::client::{
@@ -238,7 +240,7 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
// If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it
if !services().globals.new_user_displayname_suffix().is_empty() {
displayname.push_str(&(" ".to_owned() + services().globals.new_user_displayname_suffix()));
_ = write!(displayname, " {}", services().globals.config.new_user_displayname_suffix);
}
services()
@@ -531,7 +533,7 @@ pub(crate) async fn deactivate_route(body: Ruma<deactivate::v3::Request>) -> Res
}
// Make the user leave all rooms before deactivation
client_server::leave_all_rooms(sender_user).await?;
client_server::leave_all_rooms(sender_user).await;
// Remove devices and mark account as deactivated
services().users.deactivate_account(sender_user)?;

View File

@@ -269,9 +269,7 @@ async fn alias_checks(room_alias: &OwnedRoomAliasId, appservice_info: &Option<Re
if !info.aliases.is_match(room_alias.as_str()) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
}
}
if services().appservice.is_exclusive_alias(room_alias).await {
} else if services().appservice.is_exclusive_alias(room_alias).await {
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias reserved by appservice."));
}

View File

@@ -1,11 +1,14 @@
use ruma::api::client::{
backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
get_latest_backup_info, update_backup_version,
use ruma::{
api::client::{
backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
get_latest_backup_info, update_backup_version,
},
error::ErrorKind,
},
error::ErrorKind,
UInt,
};
use crate::{services, Error, Result, Ruma};
@@ -56,7 +59,8 @@ pub(crate) async fn get_latest_backup_info_route(
Ok(get_latest_backup_info::v3::Response {
algorithm,
count: (services().key_backups.count_keys(sender_user, &version)? as u32).into(),
count: (UInt::try_from(services().key_backups.count_keys(sender_user, &version)?)
.expect("user backup keys count should not be that high")),
etag: services().key_backups.get_etag(sender_user, &version)?,
version,
})
@@ -76,10 +80,12 @@ pub(crate) async fn get_backup_info_route(
Ok(get_backup_info::v3::Response {
algorithm,
count: (services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
count: (UInt::try_from(
services()
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
@@ -139,10 +145,12 @@ pub(crate) async fn add_backup_keys_route(
}
Ok(add_backup_keys::v3::Response {
count: (services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
count: (UInt::try_from(
services()
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
@@ -181,10 +189,12 @@ pub(crate) async fn add_backup_keys_for_room_route(
}
Ok(add_backup_keys_for_room::v3::Response {
count: (services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
count: (UInt::try_from(
services()
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
@@ -221,10 +231,12 @@ pub(crate) async fn add_backup_keys_for_session_route(
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;
Ok(add_backup_keys_for_session::v3::Response {
count: (services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
count: (UInt::try_from(
services()
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
@@ -294,10 +306,12 @@ pub(crate) async fn delete_backup_keys_route(
.delete_all_keys(sender_user, &body.version)?;
Ok(delete_backup_keys::v3::Response {
count: (services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
count: (UInt::try_from(
services()
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
@@ -317,10 +331,12 @@ pub(crate) async fn delete_backup_keys_for_room_route(
.delete_room_keys(sender_user, &body.version, &body.room_id)?;
Ok(delete_backup_keys_for_room::v3::Response {
count: (services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
count: (UInt::try_from(
services()
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
@@ -340,10 +356,12 @@ pub(crate) async fn delete_backup_keys_for_session_route(
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;
Ok(delete_backup_keys_for_session::v3::Response {
count: (services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
count: (UInt::try_from(
services()
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,

View File

@@ -63,8 +63,8 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
lazy_loaded.insert(base_event.sender.as_str().to_owned());
}
// Use limit with maximum 100
let limit = u64::from(body.limit).min(100) as usize;
// Use limit or else 10, with maximum 100
let limit = usize::try_from(body.limit).unwrap_or(10).min(100);
let base_event = base_event.to_room_event();

View File

@@ -20,7 +20,7 @@
},
StateEventType,
},
ServerName, UInt,
uint, ServerName, UInt,
};
use tracing::{error, info, warn};
@@ -198,8 +198,9 @@ pub(crate) async fn get_public_rooms_filtered_helper(
});
}
// Use limit or else 10, with maximum 100
let limit = limit.map_or(10, u64::from);
let mut num_since = 0_u64;
let mut num_since: u64 = 0;
if let Some(s) = &since {
let mut characters = s.chars();
@@ -363,12 +364,16 @@ pub(crate) async fn get_public_rooms_filtered_helper(
all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
let total_room_count_estimate = (all_rooms.len() as u32).into();
let total_room_count_estimate = UInt::try_from(all_rooms.len()).unwrap_or_else(|_| uint!(0));
let chunk: Vec<_> = all_rooms
.into_iter()
.skip(num_since as usize)
.take(limit as usize)
.skip(
num_since
.try_into()
.expect("num_since should not be this high"),
)
.take(limit.try_into().expect("limit should not be this high"))
.collect();
let prev_batch = if num_since == 0 {
@@ -377,10 +382,15 @@ pub(crate) async fn get_public_rooms_filtered_helper(
Some(format!("p{num_since}"))
};
let next_batch = if chunk.len() < limit as usize {
let next_batch = if chunk.len() < limit.try_into().unwrap() {
None
} else {
Some(format!("n{}", num_since + limit))
Some(format!(
"n{}",
num_since
.checked_add(limit)
.expect("num_since and limit should not be that large")
))
};
Ok(get_public_rooms_filtered::v3::Response {

View File

@@ -1,4 +1,5 @@
use std::{
cmp,
collections::{hash_map, BTreeMap, HashMap, HashSet},
time::{Duration, Instant},
};
@@ -338,7 +339,9 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
hash_map::Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
},
hash_map::Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
hash_map::Entry::Occupied(mut e) => {
*e.get_mut() = (Instant::now(), e.get().1.saturating_add(1));
},
}
};
@@ -353,10 +356,8 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
.get(server)
{
// Exponential backoff
let mut min_elapsed_duration = Duration::from_secs(5 * 60) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
debug!("Backing off query from {:?}", server);

View File

@@ -17,7 +17,11 @@
debug_warn,
service::media::{FileMeta, UrlPreviewData},
services,
utils::{self, server_name::server_is_ours},
utils::{
self,
content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename},
server_name::server_is_ours,
},
Error, Result, Ruma, RumaResponse,
};
@@ -25,7 +29,7 @@
const MXC_LENGTH: usize = 32;
/// Cache control for immutable objects
const CACHE_CONTROL_IMMUTABLE: &str = "public, max-age=31536000, immutable";
const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable";
const CORP_CROSS_ORIGIN: &str = "cross-origin";
@@ -130,7 +134,13 @@ pub(crate) async fn create_content_route(
mxc.clone(),
body.filename
.as_ref()
.map(|filename| "inline; filename=".to_owned() + filename)
.map(|filename| {
format!(
"{}; filename={}",
content_disposition_type(&body.file, &body.content_type),
sanitise_filename(filename.to_owned())
)
})
.as_deref(),
body.content_type.as_deref(),
&body.file,
@@ -173,11 +183,13 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content_disposition,
content_type,
file,
content_disposition,
}) = services().media.get(mxc.clone()).await?
{
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition));
Ok(get_content::v3::Response {
file,
content_type,
@@ -186,7 +198,7 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
})
} else if !server_is_ours(&body.server_name) && body.allow_remote {
get_remote_content(
let response = get_remote_content(
&mxc,
&body.server_name,
body.media_id.clone(),
@@ -197,6 +209,20 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
.map_err(|e| {
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Error::BadRequest(ErrorKind::NotFound, "Remote media error.")
})?;
let content_disposition = Some(make_content_disposition(
&response.file,
&response.content_type,
response.content_disposition,
));
Ok(get_content::v3::Response {
file: response.file,
content_type: response.content_type,
content_disposition,
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
})
} else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
@@ -237,13 +263,15 @@ pub(crate) async fn get_content_as_filename_route(
if let Some(FileMeta {
content_type,
file,
..
content_disposition,
}) = services().media.get(mxc.clone()).await?
{
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition));
Ok(get_content_as_filename::v3::Response {
file,
content_type,
content_disposition: Some(format!("inline; filename={}", body.filename)),
content_disposition,
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
})
@@ -257,13 +285,21 @@ pub(crate) async fn get_content_as_filename_route(
)
.await
{
Ok(remote_content_response) => Ok(get_content_as_filename::v3::Response {
content_disposition: Some(format!("inline: filename={}", body.filename)),
content_type: remote_content_response.content_type,
file: remote_content_response.file,
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
}),
Ok(remote_content_response) => {
let content_disposition = Some(make_content_disposition(
&remote_content_response.file,
&remote_content_response.content_type,
remote_content_response.content_disposition,
));
Ok(get_content_as_filename::v3::Response {
content_disposition,
content_type: remote_content_response.content_type,
file: remote_content_response.file,
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
})
},
Err(e) => {
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Err(Error::BadRequest(ErrorKind::NotFound, "Remote media error."))
@@ -308,7 +344,7 @@ pub(crate) async fn get_content_thumbnail_route(
if let Some(FileMeta {
content_type,
file,
..
content_disposition,
}) = services()
.media
.get_thumbnail(
@@ -322,11 +358,14 @@ pub(crate) async fn get_content_thumbnail_route(
)
.await?
{
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition));
Ok(get_content_thumbnail::v3::Response {
file,
content_type,
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
content_disposition,
})
} else if !server_is_ours(&body.server_name) && body.allow_remote {
if services()
@@ -371,7 +410,19 @@ pub(crate) async fn get_content_thumbnail_route(
)
.await?;
Ok(get_thumbnail_response)
let content_disposition = Some(make_content_disposition(
&get_thumbnail_response.file,
&get_thumbnail_response.content_type,
get_thumbnail_response.content_disposition,
));
Ok(get_content_thumbnail::v3::Response {
file: get_thumbnail_response.file,
content_type: get_thumbnail_response.content_type,
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
content_disposition,
})
},
Err(e) => {
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
@@ -429,18 +480,30 @@ async fn get_remote_content(
)
.await?;
let content_disposition = Some(make_content_disposition(
&content_response.file,
&content_response.content_type,
content_response.content_disposition,
));
services()
.media
.create(
None,
mxc.to_owned(),
content_response.content_disposition.as_deref(),
content_disposition.as_deref(),
content_response.content_type.as_deref(),
&content_response.file,
)
.await?;
Ok(content_response)
Ok(get_content::v3::Response {
file: content_response.file,
content_type: content_response.content_type,
content_disposition,
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
})
}
async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {

View File

@@ -1,4 +1,5 @@
use std::{
cmp,
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
sync::Arc,
time::{Duration, Instant},
@@ -20,12 +21,13 @@
room::{
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
message::RoomMessageEventContent,
},
StateEventType, TimelineEventType,
},
serde::Base64,
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
OwnedUserId, RoomId, RoomVersionId, UserId,
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
};
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use tokio::sync::RwLock;
@@ -39,6 +41,91 @@
Error, PduEvent, Result, Ruma,
};
/// Checks if the room is banned in any way possible and the sender user is not
/// an admin.
///
/// Performs automatic deactivation if `auto_deactivate_banned_room_attempts` is
/// enabled
#[tracing::instrument]
async fn banned_room_check(user_id: &UserId, room_id: Option<&RoomId>, server_name: Option<&ServerName>) -> Result<()> {
if !services().users.is_admin(user_id)? {
if let Some(room_id) = room_id {
if services().rooms.metadata.is_banned(room_id)?
|| services()
.globals
.config
.forbidden_remote_server_names
.contains(&room_id.server_name().unwrap().to_owned())
{
warn!(
"User {user_id} who is not an admin attempted to send an invite for or attempted to join a banned \
room or banned room server name: {room_id}."
);
if services()
.globals
.config
.auto_deactivate_banned_room_attempts
{
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
services()
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join"
)))
.await;
// ignore errors
leave_all_rooms(user_id).await;
_ = services().users.deactivate_account(user_id);
}
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This room is banned on this homeserver.",
));
}
} else if let Some(server_name) = server_name {
if services()
.globals
.config
.forbidden_remote_server_names
.contains(&server_name.to_owned())
{
warn!(
"User {user_id} who is not an admin tried joining a room which has the server name {server_name} \
that is globally forbidden. Rejecting.",
);
if services()
.globals
.config
.auto_deactivate_banned_room_attempts
{
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
services()
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join"
)))
.await;
// ignore errors
leave_all_rooms(user_id).await;
_ = services().users.deactivate_account(user_id);
}
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This remote server is banned on this homeserver.",
));
}
}
}
Ok(())
}
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
///
/// Tries to join the sender user into a room.
@@ -52,32 +139,7 @@ pub(crate) async fn join_room_by_id_route(
) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if services().rooms.metadata.is_banned(&body.room_id)? && !services().users.is_admin(sender_user)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This room is banned on this homeserver.",
));
}
if let Some(server) = body.room_id.server_name() {
if services()
.globals
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
&& !services().users.is_admin(sender_user)?
{
warn!(
"User {sender_user} tried joining room ID {} which has a server name that is globally forbidden. \
Rejecting.",
body.room_id
);
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This remote server is banned on this homeserver.",
));
}
}
banned_room_check(sender_user, Some(&body.room_id), body.room_id.server_name()).await?;
// There is no body.server_name for /roomId/join
let mut servers = services()
@@ -130,31 +192,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
Ok(room_id) => {
if services().rooms.metadata.is_banned(&room_id)? && !services().users.is_admin(sender_user)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This room is banned on this homeserver.",
));
}
if let Some(server) = room_id.server_name() {
if services()
.globals
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
&& !services().users.is_admin(sender_user)?
{
warn!(
"User {sender_user} tried joining room ID {room_id} which has a server name that is globally \
forbidden. Rejecting.",
);
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This remote server is banned on this homeserver.",
));
}
}
banned_room_check(sender_user, Some(&room_id), room_id.server_name()).await?;
let mut servers = body.server_name.clone();
servers.extend(
@@ -185,69 +223,9 @@ pub(crate) async fn join_room_by_id_or_alias_route(
(servers, room_id)
},
Err(room_alias) => {
if services()
.globals
.config
.forbidden_remote_server_names
.contains(&room_alias.server_name().to_owned())
&& !services().users.is_admin(sender_user)?
{
warn!(
"User {sender_user} tried joining room alias {room_alias} which has a server name that is \
globally forbidden. Rejecting.",
);
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This remote server is banned on this homeserver.",
));
}
let response = get_alias_helper(room_alias.clone(), Some(body.server_name.clone())).await?;
if services().rooms.metadata.is_banned(&response.room_id)? && !services().users.is_admin(sender_user)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This room is banned on this homeserver.",
));
}
if services()
.globals
.config
.forbidden_remote_server_names
.contains(&room_alias.server_name().to_owned())
&& !services().users.is_admin(sender_user)?
{
warn!(
"User {sender_user} tried joining room alias {room_alias} with room ID {}, which the alias has a \
server name that is globally forbidden. Rejecting.",
&response.room_id
);
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This remote server is banned on this homeserver.",
));
}
if let Some(server) = response.room_id.server_name() {
if services()
.globals
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
&& !services().users.is_admin(sender_user)?
{
warn!(
"User {sender_user} tried joining room alias {room_alias} with room ID {}, which has a server \
name that is globally forbidden. Rejecting.",
&response.room_id
);
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This remote server is banned on this homeserver.",
));
}
}
banned_room_check(sender_user, Some(&response.room_id), Some(room_alias.server_name())).await?;
let mut servers = body.server_name;
servers.extend(response.servers);
@@ -320,30 +298,7 @@ pub(crate) async fn invite_user_route(body: Ruma<invite_user::v3::Request>) -> R
));
}
if services().rooms.metadata.is_banned(&body.room_id)? && !services().users.is_admin(sender_user)? {
info!(
"Local user {} who is not an admin attempted to send an invite for banned room {}.",
&sender_user, &body.room_id
);
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This room is banned on this homeserver.",
));
}
if let Some(server) = body.room_id.server_name() {
if services()
.globals
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Server is banned on this homeserver.",
));
}
}
banned_room_check(sender_user, Some(&body.room_id), body.room_id.server_name()).await?;
if let invite_user::v3::InvitationRecipient::UserId {
user_id,
@@ -362,15 +317,6 @@ pub(crate) async fn invite_user_route(body: Ruma<invite_user::v3::Request>) -> R
pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Result<kick_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if let Ok(true) = services()
.rooms
.state_cache
.is_left(sender_user, &body.room_id)
{
info!("{} is not in room {}", &body.user_id, &body.room_id);
return Ok(kick_user::v3::Response {});
}
let mut event: RoomMemberEventContent = serde_json::from_str(
services()
.rooms
@@ -427,17 +373,6 @@ pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Resul
pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<ban_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if let Ok(Some(membership_event)) = services()
.rooms
.state_accessor
.get_member(&body.room_id, sender_user)
{
if membership_event.membership == MembershipState::Ban {
info!("{} is already banned in {}", &body.user_id, &body.room_id);
return Ok(ban_user::v3::Response {});
}
}
let event = services()
.rooms
.state_accessor
@@ -445,11 +380,11 @@ pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<
.map_or(
Ok(RoomMemberEventContent {
membership: MembershipState::Ban,
displayname: services().users.displayname(&body.user_id)?,
avatar_url: services().users.avatar_url(&body.user_id)?,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: services().users.blurhash(&body.user_id)?,
blurhash: services().users.blurhash(&body.user_id).unwrap_or_default(),
reason: body.reason.clone(),
join_authorized_via_users_server: None,
}),
@@ -457,14 +392,8 @@ pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<
serde_json::from_str(event.content.get())
.map(|event: RoomMemberEventContent| RoomMemberEventContent {
membership: MembershipState::Ban,
displayname: services()
.users
.displayname(&body.user_id)
.unwrap_or_default(),
avatar_url: services()
.users
.avatar_url(&body.user_id)
.unwrap_or_default(),
displayname: None,
avatar_url: None,
blurhash: services().users.blurhash(&body.user_id).unwrap_or_default(),
reason: body.reason.clone(),
join_authorized_via_users_server: None,
@@ -513,17 +442,6 @@ pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<
pub(crate) async fn unban_user_route(body: Ruma<unban_user::v3::Request>) -> Result<unban_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if let Ok(Some(membership_event)) = services()
.rooms
.state_accessor
.get_member(&body.room_id, sender_user)
{
if membership_event.membership != MembershipState::Ban {
info!("{} is already unbanned in {}", &body.user_id, &body.room_id);
return Ok(unban_user::v3::Response {});
}
}
let mut event: RoomMemberEventContent = serde_json::from_str(
services()
.rooms
@@ -1301,8 +1219,8 @@ async fn make_join_request(
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
let mut make_join_response_and_server = Err(Error::BadServerResponse("No server available to assist in joining."));
let mut make_join_counter = 0;
let mut incompatible_room_version_count = 0;
let mut make_join_counter: u16 = 0;
let mut incompatible_room_version_count: u8 = 0;
for remote_server in servers {
if server_is_ours(remote_server) {
@@ -1322,7 +1240,7 @@ async fn make_join_request(
.await;
trace!("make_join response: {:?}", make_join_response);
make_join_counter += 1;
make_join_counter = make_join_counter.saturating_add(1);
if let Err(ref e) = make_join_response {
trace!("make_join ErrorKind string: {:?}", e.error_code().to_string());
@@ -1336,7 +1254,7 @@ async fn make_join_request(
.to_string()
.contains("M_UNSUPPORTED_ROOM_VERSION")
{
incompatible_room_version_count += 1;
incompatible_room_version_count = incompatible_room_version_count.saturating_add(1);
}
if incompatible_room_version_count > 15 {
@@ -1393,7 +1311,9 @@ async fn validate_and_add_event_id(
Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
},
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
Entry::Occupied(mut e) => {
*e.get_mut() = (Instant::now(), e.get().1.saturating_add(1));
},
}
};
@@ -1405,10 +1325,8 @@ async fn validate_and_add_event_id(
.get(&event_id)
{
// Exponential backoff
let mut min_elapsed_duration = Duration::from_secs(5 * 60) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
debug!("Backing off from {}", event_id);
@@ -1605,8 +1523,9 @@ pub(crate) async fn invite_helper(
Ok(())
}
// Make a user leave all their joined rooms
pub(crate) async fn leave_all_rooms(user_id: &UserId) -> Result<()> {
// Make a user leave all their joined rooms, forgets all rooms, and ignores
// errors
pub(crate) async fn leave_all_rooms(user_id: &UserId) {
let all_rooms = services()
.rooms
.state_cache
@@ -1626,10 +1545,9 @@ pub(crate) async fn leave_all_rooms(user_id: &UserId) -> Result<()> {
};
// ignore errors
_ = services().rooms.state_cache.forget(&room_id, user_id);
_ = leave_room(user_id, &room_id, None).await;
}
Ok(())
}
pub(crate) async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {

View File

@@ -144,7 +144,7 @@ pub(crate) async fn get_message_events_route(
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
.await?;
let limit = u64::from(body.limit).min(100) as usize;
let limit = usize::try_from(body.limit).unwrap_or(10).min(100);
let next_token;
@@ -159,8 +159,9 @@ pub(crate) async fn get_message_events_route(
.timeline
.pdus_after(sender_user, &body.room_id, from)?
.filter_map(Result::ok) // Filter out buggy events
.filter(|(_, pdu)| contains_url_filter(pdu, &body.filter))
.filter(|(_, pdu)| visibility_filter(pdu, sender_user, &body.room_id))
.filter(|(_, pdu)| { contains_url_filter(pdu, &body.filter) && visibility_filter(pdu, sender_user, &body.room_id)
})
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
.take(limit)
.collect();
@@ -205,8 +206,7 @@ pub(crate) async fn get_message_events_route(
.timeline
.pdus_until(sender_user, &body.room_id, from)?
.filter_map(Result::ok) // Filter out buggy events
.filter(|(_, pdu)| contains_url_filter(pdu, &body.filter))
.filter(|(_, pdu)| visibility_filter(pdu, sender_user, &body.room_id))
.filter(|(_, pdu)| {contains_url_filter(pdu, &body.filter) && visibility_filter(pdu, sender_user, &body.room_id)})
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
.take(limit)
.collect();

View File

@@ -49,9 +49,20 @@ pub(crate) async fn get_presence_route(body: Ruma<get_presence::v3::Request>) ->
}
if let Some(presence) = presence_event {
let status_msg = if presence
.content
.status_msg
.as_ref()
.is_some_and(String::is_empty)
{
None
} else {
presence.content.status_msg
};
Ok(get_presence::v3::Response {
// TODO: Should ruma just use the presenceeventcontent type here?
status_msg: presence.content.status_msg,
status_msg,
currently_active: presence.content.currently_active,
last_active_ago: presence
.content

View File

@@ -91,12 +91,12 @@ fn is_report_valid(
));
}
if services()
if !services()
.rooms
.state_cache
.room_members(&pdu.room_id)
.filter_map(Result::ok)
.any(|user_id| user_id != *sender_user)
.any(|user_id| user_id == *sender_user)
{
return Err(Error::BadRequest(
ErrorKind::NotFound,

View File

@@ -801,7 +801,15 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
.map_err(|_| Error::bad_database("Invalid room event in database."))?;
// Setting events_default and invite to the greater of 50 and users_default + 1
let new_level = max(int!(50), power_levels_event_content.users_default + int!(1));
let new_level = max(
int!(50),
power_levels_event_content
.users_default
.checked_add(int!(1))
.ok_or_else(|| {
Error::BadRequest(ErrorKind::BadJson, "users_default power levels event content is not valid")
})?,
);
power_levels_event_content.events_default = new_level;
power_levels_event_content.invite = new_level;

View File

@@ -10,7 +10,7 @@
},
events::AnyStateEvent,
serde::Raw,
OwnedRoomId,
uint, OwnedRoomId,
};
use tracing::debug;
@@ -39,7 +39,12 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
});
// Use limit or else 10, with maximum 100
let limit = filter.limit.map_or(10, u64::from).min(100) as usize;
let limit: usize = filter
.limit
.unwrap_or_else(|| uint!(10))
.try_into()
.unwrap_or(10)
.min(100);
let mut room_states: BTreeMap<OwnedRoomId, Vec<Raw<AnyStateEvent>>> = BTreeMap::new();
@@ -106,14 +111,16 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
}
}
let skip = match body.next_batch.as_ref().map(|s| s.parse()) {
let skip: usize = match body.next_batch.as_ref().map(|s| s.parse()) {
Some(Ok(s)) => s,
Some(Err(_)) => return Err(Error::BadRequest(ErrorKind::InvalidParam, "Invalid next_batch token.")),
None => 0, // Default to the start
};
let mut results = Vec::new();
for _ in 0..skip + limit {
let next_batch: usize = skip.saturating_add(limit);
for _ in 0..next_batch {
if let Some(s) = searches
.iter_mut()
.map(|s| (s.peek().cloned(), s))
@@ -162,12 +169,12 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
let next_batch = if results.len() < limit {
None
} else {
Some((skip + limit).to_string())
Some(next_batch.to_string())
};
Ok(search_events::v3::Response::new(ResultCategories {
room_events: ResultRoomEvents {
count: Some((results.len() as u32).into()),
count: Some(results.len().try_into().unwrap_or_else(|_| uint!(0))),
groups: BTreeMap::new(), // TODO
next_batch,
results,

View File

@@ -2,7 +2,7 @@
use ruma::{
api::client::{error::ErrorKind, space::get_hierarchy},
UInt,
uint, UInt,
};
use crate::{service::rooms::spaces::PagnationToken, services, Error, Result, Ruma};
@@ -14,10 +14,12 @@
pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let limit = body
let limit: usize = body
.limit
.unwrap_or_else(|| UInt::from(10_u32))
.min(UInt::from(100_u32));
.unwrap_or_else(|| uint!(10))
.try_into()
.unwrap_or(10)
.min(100);
let max_depth = body
.max_depth
@@ -45,9 +47,9 @@ pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>)
.get_client_hierarchy(
sender_user,
&body.room_id,
u64::from(limit) as usize,
key.map_or(0, |token| u64::from(token.skip) as usize),
u64::from(max_depth) as usize,
limit,
key.map_or(0, |token| token.skip.try_into().unwrap_or(0)),
max_depth.try_into().unwrap_or(3),
body.suggested_only,
)
.await

View File

@@ -539,7 +539,7 @@ async fn handle_left_room(
left_state_ids.insert(leave_shortstatekey, left_event_id);
let mut i = 0;
let mut i: u8 = 0;
for (key, id) in left_state_ids {
if full_state || since_state_ids.get(&key) != Some(&id) {
let (event_type, state_key) = services().rooms.short.get_statekey_from_short(key)?;
@@ -557,7 +557,7 @@ async fn handle_left_room(
left_state_events.push(pdu.to_sync_state_event());
i += 1;
i = i.wrapping_add(1);
if i % 100 == 0 {
tokio::task::yield_now().await;
}
@@ -705,7 +705,7 @@ async fn load_joined_room(
// Recalculate heroes (first 5 members)
let mut heroes = Vec::new();
if joined_member_count + invited_member_count <= 5 {
if joined_member_count.saturating_add(invited_member_count) <= 5 {
// Go through all PDUs and for each member event, check if the user is still
// joined or invited until we have 5 or we reach the end
@@ -784,7 +784,7 @@ async fn load_joined_room(
let mut state_events = Vec::new();
let mut lazy_loaded = HashSet::new();
let mut i = 0;
let mut i: u8 = 0;
for (shortstatekey, id) in current_state_ids {
let (event_type, state_key) = services()
.rooms
@@ -798,7 +798,7 @@ async fn load_joined_room(
};
state_events.push(pdu);
i += 1;
i = i.wrapping_add(1);
if i % 100 == 0 {
tokio::task::yield_now().await;
}
@@ -819,7 +819,7 @@ async fn load_joined_room(
}
state_events.push(pdu);
i += 1;
i = i.wrapping_add(1);
if i % 100 == 0 {
tokio::task::yield_now().await;
}
@@ -1416,10 +1416,14 @@ pub(crate) async fn sync_events_v4_route(
.ranges
.into_iter()
.map(|mut r| {
r.0 =
r.0.clamp(uint!(0), UInt::from(all_joined_rooms.len() as u32 - 1));
r.1 =
r.1.clamp(r.0, UInt::from(all_joined_rooms.len() as u32 - 1));
r.0 = r.0.clamp(
uint!(0),
UInt::try_from(all_joined_rooms.len().saturating_sub(1)).unwrap_or(UInt::MAX),
);
r.1 = r.1.clamp(
r.0,
UInt::try_from(all_joined_rooms.len().saturating_sub(1)).unwrap_or(UInt::MAX),
);
let room_ids = all_joined_rooms[(u64::from(r.0) as usize)..=(u64::from(r.1) as usize)].to_vec();
new_known_rooms.extend(room_ids.iter().cloned());
for room_id in &room_ids {
@@ -1592,14 +1596,13 @@ pub(crate) async fn sync_events_v4_route(
.collect::<Vec<_>>();
let name = match heroes.len().cmp(&(1_usize)) {
Ordering::Greater => {
let firsts = heroes[1..]
.iter()
.map(|h| h.0.clone())
.collect::<Vec<_>>()
.join(", ");
let last = heroes[0].0.clone();
Some(
heroes[1..]
.iter()
.map(|h| h.0.clone())
.collect::<Vec<_>>()
.join(", ") + " and " + &last,
)
Some(format!("{firsts} and {last}"))
},
Ordering::Equal => Some(heroes[0].0.clone()),
Ordering::Less => None,

View File

@@ -1,4 +1,7 @@
use ruma::api::client::{error::ErrorKind, threads::get_threads};
use ruma::{
api::client::{error::ErrorKind, threads::get_threads},
uint,
};
use crate::{services, Error, Result, Ruma};
@@ -9,7 +12,8 @@ pub(crate) async fn get_threads_route(body: Ruma<get_threads::v1::Request>) -> R
// Use limit or else 10, with maximum 100
let limit = body
.limit
.and_then(|l| l.try_into().ok())
.unwrap_or_else(|| uint!(10))
.try_into()
.unwrap_or(10)
.min(100);

View File

@@ -22,14 +22,30 @@ pub(crate) async fn create_typing_event_route(
if let Typing::Yes(duration) = body.state {
let duration = utils::clamp(
duration.as_millis() as u64,
services().globals.config.typing_client_timeout_min_s * 1000,
services().globals.config.typing_client_timeout_max_s * 1000,
duration.as_millis().try_into().unwrap_or(u64::MAX),
services()
.globals
.config
.typing_client_timeout_min_s
.checked_mul(1000)
.unwrap(),
services()
.globals
.config
.typing_client_timeout_max_s
.checked_mul(1000)
.unwrap(),
);
services()
.rooms
.typing
.typing_add(sender_user, &body.room_id, utils::millis_since_unix_epoch() + duration)
.typing_add(
sender_user,
&body.room_id,
utils::millis_since_unix_epoch()
.checked_add(duration)
.expect("user typing timeout should not get this high"),
)
.await?;
} else {
services()

View File

@@ -17,7 +17,7 @@
/// and don't share a room with the sender
pub(crate) async fn search_users_route(body: Ruma<search_users::v3::Request>) -> Result<search_users::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let limit = u64::from(body.limit) as usize;
let limit = usize::try_from(body.limit).unwrap_or(10); // default limit is 10
let mut users = services().users.iter().filter_map(|user_id| {
// Filter out buggy users (they should not exist, but you never know...)

View File

@@ -21,7 +21,9 @@ pub(crate) async fn turn_server_route(
let (username, password) = if !turn_secret.is_empty() {
let expiry = SecondsSinceUnixEpoch::from_system_time(
SystemTime::now() + Duration::from_secs(services().globals.turn_ttl()),
SystemTime::now()
.checked_add(Duration::from_secs(services().globals.turn_ttl()))
.expect("TURN TTL should not get this high"),
)
.expect("time is valid");

View File

@@ -101,7 +101,9 @@ pub(crate) async fn get_server_keys_route() -> Result<impl IntoResponse> {
old_verify_keys: BTreeMap::new(),
signatures: BTreeMap::new(),
valid_until_ts: MilliSecondsSinceUnixEpoch::from_system_time(
SystemTime::now() + Duration::from_secs(86400 * 7),
SystemTime::now()
.checked_add(Duration::from_secs(86400 * 7))
.expect("valid_until_ts should not get this high"),
)
.expect("time is valid"),
})
@@ -398,8 +400,13 @@ pub(crate) async fn send_transaction_message_route(
.is_joined(&typing.user_id, &typing.room_id)?
{
if typing.typing {
let timeout = utils::millis_since_unix_epoch()
+ services().globals.config.typing_federation_timeout_s * 1000;
let timeout = utils::millis_since_unix_epoch().saturating_add(
services()
.globals
.config
.typing_federation_timeout_s
.saturating_mul(1000),
);
services()
.rooms
.typing
@@ -662,7 +669,7 @@ pub(crate) async fn get_missing_events_route(
}
if body.earliest_events.contains(&queued_events[i]) {
i += 1;
i = i.saturating_add(1);
continue;
}
@@ -671,7 +678,7 @@ pub(crate) async fn get_missing_events_route(
&body.room_id,
&queued_events[i],
)? {
i += 1;
i = i.saturating_add(1);
continue;
}
@@ -688,7 +695,7 @@ pub(crate) async fn get_missing_events_route(
);
events.push(PduEvent::convert_to_outgoing_federation_event(pdu));
}
i += 1;
i = i.saturating_add(1);
}
Ok(get_missing_events::v1::Response {

View File

@@ -207,6 +207,8 @@ pub(crate) struct Config {
#[serde(default = "Vec::new")]
pub(crate) auto_join_rooms: Vec<OwnedRoomId>,
#[serde(default)]
pub(crate) auto_deactivate_banned_room_attempts: bool,
#[serde(default = "default_rocksdb_log_level")]
pub(crate) rocksdb_log_level: String,
@@ -612,6 +614,10 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"Allow incoming profile lookup federation requests",
&self.allow_profile_lookup_federation_requests.to_string(),
),
(
"Auto deactivate banned room join attempts",
&self.auto_deactivate_banned_room_attempts.to_string(),
),
("Notification push path", &self.notification_push_path),
("Allow room creation", &self.allow_room_creation.to_string()),
(

View File

@@ -105,7 +105,7 @@ fn changes_since(
// Skip the data that's exactly at since, because we sent that last time
let mut first_possible = prefix.clone();
first_possible.extend_from_slice(&(since + 1).to_be_bytes());
first_possible.extend_from_slice(&(since.saturating_add(1)).to_be_bytes());
for r in self
.roomuserdataid_accountdata

View File

@@ -158,18 +158,15 @@ fn memory_usage(&self) -> String {
let max_appservice_in_room_cache = self.appservice_in_room_cache.read().unwrap().capacity();
let max_lasttimelinecount_cache = self.lasttimelinecount_cache.lock().unwrap().capacity();
let mut response = format!(
format!(
"\
auth_chain_cache: {auth_chain_cache} / {max_auth_chain_cache}
our_real_users_cache: {our_real_users_cache} / {max_our_real_users_cache}
appservice_in_room_cache: {appservice_in_room_cache} / {max_appservice_in_room_cache}
lasttimelinecount_cache: {lasttimelinecount_cache} / {max_lasttimelinecount_cache}\n\n"
);
if let Ok(db_stats) = self.db.memory_usage() {
response += &db_stats;
}
response
lasttimelinecount_cache: {lasttimelinecount_cache} / {max_lasttimelinecount_cache}\n\n
{}",
self.db.memory_usage().unwrap_or_default()
)
}
fn clear_caches(&self, amount: u32) {

View File

@@ -1,8 +1,8 @@
use ruma::{events::presence::PresenceEvent, presence::PresenceState, OwnedUserId, UInt, UserId};
use tracing::debug;
use crate::{
database::KeyValueDatabase,
debug_info,
service::{self, presence::Presence},
services,
utils::{self, user_id_from_bytes},
@@ -50,13 +50,21 @@ fn set_presence(
// tighten for state flicker?
if !state_changed && last_active_ts <= last_last_active_ts {
debug!(
debug_info!(
"presence spam {:?} last_active_ts:{:?} <= {:?}",
user_id, last_active_ts, last_last_active_ts
user_id,
last_active_ts,
last_last_active_ts
);
return Ok(());
}
let status_msg = if status_msg.as_ref().is_some_and(String::is_empty) {
None
} else {
status_msg
};
let presence = Presence::new(
presence_state.to_owned(),
currently_active.unwrap_or(false),

View File

@@ -23,10 +23,10 @@ fn relations_until<'a>(
let mut current = prefix.clone();
let count_raw = match until {
PduCount::Normal(x) => x - 1,
PduCount::Normal(x) => x.saturating_sub(1),
PduCount::Backfilled(x) => {
current.extend_from_slice(&0_u64.to_be_bytes());
u64::MAX - x - 1
u64::MAX.saturating_sub(x).saturating_sub(1)
},
};
current.extend_from_slice(&count_raw.to_be_bytes());

View File

@@ -48,7 +48,7 @@ fn readreceipts_since<'a>(
let prefix2 = prefix.clone();
let mut first_possible_edu = prefix.clone();
first_possible_edu.extend_from_slice(&(since + 1).to_be_bytes()); // +1 so we don't send the event at since
first_possible_edu.extend_from_slice(&(since.saturating_add(1)).to_be_bytes()); // +1 so we don't send the event at since
Box::new(
self.readreceiptid_readreceipt

View File

@@ -17,7 +17,7 @@ async fn state_full_ids(&self, shortstatehash: u64) -> Result<HashMap<u64, Arc<E
.expect("there is always one layer")
.1;
let mut result = HashMap::new();
let mut i = 0;
let mut i: u8 = 0;
for compressed in full_state.iter() {
let parsed = services()
.rooms
@@ -25,7 +25,7 @@ async fn state_full_ids(&self, shortstatehash: u64) -> Result<HashMap<u64, Arc<E
.parse_compressed_state_event(compressed)?;
result.insert(parsed.0, parsed.1);
i += 1;
i = i.wrapping_add(1);
if i % 100 == 0 {
tokio::task::yield_now().await;
}
@@ -44,7 +44,7 @@ async fn state_full(&self, shortstatehash: u64) -> Result<HashMap<(StateEventTyp
.1;
let mut result = HashMap::new();
let mut i = 0;
let mut i: u8 = 0;
for compressed in full_state.iter() {
let (_, eventid) = services()
.rooms
@@ -63,7 +63,7 @@ async fn state_full(&self, shortstatehash: u64) -> Result<HashMap<(StateEventTyp
);
}
i += 1;
i = i.wrapping_add(1);
if i % 100 == 0 {
tokio::task::yield_now().await;
}

View File

@@ -154,11 +154,11 @@ fn update_joined_count(&self, room_id: &RoomId) -> Result<()> {
if user_is_local(&joined) && !services().users.is_deactivated(&joined).unwrap_or(true) {
real_users.insert(joined);
}
joinedcount += 1;
joinedcount = joinedcount.saturating_add(1);
}
for _invited in self.room_members_invited(room_id).filter_map(Result::ok) {
invitedcount += 1;
invitedcount = invitedcount.saturating_add(1);
}
self.roomid_joinedcount

View File

@@ -256,7 +256,7 @@ fn pdu_count(pdu_id: &[u8]) -> Result<PduCount> {
utils::u64_from_bytes(&pdu_id[pdu_id.len() - 2 * size_of::<u64>()..pdu_id.len() - size_of::<u64>()]);
if matches!(second_last_u64, Ok(0)) {
Ok(PduCount::Backfilled(u64::MAX - last_u64))
Ok(PduCount::Backfilled(u64::MAX.saturating_sub(last_u64)))
} else {
Ok(PduCount::Normal(last_u64))
}
@@ -275,22 +275,18 @@ fn count_to_id(room_id: &RoomId, count: PduCount, offset: u64, subtract: bool) -
let count_raw = match count {
PduCount::Normal(x) => {
if subtract {
x - offset
x.saturating_sub(offset)
} else {
x + offset
x.saturating_add(offset)
}
},
PduCount::Backfilled(x) => {
pdu_id.extend_from_slice(&0_u64.to_be_bytes());
let num = u64::MAX - x;
let num = u64::MAX.saturating_sub(x);
if subtract {
if num > 0 {
num - offset
} else {
num
}
num.saturating_sub(offset)
} else {
num + offset
num.saturating_add(offset)
}
},
};

View File

@@ -110,7 +110,8 @@ fn get_shared_rooms<'a>(
.enumerate()
.find(|(_, &b)| b == 0xFF)
.ok_or_else(|| Error::bad_database("Invalid userroomid_joined in db."))?
.0 + 1; // +1 because the room id starts AFTER the separator
.0
.saturating_add(1); // +1 because the room id starts AFTER the separator
let room_id = key[roomid_index..].to_vec();

View File

@@ -5,7 +5,7 @@
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
events::{AnyToDeviceEvent, StateEventType},
serde::Raw,
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedDeviceKeyId,
uint, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedDeviceKeyId,
OwnedMxcUri, OwnedUserId, UInt, UserId,
};
use tracing::warn;
@@ -414,7 +414,7 @@ fn count_one_time_keys(
.algorithm(),
)
}) {
*counts.entry(algorithm?).or_default() += UInt::from(1_u32);
*counts.entry(algorithm?).or_default() += uint!(1);
}
Ok(counts)
@@ -561,7 +561,7 @@ fn keys_changed<'a>(
prefix.push(0xFF);
let mut start = prefix.clone();
start.extend_from_slice(&(from + 1).to_be_bytes());
start.extend_from_slice(&(from.saturating_add(1)).to_be_bytes());
let to = to.unwrap_or(u64::MAX);

View File

@@ -173,13 +173,11 @@ pub(crate) async fn migrations(db: &KeyValueDatabase, config: &Config) -> Result
let mut current_sstatehash: Option<u64> = None;
let mut current_room = None;
let mut current_state = HashSet::new();
let mut counter = 0;
let mut handle_state = |current_sstatehash: u64,
current_room: &RoomId,
current_state: HashSet<_>,
last_roomstates: &mut HashMap<_, _>| {
counter += 1;
let handle_state = |current_sstatehash: u64,
current_room: &RoomId,
current_state: HashSet<_>,
last_roomstates: &mut HashMap<_, _>| {
let last_roomsstatehash = last_roomstates.get(current_room);
let states_parents = last_roomsstatehash.map_or_else(

View File

@@ -340,6 +340,7 @@ pub(crate) async fn load_or_create(config: Config, tracing_reload_handler: LogLe
roomid_inviteviaservers: builder.open_tree("roomid_inviteviaservers")?,
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
auth_chain_cache: Mutex::new(LruCache::new(
(f64::from(config.auth_chain_cache_capacity) * config.conduit_cache_capacity_modifier) as usize,
)),
@@ -401,19 +402,8 @@ fn check_db_setup(config: &Config) -> Result<()> {
let sqlite_exists = path.join("conduit.db").exists();
let rocksdb_exists = path.join("IDENTITY").exists();
let mut count = 0;
if sqlite_exists {
count += 1;
}
if rocksdb_exists {
count += 1;
}
if count > 1 {
warn!("Multiple databases at database_path detected");
return Ok(());
if sqlite_exists && rocksdb_exists {
return Err(Error::bad_config("Multiple databases at database_path detected."));
}
if sqlite_exists && config.database_backend != "sqlite" {

View File

@@ -35,7 +35,11 @@ pub(crate) struct Engine {
impl KeyValueDatabaseEngine for Arc<Engine> {
fn open(config: &Config) -> Result<Self> {
let cache_capacity_bytes = config.db_cache_capacity_mb * 1024.0 * 1024.0;
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let row_cache_capacity_bytes = (cache_capacity_bytes * 0.50) as usize;
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let col_cache_capacity_bytes = (cache_capacity_bytes * 0.50) as usize;
let mut col_cache = HashMap::new();
@@ -128,6 +132,7 @@ fn uncork(&self) -> Result<()> {
Ok(())
}
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn memory_usage(&self) -> Result<String> {
let mut res = String::new();
let stats = rust_rocksdb::perf::get_memory_usage_stats(Some(&[&self.rocks]), Some(&[&self.row_cache]))?;

View File

@@ -145,7 +145,14 @@ pub(crate) fn cf_options(cfg: &Config, name: &str, mut opts: Options, cache: &mu
cache_size(cfg, cfg.statekeyshort_cache_capacity, 1024),
),
"pduid_pdu" => set_table_with_new_cache(&mut opts, cfg, cache, name, cfg.pdu_cache_capacity as usize * 1536),
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
"pduid_pdu" => set_table_with_new_cache(
&mut opts,
cfg,
cache,
name,
(cfg.pdu_cache_capacity as usize).saturating_mul(1536),
),
"eventid_outlierpdu" => set_table_with_shared_cache(&mut opts, cfg, cache, name, "pduid_pdu"),
@@ -309,7 +316,10 @@ fn set_table_with_shared_cache(
fn cache_size(config: &Config, base_size: u32, entity_size: usize) -> usize {
let ents = f64::from(base_size) * config.conduit_cache_capacity_modifier;
ents as usize * entity_size
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
(ents as usize)
.checked_mul(entity_size)
.expect("cache capacity size is too large")
}
fn table_options(_config: &Config) -> BlockBasedOptions {

View File

@@ -21,7 +21,7 @@
struct PreparedStatementIterator<'a> {
iterator: Box<dyn Iterator<Item = TupleOfBytes> + 'a>,
_statement_ref: NonAliasingBox<rusqlite::Statement<'a>>,
_statement_ref: AliasableBox<rusqlite::Statement<'a>>,
}
impl Iterator for PreparedStatementIterator<'_> {
@@ -30,16 +30,25 @@ impl Iterator for PreparedStatementIterator<'_> {
fn next(&mut self) -> Option<Self::Item> { self.iterator.next() }
}
struct NonAliasingBox<T>(*mut T);
impl<T> Drop for NonAliasingBox<T> {
struct AliasableBox<T>(*mut T);
impl<T> Drop for AliasableBox<T> {
fn drop(&mut self) {
// TODO: figure out why this is necessary, but also this is sqlite so dont think
// i care that much. i tried checking commit history but couldn't find out why
// this was done.
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
_ = Box::from_raw(self.0);
};
// SAFETY: This is cursed and relies on non-local reasoning.
//
// In order for this to be safe:
//
// * All aliased references to this value must have been dropped first, for
// example by coming after its referrers in struct fields, because struct
// fields are automatically dropped in order from top to bottom in the absence
// of an explicit Drop impl. Otherwise, the referrers may read into
// deallocated memory.
// * This type must not be copyable or cloneable. Otherwise, double-free can
// occur.
//
// These conditions are met, but again, note that changing safe code in
// this module can result in unsoundness if any of these constraints are
// violated.
unsafe { drop(Box::from_raw(self.0)) }
}
}
@@ -93,8 +102,13 @@ fn open(config: &Config) -> Result<Self> {
// 2. divide by permanent connections + permanent iter connections + write
// connection
// 3. round down to nearest integer
let cache_size_per_thread: u32 =
((config.db_cache_capacity_mb * 1024.0) / ((num_cpus::get().max(1) * 2) + 1) as f64) as u32;
#[allow(
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
let cache_size_per_thread = ((config.db_cache_capacity_mb * 1024.0) / ((num_cpus::get() as f64 * 2.0) + 1.0)) as u32;
let writer = Mutex::new(Engine::prepare_conn(&path, cache_size_per_thread)?);
@@ -161,7 +175,7 @@ fn iter_with_guard<'a>(&'a self, guard: &'a Connection) -> Box<dyn Iterator<Item
.unwrap(),
));
let statement_ref = NonAliasingBox(statement);
let statement_ref = AliasableBox(statement);
//let name = self.name.clone();
@@ -250,7 +264,7 @@ fn iter_from<'a>(&'a self, from: &[u8], backwards: bool) -> Box<dyn Iterator<Ite
.unwrap(),
));
let statement_ref = NonAliasingBox(statement);
let statement_ref = AliasableBox(statement);
let iterator = Box::new(
statement
@@ -272,7 +286,7 @@ fn iter_from<'a>(&'a self, from: &[u8], backwards: bool) -> Box<dyn Iterator<Ite
.unwrap(),
));
let statement_ref = NonAliasingBox(statement);
let statement_ref = AliasableBox(statement);
let iterator = Box::new(
statement

View File

@@ -47,7 +47,7 @@ pub(crate) fn wake(&self, key: &[u8]) {
let mut watchers = self.watchers.write().unwrap();
for prefix in triggered {
if let Some(tx) = watchers.remove(prefix) {
_ = tx.0.send(());
tx.0.send(()).expect("channel should still be open");
}
}
};

View File

@@ -6,7 +6,7 @@
Router,
};
use http::{
header::{self, HeaderName},
header::{self, HeaderName, HeaderValue},
Method, StatusCode, Uri,
};
use ruma::api::client::{
@@ -17,6 +17,7 @@
use tower_http::{
catch_panic::CatchPanicLayer,
cors::{self, CorsLayer},
set_header::SetResponseHeaderLayer,
trace::{DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, TraceLayer},
ServiceBuilderExt as _,
};
@@ -32,6 +33,9 @@ pub(crate) async fn build(server: &Server) -> io::Result<axum::routing::IntoMake
let base_middlewares = base_middlewares.layer(sentry_tower::NewSentryLayer::<http::Request<_>>::new_from_top());
let x_forwarded_for = HeaderName::from_static("x-forwarded-for");
let permissions_policy = HeaderName::from_static("permissions-policy");
let origin_agent_cluster = HeaderName::from_static("origin-agent-cluster"); // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster
let middlewares = base_middlewares
.sensitive_headers([header::AUTHORIZATION])
.sensitive_request_headers([x_forwarded_for].into())
@@ -44,6 +48,33 @@ pub(crate) async fn build(server: &Server) -> io::Result<axum::routing::IntoMake
.on_response(DefaultOnResponse::new().level(Level::DEBUG)),
)
.layer(axum::middleware::from_fn(request_handle))
.layer(SetResponseHeaderLayer::if_not_present(
origin_agent_cluster,
HeaderValue::from_static("?1"),
))
.layer(SetResponseHeaderLayer::if_not_present(
header::X_CONTENT_TYPE_OPTIONS,
HeaderValue::from_static("nosniff"),
))
.layer(SetResponseHeaderLayer::if_not_present(
header::X_XSS_PROTECTION,
HeaderValue::from_static("0"),
))
.layer(SetResponseHeaderLayer::if_not_present(
header::X_FRAME_OPTIONS,
HeaderValue::from_static("DENY"),
))
.layer(SetResponseHeaderLayer::if_not_present(
permissions_policy,
HeaderValue::from_static("interest-cohort=(),browsing-topics=()"),
))
.layer(SetResponseHeaderLayer::if_not_present(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static(
"sandbox; default-src 'none'; font-src 'none'; script-src 'none'; plugin-types application/pdf; \
style-src 'unsafe-inline'; object-src 'self'; frame-ancesors 'none';",
),
))
.layer(cors_layer(server))
.layer(DefaultBodyLimit::max(
server
@@ -130,10 +161,13 @@ fn request_result_log(method: &Method, uri: &Uri, result: &axum::response::Respo
}
}
/// Cross-Origin-Resource-Sharing header as defined by spec:
/// <https://spec.matrix.org/latest/client-server-api/#web-browser-clients>
fn cors_layer(_server: &Server) -> CorsLayer {
const METHODS: [Method; 6] = [
const METHODS: [Method; 7] = [
Method::GET,
Method::HEAD,
Method::PATCH,
Method::POST,
Method::PUT,
Method::DELETE,

View File

@@ -4,7 +4,7 @@
pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
let appservice_config = body[1..body.len() - 1].join("\n");
let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n");
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
match parsed_config {
Ok(yaml) => match services().appservice.register_appservice(yaml).await {

View File

@@ -121,7 +121,7 @@ pub(crate) async fn get_remote_pdu_list(
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
let list = body
.clone()
.drain(1..body.len() - 1)
.drain(1..body.len().checked_sub(1).unwrap())
.filter_map(|pdu| EventId::parse(pdu).ok())
.collect::<Vec<_>>();
@@ -381,7 +381,7 @@ pub(crate) async fn change_log_level(
pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
let string = body[1..body.len() - 1].join("\n");
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) {
Ok(mut value) => {
ruma::signatures::sign_json(
@@ -404,7 +404,7 @@ pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent
pub(crate) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
let string = body[1..body.len() - 1].join("\n");
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) {
Ok(value) => {
let pub_key_map = RwLock::new(BTreeMap::new());

View File

@@ -139,14 +139,19 @@ pub(crate) async fn delete(
pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
let mxc_list = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
let mxc_list = body
.clone()
.drain(1..body.len().checked_sub(1).unwrap())
.collect::<Vec<_>>();
let mut mxc_deletion_count = 0;
let mut mxc_deletion_count: usize = 0;
for mxc in mxc_list {
debug!("Deleting MXC {mxc} in bulk");
services().media.delete(mxc.to_owned()).await?;
mxc_deletion_count += 1;
mxc_deletion_count = mxc_deletion_count
.checked_add(1)
.expect("mxc_deletion_count should not get this high");
}
return Ok(RoomMessageEventContent::text_plain(format!(

View File

@@ -22,7 +22,7 @@ pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMe
let rooms = rooms
.into_iter()
.skip(page.saturating_sub(1) * PAGE_SIZE)
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
.take(PAGE_SIZE)
.collect::<Vec<_>>();

View File

@@ -39,7 +39,7 @@ pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
let rooms = rooms
.into_iter()
.skip(page.saturating_sub(1) * PAGE_SIZE)
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
.take(PAGE_SIZE)
.collect::<Vec<_>>();

View File

@@ -33,9 +33,9 @@ pub(crate) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventCon
Ok(RoomMessageEventContent::text_plain(format!(
"Services:\n{response0}\n\nDatabase:\n{response1}\n{}",
if !response2.is_empty() {
"Allocator:\n{response2}"
format!("Allocator:\n {response2}")
} else {
""
String::new()
}
)))
}

View File

@@ -61,8 +61,13 @@ pub(crate) async fn create(
// If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it
if !services().globals.new_user_displayname_suffix().is_empty() {
displayname.push_str(&(" ".to_owned() + services().globals.new_user_displayname_suffix()));
if !services()
.globals
.config
.new_user_displayname_suffix
.is_empty()
{
_ = write!(displayname, " {}", services().globals.config.new_user_displayname_suffix);
}
services()
@@ -162,7 +167,7 @@ pub(crate) async fn deactivate(
services().users.deactivate_account(&user_id)?;
if leave_rooms {
leave_all_rooms(&user_id).await?;
leave_all_rooms(&user_id).await;
}
Ok(RoomMessageEventContent::text_plain(format!(
@@ -234,7 +239,7 @@ pub(crate) async fn deactivate_all(body: Vec<&str>, leave_rooms: bool, force: bo
}
}
let mut deactivation_count = 0;
let mut deactivation_count: u16 = 0;
let mut admins = Vec::new();
if !force {
@@ -271,13 +276,13 @@ pub(crate) async fn deactivate_all(body: Vec<&str>, leave_rooms: bool, force: bo
}
if services().users.deactivate_account(user_id).is_ok() {
deactivation_count += 1;
deactivation_count = deactivation_count.saturating_add(1);
}
}
if leave_rooms {
for &user_id in &user_ids {
_ = leave_all_rooms(user_id).await;
leave_all_rooms(user_id).await;
}
}
@@ -311,7 +316,7 @@ pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
},
};
if user_id.server_name() != services().globals.server_name() {
if !user_is_local(&user_id) {
return Ok(RoomMessageEventContent::text_plain("User does not belong to our server."));
}
@@ -335,7 +340,8 @@ pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
rooms.reverse();
let output_plain = format!(
"Rooms {user_id} Joined:\n{}",
"Rooms {user_id} Joined ({}):\n{}",
rooms.len(),
rooms
.iter()
.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
@@ -343,8 +349,9 @@ pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
.join("\n")
);
let output_html = format!(
"<table><caption>Rooms {user_id} \
Joined</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
"<table><caption>Rooms {user_id} Joined \
({})</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
rooms.len(),
rooms
.iter()
.fold(String::new(), |mut output, (id, members, name)| {

View File

@@ -89,6 +89,8 @@ pub(crate) fn new(config: &Config, resolver: &Arc<resolver::Resolver>) -> Client
fn base(config: &Config) -> Result<reqwest::ClientBuilder> {
let version = conduwuit_version();
let user_agent = format!("Conduwuit/{version}");
let mut builder = reqwest::Client::builder()
.hickory_dns(true)
.connect_timeout(Duration::from_secs(config.request_conn_timeout))
@@ -96,7 +98,7 @@ fn base(config: &Config) -> Result<reqwest::ClientBuilder> {
.timeout(Duration::from_secs(config.request_total_timeout))
.pool_idle_timeout(Duration::from_secs(config.request_idle_timeout))
.pool_max_idle_per_host(config.request_idle_per_host.into())
.user_agent("Conduwuit".to_owned() + "/" + &version)
.user_agent(user_agent)
.redirect(redirect::Policy::limited(6))
.connection_verbose(true);

View File

@@ -30,6 +30,7 @@ pub(crate) struct Hooked {
}
impl Resolver {
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub(crate) fn new(config: &Config) -> Self {
let (sys_conf, mut opts) = hickory_resolver::system_conf::read_system_conf()
.map_err(|e| {

View File

@@ -16,6 +16,7 @@
#[derive(Debug)]
pub(crate) struct FileMeta {
#[allow(dead_code)]
pub(crate) content_disposition: Option<String>,
pub(crate) content_type: Option<String>,
pub(crate) file: Vec<u8>,
@@ -187,7 +188,9 @@ pub(crate) async fn delete_all_remote_media_at_after_time(&self, time: String) -
Ok(duration) => {
debug!("Parsed duration: {:?}", duration);
debug!("System time now: {:?}", SystemTime::now());
SystemTime::now() - duration
SystemTime::now().checked_sub(duration).ok_or_else(|| {
Error::bad_database("Duration specified is not valid against the current system time")
})?
},
Err(e) => {
error!("Failed to parse user-specified time duration: {}", e);

View File

@@ -40,6 +40,7 @@ pub(crate) struct Services<'a> {
}
impl Services<'_> {
#[allow(clippy::as_conversions, clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub(crate) fn build<
D: appservice::Data
+ pusher::Data

View File

@@ -188,13 +188,14 @@ pub(crate) fn get_actions<'a>(
let ctx = PushConditionRoomCtx {
room_id: room_id.to_owned(),
member_count: UInt::from(
member_count: UInt::try_from(
services()
.rooms
.state_cache
.room_joined_count(room_id)?
.unwrap_or(1) as u32,
),
.unwrap_or(1),
)
.unwrap_or_else(|_| uint!(0)),
user_id: user.to_owned(),
user_display_name: services()
.users

View File

@@ -6,9 +6,9 @@
pub(crate) use data::Data;
use ruma::{api::client::error::ErrorKind, EventId, RoomId};
use tracing::{debug, error, warn};
use tracing::{debug, error, trace, warn};
use crate::{services, Error, Result};
use crate::{services, utils::debug_slice_truncated, Error, Result};
pub(crate) struct Service {
pub(crate) db: &'static dyn Data,
@@ -30,7 +30,7 @@ pub(crate) async fn event_ids_iter<'a>(
.filter_map(move |sid| services().rooms.short.get_eventid_from_short(sid).ok()))
}
#[tracing::instrument(skip_all)]
#[tracing::instrument(skip(self), fields(starting_events = debug_slice_truncated(starting_events, 5)))]
pub(crate) async fn get_auth_chain(&self, room_id: &RoomId, starting_events: &[&EventId]) -> Result<Vec<u64>> {
const NUM_BUCKETS: usize = 50; //TODO: change possible w/o disrupting db?
const BUCKET: BTreeSet<(u64, &EventId)> = BTreeSet::new();
@@ -68,6 +68,7 @@ pub(crate) async fn get_auth_chain(&self, room_id: &RoomId, starting_events: &[&
.auth_chain
.get_cached_eventid_authchain(&chunk_key)?
{
trace!("Found cache entry for whole chunk");
full_auth_chain.extend(cached.iter().copied());
hits += 1;
continue;
@@ -82,6 +83,7 @@ pub(crate) async fn get_auth_chain(&self, room_id: &RoomId, starting_events: &[&
.auth_chain
.get_cached_eventid_authchain(&[sevent_id])?
{
trace!(?event_id, "Found cache entry for event");
chunk_cache.extend(cached.iter().copied());
hits2 += 1;
} else {
@@ -132,15 +134,18 @@ pub(crate) async fn get_auth_chain(&self, room_id: &RoomId, starting_events: &[&
Ok(full_auth_chain)
}
#[tracing::instrument(skip(self, event_id))]
#[tracing::instrument(skip(self, room_id))]
fn get_auth_chain_inner(&self, room_id: &RoomId, event_id: &EventId) -> Result<HashSet<u64>> {
let mut todo = vec![Arc::from(event_id)];
let mut found = HashSet::new();
while let Some(event_id) = todo.pop() {
trace!(?event_id, "processing auth event");
match services().rooms.timeline.get_pdu(&event_id) {
Ok(Some(pdu)) => {
if pdu.room_id != room_id {
error!(?event_id, ?pdu, "auth event for incorrect room_id");
return Err(Error::BadRequest(ErrorKind::forbidden(), "Evil event in db"));
}
for auth_event in &pdu.auth_events {
@@ -150,6 +155,7 @@ fn get_auth_chain_inner(&self, room_id: &RoomId, event_id: &EventId) -> Result<H
.get_or_create_shorteventid(auth_event)?;
if found.insert(sauthevent) {
trace!(?event_id, ?auth_event, "adding auth event to processing queue");
todo.push(auth_event.clone());
}
}

View File

@@ -236,6 +236,7 @@ pub(crate) async fn handle_prev_pdu<'a>(
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
let duration = time.elapsed();
if duration < min_duration {
debug!(
duration = ?duration,
@@ -1003,7 +1004,7 @@ pub(crate) fn fetch_and_handle_outliers<'a>(
hash_map::Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
},
hash_map::Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
hash_map::Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1.saturating_add(1)),
}
};
@@ -1034,10 +1035,9 @@ pub(crate) fn fetch_and_handle_outliers<'a>(
.get(&*next_id)
{
// Exponential backoff
let mut min_elapsed_duration = Duration::from_secs(5 * 60) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration =
cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
info!("Backing off from {}", next_id);
@@ -1143,10 +1143,9 @@ pub(crate) fn fetch_and_handle_outliers<'a>(
.get(&**next_id)
{
// Exponential backoff
let mut min_elapsed_duration = Duration::from_secs(5 * 60) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration =
cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
debug!("Backing off from {}", next_id);

View File

@@ -5,7 +5,7 @@
use ruma::{
api::{client::relations::get_relating_events, Direction},
events::{relation::RelationType, TimelineEventType},
EventId, RoomId, UInt, UserId,
uint, EventId, RoomId, UInt, UserId,
};
use serde::Deserialize;
@@ -57,8 +57,9 @@ pub(crate) fn paginate_relations_with_filter(
// Use limit or else 10, with maximum 100
let limit = limit
.and_then(|u| u32::try_from(u).ok())
.map_or(10_usize, |u| u as usize)
.unwrap_or_else(|| uint!(10))
.try_into()
.unwrap_or(10)
.min(100);
let next_token;

View File

@@ -818,7 +818,7 @@ fn into_https_string(self) -> String {
fn into_uri_string(self) -> String {
match self {
Self::Literal(addr) => addr.to_string(),
Self::Named(host, port) => host + &port,
Self::Named(host, port) => format!("{host}{port}"),
}
}

View File

@@ -0,0 +1,88 @@
use infer::MatcherType;
/// Returns a Content-Disposition of `attachment` or `inline`, depending on the
/// *parsed* contents of the file uploaded via format magic keys using `infer`
/// crate (basically libmagic without needing libmagic).
///
/// This forbids trusting what the client or remote server says the file is from
/// their `Content-Type` and we try to detect it ourselves. Also returns
/// `attachment` if the Content-Type does not match what we detected.
///
/// TODO: add a "strict" function for comparing the Content-Type with what we
/// detected: `file_type.mime_type() != content_type`
pub(crate) fn content_disposition_type(buf: &[u8], _content_type: &Option<String>) -> &'static str {
let Some(file_type) = infer::get(buf) else {
return "attachment";
};
match file_type.matcher_type() {
MatcherType::Image | MatcherType::Audio | MatcherType::Text | MatcherType::Video => "inline",
_ => "attachment",
}
}
/// sanitises the file name for the Content-Disposition using
/// `sanitize_filename` crate
#[tracing::instrument]
pub(crate) fn sanitise_filename(filename: String) -> String {
let options = sanitize_filename::Options {
truncate: false,
..Default::default()
};
sanitize_filename::sanitize_with_options(filename, options)
}
/// creates the final Content-Disposition based on whether the filename exists
/// or not.
///
/// if filename exists: `Content-Disposition: attachment/inline;
/// filename=filename.ext` else: `Content-Disposition: attachment/inline`
#[tracing::instrument(skip(file))]
pub(crate) fn make_content_disposition(
file: &[u8], content_type: &Option<String>, content_disposition: Option<String>,
) -> String {
let filename = content_disposition.map_or_else(String::new, |content_disposition| {
let (_, filename) = content_disposition
.split_once("filename=")
.unwrap_or(("", ""));
if filename.is_empty() {
String::new()
} else {
sanitise_filename(filename.to_owned())
}
});
if !filename.is_empty() {
// Content-Disposition: attachment/inline; filename=filename.ext
format!("{}; filename={}", content_disposition_type(file, content_type), filename)
} else {
// Content-Disposition: attachment/inline
String::from(content_disposition_type(file, content_type))
}
}
#[cfg(test)]
mod tests {
#[test]
fn string_sanitisation() {
const SAMPLE: &str =
"🏳this\\r\\n įs \r\\n ä \\r\nstrïng 🥴that\n\r ../../../../../../../may be\r\n malicious🏳";
const SANITISED: &str = "🏳thisrn įs n ä rstrïng 🥴that ..............may be malicious🏳";
let options = sanitize_filename::Options {
windows: true,
truncate: true,
replacement: "",
};
// cargo test -- --nocapture
println!("{}", SAMPLE);
println!("{}", sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
println!("{:?}", SAMPLE);
println!("{:?}", sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
assert_eq!(SANITISED, sanitize_filename::sanitize_with_options(SAMPLE, options.clone()));
}
}

View File

@@ -1,4 +1,5 @@
pub(crate) mod clap;
pub(crate) mod content_disposition;
pub(crate) mod debug;
pub(crate) mod error;
pub(crate) mod server_name;
@@ -21,6 +22,7 @@
pub(crate) fn clamp<T: Ord>(val: T, min: T, max: T) -> T { cmp::min(cmp::max(val, min), max) }
#[allow(clippy::as_conversions)]
pub(crate) fn millis_since_unix_epoch() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -196,10 +198,60 @@ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
/// git commit hashes.
pub(crate) fn conduwuit_version() -> String {
match option_env!("CONDUWUIT_VERSION_EXTRA") {
Some(extra) => format!("{} ({})", env!("CARGO_PKG_VERSION"), extra),
Some(extra) => {
if extra.is_empty() {
env!("CARGO_PKG_VERSION").to_owned()
} else {
format!("{} ({})", env!("CARGO_PKG_VERSION"), extra)
}
},
None => match option_env!("CONDUIT_VERSION_EXTRA") {
Some(extra) => format!("{} ({})", env!("CARGO_PKG_VERSION"), extra),
Some(extra) => {
if extra.is_empty() {
env!("CARGO_PKG_VERSION").to_owned()
} else {
format!("{} ({})", env!("CARGO_PKG_VERSION"), extra)
}
},
None => env!("CARGO_PKG_VERSION").to_owned(),
},
}
}
/// Debug-formats the given slice, but only up to the first `max_len` elements.
/// Any further elements are replaced by an ellipsis.
///
/// See also [`debug_slice_truncated()`],
pub(crate) struct TruncatedDebugSlice<'a, T> {
inner: &'a [T],
max_len: usize,
}
impl<T: fmt::Debug> fmt::Debug for TruncatedDebugSlice<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.inner.len() <= self.max_len {
write!(f, "{:?}", self.inner)
} else {
f.debug_list()
.entries(&self.inner[..self.max_len])
.entry(&"...")
.finish()
}
}
}
/// See [`TruncatedDebugSlice`]. Useful for `#[instrument]`:
///
/// ```
/// #[tracing::instrument(fields(
/// foos = debug_slice_truncated(foos, N)
/// ))]
/// ```
pub(crate) fn debug_slice_truncated<T: fmt::Debug>(
slice: &[T], max_len: usize,
) -> tracing::field::DebugValue<TruncatedDebugSlice<'_, T>> {
tracing::field::debug(TruncatedDebugSlice {
inner: slice,
max_len,
})
}