Compare commits

...

64 Commits

Author SHA1 Message Date
strawberry b3986b3350 sdkfjsadklfsdklfdjsakfljas
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-21 22:05:56 -04:00
strawberry d5db11eb45 significantly drop URL preview timeouts
theres no reason for us to spend so long trying to get
a preview

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 22:18:23 -04:00
strawberry ba80fbd2a4 raise connection pooling idle timeout to 50 seconds
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 22:17:17 -04:00
strawberry 3a1941a972 raise get_keys_helper timeout even more
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 22:16:39 -04:00
strawberry e6ab3ac2ad update book.toml for conduwuit
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 21:33:08 -04:00
strawberry fd428e9512 slight request logging improvements
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 21:20:04 -04:00
strawberry 32188ba1f9 auto join rooms from admin room created users too
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 21:16:03 -04:00
strawberry a51bc163f5 fix wrong error message about presence
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 18:28:34 -04:00
strawberry f27d98cfb5 skip rooms we have not joined before for auto-join
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 18:09:07 -04:00
strawberry d62c01b3c0 default to None if "name" in m.room.name is empty
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 17:43:48 -04:00
strawberry debc8b6164 simplify heroes get_avatar
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 17:41:16 -04:00
strawberry fee442c5c5 feat: automatically join rooms on registration
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 12:11:25 -04:00
strawberry 924adfb4e0 use unwrap_or_default if timestamp conversion fails
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:53:39 -04:00
strawberry f25764a158 check+clarify online backups are RocksDB only
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:50:22 -04:00
strawberry 59cdec4932 return helpful message instead of empty message if no backups
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:48:40 -04:00
strawberry b036a4fa75 make database_backup_path a PathBuf
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:44:02 -04:00
strawberry 00baef9c00 make database_path a PathBuf
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:27:32 -04:00
renovate[bot] 0e95b2d3cb chore(deps): update docker docker tag to v25.0.5 2024-03-20 00:27:32 -04:00
strawberry ea3834b19b fix lints
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:03:07 -04:00
Jason Volk b9d7185290 add database backup with admin commands
Signed-off-by: Jason Volk <jason@zemos.net>
2024-03-20 00:00:50 -04:00
strawberry e5f00926d7 db_cache_capacity_mb defaults to 256.0 now
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:00:43 -04:00
Jason Volk ede82e7b90 reconfigure and optimize rocksdb options.
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-20 00:00:43 -04:00
Jason Volk 8d1b597b6b add sync() to db abstraction for fsync(2). 2024-03-20 00:00:43 -04:00
Jason Volk f43b8d449b add rocksdb env to options. keep options in engine state.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-03-19 19:14:00 -04:00
Jason Volk 15c3e03908 add abstract fallbacks for kv batch methods.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-03-19 19:13:46 -04:00
strawberry fdc6e05443 bump rocksdb, deps, switch to hickory dns/resolver
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 19:12:49 -04:00
renovate[bot] 05002267a7 fix(deps): update rust crate serde_yaml to 0.9.33 2024-03-19 19:01:27 -04:00
strawberry 2aec32c007 fix docs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:51:24 -04:00
Jason Volk 148628dbc8 fix zealous client connection close (regression 809c9b4481)
Signed-off-by: Jason Volk <jason@zemos.net>
2024-03-19 00:50:12 -04:00
strawberry 7809f0a6ae bump ruma and cargo.lock
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 85cb559002 update DIFFERENCES.md
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 61f49ecf0e rename forbidden_room_names to forbidden_alias_names
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 5e880ac44a declare various missing server capabilities
this should fix FluffyChat password resets, and
other possible client issues that expose features
based on server capabilities.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 7f7bd91e8a add comment for future org.matrix.msc2285.stable
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 96e1938616 remove unnecessary assert for test
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 694b926366 move docs/gh pages into separate workflow
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry c8c3a9ed43 docs: s/conduit-example/conduwuit-example
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 896c372cfe disable broken assertions_on_result_states lint
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 2be43d3712 fix failing to register new appservice accounts
this is such a terribly named enum field.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 0301c7c083 fix Appservice AccessTokenOptional authentication type endpoints
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry a2a7b81076 fix test
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry ea6425712a dont setup github pages if pull request
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 19f313b91d deploy book to github pages
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry e2827a1a79 build documentation/book in github actions
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry b1b88601ab docs: dont point everyone to element
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Samuel Meenzen e9ce642795 docs: build docs using mdBook, build in CI, deploy to gitlab pages
squashed from https://gitlab.com/famedly/conduit/-/merge_requests/604

added differences.md

Co-authored-by: Charles Hall <charles@computer.surgery>
Co-authored-by: strawberry <strawberry@puppygock.gay>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry a7966b8f05 config option to allow incoming remote read receipts
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry b78d79a45a ignore deactivated users and remote user profiles wih forbidden_usernames
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 8c3f946e97 bump conduwuit version to 0.1.8
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Jason Volk abceae26de add flush suite to sending service; trigger on read receipts.
Signed-off-by: Jason Volk <jason@zemos.net>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Jason Volk 95ea665649 complete federation destination caching preempting getaddrinfo(3).
fixed some clippy lints and spacing adjusted

Signed-off-by: Jason Volk <jason@zemos.net>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Jason Volk 6fe0ea05b8 add remove_batch with transaction to database abstraction.
adjusted to make building sqlite happy again

Signed-off-by: Jason Volk <jason@zemos.net>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Jason Volk ba03d55879 clear dns and tls-override caches from !admin command.
Signed-off-by: Jason Volk <jason@zemos.net>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 9b5c4697bf bump async-trait and ruma
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 16572a868a slight inclusive wording changes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry b079b94715 track media uploads by user
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 19135eaa58 document forbidden room aliases and usernames
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Matthias Ahouansou e7c6b8c91b feat(spaces): hierarchy over federation
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 1ecbf55d68 check allow_federation in send_federation_request
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Matthias Ahouansou 4c841cd909 refactor: check if federation is disabled inside the authcheck where possible
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
Matthias Ahouansou c48535ef32 fix: avoid panics when admin room is not available
Co-authored-by: strawberry <strawberry@puppygock.gay>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry 5473631e1d admin command to see a room's full state from our database
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry de8f773620 slight wording updates
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
strawberry b4fa306a20 remove rocksdb optimize_level_style_compaction
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-03-19 00:17:41 -04:00
62 changed files with 2624 additions and 945 deletions
+8 -1
View File
@@ -6,16 +6,23 @@ on:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
# Required to make some things output color
TERM: ansi
# Publishing to my nix binary cache
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
# Just in case incremental is still being set to true, speeds up CI
CARGO_INCREMENTAL: 0
# Custom nix binary cache if fork is being used
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
permissions:
packages: write
contents: read
jobs:
ci:
@@ -247,4 +254,4 @@ jobs:
if [[ -n "$GITHUB_REF_NAME" ]]; then
docker manifest create $IMAGE_NAME:latest --amend $IMAGE_NAME:$GITHUB_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$GITHUB_SHA-$IMAGE_SUFFIX_ARM64V8
docker manifest push $IMAGE_NAME:latest
fi
fi
+117
View File
@@ -0,0 +1,117 @@
name: Documentation and GitHub Pages
on:
pull_request:
push:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
# Required to make some things output color
TERM: ansi
# Publishing to my nix binary cache
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
# Custom nix binary cache if fork is being used
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
docs:
name: Documentation and GitHub Pages
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Sync repository
uses: actions/checkout@v4
- name: Setup GitHub Pages
if: github.event_name != 'pull_request'
uses: actions/configure-pages@v4
- name: Install Nix (with flakes and nix-command enabled)
uses: cachix/install-nix-action@v26
with:
nix_path: nixpkgs=channel:nixos-unstable
# Add `nix-community`, Crane, upstream Conduit, and conduwuit binary caches
extra_nix_config: |
experimental-features = nix-command flakes
extra-substituters = https://nix-community.cachix.org
extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=
extra-substituters = https://crane.cachix.org
extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=
extra-substituters = https://nix.computer.surgery/conduit
extra-trusted-public-keys = conduit:ZGAf6P6LhNvnoJJ3Me3PRg7tlLSrPxcQ2RiE5LIppjo=
extra-substituters = https://attic.kennel.juneis.dog/conduit
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
extra-substituters = https://attic.kennel.juneis.dog/conduwuit
extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
- name: Add alternative Nix binary caches if specified
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
run: |
echo "extra-substituters = ${{ env.ATTIC_ENDPOINT }}" >> /etc/nix/nix.conf
echo "extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}" >> /etc/nix/nix.conf
- name: Pop/push Magic Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Configure `nix-direnv`
run: |
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
- name: Install `direnv` and `nix-direnv`
run: nix-env -f "<nixpkgs>" -iA direnv -iA nix-direnv
# Do this to shorten the logs for the real CI step
- name: Populate `/nix/store`
run: nix develop --command true
- name: Allow direnv
run: direnv allow
- name: Cache x86_64 inputs for devShell
run: |
./bin/nix-build-and-cache .#devShells.x86_64-linux.default.inputDerivation
- name: Build documentation (book)
run: |
./bin/nix-build-and-cache .#book
cp -r --dereference result public
- name: Upload generated documentation (book) as normal artifact
uses: actions/upload-artifact@v4
with:
name: public
path: public
if-no-files-found: error
# don't compress again
compression-level: 0
- name: Upload generated documentation (book) as GitHub Pages artifact
if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v3
with:
path: public
- name: Deploy to GitHub Pages
if: github.event_name != 'pull_request'
id: deployment
uses: actions/deploy-pages@v4
+3
View File
@@ -75,5 +75,8 @@ test-conduit.toml
# Gitlab CI cache
/.gitlab-ci.d
# mdbook output
public/
# macOS
.DS_Store
+19 -2
View File
@@ -105,6 +105,10 @@ artifacts:
- ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl
- cp result oci-image-arm64v8.tar.gz
- ./bin/nix-build-and-cache .#book
# We can't just copy the symlink, we need to dereference it https://gitlab.com/gitlab-org/gitlab/-/issues/19746
- cp -r --dereference result public
artifacts:
paths:
- x86_64-unknown-linux-musl
@@ -112,6 +116,7 @@ artifacts:
- x86_64-unknown-linux-musl.deb
- oci-image-amd64.tar.gz
- oci-image-arm64v8.tar.gz
- public
rules:
# CI required for all MRs
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
@@ -124,9 +129,9 @@ artifacts:
.push-oci-image:
stage: publish
image: docker:25.0.4
image: docker:25.0.5
services:
- docker:25.0.4-dind
- docker:25.0.5-dind
variables:
IMAGE_SUFFIX_AMD64: amd64
IMAGE_SUFFIX_ARM64V8: arm64v8
@@ -164,3 +169,15 @@ oci-image:push-gitlab:
IMAGE_NAME: $CI_REGISTRY_IMAGE/conduwuit
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
pages:
stage: publish
dependencies:
- artifacts
only:
- next
script:
- "true"
artifacts:
paths:
- public
+3
View File
@@ -0,0 +1,3 @@
# Docs: Map markdown to html files
- source: /docs/(.+)\.md/
public: '\1.html'
Generated
+114 -103
View File
@@ -96,13 +96,13 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.77"
version = "0.1.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -245,7 +245,7 @@ version = "0.69.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.5.0",
"cexpr",
"clang-sys",
"itertools 0.12.1",
@@ -256,7 +256,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -267,9 +267,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "blake2"
@@ -355,6 +355,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "chrono"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
dependencies = [
"num-traits",
]
[[package]]
name = "clang-sys"
version = "1.7.0"
@@ -395,7 +404,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -412,7 +421,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "conduit"
version = "0.7.0-alpha+conduwuit-0.1.7"
version = "0.7.0-alpha+conduwuit-0.1.8"
dependencies = [
"argon2",
"async-trait",
@@ -421,11 +430,13 @@ dependencies = [
"axum-server-dual-protocol",
"base64 0.22.0",
"bytes",
"chrono",
"clap",
"cyborgtime",
"either",
"figment",
"futures-util",
"hickory-resolver",
"hmac",
"http",
"hyper",
@@ -467,7 +478,6 @@ dependencies = [
"tracing-flame",
"tracing-opentelemetry",
"tracing-subscriber",
"trust-dns-resolver",
"webpage",
]
@@ -567,7 +577,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -664,7 +674,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -789,7 +799,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -950,6 +960,51 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hickory-proto"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf"
dependencies = [
"async-trait",
"cfg-if",
"data-encoding",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
"idna 0.4.0",
"ipnet",
"once_cell",
"rand",
"thiserror",
"tinyvec",
"tokio",
"tracing",
"url",
]
[[package]]
name = "hickory-resolver"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8"
dependencies = [
"cfg-if",
"futures-util",
"hickory-proto",
"ipconfig",
"lru-cache",
"once_cell",
"parking_lot",
"rand",
"resolv-conf",
"smallvec",
"thiserror",
"tokio",
"tracing",
]
[[package]]
name = "hmac"
version = "0.12.1"
@@ -1112,9 +1167,9 @@ dependencies = [
[[package]]
name = "image-webp"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba6107a25f04af48ceeb4093eebc9b405ee5a1813a0bab5ecf1805d3eabb3337"
checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c"
dependencies = [
"byteorder",
"thiserror",
@@ -1301,7 +1356,7 @@ dependencies = [
[[package]]
name = "libsqlite3-sys"
version = "0.28.0"
source = "git+https://github.com/rusqlite/rusqlite?branch=master#983f08b8d753deb60649661c8a1c3bc3bd4f358a"
source = "git+https://github.com/rusqlite/rusqlite?branch=master#20487e8c27c483de1a83c33176e47dd998e2a7d1"
dependencies = [
"cc",
"pkg-config",
@@ -1470,7 +1525,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.5.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -1755,7 +1810,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -1829,7 +1884,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -1924,7 +1979,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
"version_check",
"yansi",
]
@@ -2029,9 +2084,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.11.26"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
@@ -2039,6 +2094,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"hickory-resolver",
"http",
"http-body",
"hyper",
@@ -2097,7 +2153,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.9.4"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"assign",
"js_int",
@@ -2116,7 +2172,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.9.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"js_int",
"ruma-common",
@@ -2128,7 +2184,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.17.4"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"as_variant",
"assign",
@@ -2147,7 +2203,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.12.1"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"as_variant",
"base64 0.21.7",
@@ -2166,6 +2222,7 @@ dependencies = [
"serde_html_form",
"serde_json",
"thiserror",
"time",
"tracing",
"url",
"uuid",
@@ -2175,7 +2232,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.27.11"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"as_variant",
"indexmap",
@@ -2197,7 +2254,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.8.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"js_int",
"ruma-common",
@@ -2209,7 +2266,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.3"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"js_int",
"thiserror",
@@ -2218,7 +2275,7 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.8.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"js_int",
"ruma-common",
@@ -2228,7 +2285,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.12.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"once_cell",
"proc-macro-crate",
@@ -2236,14 +2293,14 @@ dependencies = [
"quote",
"ruma-identifiers-validation",
"serde",
"syn 2.0.52",
"syn 2.0.53",
"toml",
]
[[package]]
name = "ruma-push-gateway-api"
version = "0.8.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"js_int",
"ruma-common",
@@ -2255,7 +2312,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.14.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"base64 0.21.7",
"ed25519-dalek",
@@ -2271,7 +2328,7 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.10.0"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
dependencies = [
"itertools 0.11.0",
"js_int",
@@ -2286,9 +2343,9 @@ dependencies = [
[[package]]
name = "rusqlite"
version = "0.31.0"
source = "git+https://github.com/rusqlite/rusqlite?branch=master#983f08b8d753deb60649661c8a1c3bc3bd4f358a"
source = "git+https://github.com/rusqlite/rusqlite?branch=master#20487e8c27c483de1a83c33176e47dd998e2a7d1"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.5.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -2298,8 +2355,8 @@ dependencies = [
[[package]]
name = "rust-librocksdb-sys"
version = "0.18.1+8.11.3"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=3e4a0f632a8c0c2839c7d183725c53895110d907#3e4a0f632a8c0c2839c7d183725c53895110d907"
version = "0.19.0+9.0.0"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=4b8491b9db45066115435881184902bbb23d3811#4b8491b9db45066115435881184902bbb23d3811"
dependencies = [
"bindgen",
"bzip2-sys",
@@ -2314,8 +2371,8 @@ dependencies = [
[[package]]
name = "rust-rocksdb"
version = "0.22.7"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=3e4a0f632a8c0c2839c7d183725c53895110d907#3e4a0f632a8c0c2839c7d183725c53895110d907"
version = "0.22.8"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=4b8491b9db45066115435881184902bbb23d3811#4b8491b9db45066115435881184902bbb23d3811"
dependencies = [
"libc",
"rust-librocksdb-sys",
@@ -2474,7 +2531,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -2544,9 +2601,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.9.32"
version = "0.9.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9"
dependencies = [
"indexmap",
"itoa",
@@ -2740,9 +2797,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.52"
version = "2.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
dependencies = [
"proc-macro2",
"quote",
@@ -2804,7 +2861,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -2942,7 +2999,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -3048,7 +3105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"async-compression",
"bitflags 2.4.2",
"bitflags 2.5.0",
"bytes",
"futures-core",
"futures-util",
@@ -3096,7 +3153,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@@ -3167,52 +3224,6 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "trust-dns-proto"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374"
dependencies = [
"async-trait",
"cfg-if",
"data-encoding",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
"idna 0.4.0",
"ipnet",
"once_cell",
"rand",
"smallvec",
"thiserror",
"tinyvec",
"tokio",
"tracing",
"url",
]
[[package]]
name = "trust-dns-resolver"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6"
dependencies = [
"cfg-if",
"futures-util",
"ipconfig",
"lru-cache",
"once_cell",
"parking_lot",
"rand",
"resolv-conf",
"smallvec",
"thiserror",
"tokio",
"tracing",
"trust-dns-proto",
]
[[package]]
name = "try-lock"
version = "0.2.5"
@@ -3272,9 +3283,9 @@ dependencies = [
[[package]]
name = "unsafe-libyaml"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
@@ -3307,9 +3318,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
]
@@ -3368,7 +3379,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
"wasm-bindgen-shared",
]
@@ -3402,7 +3413,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3670,7 +3681,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
+13 -6
View File
@@ -6,7 +6,7 @@ authors = ["strawberry <strawberry@puppygock.gay>", "timokoesters <timo@koesters
homepage = "https://puppygock.gay/conduwuit"
repository = "https://gitlab.com/girlbossceo/conduwuit"
readme = "README.md"
version = "0.7.0-alpha+conduwuit-0.1.7"
version = "0.7.0-alpha+conduwuit-0.1.8"
edition = "2021"
# See also `rust-toolchain.toml`
@@ -27,7 +27,7 @@ base64 = "0.22.0"
ring = "0.17.8"
# Used when querying the SRV record of other servers
trust-dns-resolver = "0.23.2"
hickory-resolver = "0.24.0"
# Used to find matching events for appservices
regex = "1.10.3"
@@ -48,7 +48,7 @@ serde_html_form = "0.2.5"
hmac = "0.12.1"
sha-1 = "0.10.1"
async-trait = "0.1.77"
async-trait = "0.1.78"
# used for checking if an IP is in specific subnets / CIDR ranges easier
ipaddress = "0.1.3"
@@ -67,6 +67,12 @@ cyborgtime = "2.1.1"
bytes = "1.5.0"
http = "0.2.12"
# standard date and time tools
[dependencies.chrono]
version = "0.4.35"
features = ["alloc"]
default-features = false
# Web framework
[dependencies.axum]
version = "0.6.20"
@@ -101,11 +107,12 @@ features = [
]
[dependencies.reqwest]
version = "0.11.26"
version = "0.11.27"
default-features = false
features = [
"rustls-tls-native-roots",
"socks",
"trust-dns",
]
# all the serde stuff
@@ -115,7 +122,7 @@ version = "1.0.197"
features = ["rc"]
# Used for appservice registration files
[dependencies.serde_yaml]
version = "0.9.32"
version = "0.9.33"
# Used for ruma wrapper
[dependencies.serde_json]
version = "1.0.114"
@@ -254,7 +261,7 @@ features = [
[dependencies.rust-rocksdb]
git = "https://github.com/zaidoon1/rust-rocksdb"
#branch = "master"
rev = "3e4a0f632a8c0c2839c7d183725c53895110d907"
rev = "4b8491b9db45066115435881184902bbb23d3811"
optional = true
default-features = true
features = [
+21 -23
View File
@@ -1,9 +1,17 @@
# conduwuit
### a well maintained fork of [Conduit](https://conduit.rs/)
[![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)
<!-- ANCHOR: catchphrase -->
### a well maintained fork of [Conduit](https://conduit.rs/)
<!-- ANCHOR_END: catchphrase -->
Visit the [Conduwuit documentation](https://conduwuit.puppyirl.gay/) for more information.
Alternatively you can open [docs/introduction.md](docs/introduction.md) in this repository.
<!-- ANCHOR: body -->
#### What is Matrix?
[Matrix](https://matrix.org) is an open network for secure and decentralized
communication. Users from every Matrix homeserver can chat with users from all
other Matrix servers. You can even use bridges (also called Matrix Appservices)
@@ -27,11 +35,7 @@ from time to time.
There are still a few nice to have features missing that some users may notice:
- Outgoing read receipts and typing indicators (receiving works)
#### What's different about your fork than upstream Conduit?
See [DIFFERENCES.md](DIFFERENCES.md)
- Outgoing typing indicators (receiving works)
#### Why does this fork exist? Why don't you contribute back upstream?
@@ -45,16 +49,9 @@ I now intend on contributing back as time and mental energy sees fit, but my for
- Have a **stable** testing ground for some MRs or new features and bug fixes
And various other reasons that may not be listed here.
<!-- ANCHOR_END: body -->
#### How can I deploy my own?
conduwuit officially supports Linux, macOS, BSD, and Windows.
- Simple install (this was tested the most): [DEPLOY.md](DEPLOY.md)
- Nix/NixOS (and binary cache): [nix/README.md](nix/README.md)
If you want to connect an Appservice to Conduit, take a look at [APPSERVICES.md](APPSERVICES.md).
<!-- ANCHOR: footer -->
#### How can I contribute?
1. Look for an issue you would like to work on and make sure it's not assigned
@@ -72,9 +69,9 @@ If you run into any question, feel free to
#### Donate
Liberapay: <https://liberapay.com/girlbossceo>\
Ko-fi: <https://ko-fi.com/puppygock>\
GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
- Liberapay: <https://liberapay.com/girlbossceo>
- Ko-fi: <https://ko-fi.com/puppygock>
- GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
#### Logo
@@ -86,8 +83,9 @@ Both.
#### Mirrors of conduwuit
GitHub: <https://github.com/girlbossceo/conduwuit>\
GitLab: <https://gitlab.com/girlbossceo/conduwuit>\
git.gay: <https://git.gay/june/conduwuit>\
Codeberg: <https://codeberg.org/girlbossceo/conduwuit>\
sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
- GitHub: <https://github.com/girlbossceo/conduwuit>
- GitLab: <https://gitlab.com/girlbossceo/conduwuit>
- git.gay: <https://git.gay/june/conduwuit>
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
<!-- ANCHOR_END: footer -->
+18
View File
@@ -0,0 +1,18 @@
[book]
title = "conduwuit"
description = "conduwuit, which is a fork of Conduit, is a simple, fast and reliable chat server for the Matrix protocol"
language = "en"
multilingual = false
src = "docs"
[build]
build-dir = "public"
create-missing = true
[output.html]
git-repository-url = "https://github.com/girlbossceo/conduwuit"
edit-url-template = "https://github.com/girlbossceo/conduwuit/edit/main/{path}"
git-repository-icon = "fa-github-square"
[output.html.search]
limit-results = 15
+27 -6
View File
@@ -161,7 +161,19 @@ registration_token = "change this token for something specific to your server"
# controls whether non-admin local users are forbidden from sending room invites (local and remote),
# and if non-admin users can receive remote room invites. admins are always allowed to send and receive all room invites.
# defaults to false
# block_non_admin_invites = falsse
# block_non_admin_invites = false
# List of forbidden username patterns/strings. Values in this list are matched as *contains*.
# This is checked upon username availability check, registration, and startup as warnings if any local users in your database
# have a forbidden username.
# No default.
# forbidden_usernames = []
# List of forbidden room aliases and room IDs as patterns/strings. Values in this list are matched as *contains*.
# This is checked upon room alias creation, custom room ID creation if used, and startup as warnings if any room aliases
# in your database have a forbidden room alias/ID.
# No default.
# forbidden_alias_names = []
# Set this to true to allow your server's public room directory to be federated.
# Set this to false to protect against /publicRooms spiders, but will forbid external users
@@ -251,6 +263,12 @@ url_preview_check_root_domain = false
# Defaults to true as this is the fastest option for federation.
#query_trusted_key_servers_first = true
# List/vector of room **IDs** that conduwuit will make newly registered users join.
# The room IDs specified must be rooms that you have joined at least once on the server, and must be public.
#
# No default.
#auto_join_rooms = []
### Generic database options
@@ -262,8 +280,8 @@ url_preview_check_root_domain = false
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches.
# May be useful if you have significant memory to spare to increase performance.
# Defaults to 300.0
#db_cache_capacity_mb = 300.0
# Defaults to 256.0
#db_cache_capacity_mb = 256.0
# Interval in seconds when conduwuit will run database cleanup operations.
#
@@ -302,7 +320,7 @@ url_preview_check_root_domain = false
# Amount of threads that RocksDB will use for parallelism. Set to 0 to use all your physical cores.
# Conduit eagerly spawns threads mainly for federation, so it may not be desirable to use all your cores / logical threads.
#
# Defaults to your CPU physical core count (not logical threads) count divided by 2 (half)
# Defaults to your CPU physical core count (not logical threads).
#rocksdb_parallelism_threads = 0
# Maximum number of LOG files RocksDB will keep. This must *not* be set to 0. It must be at least 1.
@@ -345,7 +363,7 @@ url_preview_check_root_domain = false
### Presence
### Presence / Typing Indicators / Read Receipts
# Config option to control local (your server only) presence updates/requests. Defaults to false.
# Note that presence on conduwuit is very fast unlike Synapse's.
@@ -373,6 +391,9 @@ url_preview_check_root_domain = false
# Config option to control how many seconds before presence updates that you are offline. Defaults to 30 minutes.
#presence_offline_timeout_s = 1800
# Config option to control whether we should receive remote incoming read receipts.
# Defaults to true.
#allow_incoming_read_receipts = true
# Other options not in [global]:
@@ -387,4 +408,4 @@ url_preview_check_root_domain = false
# Whether to listen and allow for HTTP and HTTPS connections (insecure!)
# This config option is only available if conduwuit was built with `axum_dual_protocol` feature (not default feature)
# Defaults to false
#dual_protocol = false
#dual_protocol = false
+1 -1
View File
@@ -5,7 +5,7 @@ Installation
------------
Information about downloading, building and deploying the Debian package, see
the "Installing Conduit" section in [DEPLOY.md](../DEPLOY.md).
the "Installing Conduit" section in the Deploying docs.
All following sections until "Setting up the Reverse Proxy" be ignored because
this is handled automatically by the packaging.
+13
View File
@@ -0,0 +1,13 @@
# Summary
- [Introduction](introduction.md)
- [Differences from upstream Conduit](differences.md)
- [Example configuration](configuration.md)
- [Deployment options](deploying.md)
- [Simple (Recommended)](deploying/simple.md)
- [Debian](deploying/debian.md)
- [Docker](deploying/docker.md)
- [Nix](deploying/nix.md)
- [TURN](turn.md)
- [Appservices](appservices.md)
+5
View File
@@ -0,0 +1,5 @@
# Example configuration
``` toml
{{#include ../conduwuit-example.toml}}
```
+8
View File
@@ -0,0 +1,8 @@
# Deployment options
There are several ways to deploy a Conduit server.
- [Simple (Recommended)](deploying/simple.md) - This is the recommended way to set up Conduit.
- [Debian](deploying/debian.md) - If you're using a debian-based system, you may find the `deb` package useful.
- [Docker](deploying/docker.md) - We provide multi-arch OCI images for Conduit.
- [Nix](deploying/nix.md) - Nix flake based setup.
+1
View File
@@ -0,0 +1 @@
{{#include ../../debian/README.md}}
@@ -70,7 +70,7 @@ docker run -d -p 8448:6167 \
or you can use [docker-compose](#docker-compose).
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../conduwuit-example.toml).
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../configuration.md).
You can pass in different env vars to change config values on the fly. You can even configure Conduit completely by using env vars, but for that you need
to pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible values, please take a look at the `docker-compose.yml` file.
@@ -89,7 +89,7 @@ When picking the traefik-related compose file, rename it so it matches `docker-c
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
for your server.
Additional info about deploying Conduit can be found [here](../DEPLOY.md).
Additional info about deploying Conduit can be found [here](simple.md).
### Build
@@ -131,7 +131,7 @@ 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 `conduit.toml` config file, an example can be found [here](../conduwuit-example.toml), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
3. Create the `conduit.toml` config file, an example can be found [here](../configuration.md), or set `CONDUIT_CONFIG=""` and configure Conduit 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.
+7 -5
View File
@@ -1,4 +1,6 @@
# Deploying Conduit
# Simple setup
This is the recommended way to set up Conduit. It is the easiest way to get started and is suitable for most use cases.
### Please note that this documentation is not fully representative of conduwuit at the moment. Assume majority of it is outdated.
@@ -118,7 +120,7 @@ Now we need to create the Conduit's config file in `/etc/conduwuit/conduwuit.tom
to read it. You need to change at least the server name.**
You can also choose to use a different database backend, but right now only `rocksdb` and `sqlite` are recommended.
See the following example config at [conduwuit-example.toml](conduwuit-example.toml)
See the following example config at [conduwuit-example.toml](../configuration.md)
## Setting the correct file permissions
@@ -282,7 +284,7 @@ $ sudo systemctl enable conduit
## How do I know it works?
You can open <https://app.element.io>, enter your homeserver and try to register.
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your homeserver and try to register.
You can also use these commands as a quick health check.
@@ -300,8 +302,8 @@ $ curl https://your.server.name:8448/_matrix/client/versions
## Audio/Video calls
For Audio/Video call functionality see the [TURN Guide](TURN.md).
For Audio/Video call functionality see the [TURN Guide](../turn.md).
## Appservices
If you want to set up an appservice, take a look at the [Appservice Guide](APPSERVICES.md).
If you want to set up an appservice, take a look at the [Appservice Guide](../appservices.md).
+6
View File
@@ -82,3 +82,9 @@
- Admin debug command to fetch a PDU from a remote server and inserts it into our database/timeline
- Update rusqlite/sqlite (not that you should be using it)
- Disable update check by default as it's not useful for conduwuit
- Declare various missing server capabilities at `/_matrix/client/v3/capabilities` which also fixes FluffyChat password resets, and other clients that expose basic features based on this endpoint's response
- Config option to disable incoming remote read receipts if desired
- Extend clear cache admin command to support clearing DNS and TLS name override caches
- Responsive outgoing read receipt EDU support
- Federation destination DNS cache support to call *less* of the thread-blocking `getaddrinfo(3)` significantly less, improving federation ping/latency
- Store the sender user with the MXC URL for all media uploads (`/upload`) (not for thumbnails or media requests which are unauthenticated)
+17
View File
@@ -0,0 +1,17 @@
# Conduit
{{#include ../README.md:catchphrase}}
{{#include ../README.md:body}}
#### What's different about your fork than upstream Conduit?
See [differences.md](differences.md)
#### How can I deploy my own?
- [Deployment options](deploying.md)
If you want to connect an Appservice to Conduit, take a look at the [appservices documentation](appservices.md).
{{#include ../README.md:footer}}
View File
+10
View File
@@ -50,6 +50,11 @@ name = "cargo-deb"
group = "versions"
script = "cargo deb --version"
[[task]]
name = "lychee"
group = "versions"
script = "lychee --version"
[[task]]
name = "cargo-audit"
group = "security"
@@ -77,6 +82,11 @@ name = "cargo-clippy"
group = "lints"
script = "cargo clippy --workspace --all-targets --all-features --color=always -- -D warnings"
[[task]]
name = "lychee"
group = "lints"
script = "lychee --offline docs"
[[task]]
name = "cargo"
group = "tests"
+32
View File
@@ -208,6 +208,35 @@
packages = {
default = package pkgsHost;
oci-image = mkOciImage pkgsHost self.packages.${system}.default;
book =
let
package = self.packages.${system}.default;
in
pkgsHost.stdenv.mkDerivation {
pname = "${package.pname}-book";
version = package.version;
src = nix-filter {
root = ./.;
include = [
"book.toml"
"conduwuit-example.toml"
"README.md"
"debian/README.md"
"docs"
];
};
nativeBuildInputs = (with pkgsHost; [
mdbook
]);
buildPhase = ''
mdbook build
mv public $out
'';
};
}
//
builtins.listToAttrs
@@ -276,6 +305,9 @@
# Needed for our script for Complement
jq
# Needed for finding broken markdown links
lychee
]);
};
});
+41 -5
View File
@@ -12,10 +12,13 @@ use ruma::{
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
push, UserId,
};
use tracing::{info, warn};
use tracing::{error, info, warn};
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{api::client_server, services, utils, Error, Result, Ruma};
use crate::{
api::client_server::{self, join_room_by_id_helper},
services, utils, Error, Result, Ruma,
};
const RANDOM_USER_ID_LENGTH: usize = 10;
@@ -275,10 +278,43 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
// If this is the first real user, grant them admin privileges except for guest
// users Note: the server user, @conduit:servername, is generated first
if services().users.count()? == 2 && !is_guest {
services().admin.make_user_admin(&user_id, displayname).await?;
if !is_guest {
if let Some(admin_room) = services().admin.get_admin_room()? {
if services().rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
services().admin.make_user_admin(&user_id, displayname).await?;
warn!("Granting {} admin privileges as the first user", user_id);
warn!("Granting {} admin privileges as the first user", user_id);
}
}
}
if !services().globals.config.auto_join_rooms.is_empty() {
for room in &services().globals.config.auto_join_rooms {
if !services().rooms.state_cache.server_in_room(services().globals.server_name(), room)? {
warn!("Skipping room {room} to automatically join as we have never joined before.");
continue;
}
if let Some(room_id_server_name) = room.server_name() {
match join_room_by_id_helper(
Some(&user_id),
room,
Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
None,
)
.await
{
Ok(_) => {
info!("Automatically joined room {room} for user {user_id}");
},
Err(e) => {
// don't return this error so we don't fail registrations
error!("Failed to automatically join room {room} for user {user_id}: {e}");
},
};
}
}
}
Ok(register::v3::Response {
+1 -1
View File
@@ -21,7 +21,7 @@ pub async fn create_alias_route(body: Ruma<create_alias::v3::Request>) -> Result
return Err(Error::BadRequest(ErrorKind::InvalidParam, "Alias is from another server."));
}
if services().globals.forbidden_room_names().is_match(body.room_alias.alias()) {
if services().globals.forbidden_alias_names().is_match(body.room_alias.alias()) {
return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias is forbidden."));
}
+20 -2
View File
@@ -1,12 +1,13 @@
use std::collections::BTreeMap;
use ruma::api::client::discovery::get_capabilities::{
self, Capabilities, RoomVersionStability, RoomVersionsCapability,
self, Capabilities, ChangePasswordCapability, RoomVersionStability, RoomVersionsCapability, SetAvatarUrlCapability,
SetDisplayNameCapability, ThirdPartyIdChangesCapability,
};
use crate::{services, Result, Ruma};
/// # `GET /_matrix/client/r0/capabilities`
/// # `GET /_matrix/client/v3/capabilities`
///
/// Get information on the supported feature set and other relevent capabilities
/// of this server.
@@ -27,6 +28,23 @@ pub async fn get_capabilities_route(
available,
};
capabilities.change_password = ChangePasswordCapability {
enabled: true,
};
capabilities.set_avatar_url = SetAvatarUrlCapability {
enabled: true,
};
capabilities.set_displayname = SetDisplayNameCapability {
enabled: true,
};
// conduit does not implement 3PID stuff
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
enabled: false,
};
Ok(get_capabilities::v3::Response {
capabilities,
})
+2 -2
View File
@@ -313,7 +313,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
(
server,
tokio::time::timeout(
Duration::from_secs(50),
Duration::from_secs(90),
services().sending.send_federation_request(
server,
federation::keys::get_keys::v1::Request {
@@ -323,7 +323,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
)
.await
.map_err(|e| {
error!("get_keys_helper query took too long: {}", e);
error!("get_keys_helper query took too long: {e}");
Error::BadServerResponse("get_keys_helper query took too long")
}),
)
+10 -1
View File
@@ -138,6 +138,8 @@ pub async fn get_media_preview_v1_route(
/// - Some metadata will be saved in the database
/// - Media will be saved in the media/ directory
pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Result<create_content::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = format!(
"mxc://{}/{}",
services().globals.server_name(),
@@ -147,6 +149,7 @@ pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Re
services()
.media
.create(
Some(sender_user.clone()),
mxc.clone(),
body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(),
body.content_type.as_deref(),
@@ -175,6 +178,8 @@ pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Re
pub async fn create_content_v1_route(
body: Ruma<create_content::v3::Request>,
) -> Result<RumaResponse<create_content::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = format!(
"mxc://{}/{}",
services().globals.server_name(),
@@ -184,6 +189,7 @@ pub async fn create_content_v1_route(
services()
.media
.create(
Some(sender_user.clone()),
mxc.clone(),
body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(),
body.content_type.as_deref(),
@@ -231,6 +237,7 @@ pub async fn get_remote_content(
services()
.media
.create(
None,
mxc.to_owned(),
content_response.content_disposition.as_deref(),
content_response.content_type.as_deref(),
@@ -484,6 +491,7 @@ pub async fn get_content_thumbnail_route(
services()
.media
.upload_thumbnail(
None,
mxc,
None,
get_thumbnail_response.content_type.as_deref(),
@@ -566,6 +574,7 @@ pub async fn get_content_thumbnail_v1_route(
services()
.media
.upload_thumbnail(
None,
mxc,
None,
get_thumbnail_response.content_type.as_deref(),
@@ -589,7 +598,7 @@ async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPrevie
utils::random_string(MXC_LENGTH)
);
services().media.create(mxc.clone(), None, None, &image).await?;
services().media.create(None, mxc.clone(), None, None, &image).await?;
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
Err(_) => (None, None),
+1 -1
View File
@@ -474,7 +474,7 @@ pub async fn joined_members_route(body: Ruma<joined_members::v3::Request>) -> Re
})
}
async fn join_room_by_id_helper(
pub(crate) async fn join_room_by_id_helper(
sender_user: Option<&UserId>, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
) -> Result<join_room_by_id::v3::Response> {
+4
View File
@@ -81,6 +81,8 @@ pub async fn set_read_marker_route(body: Ruma<set_read_marker::v3::Request>) ->
room_id: body.room_id.clone(),
},
)?;
services().sending.flush_room(&body.room_id)?;
}
Ok(set_read_marker::v3::Response {})
@@ -136,6 +138,8 @@ pub async fn create_receipt_route(body: Ruma<create_receipt::v3::Request>) -> Re
room_id: body.room_id.clone(),
},
)?;
services().sending.flush_room(&body.room_id)?;
},
create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services()
+2 -2
View File
@@ -80,7 +80,7 @@ pub async fn create_room_route(body: Ruma<create_room::v3::Request>) -> Result<c
}
// apply forbidden room alias checks to custom room IDs too
if services().globals.forbidden_room_names().is_match(&custom_room_id_s) {
if services().globals.forbidden_alias_names().is_match(&custom_room_id_s) {
return Err(Error::BadRequest(ErrorKind::Unknown, "Custom room ID is forbidden."));
}
@@ -144,7 +144,7 @@ pub async fn create_room_route(body: Ruma<create_room::v3::Request>) -> Result<c
}
// check if room alias is forbidden
if services().globals.forbidden_room_names().is_match(localpart) {
if services().globals.forbidden_alias_names().is_match(localpart) {
return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias name is forbidden."));
}
+32 -6
View File
@@ -1,6 +1,11 @@
use ruma::api::client::space::get_hierarchy;
use std::str::FromStr;
use crate::{services, Result, Ruma};
use ruma::{
api::client::{error::ErrorKind, space::get_hierarchy},
UInt,
};
use crate::{service::rooms::spaces::PagnationToken, services, Error, Result, Ruma};
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy``
///
@@ -9,11 +14,32 @@ use crate::{services, Result, Ruma};
pub 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 skip = body.from.as_ref().and_then(|s| s.parse::<usize>().ok()).unwrap_or(0);
let limit = body.limit.unwrap_or_else(|| UInt::from(10_u32)).min(UInt::from(100_u32));
let limit = body.limit.map_or(10, u64::from).min(100) as usize;
let max_depth = body.max_depth.unwrap_or_else(|| UInt::from(3_u32)).min(UInt::from(10_u32));
let max_depth = body.max_depth.map_or(3, u64::from).min(10) as usize + 1; // +1 to skip the space room itself
let key = body.from.as_ref().and_then(|s| PagnationToken::from_str(s).ok());
services().rooms.spaces.get_hierarchy(sender_user, &body.room_id, limit, skip, max_depth, body.suggested_only).await
// Should prevent unexpeded behaviour in (bad) clients
if let Some(ref token) = key {
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"suggested_only and max_depth cannot change on paginated requests",
));
}
}
services()
.rooms
.spaces
.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,
body.suggested_only,
)
.await
}
+1 -1
View File
@@ -75,7 +75,7 @@ pub async fn send_state_event_for_empty_key_route(
.into())
}
/// # `GET /_matrix/client/r0/rooms/{roomid}/state`
/// # `GET /_matrix/client/v3/rooms/{roomid}/state`
///
/// Get all state events for a room.
///
+1 -3
View File
@@ -1333,9 +1333,7 @@ pub async fn sync_events_v4_route(
ruma::JsOption::Some(heroes_avatar)
} else {
match services().rooms.state_accessor.get_avatar(room_id)? {
ruma::JsOption::Some(avatar) => {
avatar.url.map_or(ruma::JsOption::Undefined, ruma::JsOption::Some)
},
ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
ruma::JsOption::Null => ruma::JsOption::Null,
ruma::JsOption::Undefined => ruma::JsOption::Undefined,
}
+1
View File
@@ -38,6 +38,7 @@ pub async fn get_supported_versions_route(
],
unstable_features: BTreeMap::from_iter([
("org.matrix.e2e_cross_signing".to_owned(), true),
//("org.matrix.msc2285.stable".to_owned(), true),
("org.matrix.msc2836".to_owned(), true),
("org.matrix.msc3827".to_owned(), true),
("org.matrix.msc2946".to_owned(), true),
+28 -2
View File
@@ -84,8 +84,7 @@ where
appservice_registration
{
match metadata.authentication {
// TODO: verify if just or'ing `AuthScheme::AppserviceToken` is correct here
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken => {
AuthScheme::AccessToken => {
let user_id = query_params.user_id.map_or_else(
|| {
UserId::parse_with_server_name(
@@ -97,6 +96,8 @@ where
|s| UserId::parse(s).unwrap(),
);
debug!("User ID: {:?}", user_id);
if !services().users.exists(&user_id)? {
return Err(Error::BadRequest(ErrorKind::Forbidden, "User does not exist."));
}
@@ -104,6 +105,27 @@ where
// TODO: Check if appservice is allowed to be that user
(Some(user_id), None, None, true)
},
AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken => {
let user_id = query_params.user_id.map_or_else(
|| {
UserId::parse_with_server_name(
registration.sender_localpart.as_str(),
services().globals.server_name(),
)
.unwrap()
},
|s| UserId::parse(s).unwrap(),
);
debug!("User ID: {:?}", user_id);
if !services().users.exists(&user_id)? {
(None, None, None, true)
} else {
// TODO: Check if appservice is allowed to be that user
(Some(user_id), None, None, true)
}
},
AuthScheme::ServerSignatures => (None, None, None, true),
AuthScheme::None => (None, None, None, true),
}
@@ -153,6 +175,10 @@ where
// treat non-appservice registrations as None authentication
AuthScheme::AppserviceToken => (None, None, None, false),
AuthScheme::ServerSignatures => {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let TypedHeader(Authorization(x_matrix)) =
parts.extract::<TypedHeader<Authorization<XMatrix>>>().await.map_err(|e| {
warn!("Missing or invalid Authorization header: {}", e);
+71 -102
View File
@@ -13,6 +13,7 @@ use std::{
use axum::{response::IntoResponse, Json};
use futures_util::future::TryFutureExt;
use get_profile_information::v1::ProfileField;
use hickory_resolver::{error::ResolveError, lookup::SrvLookup};
use http::header::{HeaderValue, AUTHORIZATION};
use ipaddress::IPAddress;
use ruma::{
@@ -28,6 +29,7 @@ use ruma::{
keys::{claim_keys, get_keys},
membership::{create_invite, create_join_event, prepare_join_event},
query::{get_profile_information, get_room_information},
space::get_hierarchy,
transactions::{
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
send_transaction_message,
@@ -52,7 +54,6 @@ use ruma::{
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
use trust_dns_resolver::{error::ResolveError, lookup::SrvLookup};
use crate::{
api::client_server::{self, claim_keys_helper, get_keys_helper},
@@ -364,7 +365,10 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
None => {
if let Some(pos) = destination_str.find(':') {
debug!("2: Hostname with included port");
let (host, port) = destination_str.split_at(pos);
query_and_cache_override(host, host, port.parse::<u16>().unwrap_or(8448)).await;
FedDest::Named(host.to_owned(), port.to_owned())
} else {
debug!("Requesting well known for {destination}");
@@ -377,30 +381,23 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
None => {
if let Some(pos) = delegated_hostname.find(':') {
debug!("3.2: Hostname with port in .well-known file");
let (host, port) = delegated_hostname.split_at(pos);
query_and_cache_override(host, host, port.parse::<u16>().unwrap_or(8448)).await;
FedDest::Named(host.to_owned(), port.to_owned())
} else {
debug!("Delegated hostname has no port in this branch");
if let Some(hostname_override) = query_srv_record(&delegated_hostname).await {
debug!("3.3: SRV lookup successful");
let force_port = hostname_override.port();
if let Ok(override_ip) = services()
.globals
.dns_resolver()
.lookup_ip(hostname_override.hostname())
.await
{
services().globals.tls_name_override.write().unwrap().insert(
delegated_hostname.clone(),
(override_ip.iter().collect(), force_port.unwrap_or(8448)),
);
} else {
debug!(
"Using SRV record {}, but could not resolve to IP",
hostname_override.hostname()
);
}
let force_port = hostname_override.port();
query_and_cache_override(
&delegated_hostname,
&hostname_override.hostname(),
force_port.unwrap_or(8448),
)
.await;
if let Some(port) = force_port {
FedDest::Named(delegated_hostname, format!(":{port}"))
@@ -409,6 +406,7 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
}
} else {
debug!("3.4: No SRV records, just use the hostname from .well-known");
query_and_cache_override(&delegated_hostname, &delegated_hostname, 8448).await;
add_port_to_hostname(&delegated_hostname)
}
}
@@ -420,21 +418,14 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
match query_srv_record(&destination_str).await {
Some(hostname_override) => {
debug!("4: SRV record found");
let force_port = hostname_override.port();
if let Ok(override_ip) =
services().globals.dns_resolver().lookup_ip(hostname_override.hostname()).await
{
services().globals.tls_name_override.write().unwrap().insert(
hostname.clone(),
(override_ip.iter().collect(), force_port.unwrap_or(8448)),
);
} else {
debug!(
"Using SRV record {}, but could not resolve to IP",
hostname_override.hostname()
);
}
let force_port = hostname_override.port();
query_and_cache_override(
&hostname,
&hostname_override.hostname(),
force_port.unwrap_or(8448),
)
.await;
if let Some(port) = force_port {
FedDest::Named(hostname.clone(), format!(":{port}"))
@@ -444,6 +435,7 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
},
None => {
debug!("5: No SRV record found");
query_and_cache_override(&destination_str, &destination_str, 8448).await;
add_port_to_hostname(&destination_str)
},
}
@@ -452,7 +444,6 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
}
},
};
debug!("Actual destination: {actual_destination:?}");
// Can't use get_ip_with_port here because we don't want to add a port
// to an IP address if it wasn't specified
@@ -466,9 +457,29 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
} else {
FedDest::Named(hostname, ":8448".to_owned())
};
debug!("Actual destination: {actual_destination:?} hostname: {hostname:?}");
(actual_destination, hostname)
}
async fn query_and_cache_override(overname: &'_ str, hostname: &'_ str, port: u16) {
match services().globals.dns_resolver().lookup_ip(hostname.to_owned()).await {
Ok(override_ip) => {
debug!("Caching result of {:?} overriding {:?}", hostname, overname);
services()
.globals
.tls_name_override
.write()
.unwrap()
.insert(overname.to_owned(), (override_ip.iter().collect(), port));
},
Err(e) => {
debug!("Got {:?} for {:?} to override {:?}", e.kind(), hostname, overname);
},
}
}
async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
fn handle_successful_srv(srv: &SrvLookup) -> Option<FedDest> {
srv.iter().next().map(|result| {
@@ -500,6 +511,10 @@ async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
}
async fn request_well_known(destination: &str) -> Option<String> {
if !services().globals.tls_name_override.read().unwrap().contains_key(destination) {
query_and_cache_override(destination, destination, 8448).await;
}
let response = services()
.globals
.default_client()
@@ -619,10 +634,6 @@ pub async fn get_server_keys_deprecated_route() -> impl IntoResponse { get_serve
pub async fn get_public_rooms_filtered_route(
body: Ruma<get_public_rooms_filtered::v1::Request>,
) -> Result<get_public_rooms_filtered::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if !services().globals.allow_public_room_directory_over_federation() {
return Err(Error::bad_config("Room directory is not public."));
}
@@ -650,10 +661,6 @@ pub async fn get_public_rooms_filtered_route(
pub async fn get_public_rooms_route(
body: Ruma<get_public_rooms::v1::Request>,
) -> Result<get_public_rooms::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if !services().globals.allow_public_room_directory_over_federation() {
return Err(Error::bad_config("Room directory is not public."));
}
@@ -707,10 +714,6 @@ pub fn parse_incoming_pdu(pdu: &RawJsonValue) -> Result<(OwnedEventId, Canonical
pub async fn send_transaction_message_route(
body: Ruma<send_transaction_message::v1::Request>,
) -> Result<send_transaction_message::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
let mut resolved_map = BTreeMap::new();
@@ -820,6 +823,10 @@ pub async fn send_transaction_message_route(
}
},
Edu::Receipt(receipt) => {
if !services().globals.allow_incoming_read_receipts() {
continue;
}
for (room_id, room_updates) in receipt.receipts {
for (user_id, user_updates) in room_updates.read {
if let Some((event_id, _)) = user_updates
@@ -946,10 +953,6 @@ pub async fn send_transaction_message_route(
/// - Only works if a user of this server is currently invited or joined the
/// room
pub async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Result<get_event::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
let event = services().rooms.timeline.get_pdu_json(&body.event_id)?.ok_or_else(|| {
@@ -985,10 +988,6 @@ pub async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Result<get_e
/// Retrieves events from before the sender joined the room, if the room's
/// history visibility allows.
pub async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) -> Result<get_backfill::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
debug!("Got backfill request from: {}", sender_servername);
@@ -1041,10 +1040,6 @@ pub async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) -> Result
pub async fn get_missing_events_route(
body: Ruma<get_missing_events::v1::Request>,
) -> Result<get_missing_events::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
@@ -1118,10 +1113,6 @@ pub async fn get_missing_events_route(
pub async fn get_event_authorization_route(
body: Ruma<get_event_authorization::v1::Request>,
) -> Result<get_event_authorization::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
@@ -1157,10 +1148,6 @@ pub async fn get_event_authorization_route(
///
/// Retrieves the current state of the room.
pub async fn get_room_state_route(body: Ruma<get_room_state::v1::Request>) -> Result<get_room_state::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
@@ -1211,10 +1198,6 @@ pub async fn get_room_state_route(body: Ruma<get_room_state::v1::Request>) -> Re
pub async fn get_room_state_ids_route(
body: Ruma<get_room_state_ids::v1::Request>,
) -> Result<get_room_state_ids::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
@@ -1253,10 +1236,6 @@ pub async fn get_room_state_ids_route(
pub async fn create_join_event_template_route(
body: Ruma<prepare_join_event::v1::Request>,
) -> Result<prepare_join_event::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if !services().rooms.metadata.exists(&body.room_id)? {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server."));
}
@@ -1343,10 +1322,6 @@ pub async fn create_join_event_template_route(
async fn create_join_event(
sender_servername: &ServerName, room_id: &RoomId, pdu: &RawJsonValue,
) -> Result<create_join_event::v1::RoomState> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if !services().rooms.metadata.exists(room_id)? {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server."));
}
@@ -1500,10 +1475,6 @@ pub async fn create_join_event_v2_route(
///
/// Invites a remote user to a room.
pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Result<create_invite::v2::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
services().rooms.event_handler.acl_check(sender_servername, &body.room_id)?;
@@ -1587,7 +1558,7 @@ pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Resu
let mut event: JsonObject = serde_json::from_str(body.event.get())
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid invite event bytes."))?;
event.insert("event_id".to_owned(), "$dummy".into());
event.insert("event_id".to_owned(), "$placeholder".into());
let pdu: PduEvent = serde_json::from_value(event.into()).map_err(|e| {
warn!("Invalid invite event: {}", e);
@@ -1622,10 +1593,6 @@ pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Resu
///
/// Gets information on all devices of the user.
pub async fn get_devices_route(body: Ruma<get_devices::v1::Request>) -> Result<get_devices::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if body.user_id.server_name() != services().globals.server_name() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@@ -1673,10 +1640,6 @@ pub async fn get_devices_route(body: Ruma<get_devices::v1::Request>) -> Result<g
pub async fn get_room_information_route(
body: Ruma<get_room_information::v1::Request>,
) -> Result<get_room_information::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let room_id = services()
.rooms
.alias
@@ -1695,10 +1658,6 @@ pub async fn get_room_information_route(
pub async fn get_profile_information_route(
body: Ruma<get_profile_information::v1::Request>,
) -> Result<get_profile_information::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if body.user_id.server_name() != services().globals.server_name() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@@ -1738,10 +1697,6 @@ pub async fn get_profile_information_route(
///
/// Gets devices and identity keys for the given users.
pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_keys::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if body.device_keys.iter().any(|(u, _)| u.server_name() != services().globals.server_name()) {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@@ -1768,10 +1723,6 @@ pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_key
///
/// Claims one-time keys.
pub async fn claim_keys_route(body: Ruma<claim_keys::v1::Request>) -> Result<claim_keys::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if body.one_time_keys.iter().any(|(u, _)| u.server_name() != services().globals.server_name()) {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@@ -1788,6 +1739,10 @@ pub async fn claim_keys_route(body: Ruma<claim_keys::v1::Request>) -> Result<cla
/// # `GET /.well-known/matrix/server`
pub async fn well_known_server_route() -> Result<impl IntoResponse> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let server_url = match services().globals.well_known_server() {
Some(url) => url.clone(),
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
@@ -1798,6 +1753,20 @@ pub async fn well_known_server_route() -> Result<impl IntoResponse> {
})))
}
/// # `GET /_matrix/federation/v1/hierarchy/{roomId}`
///
/// Gets the space tree in a depth-first manner to locate child rooms of a given
/// space.
pub async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> {
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
if services().rooms.metadata.exists(&body.room_id)? {
services().rooms.spaces.get_federation_hierarchy(&body.room_id, sender_servername, body.suggested_only).await
} else {
Err(Error::BadRequest(ErrorKind::NotFound, "Room does not exist."))
}
}
#[cfg(test)]
mod tests {
use super::{add_port_to_hostname, get_ip_with_port, FedDest};
+38 -8
View File
@@ -10,7 +10,7 @@ use either::Either;
use figment::Figment;
use itertools::Itertools;
use regex::RegexSet;
use ruma::{OwnedServerName, RoomVersionId};
use ruma::{OwnedRoomId, OwnedServerName, RoomVersionId};
use serde::{de::IgnoredAny, Deserialize};
use tracing::{debug, error, warn};
@@ -41,7 +41,10 @@ pub struct Config {
pub server_name: OwnedServerName,
#[serde(default = "default_database_backend")]
pub database_backend: String,
pub database_path: String,
pub database_path: PathBuf,
pub database_backup_path: Option<PathBuf>,
#[serde(default = "default_database_backups_to_keep")]
pub database_backups_to_keep: i16,
#[serde(default = "default_db_cache_capacity_mb")]
pub db_cache_capacity_mb: f64,
#[serde(default = "default_new_user_displayname_suffix")]
@@ -107,6 +110,9 @@ pub struct Config {
#[serde(default = "default_turn_ttl")]
pub turn_ttl: u64,
#[serde(default = "Vec::new")]
pub auto_join_rooms: Vec<OwnedRoomId>,
#[serde(default = "default_rocksdb_log_level")]
pub rocksdb_log_level: String,
#[serde(default = "default_rocksdb_max_log_file_size")]
@@ -144,6 +150,9 @@ pub struct Config {
#[serde(default = "default_presence_offline_timeout_s")]
pub presence_offline_timeout_s: u64,
#[serde(default = "true_fn")]
pub allow_incoming_read_receipts: bool,
#[serde(default)]
pub zstd_compression: bool,
@@ -169,7 +178,7 @@ pub struct Config {
#[serde(default = "RegexSet::empty")]
#[serde(with = "serde_regex")]
pub forbidden_room_names: RegexSet,
pub forbidden_alias_names: RegexSet,
#[serde(default = "RegexSet::empty")]
#[serde(with = "serde_regex")]
@@ -248,7 +257,15 @@ impl fmt::Display for Config {
let lines = [
("Server name", self.server_name.host()),
("Database backend", &self.database_backend),
("Database path", &self.database_path),
("Database path", &self.database_path.to_string_lossy()),
(
"Database backup path",
match &self.database_backup_path {
Some(path) => path.to_str().unwrap(),
None => "",
},
),
("Database backups to keep", &self.database_backups_to_keep.to_string()),
("Database cache capacity (MB)", &self.db_cache_capacity_mb.to_string()),
("Cache capacity modifier", &self.conduit_cache_capacity_modifier.to_string()),
("PDU cache capacity", &self.pdu_cache_capacity.to_string()),
@@ -282,6 +299,10 @@ impl fmt::Display for Config {
"Allow local presence requests (updates)",
&self.allow_local_presence.to_string(),
),
(
"Allow incoming remote read receipts",
&self.allow_incoming_read_receipts.to_string(),
),
(
"Block non-admin room invites (local and remote, admins can still send and receive invites)",
&self.block_non_admin_invites.to_string(),
@@ -346,6 +367,13 @@ impl fmt::Display for Config {
}
&lst.join(", ")
}),
("Auto Join Rooms", {
let mut lst = vec![];
for room in &self.auto_join_rooms {
lst.push(room);
}
&lst.into_iter().join(", ")
}),
#[cfg(feature = "compression-zstd")]
("zstd Response Body Compression", &self.zstd_compression.to_string()),
#[cfg(feature = "rocksdb")]
@@ -398,8 +426,8 @@ impl fmt::Display for Config {
("Forbidden usernames", {
&self.forbidden_usernames.patterns().iter().join(", ")
}),
("Forbidden room names", {
&self.forbidden_room_names.patterns().iter().join(", ")
("Forbidden room aliases", {
&self.forbidden_alias_names.patterns().iter().join(", ")
}),
(
"URL preview domain contains allowlist",
@@ -439,9 +467,11 @@ fn default_port() -> ListeningPort {
fn default_unix_socket_perms() -> u32 { 660 }
fn default_database_backups_to_keep() -> i16 { 1 }
fn default_database_backend() -> String { "rocksdb".to_owned() }
fn default_db_cache_capacity_mb() -> f64 { 300.0 }
fn default_db_cache_capacity_mb() -> f64 { 256.0 }
fn default_conduit_cache_capacity_modifier() -> f64 { 1.0 }
@@ -482,7 +512,7 @@ fn default_rocksdb_max_log_file_size() -> usize {
4 * 1024 * 1024
}
fn default_rocksdb_parallelism_threads() -> usize { num_cpus::get_physical() / 2 }
fn default_rocksdb_parallelism_threads() -> usize { 0 }
fn default_rocksdb_compression_algo() -> String { "zstd".to_owned() }
+30 -3
View File
@@ -1,4 +1,4 @@
use std::{future::Future, pin::Pin, sync::Arc};
use std::{error::Error, future::Future, pin::Pin, sync::Arc};
use super::Config;
use crate::Result;
@@ -18,6 +18,8 @@ pub(crate) trait KeyValueDatabaseEngine: Send + Sync {
Self: Sized;
fn open_tree(&self, name: &'static str) -> Result<Arc<dyn KvTree>>;
fn flush(&self) -> Result<()>;
#[allow(dead_code)]
fn sync(&self) -> Result<()> { Ok(()) }
fn cleanup(&self) -> Result<()> { Ok(()) }
fn memory_usage(&self) -> Result<String> {
Ok("Current database engine does not support memory usage reporting.".to_owned())
@@ -25,6 +27,10 @@ pub(crate) trait KeyValueDatabaseEngine: Send + Sync {
#[allow(dead_code)]
fn clear_caches(&self) {}
fn backup(&self) -> Result<(), Box<dyn Error>> { unimplemented!() }
fn backup_list(&self) -> Result<String> { Ok(String::new()) }
}
pub(crate) trait KvTree: Send + Sync {
@@ -39,16 +45,37 @@ pub(crate) trait KvTree: Send + Sync {
}
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()>;
fn insert_batch(&self, iter: &mut dyn Iterator<Item = (Vec<u8>, Vec<u8>)>) -> Result<()>;
fn insert_batch(&self, iter: &mut dyn Iterator<Item = (Vec<u8>, Vec<u8>)>) -> Result<()> {
for (key, value) in iter {
self.insert(&key, &value)?;
}
Ok(())
}
fn remove(&self, key: &[u8]) -> Result<()>;
#[allow(dead_code)]
fn remove_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()> {
for key in iter {
self.remove(&key)?;
}
Ok(())
}
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a>;
fn iter_from<'a>(&'a self, from: &[u8], backwards: bool) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a>;
fn increment(&self, key: &[u8]) -> Result<Vec<u8>>;
fn increment_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()>;
fn increment_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()> {
for key in iter {
self.increment(&key)?;
}
Ok(())
}
fn scan_prefix<'a>(&'a self, prefix: Vec<u8>) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a>;
+164 -61
View File
@@ -4,19 +4,24 @@ use std::{
sync::{Arc, RwLock},
};
use chrono::{DateTime, Utc};
use rust_rocksdb::{
backup::{BackupEngine, BackupEngineOptions},
LogLevel::{Debug, Error, Fatal, Info, Warn},
WriteBatchWithTransaction,
};
use tracing::{debug, info};
use tracing::{debug, error, info};
use super::{super::Config, watchers::Watchers, KeyValueDatabaseEngine, KvTree};
use crate::{utils, Result};
pub(crate) struct Engine {
rocks: rust_rocksdb::DBWithThreadMode<rust_rocksdb::MultiThreaded>,
cache: rust_rocksdb::Cache,
row_cache: rust_rocksdb::Cache,
col_cache: rust_rocksdb::Cache,
old_cfs: Vec<String>,
opts: rust_rocksdb::Options,
env: rust_rocksdb::Env,
config: Config,
}
@@ -27,24 +32,13 @@ struct RocksDbEngineTree<'a> {
write_lock: RwLock<()>,
}
fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rocksdb::Options {
// block-based options: https://docs.rs/rocksdb/latest/rocksdb/struct.BlockBasedOptions.html#
let mut block_based_options = rust_rocksdb::BlockBasedOptions::default();
block_based_options.set_block_cache(rocksdb_cache);
// "Difference of spinning disk"
// https://zhangyuchi.gitbooks.io/rocksdbbook/content/RocksDB-Tuning-Guide.html
block_based_options.set_block_size(64 * 1024);
block_based_options.set_cache_index_and_filter_blocks(true);
block_based_options.set_bloom_filter(10.0, false);
block_based_options.set_pin_l0_filter_and_index_blocks_in_cache(true);
block_based_options.set_optimize_filters_for_memory(true);
fn db_options(
config: &Config, env: &rust_rocksdb::Env, row_cache: &rust_rocksdb::Cache, col_cache: &rust_rocksdb::Cache,
) -> rust_rocksdb::Options {
// database options: https://docs.rs/rocksdb/latest/rocksdb/struct.Options.html#
let mut db_opts = rust_rocksdb::Options::default();
// Logging
let rocksdb_log_level = match config.rocksdb_log_level.as_ref() {
"debug" => Debug,
"info" => Info,
@@ -52,7 +46,52 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
"fatal" => Fatal,
_ => Error,
};
db_opts.set_log_level(rocksdb_log_level);
db_opts.set_max_log_file_size(config.rocksdb_max_log_file_size);
db_opts.set_log_file_time_to_roll(config.rocksdb_log_time_to_roll);
db_opts.set_keep_log_file_num(config.rocksdb_max_log_files);
// Processing
let threads = if config.rocksdb_parallelism_threads == 0 {
num_cpus::get_physical() // max cores if user specified 0
} else {
config.rocksdb_parallelism_threads
};
db_opts.set_max_background_jobs(threads.try_into().unwrap());
db_opts.set_max_subcompactions(threads.try_into().unwrap());
// IO
db_opts.set_use_direct_reads(true);
db_opts.set_use_direct_io_for_flush_and_compaction(true);
if config.rocksdb_optimize_for_spinning_disks {
db_opts.set_skip_stats_update_on_db_open(true); // speeds up opening DB on hard
// drives
}
// Blocks
let mut block_based_options = rust_rocksdb::BlockBasedOptions::default();
block_based_options.set_block_size(4 * 1024);
block_based_options.set_metadata_block_size(4 * 1024);
block_based_options.set_bloom_filter(9.6, true);
block_based_options.set_optimize_filters_for_memory(true);
block_based_options.set_cache_index_and_filter_blocks(true);
block_based_options.set_pin_top_level_index_and_filter(true);
block_based_options.set_block_cache(col_cache);
db_opts.set_row_cache(row_cache);
// Buffers
db_opts.set_write_buffer_size(2 * 1024 * 1024);
db_opts.set_max_write_buffer_number(2);
db_opts.set_min_write_buffer_number(1);
// Files
db_opts.set_level_zero_file_num_compaction_trigger(1);
db_opts.set_target_file_size_base(64 * 1024 * 1024);
db_opts.set_max_bytes_for_level_base(128 * 1024 * 1024);
db_opts.set_ttl(14 * 24 * 60 * 60);
// Compression
let rocksdb_compression_algo = match config.rocksdb_compression_algo.as_ref() {
"zstd" => rust_rocksdb::DBCompressionType::Zstd,
"zlib" => rust_rocksdb::DBCompressionType::Zlib,
@@ -61,29 +100,6 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
_ => rust_rocksdb::DBCompressionType::Zstd,
};
let threads = if config.rocksdb_parallelism_threads == 0 {
num_cpus::get_physical() // max cores if user specified 0
} else {
config.rocksdb_parallelism_threads
};
db_opts.set_log_level(rocksdb_log_level);
db_opts.set_max_log_file_size(config.rocksdb_max_log_file_size);
db_opts.set_log_file_time_to_roll(config.rocksdb_log_time_to_roll);
db_opts.set_keep_log_file_num(config.rocksdb_max_log_files);
if config.rocksdb_optimize_for_spinning_disks {
db_opts.set_skip_stats_update_on_db_open(true); // speeds up opening DB on hard drives
db_opts.set_compaction_readahead_size(4 * 1024 * 1024); // "If youre running RocksDB on spinning disks, you should set this to at least
// 2MB. That way RocksDBs compaction is doing sequential instead of random
// reads."
db_opts.set_target_file_size_base(256 * 1024 * 1024);
} else {
db_opts.set_max_bytes_for_level_base(512 * 1024 * 1024);
db_opts.set_use_direct_reads(true);
db_opts.set_use_direct_io_for_flush_and_compaction(true);
}
if config.rocksdb_bottommost_compression {
db_opts.set_bottommost_compression_type(rocksdb_compression_algo);
db_opts.set_bottommost_zstd_max_train_bytes(0, true);
@@ -94,19 +110,10 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
// -14 w_bits is only read by zlib.
db_opts.set_compression_options(-14, config.rocksdb_compression_level, 0, 0);
db_opts.set_block_based_table_factory(&block_based_options);
db_opts.create_if_missing(true);
db_opts.increase_parallelism(
threads.try_into().expect("Failed to convert \"rocksdb_parallelism_threads\" usize into i32"),
);
db_opts.set_compression_type(rocksdb_compression_algo);
db_opts.optimize_level_style_compaction(10 * 1024 * 1024);
// https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning
db_opts.set_level_compaction_dynamic_level_bytes(true);
db_opts.set_max_background_jobs(6);
db_opts.set_bytes_per_sync(1_048_576);
// Misc
db_opts.create_if_missing(true);
// https://github.com/facebook/rocksdb/wiki/WAL-Recovery-Modes#ktoleratecorruptedtailrecords
//
@@ -119,15 +126,21 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
let prefix_extractor = rust_rocksdb::SliceTransform::create_fixed_prefix(1);
db_opts.set_prefix_extractor(prefix_extractor);
db_opts.set_block_based_table_factory(&block_based_options);
db_opts.set_env(env);
db_opts
}
impl KeyValueDatabaseEngine for Arc<Engine> {
fn open(config: &Config) -> Result<Self> {
let cache_capacity_bytes = (config.db_cache_capacity_mb * 1024.0 * 1024.0) as usize;
let rocksdb_cache = rust_rocksdb::Cache::new_lru_cache(cache_capacity_bytes);
let cache_capacity_bytes = config.db_cache_capacity_mb * 1024.0 * 1024.0;
let row_cache_capacity_bytes = (cache_capacity_bytes * 0.25) as usize;
let col_cache_capacity_bytes = (cache_capacity_bytes * 0.75) as usize;
let db_opts = db_options(&rocksdb_cache, config);
let db_env = rust_rocksdb::Env::new()?;
let row_cache = rust_rocksdb::Cache::new_lru_cache(row_cache_capacity_bytes);
let col_cache = rust_rocksdb::Cache::new_lru_cache(col_cache_capacity_bytes);
let db_opts = db_options(config, &db_env, &row_cache, &col_cache);
debug!("Listing column families in database");
let cfs =
@@ -139,13 +152,16 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
let db = rust_rocksdb::DBWithThreadMode::<rust_rocksdb::MultiThreaded>::open_cf_descriptors(
&db_opts,
&config.database_path,
cfs.iter().map(|name| rust_rocksdb::ColumnFamilyDescriptor::new(name, db_options(&rocksdb_cache, config))),
cfs.iter().map(|name| rust_rocksdb::ColumnFamilyDescriptor::new(name, db_opts.clone())),
)?;
Ok(Arc::new(Engine {
rocks: db,
cache: rocksdb_cache,
row_cache,
col_cache,
old_cfs: cfs,
opts: db_opts,
env: db_env,
config: config.clone(),
}))
}
@@ -154,7 +170,7 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
if !self.old_cfs.contains(&name.to_owned()) {
// Create if it didn't exist
debug!("Creating new column family in database: {}", name);
let _ = self.rocks.create_cf(name, &db_options(&self.cache, &self.config));
let _ = self.rocks.create_cf(name, &self.opts);
}
Ok(Arc::new(RocksDbEngineTree {
@@ -166,23 +182,35 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
}
fn flush(&self) -> Result<()> {
debug!("Running flush_wal (no sync)");
rust_rocksdb::DBCommon::flush_wal(&self.rocks, false)?;
Ok(())
}
fn sync(&self) -> Result<()> {
rust_rocksdb::DBCommon::flush_wal(&self.rocks, true)?;
Ok(())
}
fn memory_usage(&self) -> Result<String> {
let stats = rust_rocksdb::perf::get_memory_usage_stats(Some(&[&self.rocks]), Some(&[&self.cache]))?;
let stats = rust_rocksdb::perf::get_memory_usage_stats(
Some(&[&self.rocks]),
Some(&[&self.row_cache, &self.col_cache]),
)?;
Ok(format!(
"Approximate memory usage of all the mem-tables: {:.3} MB\nApproximate memory usage of un-flushed \
mem-tables: {:.3} MB\nApproximate memory usage of all the table readers: {:.3} MB\nApproximate memory \
usage by cache: {:.3} MB\nApproximate memory usage by cache pinned: {:.3} MB\n",
usage by cache: {:.3} MB\nApproximate memory usage by row cache: {:.3} MB pinned: {:.3} MB\nApproximate \
memory usage by column cache: {:.3} MB pinned: {:.3} MB\n",
stats.mem_table_total as f64 / 1024.0 / 1024.0,
stats.mem_table_unflushed as f64 / 1024.0 / 1024.0,
stats.mem_table_readers_total as f64 / 1024.0 / 1024.0,
stats.cache_total as f64 / 1024.0 / 1024.0,
self.cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
self.row_cache.get_usage() as f64 / 1024.0 / 1024.0,
self.row_cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
self.col_cache.get_usage() as f64 / 1024.0 / 1024.0,
self.col_cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
))
}
@@ -195,6 +223,69 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
Ok(())
}
fn backup(&self) -> Result<(), Box<dyn std::error::Error>> {
let path = self.config.database_backup_path.as_ref();
if path.is_none() || path.is_some_and(|path| path.as_os_str().is_empty()) {
return Ok(());
}
let options = BackupEngineOptions::new(path.unwrap())?;
let mut engine = BackupEngine::open(&options, &self.env)?;
let ret = if self.config.database_backups_to_keep > 0 {
match engine.create_new_backup_flush(&self.rocks, true) {
Err(e) => return Err(Box::new(e)),
Ok(_) => {
let _info = engine.get_backup_info();
let info = &_info.last().unwrap();
info!(
"Created database backup #{} using {} bytes in {} files",
info.backup_id, info.size, info.num_files,
);
Ok(())
},
}
} else {
Ok(())
};
if self.config.database_backups_to_keep >= 0 {
let keep = u32::try_from(self.config.database_backups_to_keep)?;
if let Err(e) = engine.purge_old_backups(keep.try_into()?) {
error!("Failed to purge old backup: {:?}", e.to_string())
}
}
ret
}
fn backup_list(&self) -> Result<String> {
let path = self.config.database_backup_path.as_ref();
if path.is_none() || path.is_some_and(|path| path.as_os_str().is_empty()) {
return Ok(
"Configure database_backup_path to enable backups, or the path specified is not valid".to_owned(),
);
}
let mut res = String::new();
let options = BackupEngineOptions::new(path.unwrap())?;
let engine = BackupEngine::open(&options, &self.env)?;
for info in engine.get_backup_info() {
std::fmt::write(
&mut res,
format_args!(
"#{} {}: {} bytes, {} files\n",
info.backup_id,
DateTime::<Utc>::from_timestamp(info.timestamp, 0).unwrap_or_default().to_rfc2822(),
info.size,
info.num_files,
),
)
.unwrap();
}
Ok(res)
}
// TODO: figure out if this is needed for rocksdb
#[allow(dead_code)]
fn clear_caches(&self) {}
@@ -252,6 +343,18 @@ impl KvTree for RocksDbEngineTree<'_> {
Ok(self.db.rocks.delete_cf_opt(&self.cf(), key, &writeoptions)?)
}
fn remove_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()> {
let writeoptions = rust_rocksdb::WriteOptions::default();
let mut batch = WriteBatchWithTransaction::<false>::default();
for key in iter {
batch.delete_cf(&self.cf(), key);
}
Ok(self.db.rocks.write_opt(batch, &writeoptions)?)
}
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a> {
let mut readoptions = rust_rocksdb::ReadOptions::default();
readoptions.set_total_order_seek(true);
+5 -1
View File
@@ -52,7 +52,7 @@ impl service::globals::Data for KeyValueDatabase {
let mut futures = FuturesUnordered::new();
// Return when *any* user changed his key
// Return when *any* user changed their key
// TODO: only send for user they share a room with
futures.push(self.todeviceid_events.watch_prefix(&userdeviceid_prefix));
@@ -272,4 +272,8 @@ lasttimelinecount_cache: {lasttimelinecount_cache}\n"
self.global.insert(b"version", &new_version.to_be_bytes())?;
Ok(())
}
fn backup(&self) -> Result<(), Box<dyn std::error::Error>> { self.db.backup() }
fn backup_list(&self) -> Result<String> { self.db.backup_list() }
}
+46 -6
View File
@@ -1,15 +1,17 @@
use ruma::api::client::error::ErrorKind;
use ruma::{api::client::error::ErrorKind, OwnedUserId};
use tracing::debug;
use crate::{
database::KeyValueDatabase,
service::{self, media::UrlPreviewData},
utils, Error, Result,
utils::string_from_bytes,
Error, Result,
};
impl service::media::Data for KeyValueDatabase {
fn create_file_metadata(
&self, mxc: String, width: u32, height: u32, content_disposition: Option<&str>, content_type: Option<&str>,
&self, sender_user: Option<&str>, mxc: String, width: u32, height: u32, content_disposition: Option<&str>,
content_type: Option<&str>,
) -> Result<Vec<u8>> {
let mut key = mxc.as_bytes().to_vec();
key.push(0xFF);
@@ -22,6 +24,16 @@ impl service::media::Data for KeyValueDatabase {
self.mediaid_file.insert(&key, &[])?;
if let Some(user) = sender_user {
let mut key = mxc.as_bytes().to_vec();
key.push(0xFF);
let mut user = user.as_bytes().to_vec();
user.push(0xFF);
self.mediaid_user.insert(&key, &user)?;
}
Ok(key)
}
@@ -31,13 +43,22 @@ impl service::media::Data for KeyValueDatabase {
let mut prefix = mxc.as_bytes().to_vec();
prefix.push(0xFF);
debug!("MXC db prefix: {:?}", prefix);
debug!("MXC db prefix: {prefix:?}");
for (key, _) in self.mediaid_file.scan_prefix(prefix) {
debug!("Deleting key: {:?}", key);
self.mediaid_file.remove(&key)?;
}
for (key, value) in self.mediaid_user.scan_prefix(mxc.as_bytes().to_vec()) {
if key == mxc.as_bytes().to_vec() {
let user = string_from_bytes(&value).unwrap_or_default();
debug!("Deleting key \"{key:?}\" which was uploaded by user {user}");
self.mediaid_user.remove(&key)?;
}
}
Ok(())
}
@@ -48,6 +69,8 @@ impl service::media::Data for KeyValueDatabase {
let mut prefix = mxc.as_bytes().to_vec();
prefix.push(0xFF);
debug!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA prefix: {:?}", prefix);
let mut keys: Vec<Vec<u8>> = vec![];
for (key, _) in self.mediaid_file.scan_prefix(prefix) {
@@ -65,6 +88,23 @@ impl service::media::Data for KeyValueDatabase {
Ok(keys)
}
fn get_all_media_keys_by_user(&self, user_id: OwnedUserId) -> Result<Vec<Vec<u8>>> {
debug!("User ID: {user_id:?}");
let mut user = user_id.as_bytes().to_vec();
user.push(0xFF);
let mut keys: Vec<Vec<u8>> = vec![];
for (key, value) in self.mediaid_user.iter() {
if value == user {
keys.push(key);
}
}
Ok(keys)
}
fn search_file_metadata(
&self, mxc: String, width: u32, height: u32,
) -> Result<(Option<String>, Option<String>, Vec<u8>)> {
@@ -85,7 +125,7 @@ impl service::media::Data for KeyValueDatabase {
let content_type = parts
.next()
.map(|bytes| {
utils::string_from_bytes(bytes)
string_from_bytes(bytes)
.map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode."))
})
.transpose()?;
@@ -97,7 +137,7 @@ impl service::media::Data for KeyValueDatabase {
None
} else {
Some(
utils::string_from_bytes(content_disposition_bytes)
string_from_bytes(content_disposition_bytes)
.map_err(|_| Error::bad_database("Content Disposition in mediaid_file is invalid unicode."))?,
)
};
@@ -268,6 +268,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
}))
}
/// Returns the number of users which are currently in a room
#[tracing::instrument(skip(self))]
fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
self.roomid_joinedcount
@@ -276,6 +277,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
.transpose()
}
/// Returns the number of users which are currently invited to a room
#[tracing::instrument(skip(self))]
fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
self.roomid_invitedcount
+4
View File
@@ -90,6 +90,10 @@ impl service::sending::Data for KeyValueDatabase {
fn mark_as_active(&self, events: &[(SendingEventType, Vec<u8>)]) -> Result<()> {
for (e, key) in events {
if key.is_empty() {
continue;
}
let value = if let SendingEventType::Edu(value) = &e {
&**value
} else {
+10 -3
View File
@@ -157,6 +157,7 @@ pub struct KeyValueDatabase {
//pub media: media::Media,
pub(super) mediaid_file: Arc<dyn KvTree>, // MediaId = MXC + WidthHeight + ContentDisposition + ContentType
pub(super) url_previews: Arc<dyn KvTree>,
pub(super) mediaid_user: Arc<dyn KvTree>,
//pub key_backups: key_backups::KeyBackups,
pub(super) backupid_algorithm: Arc<dyn KvTree>, // BackupId = UserId + Version(Count)
pub(super) backupid_etag: Arc<dyn KvTree>, // BackupId = UserId + Version(Count)
@@ -365,6 +366,7 @@ impl KeyValueDatabase {
roomusertype_roomuserdataid: builder.open_tree("roomusertype_roomuserdataid")?,
mediaid_file: builder.open_tree("mediaid_file")?,
url_previews: builder.open_tree("url_previews")?,
mediaid_user: builder.open_tree("mediaid_user")?,
backupid_algorithm: builder.open_tree("backupid_algorithm")?,
backupid_etag: builder.open_tree("backupid_etag")?,
backupkeyid_backup: builder.open_tree("backupkeyid_backup")?,
@@ -931,8 +933,13 @@ impl KeyValueDatabase {
{
let patterns = &services().globals.config.forbidden_usernames;
if !patterns.is_empty() {
for user in services().users.iter() {
let user_id = user?;
for user_id in services()
.users
.iter()
.filter_map(std::result::Result::ok)
.filter(|user| !services().users.is_deactivated(user).unwrap_or(true))
.filter(|user| user.server_name() == services().globals.server_name())
{
let matches = patterns.matches(user_id.localpart());
if matches.matched_any() {
warn!(
@@ -946,7 +953,7 @@ impl KeyValueDatabase {
}
{
let patterns = &services().globals.config.forbidden_room_names;
let patterns = &services().globals.config.forbidden_alias_names;
if !patterns.is_empty() {
for address in services().rooms.metadata.iter_ids() {
let room_id = address?;
+14 -4
View File
@@ -280,7 +280,7 @@ async fn main() {
}
if config.allow_outgoing_presence && !config.allow_local_presence {
error!("Outgoing presence requires allowing local presence. Please enable \"allow_outgoing_presence\".");
error!("Outgoing presence requires allowing local presence. Please enable \"allow_local_presence\".");
return;
}
@@ -535,11 +535,15 @@ async fn unrecognized_method<B: Send + 'static>(
let uri = req.uri().clone();
let inner = next.run(req).await;
if inner.status() == StatusCode::METHOD_NOT_ALLOWED {
warn!("Method not allowed: {method} {uri}");
if uri.path().contains("_matrix/") {
warn!("Method not allowed: {method} {uri}");
} else {
info!("Method not allowed: {method} {uri}");
}
return Ok(RumaResponse(UiaaResponse::MatrixError(RumaError {
body: ErrorBody::Standard {
kind: ErrorKind::Unrecognized,
message: "M_UNRECOGNIZED: Unrecognized request".to_owned(),
message: "M_UNRECOGNIZED: Method not allowed for endpoint".to_owned(),
},
status_code: StatusCode::METHOD_NOT_ALLOWED,
}))
@@ -739,6 +743,7 @@ fn routes() -> Router {
.ruma_route(server_server::get_profile_information_route)
.ruma_route(server_server::get_keys_route)
.ruma_route(server_server::claim_keys_route)
.ruma_route(server_server::get_hierarchy_route)
.route("/_matrix/client/r0/rooms/:room_id/initialSync", get(initial_sync))
.route("/_matrix/client/v3/rooms/:room_id/initialSync", get(initial_sync))
.route("/client/server.json", get(client_server::syncv3_client_server_json))
@@ -805,7 +810,12 @@ async fn shutdown_signal(handle: ServerHandle, tx: Sender<()>) -> Result<()> {
}
async fn not_found(uri: Uri) -> impl IntoResponse {
warn!("Not found: {uri}");
if uri.path().contains("_matrix/") {
warn!("Not found: {uri}");
} else {
info!("Not found: {uri}");
}
Error::BadRequest(ErrorKind::Unrecognized, "Unrecognized request")
}
+312 -160
View File
@@ -30,7 +30,9 @@ use tracing::{debug, error, info, warn};
use super::pdu::PduBuilder;
use crate::{
api::{
client_server::{get_alias_helper, leave_all_rooms, leave_room, AUTO_GEN_PASSWORD_LENGTH},
client_server::{
get_alias_helper, join_room_by_id_helper, leave_all_rooms, leave_room, AUTO_GEN_PASSWORD_LENGTH,
},
server_server::parse_incoming_pdu,
},
services,
@@ -92,6 +94,12 @@ enum MediaCommand {
event_id: Option<Box<EventId>>,
},
/// Deletes all **uploaded** local media from the specified user.
DeleteFromUser {
/// User ID to delete all media from
user_id: Box<UserId>,
},
/// - Deletes a codeblock list of MXC URLs from our database and on the
/// filesystem
DeleteList,
@@ -103,6 +111,13 @@ enum MediaCommand {
/// past 5 minutes
duration: String,
},
/// - Lists all **uploaded** local media from the specified user with their
/// MXC URLs.
ListFromUser {
/// User ID to list all media from
user_id: Box<UserId>,
},
}
#[cfg_attr(test, derive(Debug))]
@@ -396,6 +411,21 @@ enum DebugCommand {
server: Box<ServerName>,
},
/// - Gets all the room state events for the specified room.
///
/// This is functionally equivalent to `GET
/// /_matrix/client/v3/rooms/{roomid}/state`, except the admin command does
/// *not* check if the sender user is allowed to see state events. This is
/// done because it's implied that server admins here have database access
/// and can see/get room info themselves anyways if they were malicious
/// admins.
///
/// Of course the check is still done on the actual client API.
GetRoomState {
/// Room ID
room_id: Box<RoomId>,
},
/// - Forces device lists for all local and remote users to be updated (as
/// having new keys available)
ForceDeviceListUpdates,
@@ -421,6 +451,13 @@ enum ServerCommand {
ClearServiceCaches {
amount: u32,
},
/// - Performs an online backup of the database (only available for RocksDB
/// at the moment)
BackupDatabase,
/// - List database backups
ListBackups,
}
#[derive(Debug)]
@@ -458,60 +495,50 @@ impl Service {
let conduit_user = UserId::parse(format!("@conduit:{}", services().globals.server_name()))
.expect("@conduit:server_name is valid");
let conduit_room = services()
.rooms
.alias
.resolve_local_alias(
format!("#admins:{}", services().globals.server_name())
.as_str()
.try_into()
.expect("#admins:server_name is a valid room alias"),
)
.expect("Database data for admin room alias must be valid")
.expect("Admin room must exist");
if let Ok(Some(conduit_room)) = services().admin.get_admin_room() {
loop {
tokio::select! {
Some(event) = receiver.recv() => {
let (mut message_content, reply) = match event {
AdminRoomEvent::SendMessage(content) => (content, None),
AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
(self.process_admin_message(room_message).await, Some(reply_id))
}
};
loop {
tokio::select! {
Some(event) = receiver.recv() => {
let (mut message_content, reply) = match event {
AdminRoomEvent::SendMessage(content) => (content, None),
AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
(self.process_admin_message(room_message).await, Some(reply_id))
let mutex_state = Arc::clone(
services().globals
.roomid_mutex_state
.write()
.await
.entry(conduit_room.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
if let Some(reply) = reply {
message_content.relates_to = Some(Reply { in_reply_to: InReplyTo { event_id: reply.into() } });
}
};
let mutex_state = Arc::clone(
services().globals
.roomid_mutex_state
.write()
.await
.entry(conduit_room.clone())
.or_default(),
);
services().rooms.timeline.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&message_content)
.expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
},
&conduit_user,
&conduit_room,
&state_lock)
.await
.unwrap();
let state_lock = mutex_state.lock().await;
if let Some(reply) = reply {
message_content.relates_to = Some(Reply { in_reply_to: InReplyTo { event_id: reply.into() } });
drop(state_lock);
}
services().rooms.timeline.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&message_content)
.expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
},
&conduit_user,
&conduit_room,
&state_lock)
.await
.unwrap();
drop(state_lock);
}
}
}
@@ -792,6 +819,17 @@ impl Service {
));
}
},
MediaCommand::DeleteFromUser {
user_id,
} => {
let deleted_count = services().media.delete_from_user(user_id.into()).await?;
debug!("Deleted {deleted_count} total media files.");
return Ok(RoomMessageEventContent::text_plain(format!(
"Deleted {deleted_count} total media files.",
)));
},
MediaCommand::DeleteList => {
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<_>>();
@@ -825,6 +863,25 @@ impl Service {
deleted_count
)));
},
MediaCommand::ListFromUser {
user_id,
} => {
let mxc_list = services().media.list_all_media_by_user(user_id.clone().into()).await?;
let output_plain = format!(
"All MXC URLs {user_id} Uploaded:\n{}",
mxc_list.iter().map(ToOwned::to_owned).collect::<Vec<_>>().join("\n")
);
let output_html = format!(
"<table><caption>All MXC URLs {user_id} \
Uploaded</caption>\n<tr><th>URL</th></tr>\n{}</table>",
mxc_list.iter().fold(String::new(), |mut output, mxc| {
writeln!(output, "<tr><td>{}</td></tr>", mxc).unwrap();
output
})
);
RoomMessageEventContent::text_html(output_plain, output_html)
},
}
},
AdminCommand::Users(command) => match command {
@@ -888,6 +945,35 @@ impl Service {
.expect("to json value always works"),
)?;
if !services().globals.config.auto_join_rooms.is_empty() {
for room in &services().globals.config.auto_join_rooms {
if !services().rooms.state_cache.server_in_room(services().globals.server_name(), room)? {
warn!("Skipping room {room} to automatically join as we have never joined before.");
continue;
}
if let Some(room_id_server_name) = room.server_name() {
match join_room_by_id_helper(
Some(&user_id),
room,
Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
None,
)
.await
{
Ok(_) => {
info!("Automatically joined room {room} for user {user_id}");
},
Err(e) => {
// don't return this error so we don't fail registrations
error!("Failed to automatically join room {room} for user {user_id}: {e}");
},
};
}
}
}
// we dont add a device since we're not the user, just the creator
// Inhibit login does not work for guests
@@ -1096,14 +1182,13 @@ impl Service {
format!("#admins:{}", services().globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
let admin_room_id = services()
.rooms
.alias
.resolve_local_alias(&admin_room_alias)?
.expect("Admin room must exist");
if room.to_string().eq(&admin_room_id) || room.to_string().eq(&admin_room_alias) {
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
if let Some(admin_room_id) = services().admin.get_admin_room()? {
if room.to_string().eq(&admin_room_id) || room.to_string().eq(&admin_room_alias) {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to ban the admin room.",
));
}
}
let room_id = if room.is_room_id() {
@@ -1267,23 +1352,15 @@ impl Service {
let mut room_ban_count = 0;
let mut room_ids: Vec<&RoomId> = Vec::new();
let admin_room_alias: Box<RoomAliasId> =
format!("#admins:{}", services().globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
let admin_room_id = services()
.rooms
.alias
.resolve_local_alias(&admin_room_alias)?
.expect("Admin room must exist");
for &room_id in &rooms_s {
match <&RoomId>::try_from(room_id) {
Ok(owned_room_id) => {
// silently ignore deleting admin room
if owned_room_id.eq(&admin_room_id) {
info!("User specified admin room in bulk ban list, ignoring");
continue;
if let Some(admin_room_id) = services().admin.get_admin_room()? {
if owned_room_id.eq(&admin_room_id) {
info!("User specified admin room in bulk ban list, ignoring");
continue;
}
}
room_ids.push(owned_room_id);
@@ -1870,6 +1947,35 @@ impl Service {
RoomMessageEventContent::text_plain("Done.")
},
ServerCommand::ListBackups => {
let result = services().globals.db.backup_list()?;
if result.is_empty() {
return Ok(RoomMessageEventContent::text_plain("No backups found."));
}
RoomMessageEventContent::text_plain(result)
},
ServerCommand::BackupDatabase => {
if !cfg!(feature = "rocksdb") {
return Ok(RoomMessageEventContent::text_plain(
"Only RocksDB supports online backups in conduwuit.",
));
}
let mut result = tokio::task::spawn_blocking(move || match services().globals.db.backup() {
Ok(_) => String::new(),
Err(e) => (*e).to_string(),
})
.await
.unwrap();
if result.is_empty() {
result = services().globals.db.backup_list()?;
}
RoomMessageEventContent::text_plain(&result)
},
},
AdminCommand::Debug(command) => match command {
DebugCommand::GetAuthChain {
@@ -2061,6 +2167,41 @@ impl Service {
},
}
},
DebugCommand::GetRoomState {
room_id,
} => {
let room_state = services()
.rooms
.state_accessor
.room_state_full(&room_id)
.await?
.values()
.map(|pdu| pdu.to_state_event())
.collect::<Vec<_>>();
if room_state.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
"Unable to find room state in our database (vector is empty)",
));
}
let json_text = serde_json::to_string_pretty(&room_state).map_err(|e| {
error!("Failed converting room state vector in our database to pretty JSON: {e}");
Error::bad_database(
"Failed to convert room state events to pretty JSON, possible invalid room state events \
in our database",
)
})?;
return Ok(RoomMessageEventContent::text_html(
format!("{}\n```json\n{}\n```", "Found full room state", json_text),
format!(
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
"Found full room state",
HtmlEscape(&json_text)
),
));
},
DebugCommand::ForceDeviceListUpdates => {
// Force E2EE device list updates for all users
for user_id in services().users.iter().filter_map(std::result::Result::ok) {
@@ -2393,105 +2534,113 @@ impl Service {
Ok(())
}
/// Gets the room ID of the admin room
///
/// Errors are propagated from the database, and will have None if there is
/// no admin room
pub(crate) fn get_admin_room(&self) -> Result<Option<OwnedRoomId>> {
let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
services().rooms.alias.resolve_local_alias(&admin_room_alias)
}
/// Invite the user to the conduit admin room.
///
/// In conduit, this is equivalent to granting admin privileges.
pub(crate) async fn make_user_admin(&self, user_id: &UserId, displayname: String) -> Result<()> {
let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
let room_id = services().rooms.alias.resolve_local_alias(&admin_room_alias)?.expect("Admin room must exist");
if let Some(room_id) = services().admin.get_admin_room()? {
let mutex_state =
Arc::clone(services().globals.roomid_mutex_state.write().await.entry(room_id.clone()).or_default());
let state_lock = mutex_state.lock().await;
let mutex_state =
Arc::clone(services().globals.roomid_mutex_state.write().await.entry(room_id.clone()).or_default());
let state_lock = mutex_state.lock().await;
// Use the server user to grant the new admin's power level
let conduit_user = UserId::parse_with_server_name("conduit", services().globals.server_name())
.expect("@conduit:server_name is valid");
// Use the server user to grant the new admin's power level
let conduit_user = UserId::parse_with_server_name("conduit", services().globals.server_name())
.expect("@conduit:server_name is valid");
// Invite and join the real user
services()
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Invite,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)
.await?;
services()
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: Some(displayname),
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
user_id,
&room_id,
&state_lock,
)
.await?;
// Invite and join the real user
services()
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Invite,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)
.await?;
services()
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: Some(displayname),
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
user_id,
&room_id,
&state_lock,
)
.await?;
// Set power level
let mut users = BTreeMap::new();
users.insert(conduit_user.clone(), 100.into());
users.insert(user_id.to_owned(), 100.into());
// Set power level
let mut users = BTreeMap::new();
users.insert(conduit_user.clone(), 100.into());
users.insert(user_id.to_owned(), 100.into());
services()
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)
.await?;
services()
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)
.await?;
// Send welcome message
services().rooms.timeline.build_and_append_pdu(
// Send welcome message
services().rooms.timeline.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&RoomMessageEventContent::text_html(
@@ -2508,7 +2657,10 @@ impl Service {
&state_lock,
).await?;
Ok(())
Ok(())
} else {
Ok(())
}
}
}
+3 -1
View File
@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::{collections::BTreeMap, error::Error};
use async_trait::async_trait;
use ruma::{
@@ -32,4 +32,6 @@ pub trait Data: Send + Sync {
fn signing_keys_for(&self, origin: &ServerName) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>>;
fn database_version(&self) -> Result<u64>;
fn bump_database_version(&self, new_version: u64) -> Result<()>;
fn backup(&self) -> Result<(), Box<dyn Error>> { unimplemented!() }
fn backup_list(&self) -> Result<String> { Ok(String::new()) }
}
+12 -5
View File
@@ -17,6 +17,7 @@ use argon2::Argon2;
use base64::{engine::general_purpose, Engine as _};
pub use data::Data;
use futures_util::FutureExt;
use hickory_resolver::TokioAsyncResolver;
use hyper::{
client::connect::dns::{GaiResolver, Name},
service::Service as HyperService,
@@ -34,7 +35,6 @@ use ruma::{
};
use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore};
use tracing::{error, info};
use trust_dns_resolver::TokioAsyncResolver;
use crate::{api::server_server::FedDest, services, Config, Error, Result};
@@ -327,6 +327,8 @@ impl Service<'_> {
pub fn turn_secret(&self) -> &String { &self.config.turn_secret }
pub fn auto_join_rooms(&self) -> &[OwnedRoomId] { &self.config.auto_join_rooms }
pub fn notification_push_path(&self) -> &String { &self.config.notification_push_path }
pub fn emergency_password(&self) -> &Option<String> { &self.config.emergency_password }
@@ -345,7 +347,7 @@ impl Service<'_> {
pub fn url_preview_check_root_domain(&self) -> bool { self.config.url_preview_check_root_domain }
pub fn forbidden_room_names(&self) -> &RegexSet { &self.config.forbidden_room_names }
pub fn forbidden_alias_names(&self) -> &RegexSet { &self.config.forbidden_alias_names }
pub fn forbidden_usernames(&self) -> &RegexSet { &self.config.forbidden_usernames }
@@ -359,6 +361,8 @@ impl Service<'_> {
pub fn presence_offline_timeout_s(&self) -> u64 { self.config.presence_offline_timeout_s }
pub fn allow_incoming_read_receipts(&self) -> bool { self.config.allow_incoming_read_receipts }
pub fn rocksdb_log_level(&self) -> &String { &self.config.rocksdb_log_level }
pub fn rocksdb_max_log_file_size(&self) -> usize { self.config.rocksdb_max_log_file_size }
@@ -495,7 +499,9 @@ fn reqwest_client_builder(config: &Config) -> Result<reqwest::ClientBuilder> {
});
let mut reqwest_client_builder = reqwest::Client::builder()
.pool_max_idle_per_host(0)
.hickory_dns(true)
.pool_max_idle_per_host(1)
.pool_idle_timeout(Duration::from_secs(50))
.connect_timeout(Duration::from_secs(60))
.timeout(Duration::from_secs(60 * 5))
.redirect(redirect_policy)
@@ -522,9 +528,10 @@ fn url_preview_reqwest_client_builder(config: &Config) -> Result<reqwest::Client
});
let mut reqwest_client_builder = reqwest::Client::builder()
.hickory_dns(true)
.pool_max_idle_per_host(0)
.connect_timeout(Duration::from_secs(60))
.timeout(Duration::from_secs(60 * 5))
.connect_timeout(Duration::from_secs(20))
.timeout(Duration::from_secs(30))
.redirect(redirect_policy)
.user_agent("Conduwuit".to_owned() + "/" + env!("CARGO_PKG_VERSION"));
+6 -1
View File
@@ -1,8 +1,11 @@
use ruma::OwnedUserId;
use crate::Result;
pub trait Data: Send + Sync {
fn create_file_metadata(
&self, mxc: String, width: u32, height: u32, content_disposition: Option<&str>, content_type: Option<&str>,
&self, sender_user: Option<&str>, mxc: String, width: u32, height: u32, content_disposition: Option<&str>,
content_type: Option<&str>,
) -> Result<Vec<u8>>;
fn delete_file_mxc(&self, mxc: String) -> Result<()>;
@@ -14,6 +17,8 @@ pub trait Data: Send + Sync {
fn search_mxc_metadata_prefix(&self, mxc: String) -> Result<Vec<Vec<u8>>>;
fn get_all_media_keys_by_user(&self, user_id: OwnedUserId) -> Result<Vec<Vec<u8>>>;
fn get_all_media_keys(&self) -> Result<Vec<Vec<u8>>>;
fn remove_url_preview(&self, url: &str) -> Result<()>;
+95 -13
View File
@@ -3,7 +3,7 @@ use std::{collections::HashMap, io::Cursor, sync::Arc, time::SystemTime};
pub(crate) use data::Data;
use image::imageops::FilterType;
use ruma::OwnedMxcUri;
use ruma::{OwnedMxcUri, OwnedUserId};
use serde::Serialize;
use tokio::{
fs::{self, File},
@@ -12,7 +12,7 @@ use tokio::{
};
use tracing::{debug, error};
use crate::{services, utils, Error, Result};
use crate::{services, utils::string_from_bytes, Error, Result};
#[derive(Debug)]
pub struct FileMeta {
@@ -45,10 +45,15 @@ pub struct Service {
impl Service {
/// Uploads a file.
pub async fn create(
&self, mxc: String, content_disposition: Option<&str>, content_type: Option<&str>, file: &[u8],
&self, sender_user: Option<OwnedUserId>, mxc: String, content_disposition: Option<&str>,
content_type: Option<&str>, file: &[u8],
) -> Result<()> {
// Width, Height = 0 if it's not a thumbnail
let key = self.db.create_file_metadata(mxc, 0, 0, content_disposition, content_type)?;
let key = if let Some(user) = sender_user {
self.db.create_file_metadata(Some(user.as_str()), mxc, 0, 0, content_disposition, content_type)?
} else {
self.db.create_file_metadata(None, mxc, 0, 0, content_disposition, content_type)?
};
let path;
@@ -105,12 +110,83 @@ impl Service {
}
}
/// Deletes all media in the database and from the media directory by a
/// specific user
pub async fn delete_from_user(&self, user_id: OwnedUserId) -> Result<u32> {
if let Ok(user_keys) = self.db.get_all_media_keys_by_user(user_id.clone()) {
if user_keys.is_empty() {
error!("User \"{user_id}\" has not uploaded any media.");
return Err(Error::bad_database("User has not uploaded any media."));
}
let mut mxc_deletion_count = 0;
for key in user_keys {
let mxc = String::from_utf8_lossy(&key);
let keys = self.db.search_mxc_metadata_prefix(mxc.to_string())?; // the MXC alone does not determine the file path, it is only the prefix
for key in keys {
let mxc = String::from_utf8_lossy(&key);
debug!("Deleting MXC {mxc} from database");
self.delete(mxc.to_string()).await?;
mxc_deletion_count += 1;
}
}
Ok(mxc_deletion_count)
} else {
error!("Failed to find any media keys for user \"{user_id}\" in our database");
Err(Error::bad_database(
"Failed to find any media keys for the provided user in our database",
))
}
}
pub async fn list_all_media_by_user(&self, user_id: OwnedUserId) -> Result<Vec<String>> {
if let Ok(keys) = self.db.get_all_media_keys_by_user(user_id.clone()) {
if keys.is_empty() {
error!("User \"{user_id}\" has not uploaded any media.");
return Err(Error::bad_database("User has not uploaded any media."));
}
let mut mxc_list: Vec<String> = vec![];
for key in keys {
let mxc_string = string_from_bytes(&key).map_err(|e| {
error!("Failed to convert MXC key to string in database for user \"{user_id}\": {e}");
Error::bad_database("Failed to convert MXC key to string in database")
})?;
// TODO: add the file name
mxc_list.push(mxc_string);
}
Ok(mxc_list)
} else {
error!("Failed to find any media keys for user \"{user_id}\" in our database");
Err(Error::bad_database(
"Failed to find any media keys for the provided user in our database",
))
}
}
/// Uploads or replaces a file thumbnail.
#[allow(clippy::too_many_arguments)]
pub async fn upload_thumbnail(
&self, mxc: String, content_disposition: Option<&str>, content_type: Option<&str>, width: u32, height: u32,
file: &[u8],
&self, sender_user: Option<OwnedUserId>, mxc: String, content_disposition: Option<&str>,
content_type: Option<&str>, width: u32, height: u32, file: &[u8],
) -> Result<()> {
let key = self.db.create_file_metadata(mxc, width, height, content_disposition, content_type)?;
let key = if let Some(user) = sender_user {
self.db.create_file_metadata(Some(user.as_str()), mxc, width, height, content_disposition, content_type)?
} else {
self.db.create_file_metadata(None, mxc, width, height, content_disposition, content_type)?
};
let path;
#[allow(clippy::unnecessary_operation)] // error[E0658]: attributes on expressions are experimental
@@ -183,13 +259,13 @@ impl Service {
debug!("Full MXC key from database: {:?}", key);
// we need to get the MXC URL from the first part of the key (the first 0xff /
// 255 push) this code does look kinda crazy but blame conduit for using magic
// keys
// 255 push). this is all necessary because of conduit using magic keys for
// media
let mut parts = key.split(|&b| b == 0xFF);
let mxc = parts
.next()
.map(|bytes| {
utils::string_from_bytes(bytes).map_err(|e| {
string_from_bytes(bytes).map_err(|e| {
error!("Failed to parse MXC unicode bytes from our database: {}", e);
Error::bad_database("Failed to parse MXC unicode bytes from our database")
})
@@ -403,6 +479,7 @@ impl Service {
// Save thumbnail in database so we don't have to generate it again next time
let thumbnail_key = self.db.create_file_metadata(
None,
mxc,
width,
height,
@@ -470,7 +547,8 @@ mod tests {
impl Data for MockedKVDatabase {
fn create_file_metadata(
&self, mxc: String, width: u32, height: u32, content_disposition: Option<&str>, content_type: Option<&str>,
&self, _sender_user: Option<&str>, mxc: String, width: u32, height: u32, content_disposition: Option<&str>,
content_type: Option<&str>,
) -> Result<Vec<u8>> {
// copied from src/database/key_value/media.rs
let mut key = mxc.as_bytes().to_vec();
@@ -489,6 +567,8 @@ mod tests {
fn search_mxc_metadata_prefix(&self, _mxc: String) -> Result<Vec<Vec<u8>>> { todo!() }
fn get_all_media_keys_by_user(&self, _user_id: OwnedUserId) -> Result<Vec<Vec<u8>>> { todo!() }
fn get_all_media_keys(&self) -> Result<Vec<Vec<u8>>> { todo!() }
fn search_file_metadata(
@@ -520,8 +600,10 @@ mod tests {
let content_disposition = "attachment; filename=\"this is a very long file name with spaces and special \
characters like äöüß and even emoji like 🦀.png\"";
let content_type = "image/png";
let key =
media.db.create_file_metadata(mxc, width, height, Some(content_disposition), Some(content_type)).unwrap();
let key = media
.db
.create_file_metadata(None, mxc, width, height, Some(content_disposition), Some(content_type))
.unwrap();
let mut r = PathBuf::new();
r.push("/tmp");
r.push("media");
+10 -4
View File
@@ -134,7 +134,7 @@ impl Services<'_> {
db,
},
spaces: rooms::spaces::Service {
roomid_spacechunk_cache: Mutex::new(LruCache::new(
roomid_spacehierarchy_cache: Mutex::new(LruCache::new(
(100.0 * config.conduit_cache_capacity_modifier) as usize,
)),
},
@@ -175,7 +175,7 @@ impl Services<'_> {
let user_visibility_cache = self.rooms.state_accessor.user_visibility_cache.lock().unwrap().len();
let stateinfo_cache = self.rooms.state_compressor.stateinfo_cache.lock().unwrap().len();
let lasttimelinecount_cache = self.rooms.timeline.lasttimelinecount_cache.lock().await.len();
let roomid_spacechunk_cache = self.rooms.spaces.roomid_spacechunk_cache.lock().await.len();
let roomid_spacehierarchy_cache = self.rooms.spaces.roomid_spacehierarchy_cache.lock().await.len();
format!(
"\
@@ -184,7 +184,7 @@ server_visibility_cache: {server_visibility_cache}
user_visibility_cache: {user_visibility_cache}
stateinfo_cache: {stateinfo_cache}
lasttimelinecount_cache: {lasttimelinecount_cache}
roomid_spacechunk_cache: {roomid_spacechunk_cache}"
roomid_spacehierarchy_cache: {roomid_spacehierarchy_cache}"
)
}
@@ -205,7 +205,13 @@ roomid_spacechunk_cache: {roomid_spacechunk_cache}"
self.rooms.timeline.lasttimelinecount_cache.lock().await.clear();
}
if amount > 5 {
self.rooms.spaces.roomid_spacechunk_cache.lock().await.clear();
self.rooms.spaces.roomid_spacehierarchy_cache.lock().await.clear();
}
if amount > 6 {
self.globals.tls_name_override.write().unwrap().clear();
}
if amount > 7 {
self.globals.dns_resolver().clear_cache();
}
}
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -74,7 +74,7 @@ impl Service {
.await?;
},
TimelineEventType::SpaceChild => {
services().rooms.spaces.roomid_spacechunk_cache.lock().await.remove(&pdu.room_id);
services().rooms.spaces.roomid_spacehierarchy_cache.lock().await.remove(&pdu.room_id);
},
_ => continue,
}
+3 -4
View File
@@ -230,10 +230,9 @@ impl Service {
pub fn get_name(&self, room_id: &RoomId) -> Result<Option<String>> {
services().rooms.state_accessor.room_state_get(room_id, &StateEventType::RoomName, "")?.map_or(Ok(None), |s| {
serde_json::from_str(s.content.get()).map(|c: RoomNameEventContent| Some(c.name)).map_err(|e| {
error!("Invalid room name event in database for room {}. {}", room_id, e);
Error::bad_database("Invalid room name event in database.")
})
Ok(serde_json::from_str(s.content.get())
.map(|c: RoomNameEventContent| Some(c.name))
.unwrap_or_else(|_| None))
})
}
+78 -82
View File
@@ -26,7 +26,7 @@ use ruma::{
state_res,
state_res::{Event, RoomVersion},
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
RoomId, RoomVersionId, ServerName, UserId,
};
use serde::Deserialize;
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
@@ -418,7 +418,7 @@ impl Service {
},
TimelineEventType::SpaceChild => {
if let Some(_state_key) = &pdu.state_key {
services().rooms.spaces.roomid_spacechunk_cache.lock().await.remove(&pdu.room_id);
services().rooms.spaces.roomid_spacehierarchy_cache.lock().await.remove(&pdu.room_id);
}
},
TimelineEventType::RoomMember => {
@@ -461,10 +461,6 @@ impl Service {
if let Some(body) = content.body {
services().rooms.search.index_pdu(shortroomid, &pdu_id, &body)?;
let admin_room = services().rooms.alias.resolve_local_alias(
<&RoomAliasId>::try_from(format!("#admins:{}", services().globals.server_name()).as_str())
.expect("#admins:server_name is a valid room alias"),
)?;
let server_user = format!("@conduit:{}", services().globals.server_name());
let to_conduit = body.starts_with(&format!("{server_user}: "))
@@ -477,8 +473,10 @@ impl Service {
// the administrator can execute commands as conduit
let from_conduit = pdu.sender == server_user && services().globals.emergency_password().is_none();
if to_conduit && !from_conduit && admin_room.as_ref() == Some(&pdu.room_id) {
services().admin.process_message(body, pdu.event_id.clone());
if let Some(admin_room) = services().admin.get_admin_room()? {
if to_conduit && !from_conduit && admin_room == pdu.room_id {
services().admin.process_message(body, pdu.event_id.clone());
}
}
}
},
@@ -720,84 +718,82 @@ impl Service {
) -> Result<Arc<EventId>> {
let (pdu, pdu_json) = self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?;
let admin_room = services().rooms.alias.resolve_local_alias(
<&RoomAliasId>::try_from(format!("#admins:{}", services().globals.server_name()).as_str())
.expect("#admins:server_name is a valid room alias"),
)?;
if admin_room.filter(|v| v == room_id).is_some() {
match pdu.event_type() {
TimelineEventType::RoomEncryption => {
warn!("Encryption is not allowed in the admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Encryption is not allowed in the admins room.",
));
},
TimelineEventType::RoomMember => {
#[derive(Deserialize)]
struct ExtractMembership {
membership: MembershipState,
}
let target = pdu.state_key().filter(|v| v.starts_with('@')).unwrap_or(sender.as_str());
let server_name = services().globals.server_name();
let server_user = format!("@conduit:{server_name}");
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
if content.membership == MembershipState::Leave {
if target == server_user {
warn!("Conduit user cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot leave from admins room.",
));
if let Some(admin_room) = services().admin.get_admin_room()? {
if admin_room == room_id {
match pdu.event_type() {
TimelineEventType::RoomEncryption => {
warn!("Encryption is not allowed in the admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Encryption is not allowed in the admins room.",
));
},
TimelineEventType::RoomMember => {
#[derive(Deserialize)]
struct ExtractMembership {
membership: MembershipState,
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(std::result::Result::ok)
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot leave from admins room.",
));
}
}
let target = pdu.state_key().filter(|v| v.starts_with('@')).unwrap_or(sender.as_str());
let server_name = services().globals.server_name();
let server_user = format!("@conduit:{server_name}");
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
if target == server_user {
warn!("Conduit user cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot be banned in admins room.",
));
if content.membership == MembershipState::Leave {
if target == server_user {
warn!("Conduit user cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot leave from admins room.",
));
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(std::result::Result::ok)
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot leave from admins room.",
));
}
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(std::result::Result::ok)
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot be banned in admins room.",
));
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
if target == server_user {
warn!("Conduit user cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot be banned in admins room.",
));
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(std::result::Result::ok)
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot be banned in admins room.",
));
}
}
}
},
_ => {},
},
_ => {},
}
}
}
@@ -1007,12 +1003,12 @@ impl Service {
return Ok(());
},
Err(e) => {
warn!("{backfill_server} could not provide backfill: {e}");
warn!("{backfill_server} failed to provide backfill: {e}");
},
}
}
info!("No servers could backfill");
info!("No servers could backfill, but backfill was needed");
Ok(())
}
+48 -7
View File
@@ -25,7 +25,7 @@ use ruma::{
},
device_id,
events::{push_rules::PushRulesEvent, receipt::ReceiptType, AnySyncEphemeralRoomEvent, GlobalAccountDataEventType},
push, uint, MilliSecondsSinceUnixEpoch, OwnedServerName, OwnedUserId, ServerName, UInt, UserId,
push, uint, MilliSecondsSinceUnixEpoch, OwnedServerName, OwnedUserId, RoomId, ServerName, UInt, UserId,
};
use tokio::{
select,
@@ -80,6 +80,7 @@ impl OutgoingKind {
pub enum SendingEventType {
Pdu(Vec<u8>), // pduid
Edu(Vec<u8>), // pdu json
Flush, // none
}
pub struct Service {
@@ -237,9 +238,11 @@ impl Service {
events.push(e);
}
} else {
self.db.mark_as_active(&new_events)?;
for (e, _) in new_events {
events.push(e);
if !new_events.is_empty() {
self.db.mark_as_active(&new_events)?;
for (e, _) in new_events {
events.push(e);
}
}
if let OutgoingKind::Normal(server_name) = outgoing_kind {
@@ -360,11 +363,11 @@ impl Service {
for user_id in device_list_changes {
// Empty prev id forces synapse to resync: https://github.com/matrix-org/synapse/blob/98aec1cc9da2bd6b8e34ffb282c85abf9b8b42ca/synapse/handlers/device.py#L767
// Because synapse resyncs, we can just insert dummy data
// Because synapse resyncs, we can just insert placeholder data
let edu = Edu::DeviceListUpdate(DeviceListUpdateContent {
user_id,
device_id: device_id!("dummy").to_owned(),
device_display_name: Some("Dummy".to_owned()),
device_id: device_id!("placeholder").to_owned(),
device_display_name: Some("Placeholder".to_owned()),
stream_id: uint!(1),
prev_id: Vec::new(),
deleted: None,
@@ -421,6 +424,29 @@ impl Service {
Ok(())
}
#[tracing::instrument(skip(self, room_id))]
pub fn flush_room(&self, room_id: &RoomId) -> Result<()> {
let servers: HashSet<OwnedServerName> =
services().rooms.state_cache.room_servers(room_id).filter_map(std::result::Result::ok).collect();
self.flush_servers(servers.into_iter())
}
#[tracing::instrument(skip(self, servers))]
pub fn flush_servers<I: Iterator<Item = OwnedServerName>>(&self, servers: I) -> Result<()> {
let requests = servers
.into_iter()
.filter(|server| server != services().globals.server_name())
.map(OutgoingKind::Normal)
.collect::<Vec<_>>();
for outgoing_kind in requests.into_iter() {
self.sender.send((outgoing_kind, SendingEventType::Flush, Vec::<u8>::new())).unwrap();
}
Ok(())
}
/// Cleanup event data
/// Used for instance after we remove an appservice registration
#[tracing::instrument(skip(self))]
@@ -461,6 +487,9 @@ impl Service {
SendingEventType::Edu(_) => {
// Appservices don't need EDUs (?)
},
SendingEventType::Flush => {
// flush only; no new content
},
}
}
@@ -480,6 +509,7 @@ impl Service {
.iter()
.map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
SendingEventType::Flush => &[],
})
.collect::<Vec<_>>(),
)))
@@ -521,6 +551,9 @@ impl Service {
SendingEventType::Edu(_) => {
// Push gateways don't need EDUs (?)
},
SendingEventType::Flush => {
// flush only; no new content
},
}
}
@@ -601,6 +634,9 @@ impl Service {
edu_jsons.push(raw);
}
},
SendingEventType::Flush => {
// flush only; no new content
},
}
}
@@ -618,6 +654,7 @@ impl Service {
.iter()
.map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
SendingEventType::Flush => &[],
})
.collect::<Vec<_>>(),
)))
@@ -647,6 +684,10 @@ impl Service {
where
T: OutgoingRequest + Debug,
{
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
if destination.is_ip_literal() || IPAddress::is_valid(destination.host()) {
info!(
"Destination {} is an IP literal, checking against IP range denylist.",