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
+7
View File
@@ -6,16 +6,23 @@ on:
branches: branches:
- main - main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env: env:
# Required to make some things output color # Required to make some things output color
TERM: ansi TERM: ansi
# Publishing to my nix binary cache
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }} ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
# Just in case incremental is still being set to true, speeds up CI
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
# Custom nix binary cache if fork is being used
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }} ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }} ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
permissions: permissions:
packages: write packages: write
contents: read
jobs: jobs:
ci: ci:
+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 cache
/.gitlab-ci.d /.gitlab-ci.d
# mdbook output
public/
# macOS # macOS
.DS_Store .DS_Store
+19 -2
View File
@@ -105,6 +105,10 @@ artifacts:
- ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl - ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl
- cp result oci-image-arm64v8.tar.gz - 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: artifacts:
paths: paths:
- x86_64-unknown-linux-musl - x86_64-unknown-linux-musl
@@ -112,6 +116,7 @@ artifacts:
- x86_64-unknown-linux-musl.deb - x86_64-unknown-linux-musl.deb
- oci-image-amd64.tar.gz - oci-image-amd64.tar.gz
- oci-image-arm64v8.tar.gz - oci-image-arm64v8.tar.gz
- public
rules: rules:
# CI required for all MRs # CI required for all MRs
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
@@ -124,9 +129,9 @@ artifacts:
.push-oci-image: .push-oci-image:
stage: publish stage: publish
image: docker:25.0.4 image: docker:25.0.5
services: services:
- docker:25.0.4-dind - docker:25.0.5-dind
variables: variables:
IMAGE_SUFFIX_AMD64: amd64 IMAGE_SUFFIX_AMD64: amd64
IMAGE_SUFFIX_ARM64V8: arm64v8 IMAGE_SUFFIX_ARM64V8: arm64v8
@@ -164,3 +169,15 @@ oci-image:push-gitlab:
IMAGE_NAME: $CI_REGISTRY_IMAGE/conduwuit IMAGE_NAME: $CI_REGISTRY_IMAGE/conduwuit
before_script: before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - 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]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.77" version = "0.1.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -245,7 +245,7 @@ version = "0.69.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.5.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.12.1",
@@ -256,7 +256,7 @@ dependencies = [
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -267,9 +267,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.4.2" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]] [[package]]
name = "blake2" name = "blake2"
@@ -355,6 +355,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 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]] [[package]]
name = "clang-sys" name = "clang-sys"
version = "1.7.0" version = "1.7.0"
@@ -395,7 +404,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -412,7 +421,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "conduit" name = "conduit"
version = "0.7.0-alpha+conduwuit-0.1.7" version = "0.7.0-alpha+conduwuit-0.1.8"
dependencies = [ dependencies = [
"argon2", "argon2",
"async-trait", "async-trait",
@@ -421,11 +430,13 @@ dependencies = [
"axum-server-dual-protocol", "axum-server-dual-protocol",
"base64 0.22.0", "base64 0.22.0",
"bytes", "bytes",
"chrono",
"clap", "clap",
"cyborgtime", "cyborgtime",
"either", "either",
"figment", "figment",
"futures-util", "futures-util",
"hickory-resolver",
"hmac", "hmac",
"http", "http",
"hyper", "hyper",
@@ -467,7 +478,6 @@ dependencies = [
"tracing-flame", "tracing-flame",
"tracing-opentelemetry", "tracing-opentelemetry",
"tracing-subscriber", "tracing-subscriber",
"trust-dns-resolver",
"webpage", "webpage",
] ]
@@ -567,7 +577,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -664,7 +674,7 @@ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -789,7 +799,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -950,6 +960,51 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 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]] [[package]]
name = "hmac" name = "hmac"
version = "0.12.1" version = "0.12.1"
@@ -1112,9 +1167,9 @@ dependencies = [
[[package]] [[package]]
name = "image-webp" name = "image-webp"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba6107a25f04af48ceeb4093eebc9b405ee5a1813a0bab5ecf1805d3eabb3337" checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"thiserror", "thiserror",
@@ -1301,7 +1356,7 @@ dependencies = [
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.28.0" 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 = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",
@@ -1470,7 +1525,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.5.0",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -1755,7 +1810,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -1829,7 +1884,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -1924,7 +1979,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
"version_check", "version_check",
"yansi", "yansi",
] ]
@@ -2029,9 +2084,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.26" version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
@@ -2039,6 +2094,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2",
"hickory-resolver",
"http", "http",
"http-body", "http-body",
"hyper", "hyper",
@@ -2097,7 +2153,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.9.4" 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 = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@@ -2116,7 +2172,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.9.0" 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 = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -2128,7 +2184,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.17.4" 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 = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@@ -2147,7 +2203,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.12.1" 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 = [ dependencies = [
"as_variant", "as_variant",
"base64 0.21.7", "base64 0.21.7",
@@ -2166,6 +2222,7 @@ dependencies = [
"serde_html_form", "serde_html_form",
"serde_json", "serde_json",
"thiserror", "thiserror",
"time",
"tracing", "tracing",
"url", "url",
"uuid", "uuid",
@@ -2175,7 +2232,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.27.11" 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 = [ dependencies = [
"as_variant", "as_variant",
"indexmap", "indexmap",
@@ -2197,7 +2254,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.8.0" 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 = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -2209,7 +2266,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.9.3" 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 = [ dependencies = [
"js_int", "js_int",
"thiserror", "thiserror",
@@ -2218,7 +2275,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identity-service-api" name = "ruma-identity-service-api"
version = "0.8.0" 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 = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -2228,7 +2285,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.12.0" 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 = [ dependencies = [
"once_cell", "once_cell",
"proc-macro-crate", "proc-macro-crate",
@@ -2236,14 +2293,14 @@ dependencies = [
"quote", "quote",
"ruma-identifiers-validation", "ruma-identifiers-validation",
"serde", "serde",
"syn 2.0.52", "syn 2.0.53",
"toml", "toml",
] ]
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.8.0" 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 = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -2255,7 +2312,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.14.0" 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 = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"ed25519-dalek", "ed25519-dalek",
@@ -2271,7 +2328,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-state-res" name = "ruma-state-res"
version = "0.10.0" 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 = [ dependencies = [
"itertools 0.11.0", "itertools 0.11.0",
"js_int", "js_int",
@@ -2286,9 +2343,9 @@ dependencies = [
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.31.0" 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 = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.5.0",
"fallible-iterator", "fallible-iterator",
"fallible-streaming-iterator", "fallible-streaming-iterator",
"hashlink", "hashlink",
@@ -2298,8 +2355,8 @@ dependencies = [
[[package]] [[package]]
name = "rust-librocksdb-sys" name = "rust-librocksdb-sys"
version = "0.18.1+8.11.3" version = "0.19.0+9.0.0"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=3e4a0f632a8c0c2839c7d183725c53895110d907#3e4a0f632a8c0c2839c7d183725c53895110d907" source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=4b8491b9db45066115435881184902bbb23d3811#4b8491b9db45066115435881184902bbb23d3811"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"bzip2-sys", "bzip2-sys",
@@ -2314,8 +2371,8 @@ dependencies = [
[[package]] [[package]]
name = "rust-rocksdb" name = "rust-rocksdb"
version = "0.22.7" version = "0.22.8"
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=3e4a0f632a8c0c2839c7d183725c53895110d907#3e4a0f632a8c0c2839c7d183725c53895110d907" source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=4b8491b9db45066115435881184902bbb23d3811#4b8491b9db45066115435881184902bbb23d3811"
dependencies = [ dependencies = [
"libc", "libc",
"rust-librocksdb-sys", "rust-librocksdb-sys",
@@ -2474,7 +2531,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -2544,9 +2601,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.9.32" version = "0.9.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"itoa", "itoa",
@@ -2740,9 +2797,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.52" version = "2.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2804,7 +2861,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -2942,7 +2999,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -3048,7 +3105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [ dependencies = [
"async-compression", "async-compression",
"bitflags 2.4.2", "bitflags 2.5.0",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -3096,7 +3153,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
@@ -3167,52 +3224,6 @@ dependencies = [
"tracing-log", "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]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.5" version = "0.2.5"
@@ -3272,9 +3283,9 @@ dependencies = [
[[package]] [[package]]
name = "unsafe-libyaml" name = "unsafe-libyaml"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
@@ -3307,9 +3318,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [ dependencies = [
"getrandom", "getrandom",
] ]
@@ -3368,7 +3379,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -3402,7 +3413,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -3670,7 +3681,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.53",
] ]
[[package]] [[package]]
+13 -6
View File
@@ -6,7 +6,7 @@ authors = ["strawberry <strawberry@puppygock.gay>", "timokoesters <timo@koesters
homepage = "https://puppygock.gay/conduwuit" homepage = "https://puppygock.gay/conduwuit"
repository = "https://gitlab.com/girlbossceo/conduwuit" repository = "https://gitlab.com/girlbossceo/conduwuit"
readme = "README.md" readme = "README.md"
version = "0.7.0-alpha+conduwuit-0.1.7" version = "0.7.0-alpha+conduwuit-0.1.8"
edition = "2021" edition = "2021"
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
@@ -27,7 +27,7 @@ base64 = "0.22.0"
ring = "0.17.8" ring = "0.17.8"
# Used when querying the SRV record of other servers # 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 # Used to find matching events for appservices
regex = "1.10.3" regex = "1.10.3"
@@ -48,7 +48,7 @@ serde_html_form = "0.2.5"
hmac = "0.12.1" hmac = "0.12.1"
sha-1 = "0.10.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 # used for checking if an IP is in specific subnets / CIDR ranges easier
ipaddress = "0.1.3" ipaddress = "0.1.3"
@@ -67,6 +67,12 @@ cyborgtime = "2.1.1"
bytes = "1.5.0" bytes = "1.5.0"
http = "0.2.12" http = "0.2.12"
# standard date and time tools
[dependencies.chrono]
version = "0.4.35"
features = ["alloc"]
default-features = false
# Web framework # Web framework
[dependencies.axum] [dependencies.axum]
version = "0.6.20" version = "0.6.20"
@@ -101,11 +107,12 @@ features = [
] ]
[dependencies.reqwest] [dependencies.reqwest]
version = "0.11.26" version = "0.11.27"
default-features = false default-features = false
features = [ features = [
"rustls-tls-native-roots", "rustls-tls-native-roots",
"socks", "socks",
"trust-dns",
] ]
# all the serde stuff # all the serde stuff
@@ -115,7 +122,7 @@ version = "1.0.197"
features = ["rc"] features = ["rc"]
# Used for appservice registration files # Used for appservice registration files
[dependencies.serde_yaml] [dependencies.serde_yaml]
version = "0.9.32" version = "0.9.33"
# Used for ruma wrapper # Used for ruma wrapper
[dependencies.serde_json] [dependencies.serde_json]
version = "1.0.114" version = "1.0.114"
@@ -254,7 +261,7 @@ features = [
[dependencies.rust-rocksdb] [dependencies.rust-rocksdb]
git = "https://github.com/zaidoon1/rust-rocksdb" git = "https://github.com/zaidoon1/rust-rocksdb"
#branch = "master" #branch = "master"
rev = "3e4a0f632a8c0c2839c7d183725c53895110d907" rev = "4b8491b9db45066115435881184902bbb23d3811"
optional = true optional = true
default-features = true default-features = true
features = [ features = [
+21 -23
View File
@@ -1,9 +1,17 @@
# conduwuit # 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) [![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? #### What is Matrix?
[Matrix](https://matrix.org) is an open network for secure and decentralized [Matrix](https://matrix.org) is an open network for secure and decentralized
communication. Users from every Matrix homeserver can chat with users from all communication. Users from every Matrix homeserver can chat with users from all
other Matrix servers. You can even use bridges (also called Matrix Appservices) 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: There are still a few nice to have features missing that some users may notice:
- Outgoing read receipts and typing indicators (receiving works) - Outgoing typing indicators (receiving works)
#### What's different about your fork than upstream Conduit?
See [DIFFERENCES.md](DIFFERENCES.md)
#### Why does this fork exist? Why don't you contribute back upstream? #### 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 - Have a **stable** testing ground for some MRs or new features and bug fixes
And various other reasons that may not be listed here. And various other reasons that may not be listed here.
<!-- ANCHOR_END: body -->
#### How can I deploy my own? <!-- ANCHOR: footer -->
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).
#### How can I contribute? #### How can I contribute?
1. Look for an issue you would like to work on and make sure it's not assigned 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 #### Donate
Liberapay: <https://liberapay.com/girlbossceo>\ - Liberapay: <https://liberapay.com/girlbossceo>
Ko-fi: <https://ko-fi.com/puppygock>\ - Ko-fi: <https://ko-fi.com/puppygock>
GitHub Sponsors: <https://github.com/sponsors/girlbossceo> - GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
#### Logo #### Logo
@@ -86,8 +83,9 @@ Both.
#### Mirrors of conduwuit #### Mirrors of conduwuit
GitHub: <https://github.com/girlbossceo/conduwuit>\ - GitHub: <https://github.com/girlbossceo/conduwuit>
GitLab: <https://gitlab.com/girlbossceo/conduwuit>\ - GitLab: <https://gitlab.com/girlbossceo/conduwuit>
git.gay: <https://git.gay/june/conduwuit>\ - git.gay: <https://git.gay/june/conduwuit>
Codeberg: <https://codeberg.org/girlbossceo/conduwuit>\ - Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
sourcehut: <https://git.sr.ht/~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
+26 -5
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), # 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. # and if non-admin users can receive remote room invites. admins are always allowed to send and receive all room invites.
# defaults to false # 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 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 # 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. # Defaults to true as this is the fastest option for federation.
#query_trusted_key_servers_first = true #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 ### 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. # 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. # May be useful if you have significant memory to spare to increase performance.
# Defaults to 300.0 # Defaults to 256.0
#db_cache_capacity_mb = 300.0 #db_cache_capacity_mb = 256.0
# Interval in seconds when conduwuit will run database cleanup operations. # 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. # 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. # 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 #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. # 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. # 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. # 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. # Config option to control how many seconds before presence updates that you are offline. Defaults to 30 minutes.
#presence_offline_timeout_s = 1800 #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]: # Other options not in [global]:
+1 -1
View File
@@ -5,7 +5,7 @@ Installation
------------ ------------
Information about downloading, building and deploying the Debian package, see 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 All following sections until "Setting up the Reverse Proxy" be ignored because
this is handled automatically by the packaging. 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). 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 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. 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 rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
for your server. 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 ### Build
@@ -131,7 +131,7 @@ So...step by step:
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or 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. [`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. 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`. 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. 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. ### 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.** 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. 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 ## Setting the correct file permissions
@@ -282,7 +284,7 @@ $ sudo systemctl enable conduit
## How do I know it works? ## 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. 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 ## 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 ## 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 - 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) - Update rusqlite/sqlite (not that you should be using it)
- Disable update check by default as it's not useful for conduwuit - 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" group = "versions"
script = "cargo deb --version" script = "cargo deb --version"
[[task]]
name = "lychee"
group = "versions"
script = "lychee --version"
[[task]] [[task]]
name = "cargo-audit" name = "cargo-audit"
group = "security" group = "security"
@@ -77,6 +82,11 @@ name = "cargo-clippy"
group = "lints" group = "lints"
script = "cargo clippy --workspace --all-targets --all-features --color=always -- -D warnings" script = "cargo clippy --workspace --all-targets --all-features --color=always -- -D warnings"
[[task]]
name = "lychee"
group = "lints"
script = "lychee --offline docs"
[[task]] [[task]]
name = "cargo" name = "cargo"
group = "tests" group = "tests"
+32
View File
@@ -208,6 +208,35 @@
packages = { packages = {
default = package pkgsHost; default = package pkgsHost;
oci-image = mkOciImage pkgsHost self.packages.${system}.default; 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 builtins.listToAttrs
@@ -276,6 +305,9 @@
# Needed for our script for Complement # Needed for our script for Complement
jq jq
# Needed for finding broken markdown links
lychee
]); ]);
}; };
}); });
+41 -5
View File
@@ -12,10 +12,13 @@ use ruma::{
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType}, events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
push, UserId, push, UserId,
}; };
use tracing::{info, warn}; use tracing::{error, info, warn};
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH}; 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; 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 // If this is the first real user, grant them admin privileges except for guest
// users Note: the server user, @conduit:servername, is generated first // users Note: the server user, @conduit:servername, is generated first
if services().users.count()? == 2 && !is_guest { if !is_guest {
services().admin.make_user_admin(&user_id, displayname).await?; 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 { 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.")); 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.")); return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias is forbidden."));
} }
+20 -2
View File
@@ -1,12 +1,13 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use ruma::api::client::discovery::get_capabilities::{ use ruma::api::client::discovery::get_capabilities::{
self, Capabilities, RoomVersionStability, RoomVersionsCapability, self, Capabilities, ChangePasswordCapability, RoomVersionStability, RoomVersionsCapability, SetAvatarUrlCapability,
SetDisplayNameCapability, ThirdPartyIdChangesCapability,
}; };
use crate::{services, Result, Ruma}; 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 /// Get information on the supported feature set and other relevent capabilities
/// of this server. /// of this server.
@@ -27,6 +28,23 @@ pub async fn get_capabilities_route(
available, 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 { Ok(get_capabilities::v3::Response {
capabilities, capabilities,
}) })
+2 -2
View File
@@ -313,7 +313,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
( (
server, server,
tokio::time::timeout( tokio::time::timeout(
Duration::from_secs(50), Duration::from_secs(90),
services().sending.send_federation_request( services().sending.send_federation_request(
server, server,
federation::keys::get_keys::v1::Request { federation::keys::get_keys::v1::Request {
@@ -323,7 +323,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
) )
.await .await
.map_err(|e| { .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") 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 /// - Some metadata will be saved in the database
/// - Media will be saved in the media/ directory /// - 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> { 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!( let mxc = format!(
"mxc://{}/{}", "mxc://{}/{}",
services().globals.server_name(), services().globals.server_name(),
@@ -147,6 +149,7 @@ pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Re
services() services()
.media .media
.create( .create(
Some(sender_user.clone()),
mxc.clone(), mxc.clone(),
body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(), body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(),
body.content_type.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( pub async fn create_content_v1_route(
body: Ruma<create_content::v3::Request>, body: Ruma<create_content::v3::Request>,
) -> Result<RumaResponse<create_content::v3::Response>> { ) -> Result<RumaResponse<create_content::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = format!( let mxc = format!(
"mxc://{}/{}", "mxc://{}/{}",
services().globals.server_name(), services().globals.server_name(),
@@ -184,6 +189,7 @@ pub async fn create_content_v1_route(
services() services()
.media .media
.create( .create(
Some(sender_user.clone()),
mxc.clone(), mxc.clone(),
body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(), body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(),
body.content_type.as_deref(), body.content_type.as_deref(),
@@ -231,6 +237,7 @@ pub async fn get_remote_content(
services() services()
.media .media
.create( .create(
None,
mxc.to_owned(), mxc.to_owned(),
content_response.content_disposition.as_deref(), content_response.content_disposition.as_deref(),
content_response.content_type.as_deref(), content_response.content_type.as_deref(),
@@ -484,6 +491,7 @@ pub async fn get_content_thumbnail_route(
services() services()
.media .media
.upload_thumbnail( .upload_thumbnail(
None,
mxc, mxc,
None, None,
get_thumbnail_response.content_type.as_deref(), get_thumbnail_response.content_type.as_deref(),
@@ -566,6 +574,7 @@ pub async fn get_content_thumbnail_v1_route(
services() services()
.media .media
.upload_thumbnail( .upload_thumbnail(
None,
mxc, mxc,
None, None,
get_thumbnail_response.content_type.as_deref(), 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) 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() { let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
Err(_) => (None, None), 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], sender_user: Option<&UserId>, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>, _third_party_signed: Option<&ThirdPartySigned>,
) -> Result<join_room_by_id::v3::Response> { ) -> 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(), room_id: body.room_id.clone(),
}, },
)?; )?;
services().sending.flush_room(&body.room_id)?;
} }
Ok(set_read_marker::v3::Response {}) 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(), room_id: body.room_id.clone(),
}, },
)?; )?;
services().sending.flush_room(&body.room_id)?;
}, },
create_receipt::v3::ReceiptType::ReadPrivate => { create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services() 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 // 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.")); 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 // 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.")); 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`` /// # `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> { 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 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()) .into())
} }
/// # `GET /_matrix/client/r0/rooms/{roomid}/state` /// # `GET /_matrix/client/v3/rooms/{roomid}/state`
/// ///
/// Get all state events for a room. /// 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) ruma::JsOption::Some(heroes_avatar)
} else { } else {
match services().rooms.state_accessor.get_avatar(room_id)? { match services().rooms.state_accessor.get_avatar(room_id)? {
ruma::JsOption::Some(avatar) => { ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
avatar.url.map_or(ruma::JsOption::Undefined, ruma::JsOption::Some)
},
ruma::JsOption::Null => ruma::JsOption::Null, ruma::JsOption::Null => ruma::JsOption::Null,
ruma::JsOption::Undefined => ruma::JsOption::Undefined, 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([ unstable_features: BTreeMap::from_iter([
("org.matrix.e2e_cross_signing".to_owned(), true), ("org.matrix.e2e_cross_signing".to_owned(), true),
//("org.matrix.msc2285.stable".to_owned(), true),
("org.matrix.msc2836".to_owned(), true), ("org.matrix.msc2836".to_owned(), true),
("org.matrix.msc3827".to_owned(), true), ("org.matrix.msc3827".to_owned(), true),
("org.matrix.msc2946".to_owned(), true), ("org.matrix.msc2946".to_owned(), true),
+28 -2
View File
@@ -84,8 +84,7 @@ where
appservice_registration appservice_registration
{ {
match metadata.authentication { match metadata.authentication {
// TODO: verify if just or'ing `AuthScheme::AppserviceToken` is correct here AuthScheme::AccessToken => {
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken => {
let user_id = query_params.user_id.map_or_else( let user_id = query_params.user_id.map_or_else(
|| { || {
UserId::parse_with_server_name( UserId::parse_with_server_name(
@@ -97,6 +96,8 @@ where
|s| UserId::parse(s).unwrap(), |s| UserId::parse(s).unwrap(),
); );
debug!("User ID: {:?}", user_id);
if !services().users.exists(&user_id)? { if !services().users.exists(&user_id)? {
return Err(Error::BadRequest(ErrorKind::Forbidden, "User does not exist.")); 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 // TODO: Check if appservice is allowed to be that user
(Some(user_id), None, None, true) (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::ServerSignatures => (None, None, None, true),
AuthScheme::None => (None, None, None, true), AuthScheme::None => (None, None, None, true),
} }
@@ -153,6 +175,10 @@ where
// treat non-appservice registrations as None authentication // treat non-appservice registrations as None authentication
AuthScheme::AppserviceToken => (None, None, None, false), AuthScheme::AppserviceToken => (None, None, None, false),
AuthScheme::ServerSignatures => { AuthScheme::ServerSignatures => {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let TypedHeader(Authorization(x_matrix)) = let TypedHeader(Authorization(x_matrix)) =
parts.extract::<TypedHeader<Authorization<XMatrix>>>().await.map_err(|e| { parts.extract::<TypedHeader<Authorization<XMatrix>>>().await.map_err(|e| {
warn!("Missing or invalid Authorization header: {}", e); warn!("Missing or invalid Authorization header: {}", e);
+71 -102
View File
@@ -13,6 +13,7 @@ use std::{
use axum::{response::IntoResponse, Json}; use axum::{response::IntoResponse, Json};
use futures_util::future::TryFutureExt; use futures_util::future::TryFutureExt;
use get_profile_information::v1::ProfileField; use get_profile_information::v1::ProfileField;
use hickory_resolver::{error::ResolveError, lookup::SrvLookup};
use http::header::{HeaderValue, AUTHORIZATION}; use http::header::{HeaderValue, AUTHORIZATION};
use ipaddress::IPAddress; use ipaddress::IPAddress;
use ruma::{ use ruma::{
@@ -28,6 +29,7 @@ use ruma::{
keys::{claim_keys, get_keys}, keys::{claim_keys, get_keys},
membership::{create_invite, create_join_event, prepare_join_event}, membership::{create_invite, create_join_event, prepare_join_event},
query::{get_profile_information, get_room_information}, query::{get_profile_information, get_room_information},
space::get_hierarchy,
transactions::{ transactions::{
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent}, edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
send_transaction_message, send_transaction_message,
@@ -52,7 +54,6 @@ use ruma::{
use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use trust_dns_resolver::{error::ResolveError, lookup::SrvLookup};
use crate::{ use crate::{
api::client_server::{self, claim_keys_helper, get_keys_helper}, api::client_server::{self, claim_keys_helper, get_keys_helper},
@@ -364,7 +365,10 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
None => { None => {
if let Some(pos) = destination_str.find(':') { if let Some(pos) = destination_str.find(':') {
debug!("2: Hostname with included port"); debug!("2: Hostname with included port");
let (host, port) = destination_str.split_at(pos); 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()) FedDest::Named(host.to_owned(), port.to_owned())
} else { } else {
debug!("Requesting well known for {destination}"); debug!("Requesting well known for {destination}");
@@ -377,30 +381,23 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
None => { None => {
if let Some(pos) = delegated_hostname.find(':') { if let Some(pos) = delegated_hostname.find(':') {
debug!("3.2: Hostname with port in .well-known file"); debug!("3.2: Hostname with port in .well-known file");
let (host, port) = delegated_hostname.split_at(pos); 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()) FedDest::Named(host.to_owned(), port.to_owned())
} else { } else {
debug!("Delegated hostname has no port in this branch"); debug!("Delegated hostname has no port in this branch");
if let Some(hostname_override) = query_srv_record(&delegated_hostname).await { if let Some(hostname_override) = query_srv_record(&delegated_hostname).await {
debug!("3.3: SRV lookup successful"); debug!("3.3: SRV lookup successful");
let force_port = hostname_override.port();
if let Ok(override_ip) = services() let force_port = hostname_override.port();
.globals query_and_cache_override(
.dns_resolver() &delegated_hostname,
.lookup_ip(hostname_override.hostname()) &hostname_override.hostname(),
.await force_port.unwrap_or(8448),
{ )
services().globals.tls_name_override.write().unwrap().insert( .await;
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()
);
}
if let Some(port) = force_port { if let Some(port) = force_port {
FedDest::Named(delegated_hostname, format!(":{port}")) FedDest::Named(delegated_hostname, format!(":{port}"))
@@ -409,6 +406,7 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
} }
} else { } else {
debug!("3.4: No SRV records, just use the hostname from .well-known"); 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) 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 { match query_srv_record(&destination_str).await {
Some(hostname_override) => { Some(hostname_override) => {
debug!("4: SRV record found"); debug!("4: SRV record found");
let force_port = hostname_override.port();
if let Ok(override_ip) = let force_port = hostname_override.port();
services().globals.dns_resolver().lookup_ip(hostname_override.hostname()).await query_and_cache_override(
{ &hostname,
services().globals.tls_name_override.write().unwrap().insert( &hostname_override.hostname(),
hostname.clone(), force_port.unwrap_or(8448),
(override_ip.iter().collect(), force_port.unwrap_or(8448)), )
); .await;
} else {
debug!(
"Using SRV record {}, but could not resolve to IP",
hostname_override.hostname()
);
}
if let Some(port) = force_port { if let Some(port) = force_port {
FedDest::Named(hostname.clone(), format!(":{port}")) FedDest::Named(hostname.clone(), format!(":{port}"))
@@ -444,6 +435,7 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
}, },
None => { None => {
debug!("5: No SRV record found"); debug!("5: No SRV record found");
query_and_cache_override(&destination_str, &destination_str, 8448).await;
add_port_to_hostname(&destination_str) 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 // 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 // to an IP address if it wasn't specified
@@ -466,9 +457,29 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
} else { } else {
FedDest::Named(hostname, ":8448".to_owned()) FedDest::Named(hostname, ":8448".to_owned())
}; };
debug!("Actual destination: {actual_destination:?} hostname: {hostname:?}");
(actual_destination, 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> { async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
fn handle_successful_srv(srv: &SrvLookup) -> Option<FedDest> { fn handle_successful_srv(srv: &SrvLookup) -> Option<FedDest> {
srv.iter().next().map(|result| { 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> { 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() let response = services()
.globals .globals
.default_client() .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( pub async fn get_public_rooms_filtered_route(
body: Ruma<get_public_rooms_filtered::v1::Request>, body: Ruma<get_public_rooms_filtered::v1::Request>,
) -> Result<get_public_rooms_filtered::v1::Response> { ) -> 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() { if !services().globals.allow_public_room_directory_over_federation() {
return Err(Error::bad_config("Room directory is not public.")); 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( pub async fn get_public_rooms_route(
body: Ruma<get_public_rooms::v1::Request>, body: Ruma<get_public_rooms::v1::Request>,
) -> Result<get_public_rooms::v1::Response> { ) -> 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() { if !services().globals.allow_public_room_directory_over_federation() {
return Err(Error::bad_config("Room directory is not public.")); 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( pub async fn send_transaction_message_route(
body: Ruma<send_transaction_message::v1::Request>, body: Ruma<send_transaction_message::v1::Request>,
) -> Result<send_transaction_message::v1::Response> { ) -> 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 sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
let mut resolved_map = BTreeMap::new(); let mut resolved_map = BTreeMap::new();
@@ -820,6 +823,10 @@ pub async fn send_transaction_message_route(
} }
}, },
Edu::Receipt(receipt) => { Edu::Receipt(receipt) => {
if !services().globals.allow_incoming_read_receipts() {
continue;
}
for (room_id, room_updates) in receipt.receipts { for (room_id, room_updates) in receipt.receipts {
for (user_id, user_updates) in room_updates.read { for (user_id, user_updates) in room_updates.read {
if let Some((event_id, _)) = user_updates 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 /// - Only works if a user of this server is currently invited or joined the
/// room /// room
pub async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Result<get_event::v1::Response> { 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 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(|| { 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 /// Retrieves events from before the sender joined the room, if the room's
/// history visibility allows. /// history visibility allows.
pub async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) -> Result<get_backfill::v1::Response> { 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"); let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
debug!("Got backfill request from: {}", sender_servername); 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( pub async fn get_missing_events_route(
body: Ruma<get_missing_events::v1::Request>, body: Ruma<get_missing_events::v1::Request>,
) -> Result<get_missing_events::v1::Response> { ) -> 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"); 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)? { 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( pub async fn get_event_authorization_route(
body: Ruma<get_event_authorization::v1::Request>, body: Ruma<get_event_authorization::v1::Request>,
) -> Result<get_event_authorization::v1::Response> { ) -> 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"); 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)? { 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. /// 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> { 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"); 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)? { 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( pub async fn get_room_state_ids_route(
body: Ruma<get_room_state_ids::v1::Request>, body: Ruma<get_room_state_ids::v1::Request>,
) -> Result<get_room_state_ids::v1::Response> { ) -> 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"); 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)? { 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( pub async fn create_join_event_template_route(
body: Ruma<prepare_join_event::v1::Request>, body: Ruma<prepare_join_event::v1::Request>,
) -> Result<prepare_join_event::v1::Response> { ) -> 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)? { if !services().rooms.metadata.exists(&body.room_id)? {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server.")); 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( async fn create_join_event(
sender_servername: &ServerName, room_id: &RoomId, pdu: &RawJsonValue, sender_servername: &ServerName, room_id: &RoomId, pdu: &RawJsonValue,
) -> Result<create_join_event::v1::RoomState> { ) -> 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)? { if !services().rooms.metadata.exists(room_id)? {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server.")); 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. /// Invites a remote user to a room.
pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Result<create_invite::v2::Response> { 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"); let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
services().rooms.event_handler.acl_check(sender_servername, &body.room_id)?; 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()) let mut event: JsonObject = serde_json::from_str(body.event.get())
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid invite event bytes."))?; .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| { let pdu: PduEvent = serde_json::from_value(event.into()).map_err(|e| {
warn!("Invalid invite event: {}", 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. /// 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> { 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() { if body.user_id.server_name() != services().globals.server_name() {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, 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( pub async fn get_room_information_route(
body: Ruma<get_room_information::v1::Request>, body: Ruma<get_room_information::v1::Request>,
) -> Result<get_room_information::v1::Response> { ) -> Result<get_room_information::v1::Response> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let room_id = services() let room_id = services()
.rooms .rooms
.alias .alias
@@ -1695,10 +1658,6 @@ pub async fn get_room_information_route(
pub async fn get_profile_information_route( pub async fn get_profile_information_route(
body: Ruma<get_profile_information::v1::Request>, body: Ruma<get_profile_information::v1::Request>,
) -> Result<get_profile_information::v1::Response> { ) -> 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() { if body.user_id.server_name() != services().globals.server_name() {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
@@ -1738,10 +1697,6 @@ pub async fn get_profile_information_route(
/// ///
/// Gets devices and identity keys for the given users. /// 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> { 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()) { if body.device_keys.iter().any(|(u, _)| u.server_name() != services().globals.server_name()) {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, 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. /// Claims one-time keys.
pub async fn claim_keys_route(body: Ruma<claim_keys::v1::Request>) -> Result<claim_keys::v1::Response> { 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()) { if body.one_time_keys.iter().any(|(u, _)| u.server_name() != services().globals.server_name()) {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, 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` /// # `GET /.well-known/matrix/server`
pub async fn well_known_server_route() -> Result<impl IntoResponse> { 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() { let server_url = match services().globals.well_known_server() {
Some(url) => url.clone(), Some(url) => url.clone(),
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), 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)] #[cfg(test)]
mod tests { mod tests {
use super::{add_port_to_hostname, get_ip_with_port, FedDest}; 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 figment::Figment;
use itertools::Itertools; use itertools::Itertools;
use regex::RegexSet; use regex::RegexSet;
use ruma::{OwnedServerName, RoomVersionId}; use ruma::{OwnedRoomId, OwnedServerName, RoomVersionId};
use serde::{de::IgnoredAny, Deserialize}; use serde::{de::IgnoredAny, Deserialize};
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
@@ -41,7 +41,10 @@ pub struct Config {
pub server_name: OwnedServerName, pub server_name: OwnedServerName,
#[serde(default = "default_database_backend")] #[serde(default = "default_database_backend")]
pub database_backend: String, 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")] #[serde(default = "default_db_cache_capacity_mb")]
pub db_cache_capacity_mb: f64, pub db_cache_capacity_mb: f64,
#[serde(default = "default_new_user_displayname_suffix")] #[serde(default = "default_new_user_displayname_suffix")]
@@ -107,6 +110,9 @@ pub struct Config {
#[serde(default = "default_turn_ttl")] #[serde(default = "default_turn_ttl")]
pub turn_ttl: u64, pub turn_ttl: u64,
#[serde(default = "Vec::new")]
pub auto_join_rooms: Vec<OwnedRoomId>,
#[serde(default = "default_rocksdb_log_level")] #[serde(default = "default_rocksdb_log_level")]
pub rocksdb_log_level: String, pub rocksdb_log_level: String,
#[serde(default = "default_rocksdb_max_log_file_size")] #[serde(default = "default_rocksdb_max_log_file_size")]
@@ -144,6 +150,9 @@ pub struct Config {
#[serde(default = "default_presence_offline_timeout_s")] #[serde(default = "default_presence_offline_timeout_s")]
pub presence_offline_timeout_s: u64, pub presence_offline_timeout_s: u64,
#[serde(default = "true_fn")]
pub allow_incoming_read_receipts: bool,
#[serde(default)] #[serde(default)]
pub zstd_compression: bool, pub zstd_compression: bool,
@@ -169,7 +178,7 @@ pub struct Config {
#[serde(default = "RegexSet::empty")] #[serde(default = "RegexSet::empty")]
#[serde(with = "serde_regex")] #[serde(with = "serde_regex")]
pub forbidden_room_names: RegexSet, pub forbidden_alias_names: RegexSet,
#[serde(default = "RegexSet::empty")] #[serde(default = "RegexSet::empty")]
#[serde(with = "serde_regex")] #[serde(with = "serde_regex")]
@@ -248,7 +257,15 @@ impl fmt::Display for Config {
let lines = [ let lines = [
("Server name", self.server_name.host()), ("Server name", self.server_name.host()),
("Database backend", &self.database_backend), ("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()), ("Database cache capacity (MB)", &self.db_cache_capacity_mb.to_string()),
("Cache capacity modifier", &self.conduit_cache_capacity_modifier.to_string()), ("Cache capacity modifier", &self.conduit_cache_capacity_modifier.to_string()),
("PDU cache capacity", &self.pdu_cache_capacity.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)", "Allow local presence requests (updates)",
&self.allow_local_presence.to_string(), &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)", "Block non-admin room invites (local and remote, admins can still send and receive invites)",
&self.block_non_admin_invites.to_string(), &self.block_non_admin_invites.to_string(),
@@ -346,6 +367,13 @@ impl fmt::Display for Config {
} }
&lst.join(", ") &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")] #[cfg(feature = "compression-zstd")]
("zstd Response Body Compression", &self.zstd_compression.to_string()), ("zstd Response Body Compression", &self.zstd_compression.to_string()),
#[cfg(feature = "rocksdb")] #[cfg(feature = "rocksdb")]
@@ -398,8 +426,8 @@ impl fmt::Display for Config {
("Forbidden usernames", { ("Forbidden usernames", {
&self.forbidden_usernames.patterns().iter().join(", ") &self.forbidden_usernames.patterns().iter().join(", ")
}), }),
("Forbidden room names", { ("Forbidden room aliases", {
&self.forbidden_room_names.patterns().iter().join(", ") &self.forbidden_alias_names.patterns().iter().join(", ")
}), }),
( (
"URL preview domain contains allowlist", "URL preview domain contains allowlist",
@@ -439,9 +467,11 @@ fn default_port() -> ListeningPort {
fn default_unix_socket_perms() -> u32 { 660 } 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_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 } fn default_conduit_cache_capacity_modifier() -> f64 { 1.0 }
@@ -482,7 +512,7 @@ fn default_rocksdb_max_log_file_size() -> usize {
4 * 1024 * 1024 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() } 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 super::Config;
use crate::Result; use crate::Result;
@@ -18,6 +18,8 @@ pub(crate) trait KeyValueDatabaseEngine: Send + Sync {
Self: Sized; Self: Sized;
fn open_tree(&self, name: &'static str) -> Result<Arc<dyn KvTree>>; fn open_tree(&self, name: &'static str) -> Result<Arc<dyn KvTree>>;
fn flush(&self) -> Result<()>; fn flush(&self) -> Result<()>;
#[allow(dead_code)]
fn sync(&self) -> Result<()> { Ok(()) }
fn cleanup(&self) -> Result<()> { Ok(()) } fn cleanup(&self) -> Result<()> { Ok(()) }
fn memory_usage(&self) -> Result<String> { fn memory_usage(&self) -> Result<String> {
Ok("Current database engine does not support memory usage reporting.".to_owned()) 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)] #[allow(dead_code)]
fn clear_caches(&self) {} 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 { 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(&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<()>; 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<'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 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(&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>; 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}, sync::{Arc, RwLock},
}; };
use chrono::{DateTime, Utc};
use rust_rocksdb::{ use rust_rocksdb::{
backup::{BackupEngine, BackupEngineOptions},
LogLevel::{Debug, Error, Fatal, Info, Warn}, LogLevel::{Debug, Error, Fatal, Info, Warn},
WriteBatchWithTransaction, WriteBatchWithTransaction,
}; };
use tracing::{debug, info}; use tracing::{debug, error, info};
use super::{super::Config, watchers::Watchers, KeyValueDatabaseEngine, KvTree}; use super::{super::Config, watchers::Watchers, KeyValueDatabaseEngine, KvTree};
use crate::{utils, Result}; use crate::{utils, Result};
pub(crate) struct Engine { pub(crate) struct Engine {
rocks: rust_rocksdb::DBWithThreadMode<rust_rocksdb::MultiThreaded>, 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>, old_cfs: Vec<String>,
opts: rust_rocksdb::Options,
env: rust_rocksdb::Env,
config: Config, config: Config,
} }
@@ -27,24 +32,13 @@ struct RocksDbEngineTree<'a> {
write_lock: RwLock<()>, write_lock: RwLock<()>,
} }
fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rocksdb::Options { fn db_options(
// block-based options: https://docs.rs/rocksdb/latest/rocksdb/struct.BlockBasedOptions.html# config: &Config, env: &rust_rocksdb::Env, row_cache: &rust_rocksdb::Cache, col_cache: &rust_rocksdb::Cache,
let mut block_based_options = rust_rocksdb::BlockBasedOptions::default(); ) -> rust_rocksdb::Options {
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);
// database options: https://docs.rs/rocksdb/latest/rocksdb/struct.Options.html# // database options: https://docs.rs/rocksdb/latest/rocksdb/struct.Options.html#
let mut db_opts = rust_rocksdb::Options::default(); let mut db_opts = rust_rocksdb::Options::default();
// Logging
let rocksdb_log_level = match config.rocksdb_log_level.as_ref() { let rocksdb_log_level = match config.rocksdb_log_level.as_ref() {
"debug" => Debug, "debug" => Debug,
"info" => Info, "info" => Info,
@@ -52,7 +46,52 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
"fatal" => Fatal, "fatal" => Fatal,
_ => Error, _ => 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() { let rocksdb_compression_algo = match config.rocksdb_compression_algo.as_ref() {
"zstd" => rust_rocksdb::DBCompressionType::Zstd, "zstd" => rust_rocksdb::DBCompressionType::Zstd,
"zlib" => rust_rocksdb::DBCompressionType::Zlib, "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, _ => 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 { if config.rocksdb_bottommost_compression {
db_opts.set_bottommost_compression_type(rocksdb_compression_algo); db_opts.set_bottommost_compression_type(rocksdb_compression_algo);
db_opts.set_bottommost_zstd_max_train_bytes(0, true); 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. // -14 w_bits is only read by zlib.
db_opts.set_compression_options(-14, config.rocksdb_compression_level, 0, 0); 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.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 // Misc
db_opts.set_level_compaction_dynamic_level_bytes(true); db_opts.create_if_missing(true);
db_opts.set_max_background_jobs(6);
db_opts.set_bytes_per_sync(1_048_576);
// https://github.com/facebook/rocksdb/wiki/WAL-Recovery-Modes#ktoleratecorruptedtailrecords // 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); let prefix_extractor = rust_rocksdb::SliceTransform::create_fixed_prefix(1);
db_opts.set_prefix_extractor(prefix_extractor); db_opts.set_prefix_extractor(prefix_extractor);
db_opts.set_block_based_table_factory(&block_based_options);
db_opts.set_env(env);
db_opts db_opts
} }
impl KeyValueDatabaseEngine for Arc<Engine> { impl KeyValueDatabaseEngine for Arc<Engine> {
fn open(config: &Config) -> Result<Self> { fn open(config: &Config) -> Result<Self> {
let cache_capacity_bytes = (config.db_cache_capacity_mb * 1024.0 * 1024.0) as usize; let cache_capacity_bytes = config.db_cache_capacity_mb * 1024.0 * 1024.0;
let rocksdb_cache = rust_rocksdb::Cache::new_lru_cache(cache_capacity_bytes); 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"); debug!("Listing column families in database");
let cfs = let cfs =
@@ -139,13 +152,16 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
let db = rust_rocksdb::DBWithThreadMode::<rust_rocksdb::MultiThreaded>::open_cf_descriptors( let db = rust_rocksdb::DBWithThreadMode::<rust_rocksdb::MultiThreaded>::open_cf_descriptors(
&db_opts, &db_opts,
&config.database_path, &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 { Ok(Arc::new(Engine {
rocks: db, rocks: db,
cache: rocksdb_cache, row_cache,
col_cache,
old_cfs: cfs, old_cfs: cfs,
opts: db_opts,
env: db_env,
config: config.clone(), config: config.clone(),
})) }))
} }
@@ -154,7 +170,7 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
if !self.old_cfs.contains(&name.to_owned()) { if !self.old_cfs.contains(&name.to_owned()) {
// Create if it didn't exist // Create if it didn't exist
debug!("Creating new column family in database: {}", name); 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 { Ok(Arc::new(RocksDbEngineTree {
@@ -166,23 +182,35 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
} }
fn flush(&self) -> Result<()> { fn flush(&self) -> Result<()> {
debug!("Running flush_wal (no sync)");
rust_rocksdb::DBCommon::flush_wal(&self.rocks, false)?; rust_rocksdb::DBCommon::flush_wal(&self.rocks, false)?;
Ok(()) Ok(())
} }
fn sync(&self) -> Result<()> {
rust_rocksdb::DBCommon::flush_wal(&self.rocks, true)?;
Ok(())
}
fn memory_usage(&self) -> Result<String> { 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!( Ok(format!(
"Approximate memory usage of all the mem-tables: {:.3} MB\nApproximate memory usage of un-flushed \ "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 \ 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_total as f64 / 1024.0 / 1024.0,
stats.mem_table_unflushed 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.mem_table_readers_total as f64 / 1024.0 / 1024.0,
stats.cache_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(()) 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 // TODO: figure out if this is needed for rocksdb
#[allow(dead_code)] #[allow(dead_code)]
fn clear_caches(&self) {} fn clear_caches(&self) {}
@@ -252,6 +343,18 @@ impl KvTree for RocksDbEngineTree<'_> {
Ok(self.db.rocks.delete_cf_opt(&self.cf(), key, &writeoptions)?) 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> { fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a> {
let mut readoptions = rust_rocksdb::ReadOptions::default(); let mut readoptions = rust_rocksdb::ReadOptions::default();
readoptions.set_total_order_seek(true); 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(); 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 // TODO: only send for user they share a room with
futures.push(self.todeviceid_events.watch_prefix(&userdeviceid_prefix)); 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())?; self.global.insert(b"version", &new_version.to_be_bytes())?;
Ok(()) 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 tracing::debug;
use crate::{ use crate::{
database::KeyValueDatabase, database::KeyValueDatabase,
service::{self, media::UrlPreviewData}, service::{self, media::UrlPreviewData},
utils, Error, Result, utils::string_from_bytes,
Error, Result,
}; };
impl service::media::Data for KeyValueDatabase { impl service::media::Data for KeyValueDatabase {
fn create_file_metadata( 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>> { ) -> Result<Vec<u8>> {
let mut key = mxc.as_bytes().to_vec(); let mut key = mxc.as_bytes().to_vec();
key.push(0xFF); key.push(0xFF);
@@ -22,6 +24,16 @@ impl service::media::Data for KeyValueDatabase {
self.mediaid_file.insert(&key, &[])?; 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) Ok(key)
} }
@@ -31,13 +43,22 @@ impl service::media::Data for KeyValueDatabase {
let mut prefix = mxc.as_bytes().to_vec(); let mut prefix = mxc.as_bytes().to_vec();
prefix.push(0xFF); prefix.push(0xFF);
debug!("MXC db prefix: {:?}", prefix); debug!("MXC db prefix: {prefix:?}");
for (key, _) in self.mediaid_file.scan_prefix(prefix) { for (key, _) in self.mediaid_file.scan_prefix(prefix) {
debug!("Deleting key: {:?}", key); debug!("Deleting key: {:?}", key);
self.mediaid_file.remove(&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(()) Ok(())
} }
@@ -48,6 +69,8 @@ impl service::media::Data for KeyValueDatabase {
let mut prefix = mxc.as_bytes().to_vec(); let mut prefix = mxc.as_bytes().to_vec();
prefix.push(0xFF); prefix.push(0xFF);
debug!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA prefix: {:?}", prefix);
let mut keys: Vec<Vec<u8>> = vec![]; let mut keys: Vec<Vec<u8>> = vec![];
for (key, _) in self.mediaid_file.scan_prefix(prefix) { for (key, _) in self.mediaid_file.scan_prefix(prefix) {
@@ -65,6 +88,23 @@ impl service::media::Data for KeyValueDatabase {
Ok(keys) 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( fn search_file_metadata(
&self, mxc: String, width: u32, height: u32, &self, mxc: String, width: u32, height: u32,
) -> Result<(Option<String>, Option<String>, Vec<u8>)> { ) -> Result<(Option<String>, Option<String>, Vec<u8>)> {
@@ -85,7 +125,7 @@ impl service::media::Data for KeyValueDatabase {
let content_type = parts let content_type = parts
.next() .next()
.map(|bytes| { .map(|bytes| {
utils::string_from_bytes(bytes) string_from_bytes(bytes)
.map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode.")) .map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode."))
}) })
.transpose()?; .transpose()?;
@@ -97,7 +137,7 @@ impl service::media::Data for KeyValueDatabase {
None None
} else { } else {
Some( 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."))?, .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))] #[tracing::instrument(skip(self))]
fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> { fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
self.roomid_joinedcount self.roomid_joinedcount
@@ -276,6 +277,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
.transpose() .transpose()
} }
/// Returns the number of users which are currently invited to a room
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> { fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
self.roomid_invitedcount 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<()> { fn mark_as_active(&self, events: &[(SendingEventType, Vec<u8>)]) -> Result<()> {
for (e, key) in events { for (e, key) in events {
if key.is_empty() {
continue;
}
let value = if let SendingEventType::Edu(value) = &e { let value = if let SendingEventType::Edu(value) = &e {
&**value &**value
} else { } else {
+10 -3
View File
@@ -157,6 +157,7 @@ pub struct KeyValueDatabase {
//pub media: media::Media, //pub media: media::Media,
pub(super) mediaid_file: Arc<dyn KvTree>, // MediaId = MXC + WidthHeight + ContentDisposition + ContentType pub(super) mediaid_file: Arc<dyn KvTree>, // MediaId = MXC + WidthHeight + ContentDisposition + ContentType
pub(super) url_previews: Arc<dyn KvTree>, pub(super) url_previews: Arc<dyn KvTree>,
pub(super) mediaid_user: Arc<dyn KvTree>,
//pub key_backups: key_backups::KeyBackups, //pub key_backups: key_backups::KeyBackups,
pub(super) backupid_algorithm: Arc<dyn KvTree>, // BackupId = UserId + Version(Count) pub(super) backupid_algorithm: Arc<dyn KvTree>, // BackupId = UserId + Version(Count)
pub(super) backupid_etag: 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")?, roomusertype_roomuserdataid: builder.open_tree("roomusertype_roomuserdataid")?,
mediaid_file: builder.open_tree("mediaid_file")?, mediaid_file: builder.open_tree("mediaid_file")?,
url_previews: builder.open_tree("url_previews")?, url_previews: builder.open_tree("url_previews")?,
mediaid_user: builder.open_tree("mediaid_user")?,
backupid_algorithm: builder.open_tree("backupid_algorithm")?, backupid_algorithm: builder.open_tree("backupid_algorithm")?,
backupid_etag: builder.open_tree("backupid_etag")?, backupid_etag: builder.open_tree("backupid_etag")?,
backupkeyid_backup: builder.open_tree("backupkeyid_backup")?, backupkeyid_backup: builder.open_tree("backupkeyid_backup")?,
@@ -931,8 +933,13 @@ impl KeyValueDatabase {
{ {
let patterns = &services().globals.config.forbidden_usernames; let patterns = &services().globals.config.forbidden_usernames;
if !patterns.is_empty() { if !patterns.is_empty() {
for user in services().users.iter() { for user_id in services()
let user_id = user?; .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()); let matches = patterns.matches(user_id.localpart());
if matches.matched_any() { if matches.matched_any() {
warn!( 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() { if !patterns.is_empty() {
for address in services().rooms.metadata.iter_ids() { for address in services().rooms.metadata.iter_ids() {
let room_id = address?; let room_id = address?;
+14 -4
View File
@@ -280,7 +280,7 @@ async fn main() {
} }
if config.allow_outgoing_presence && !config.allow_local_presence { 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; return;
} }
@@ -535,11 +535,15 @@ async fn unrecognized_method<B: Send + 'static>(
let uri = req.uri().clone(); let uri = req.uri().clone();
let inner = next.run(req).await; let inner = next.run(req).await;
if inner.status() == StatusCode::METHOD_NOT_ALLOWED { 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 { return Ok(RumaResponse(UiaaResponse::MatrixError(RumaError {
body: ErrorBody::Standard { body: ErrorBody::Standard {
kind: ErrorKind::Unrecognized, 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, 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_profile_information_route)
.ruma_route(server_server::get_keys_route) .ruma_route(server_server::get_keys_route)
.ruma_route(server_server::claim_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/r0/rooms/:room_id/initialSync", get(initial_sync))
.route("/_matrix/client/v3/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)) .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 { 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") 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 super::pdu::PduBuilder;
use crate::{ use crate::{
api::{ 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, server_server::parse_incoming_pdu,
}, },
services, services,
@@ -92,6 +94,12 @@ enum MediaCommand {
event_id: Option<Box<EventId>>, 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 /// - Deletes a codeblock list of MXC URLs from our database and on the
/// filesystem /// filesystem
DeleteList, DeleteList,
@@ -103,6 +111,13 @@ enum MediaCommand {
/// past 5 minutes /// past 5 minutes
duration: String, 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))] #[cfg_attr(test, derive(Debug))]
@@ -396,6 +411,21 @@ enum DebugCommand {
server: Box<ServerName>, 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 /// - Forces device lists for all local and remote users to be updated (as
/// having new keys available) /// having new keys available)
ForceDeviceListUpdates, ForceDeviceListUpdates,
@@ -421,6 +451,13 @@ enum ServerCommand {
ClearServiceCaches { ClearServiceCaches {
amount: u32, amount: u32,
}, },
/// - Performs an online backup of the database (only available for RocksDB
/// at the moment)
BackupDatabase,
/// - List database backups
ListBackups,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -458,60 +495,50 @@ impl Service {
let conduit_user = UserId::parse(format!("@conduit:{}", services().globals.server_name())) let conduit_user = UserId::parse(format!("@conduit:{}", services().globals.server_name()))
.expect("@conduit:server_name is valid"); .expect("@conduit:server_name is valid");
let conduit_room = services() if let Ok(Some(conduit_room)) = services().admin.get_admin_room() {
.rooms loop {
.alias tokio::select! {
.resolve_local_alias( Some(event) = receiver.recv() => {
format!("#admins:{}", services().globals.server_name()) let (mut message_content, reply) = match event {
.as_str() AdminRoomEvent::SendMessage(content) => (content, None),
.try_into() AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
.expect("#admins:server_name is a valid room alias"), (self.process_admin_message(room_message).await, Some(reply_id))
) }
.expect("Database data for admin room alias must be valid") };
.expect("Admin room must exist");
loop { let mutex_state = Arc::clone(
tokio::select! { services().globals
Some(event) = receiver.recv() => { .roomid_mutex_state
let (mut message_content, reply) = match event { .write()
AdminRoomEvent::SendMessage(content) => (content, None), .await
AdminRoomEvent::ProcessMessage(room_message, reply_id) => { .entry(conduit_room.clone())
(self.process_admin_message(room_message).await, Some(reply_id)) .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().rooms.timeline.build_and_append_pdu(
services().globals PduBuilder {
.roomid_mutex_state event_type: TimelineEventType::RoomMessage,
.write() content: to_raw_value(&message_content)
.await .expect("event is valid, we just created it"),
.entry(conduit_room.clone()) unsigned: None,
.or_default(), 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 { drop(state_lock);
message_content.relates_to = Some(Reply { in_reply_to: InReplyTo { event_id: reply.into() } });
} }
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 => { MediaCommand::DeleteList => {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
let mxc_list = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>(); let mxc_list = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
@@ -825,6 +863,25 @@ impl Service {
deleted_count 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 { AdminCommand::Users(command) => match command {
@@ -888,6 +945,35 @@ impl Service {
.expect("to json value always works"), .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 // we dont add a device since we're not the user, just the creator
// Inhibit login does not work for guests // Inhibit login does not work for guests
@@ -1096,14 +1182,13 @@ impl Service {
format!("#admins:{}", services().globals.server_name()) format!("#admins:{}", services().globals.server_name())
.try_into() .try_into()
.expect("#admins:server_name is a valid alias name"); .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) { if let Some(admin_room_id) = services().admin.get_admin_room()? {
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the 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() { let room_id = if room.is_room_id() {
@@ -1267,23 +1352,15 @@ impl Service {
let mut room_ban_count = 0; let mut room_ban_count = 0;
let mut room_ids: Vec<&RoomId> = Vec::new(); 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 { for &room_id in &rooms_s {
match <&RoomId>::try_from(room_id) { match <&RoomId>::try_from(room_id) {
Ok(owned_room_id) => { Ok(owned_room_id) => {
// silently ignore deleting admin room // silently ignore deleting admin room
if owned_room_id.eq(&admin_room_id) { if let Some(admin_room_id) = services().admin.get_admin_room()? {
info!("User specified admin room in bulk ban list, ignoring"); if owned_room_id.eq(&admin_room_id) {
continue; info!("User specified admin room in bulk ban list, ignoring");
continue;
}
} }
room_ids.push(owned_room_id); room_ids.push(owned_room_id);
@@ -1870,6 +1947,35 @@ impl Service {
RoomMessageEventContent::text_plain("Done.") 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 { AdminCommand::Debug(command) => match command {
DebugCommand::GetAuthChain { 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 => { DebugCommand::ForceDeviceListUpdates => {
// Force E2EE device list updates for all users // Force E2EE device list updates for all users
for user_id in services().users.iter().filter_map(std::result::Result::ok) { for user_id in services().users.iter().filter_map(std::result::Result::ok) {
@@ -2393,105 +2534,113 @@ impl Service {
Ok(()) 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. /// Invite the user to the conduit admin room.
/// ///
/// In conduit, this is equivalent to granting admin privileges. /// In conduit, this is equivalent to granting admin privileges.
pub(crate) async fn make_user_admin(&self, user_id: &UserId, displayname: String) -> Result<()> { 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()) if let Some(room_id) = services().admin.get_admin_room()? {
.try_into() let mutex_state =
.expect("#admins:server_name is a valid alias name"); Arc::clone(services().globals.roomid_mutex_state.write().await.entry(room_id.clone()).or_default());
let room_id = services().rooms.alias.resolve_local_alias(&admin_room_alias)?.expect("Admin room must exist"); let state_lock = mutex_state.lock().await;
let mutex_state = // Use the server user to grant the new admin's power level
Arc::clone(services().globals.roomid_mutex_state.write().await.entry(room_id.clone()).or_default()); let conduit_user = UserId::parse_with_server_name("conduit", services().globals.server_name())
let state_lock = mutex_state.lock().await; .expect("@conduit:server_name is valid");
// Use the server user to grant the new admin's power level // Invite and join the real user
let conduit_user = UserId::parse_with_server_name("conduit", services().globals.server_name()) services()
.expect("@conduit:server_name is valid"); .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 // Set power level
services() let mut users = BTreeMap::new();
.rooms users.insert(conduit_user.clone(), 100.into());
.timeline users.insert(user_id.to_owned(), 100.into());
.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 services()
let mut users = BTreeMap::new(); .rooms
users.insert(conduit_user.clone(), 100.into()); .timeline
users.insert(user_id.to_owned(), 100.into()); .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() // Send welcome message
.rooms services().rooms.timeline.build_and_append_pdu(
.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(
PduBuilder { PduBuilder {
event_type: TimelineEventType::RoomMessage, event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&RoomMessageEventContent::text_html( content: to_raw_value(&RoomMessageEventContent::text_html(
@@ -2508,7 +2657,10 @@ impl Service {
&state_lock, &state_lock,
).await?; ).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 async_trait::async_trait;
use ruma::{ use ruma::{
@@ -32,4 +32,6 @@ pub trait Data: Send + Sync {
fn signing_keys_for(&self, origin: &ServerName) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>>; fn signing_keys_for(&self, origin: &ServerName) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>>;
fn database_version(&self) -> Result<u64>; fn database_version(&self) -> Result<u64>;
fn bump_database_version(&self, new_version: u64) -> Result<()>; 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 _}; use base64::{engine::general_purpose, Engine as _};
pub use data::Data; pub use data::Data;
use futures_util::FutureExt; use futures_util::FutureExt;
use hickory_resolver::TokioAsyncResolver;
use hyper::{ use hyper::{
client::connect::dns::{GaiResolver, Name}, client::connect::dns::{GaiResolver, Name},
service::Service as HyperService, service::Service as HyperService,
@@ -34,7 +35,6 @@ use ruma::{
}; };
use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore}; use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore};
use tracing::{error, info}; use tracing::{error, info};
use trust_dns_resolver::TokioAsyncResolver;
use crate::{api::server_server::FedDest, services, Config, Error, Result}; 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 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 notification_push_path(&self) -> &String { &self.config.notification_push_path }
pub fn emergency_password(&self) -> &Option<String> { &self.config.emergency_password } 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 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 } 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 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_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 } 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() 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)) .connect_timeout(Duration::from_secs(60))
.timeout(Duration::from_secs(60 * 5)) .timeout(Duration::from_secs(60 * 5))
.redirect(redirect_policy) .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() let mut reqwest_client_builder = reqwest::Client::builder()
.hickory_dns(true)
.pool_max_idle_per_host(0) .pool_max_idle_per_host(0)
.connect_timeout(Duration::from_secs(60)) .connect_timeout(Duration::from_secs(20))
.timeout(Duration::from_secs(60 * 5)) .timeout(Duration::from_secs(30))
.redirect(redirect_policy) .redirect(redirect_policy)
.user_agent("Conduwuit".to_owned() + "/" + env!("CARGO_PKG_VERSION")); .user_agent("Conduwuit".to_owned() + "/" + env!("CARGO_PKG_VERSION"));
+6 -1
View File
@@ -1,8 +1,11 @@
use ruma::OwnedUserId;
use crate::Result; use crate::Result;
pub trait Data: Send + Sync { pub trait Data: Send + Sync {
fn create_file_metadata( 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>>; ) -> Result<Vec<u8>>;
fn delete_file_mxc(&self, mxc: String) -> Result<()>; 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 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 get_all_media_keys(&self) -> Result<Vec<Vec<u8>>>;
fn remove_url_preview(&self, url: &str) -> Result<()>; 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; pub(crate) use data::Data;
use image::imageops::FilterType; use image::imageops::FilterType;
use ruma::OwnedMxcUri; use ruma::{OwnedMxcUri, OwnedUserId};
use serde::Serialize; use serde::Serialize;
use tokio::{ use tokio::{
fs::{self, File}, fs::{self, File},
@@ -12,7 +12,7 @@ use tokio::{
}; };
use tracing::{debug, error}; use tracing::{debug, error};
use crate::{services, utils, Error, Result}; use crate::{services, utils::string_from_bytes, Error, Result};
#[derive(Debug)] #[derive(Debug)]
pub struct FileMeta { pub struct FileMeta {
@@ -45,10 +45,15 @@ pub struct Service {
impl Service { impl Service {
/// Uploads a file. /// Uploads a file.
pub async fn create( 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<()> { ) -> Result<()> {
// Width, Height = 0 if it's not a thumbnail // 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; 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. /// Uploads or replaces a file thumbnail.
#[allow(clippy::too_many_arguments)]
pub async fn upload_thumbnail( pub async fn upload_thumbnail(
&self, mxc: String, content_disposition: Option<&str>, content_type: Option<&str>, width: u32, height: u32, &self, sender_user: Option<OwnedUserId>, mxc: String, content_disposition: Option<&str>,
file: &[u8], content_type: Option<&str>, width: u32, height: u32, file: &[u8],
) -> Result<()> { ) -> 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; let path;
#[allow(clippy::unnecessary_operation)] // error[E0658]: attributes on expressions are experimental #[allow(clippy::unnecessary_operation)] // error[E0658]: attributes on expressions are experimental
@@ -183,13 +259,13 @@ impl Service {
debug!("Full MXC key from database: {:?}", key); debug!("Full MXC key from database: {:?}", key);
// we need to get the MXC URL from the first part of the key (the first 0xff / // 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 // 255 push). this is all necessary because of conduit using magic keys for
// keys // media
let mut parts = key.split(|&b| b == 0xFF); let mut parts = key.split(|&b| b == 0xFF);
let mxc = parts let mxc = parts
.next() .next()
.map(|bytes| { .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!("Failed to parse MXC unicode bytes from our database: {}", e);
Error::bad_database("Failed to parse MXC unicode bytes from our database") 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 // Save thumbnail in database so we don't have to generate it again next time
let thumbnail_key = self.db.create_file_metadata( let thumbnail_key = self.db.create_file_metadata(
None,
mxc, mxc,
width, width,
height, height,
@@ -470,7 +547,8 @@ mod tests {
impl Data for MockedKVDatabase { impl Data for MockedKVDatabase {
fn create_file_metadata( 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>> { ) -> Result<Vec<u8>> {
// copied from src/database/key_value/media.rs // copied from src/database/key_value/media.rs
let mut key = mxc.as_bytes().to_vec(); 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 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 get_all_media_keys(&self) -> Result<Vec<Vec<u8>>> { todo!() }
fn search_file_metadata( 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 \ let content_disposition = "attachment; filename=\"this is a very long file name with spaces and special \
characters like äöüß and even emoji like 🦀.png\""; characters like äöüß and even emoji like 🦀.png\"";
let content_type = "image/png"; let content_type = "image/png";
let key = let key = media
media.db.create_file_metadata(mxc, width, height, Some(content_disposition), Some(content_type)).unwrap(); .db
.create_file_metadata(None, mxc, width, height, Some(content_disposition), Some(content_type))
.unwrap();
let mut r = PathBuf::new(); let mut r = PathBuf::new();
r.push("/tmp"); r.push("/tmp");
r.push("media"); r.push("media");
+10 -4
View File
@@ -134,7 +134,7 @@ impl Services<'_> {
db, db,
}, },
spaces: rooms::spaces::Service { 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, (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 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 stateinfo_cache = self.rooms.state_compressor.stateinfo_cache.lock().unwrap().len();
let lasttimelinecount_cache = self.rooms.timeline.lasttimelinecount_cache.lock().await.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!( format!(
"\ "\
@@ -184,7 +184,7 @@ server_visibility_cache: {server_visibility_cache}
user_visibility_cache: {user_visibility_cache} user_visibility_cache: {user_visibility_cache}
stateinfo_cache: {stateinfo_cache} stateinfo_cache: {stateinfo_cache}
lasttimelinecount_cache: {lasttimelinecount_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(); self.rooms.timeline.lasttimelinecount_cache.lock().await.clear();
} }
if amount > 5 { 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?; .await?;
}, },
TimelineEventType::SpaceChild => { 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, _ => continue,
} }
+3 -4
View File
@@ -230,10 +230,9 @@ impl Service {
pub fn get_name(&self, room_id: &RoomId) -> Result<Option<String>> { 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| { 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| { Ok(serde_json::from_str(s.content.get())
error!("Invalid room name event in database for room {}. {}", room_id, e); .map(|c: RoomNameEventContent| Some(c.name))
Error::bad_database("Invalid room name event in database.") .unwrap_or_else(|_| None))
})
}) })
} }
+78 -82
View File
@@ -26,7 +26,7 @@ use ruma::{
state_res, state_res,
state_res::{Event, RoomVersion}, state_res::{Event, RoomVersion},
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName, uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
RoomAliasId, RoomId, RoomVersionId, ServerName, UserId, RoomId, RoomVersionId, ServerName, UserId,
}; };
use serde::Deserialize; use serde::Deserialize;
use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
@@ -418,7 +418,7 @@ impl Service {
}, },
TimelineEventType::SpaceChild => { TimelineEventType::SpaceChild => {
if let Some(_state_key) = &pdu.state_key { 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 => { TimelineEventType::RoomMember => {
@@ -461,10 +461,6 @@ impl Service {
if let Some(body) = content.body { if let Some(body) = content.body {
services().rooms.search.index_pdu(shortroomid, &pdu_id, &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 server_user = format!("@conduit:{}", services().globals.server_name());
let to_conduit = body.starts_with(&format!("{server_user}: ")) let to_conduit = body.starts_with(&format!("{server_user}: "))
@@ -477,8 +473,10 @@ impl Service {
// the administrator can execute commands as conduit // the administrator can execute commands as conduit
let from_conduit = pdu.sender == server_user && services().globals.emergency_password().is_none(); 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) { if let Some(admin_room) = services().admin.get_admin_room()? {
services().admin.process_message(body, pdu.event_id.clone()); 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>> { ) -> Result<Arc<EventId>> {
let (pdu, pdu_json) = self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?; 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( if let Some(admin_room) = services().admin.get_admin_room()? {
<&RoomAliasId>::try_from(format!("#admins:{}", services().globals.server_name()).as_str()) if admin_room == room_id {
.expect("#admins:server_name is a valid room alias"), match pdu.event_type() {
)?; TimelineEventType::RoomEncryption => {
if admin_room.filter(|v| v == room_id).is_some() { warn!("Encryption is not allowed in the admins room");
match pdu.event_type() { return Err(Error::BadRequest(
TimelineEventType::RoomEncryption => { ErrorKind::Forbidden,
warn!("Encryption is not allowed in the admins room"); "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 {
TimelineEventType::RoomMember => { membership: MembershipState,
#[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.",
));
} }
let count = services() let target = pdu.state_key().filter(|v| v.starts_with('@')).unwrap_or(sender.as_str());
.rooms let server_name = services().globals.server_name();
.state_cache let server_user = format!("@conduit:{server_name}");
.room_members(room_id) let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
.filter_map(std::result::Result::ok) .map_err(|_| Error::bad_database("Invalid content in pdu."))?;
.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.",
));
}
}
if content.membership == MembershipState::Ban && pdu.state_key().is_some() { if content.membership == MembershipState::Leave {
if target == server_user { if target == server_user {
warn!("Conduit user cannot be banned in admins room"); warn!("Conduit user cannot leave from admins room");
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::Forbidden, ErrorKind::Forbidden,
"Conduit user cannot be banned in admins room.", "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() if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
.rooms if target == server_user {
.state_cache warn!("Conduit user cannot be banned in admins room");
.room_members(room_id) return Err(Error::BadRequest(
.filter_map(std::result::Result::ok) ErrorKind::Forbidden,
.filter(|m| m.server_name() == server_name) "Conduit user cannot be banned in admins room.",
.filter(|m| m != target) ));
.count(); }
if count < 2 {
warn!("Last admin cannot be banned in admins room"); let count = services()
return Err(Error::BadRequest( .rooms
ErrorKind::Forbidden, .state_cache
"Last admin cannot be banned in admins room.", .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(()); return Ok(());
}, },
Err(e) => { 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(()) Ok(())
} }
+48 -7
View File
@@ -25,7 +25,7 @@ use ruma::{
}, },
device_id, device_id,
events::{push_rules::PushRulesEvent, receipt::ReceiptType, AnySyncEphemeralRoomEvent, GlobalAccountDataEventType}, 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::{ use tokio::{
select, select,
@@ -80,6 +80,7 @@ impl OutgoingKind {
pub enum SendingEventType { pub enum SendingEventType {
Pdu(Vec<u8>), // pduid Pdu(Vec<u8>), // pduid
Edu(Vec<u8>), // pdu json Edu(Vec<u8>), // pdu json
Flush, // none
} }
pub struct Service { pub struct Service {
@@ -237,9 +238,11 @@ impl Service {
events.push(e); events.push(e);
} }
} else { } else {
self.db.mark_as_active(&new_events)?; if !new_events.is_empty() {
for (e, _) in new_events { self.db.mark_as_active(&new_events)?;
events.push(e); for (e, _) in new_events {
events.push(e);
}
} }
if let OutgoingKind::Normal(server_name) = outgoing_kind { if let OutgoingKind::Normal(server_name) = outgoing_kind {
@@ -360,11 +363,11 @@ impl Service {
for user_id in device_list_changes { 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 // 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 { let edu = Edu::DeviceListUpdate(DeviceListUpdateContent {
user_id, user_id,
device_id: device_id!("dummy").to_owned(), device_id: device_id!("placeholder").to_owned(),
device_display_name: Some("Dummy".to_owned()), device_display_name: Some("Placeholder".to_owned()),
stream_id: uint!(1), stream_id: uint!(1),
prev_id: Vec::new(), prev_id: Vec::new(),
deleted: None, deleted: None,
@@ -421,6 +424,29 @@ impl Service {
Ok(()) 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 /// Cleanup event data
/// Used for instance after we remove an appservice registration /// Used for instance after we remove an appservice registration
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
@@ -461,6 +487,9 @@ impl Service {
SendingEventType::Edu(_) => { SendingEventType::Edu(_) => {
// Appservices don't need EDUs (?) // Appservices don't need EDUs (?)
}, },
SendingEventType::Flush => {
// flush only; no new content
},
} }
} }
@@ -480,6 +509,7 @@ impl Service {
.iter() .iter()
.map(|e| match e { .map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b, SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
SendingEventType::Flush => &[],
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
))) )))
@@ -521,6 +551,9 @@ impl Service {
SendingEventType::Edu(_) => { SendingEventType::Edu(_) => {
// Push gateways don't need EDUs (?) // Push gateways don't need EDUs (?)
}, },
SendingEventType::Flush => {
// flush only; no new content
},
} }
} }
@@ -601,6 +634,9 @@ impl Service {
edu_jsons.push(raw); edu_jsons.push(raw);
} }
}, },
SendingEventType::Flush => {
// flush only; no new content
},
} }
} }
@@ -618,6 +654,7 @@ impl Service {
.iter() .iter()
.map(|e| match e { .map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b, SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
SendingEventType::Flush => &[],
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
))) )))
@@ -647,6 +684,10 @@ impl Service {
where where
T: OutgoingRequest + Debug, 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()) { if destination.is_ip_literal() || IPAddress::is_valid(destination.host()) {
info!( info!(
"Destination {} is an IP literal, checking against IP range denylist.", "Destination {} is an IP literal, checking against IP range denylist.",