Compare commits

...

14 Commits

Author SHA1 Message Date
renovate[bot] af80565e6a chore(deps): update rust crate bytes to v1.7.1 2024-08-03 07:46:30 +00:00
Jason Volk fe0969e7ca fix release-max-perf profile [ci skip]
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 13:37:27 +00:00
Jason Volk 2e3e14b045 refactor some additional errors
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 10:58:59 +00:00
Jason Volk f51d4237c7 bump cargo
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 10:58:59 +00:00
strawberry d8a87258d6 rust: bump console-subscriber to 0.4
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-01 10:58:58 +00:00
strawberry 3da2242730 bump tokio, use default-features = false for more crates
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-01 10:58:58 +00:00
Jason Volk 5add9a8c34 support field values in err! macro
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 10:58:58 +00:00
Jason Volk 5b1642f641 split server_keys from event_handler into service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 21:10:22 +00:00
Jason Volk eb94aa7079 remove dangling sending/resolve unit
this was probably un-deleted during a rebase conflict resolution,
but remained outside of the mod tree.

Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 21:10:22 +00:00
Jason Volk 24884c3b9b move media helpers from api to services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk a0a002b17f disable legacy media compat by default; cleanse directory when disabled
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk f632b06e6d use Services instead of Server for middleware function state
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk 1f88866612 optimize api state extractor
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk ccef1a4c8b add formal wrapping for api state
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 21:32:43 +00:00
42 changed files with 919 additions and 1096 deletions
Generated
+147 -238
View File
@@ -153,34 +153,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "axum"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [
"async-trait",
"axum-core 0.3.4",
"bitflags 1.3.2",
"bytes",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper 0.1.2",
"tower",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.7.5" version = "0.7.5"
@@ -188,13 +160,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core 0.4.3", "axum-core",
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"itoa", "itoa",
"matchit", "matchit",
@@ -221,28 +193,11 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72188bed20deb981f3a4a9fe674e5980fd9e9c2bd880baa94715ad5d60d64c67" checksum = "72188bed20deb981f3a4a9fe674e5980fd9e9c2bd880baa94715ad5d60d64c67"
dependencies = [ dependencies = [
"axum 0.7.5", "axum",
"forwarded-header-value", "forwarded-header-value",
"serde", "serde",
] ]
[[package]]
name = "axum-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.4.3" version = "0.4.3"
@@ -252,8 +207,8 @@ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
@@ -270,13 +225,13 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
dependencies = [ dependencies = [
"axum 0.7.5", "axum",
"axum-core 0.4.3", "axum-core",
"bytes", "bytes",
"futures-util", "futures-util",
"headers", "headers",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
@@ -295,10 +250,10 @@ dependencies = [
"arc-swap", "arc-swap",
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"pin-project-lite", "pin-project-lite",
"rustls 0.21.12", "rustls 0.21.12",
@@ -317,7 +272,7 @@ checksum = "7ea4cd08ae2a5f075d28fa31190163c8106a1d2d3189442494bae22b39040a0d"
dependencies = [ dependencies = [
"axum-server", "axum-server",
"bytes", "bytes",
"http 1.1.0", "http",
"http-body-util", "http-body-util",
"pin-project", "pin-project",
"tokio", "tokio",
@@ -439,9 +394,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.16.1" version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@@ -457,9 +412,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.6.1" version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]] [[package]]
name = "bzip2-sys" name = "bzip2-sys"
@@ -484,9 +439,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.6" version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@@ -544,9 +499,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.11" version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -554,9 +509,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.11" version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@@ -564,9 +519,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.11" version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -639,7 +594,7 @@ dependencies = [
name = "conduit_api" name = "conduit_api"
version = "0.4.6" version = "0.4.6"
dependencies = [ dependencies = [
"axum 0.7.5", "axum",
"axum-client-ip", "axum-client-ip",
"axum-extra", "axum-extra",
"base64 0.22.1", "base64 0.22.1",
@@ -650,10 +605,9 @@ dependencies = [
"const-str", "const-str",
"futures-util", "futures-util",
"hmac", "hmac",
"http 1.1.0", "http",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"image",
"ipaddress", "ipaddress",
"jsonwebtoken", "jsonwebtoken",
"log", "log",
@@ -666,7 +620,6 @@ dependencies = [
"sha-1", "sha-1",
"tokio", "tokio",
"tracing", "tracing",
"webpage",
] ]
[[package]] [[package]]
@@ -674,7 +627,7 @@ name = "conduit_core"
version = "0.4.6" version = "0.4.6"
dependencies = [ dependencies = [
"argon2", "argon2",
"axum 0.7.5", "axum",
"bytes", "bytes",
"cargo_toml", "cargo_toml",
"checked_ops", "checked_ops",
@@ -685,7 +638,7 @@ dependencies = [
"either", "either",
"figment", "figment",
"hardened_malloc-rs", "hardened_malloc-rs",
"http 1.1.0", "http",
"http-body-util", "http-body-util",
"image", "image",
"ipaddress", "ipaddress",
@@ -741,7 +694,7 @@ dependencies = [
name = "conduit_router" name = "conduit_router"
version = "0.4.6" version = "0.4.6"
dependencies = [ dependencies = [
"axum 0.7.5", "axum",
"axum-client-ip", "axum-client-ip",
"axum-server", "axum-server",
"axum-server-dual-protocol", "axum-server-dual-protocol",
@@ -751,9 +704,9 @@ dependencies = [
"conduit_core", "conduit_core",
"conduit_service", "conduit_service",
"const-str", "const-str",
"http 1.1.0", "http",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"log", "log",
"ruma", "ruma",
@@ -781,7 +734,7 @@ dependencies = [
"cyborgtime", "cyborgtime",
"futures-util", "futures-util",
"hickory-resolver", "hickory-resolver",
"http 1.1.0", "http",
"image", "image",
"ipaddress", "ipaddress",
"itertools 0.13.0", "itertools 0.13.0",
@@ -802,13 +755,14 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"url", "url",
"webpage",
] ]
[[package]] [[package]]
name = "console-api" name = "console-api"
version = "0.7.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a257c22cd7e487dd4a13d413beabc512c5052f0bc048db0da6a84c3d8a6142fd" checksum = "86ed14aa9c9f927213c6e4f3ef75faaad3406134efe84ba2cb7983431d5f0931"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"prost", "prost",
@@ -819,9 +773,9 @@ dependencies = [
[[package]] [[package]]
name = "console-subscriber" name = "console-subscriber"
version = "0.3.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31c4cc54bae66f7d9188996404abdf7fdfa23034ef8e43478c8810828abad758" checksum = "e2e3a111a37f3333946ebf9da370ba5c5577b18eb342ec683eb488dd21980302"
dependencies = [ dependencies = [
"console-api", "console-api",
"crossbeam-channel", "crossbeam-channel",
@@ -829,6 +783,7 @@ dependencies = [
"futures-task", "futures-task",
"hdrhistogram", "hdrhistogram",
"humantime", "humantime",
"hyper-util",
"prost", "prost",
"prost-types", "prost-types",
"serde", "serde",
@@ -1372,25 +1327,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.2.6",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.5" version = "0.4.5"
@@ -1402,8 +1338,8 @@ dependencies = [
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http 1.1.0", "http",
"indexmap 2.2.6", "indexmap 2.3.0",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@@ -1450,7 +1386,7 @@ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
"headers-core", "headers-core",
"http 1.1.0", "http",
"httpdate", "httpdate",
"mime", "mime",
"sha1", "sha1",
@@ -1462,7 +1398,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [ dependencies = [
"http 1.1.0", "http",
] ]
[[package]] [[package]]
@@ -1579,17 +1515,6 @@ dependencies = [
"syn 2.0.72", "syn 2.0.72",
] ]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.1.0"
@@ -1610,17 +1535,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.1" version = "1.0.1"
@@ -1628,7 +1542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [ dependencies = [
"bytes", "bytes",
"http 1.1.0", "http",
] ]
[[package]] [[package]]
@@ -1639,8 +1553,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"pin-project-lite", "pin-project-lite",
] ]
@@ -1662,30 +1576,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.4.1" version = "1.4.1"
@@ -1695,9 +1585,9 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2 0.4.5", "h2",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
@@ -1714,8 +1604,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 1.1.0", "http",
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"rustls 0.23.12", "rustls 0.23.12",
"rustls-native-certs", "rustls-native-certs",
@@ -1728,14 +1618,15 @@ dependencies = [
[[package]] [[package]]
name = "hyper-timeout" name = "hyper-timeout"
version = "0.4.1" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793"
dependencies = [ dependencies = [
"hyper 0.14.30", "hyper",
"hyper-util",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tokio-io-timeout", "tower-service",
] ]
[[package]] [[package]]
@@ -1747,9 +1638,9 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"hyper 1.4.1", "hyper",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio", "tokio",
@@ -1817,9 +1708,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.5", "hashbrown 0.14.5",
@@ -2356,7 +2247,7 @@ checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"indexmap 2.2.6", "indexmap 2.3.0",
"js-sys", "js-sys",
"once_cell", "once_cell",
"pin-project-lite", "pin-project-lite",
@@ -2403,7 +2294,7 @@ dependencies = [
"glob", "glob",
"once_cell", "once_cell",
"opentelemetry", "opentelemetry",
"ordered-float 4.2.1", "ordered-float 4.2.2",
"percent-encoding", "percent-encoding",
"rand", "rand",
"thiserror", "thiserror",
@@ -2422,9 +2313,9 @@ dependencies = [
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "4.2.1" version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be" checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@@ -2651,9 +2542,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "precomputed-hash" name = "precomputed-hash"
@@ -2694,9 +2588,9 @@ dependencies = [
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.12.6" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost-derive", "prost-derive",
@@ -2704,12 +2598,12 @@ dependencies = [
[[package]] [[package]]
name = "prost-derive" name = "prost-derive"
version = "0.12.6" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.12.1", "itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.72",
@@ -2717,9 +2611,9 @@ dependencies = [
[[package]] [[package]]
name = "prost-types" name = "prost-types"
version = "0.12.6" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2"
dependencies = [ dependencies = [
"prost", "prost",
] ]
@@ -2904,12 +2798,12 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2 0.4.5", "h2",
"hickory-resolver", "hickory-resolver",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-rustls", "hyper-rustls",
"hyper-util", "hyper-util",
"ipnet", "ipnet",
@@ -3009,7 +2903,7 @@ dependencies = [
"assign", "assign",
"bytes", "bytes",
"date_header", "date_header",
"http 1.1.0", "http",
"js_int", "js_int",
"js_option", "js_option",
"maplit", "maplit",
@@ -3032,8 +2926,8 @@ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
"form_urlencoded", "form_urlencoded",
"http 1.1.0", "http",
"indexmap 2.2.6", "indexmap 2.3.0",
"js_int", "js_int",
"konst", "konst",
"percent-encoding", "percent-encoding",
@@ -3059,7 +2953,7 @@ version = "0.28.1"
source = "git+https://github.com/girlbossceo/ruwuma?rev=c76e2873c1593a3308d4ba3e0e4a1db65acf8536#c76e2873c1593a3308d4ba3e0e4a1db65acf8536" source = "git+https://github.com/girlbossceo/ruwuma?rev=c76e2873c1593a3308d4ba3e0e4a1db65acf8536#c76e2873c1593a3308d4ba3e0e4a1db65acf8536"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap 2.2.6", "indexmap 2.3.0",
"js_int", "js_int",
"js_option", "js_option",
"percent-encoding", "percent-encoding",
@@ -3140,7 +3034,7 @@ version = "0.3.0"
source = "git+https://github.com/girlbossceo/ruwuma?rev=c76e2873c1593a3308d4ba3e0e4a1db65acf8536#c76e2873c1593a3308d4ba3e0e4a1db65acf8536" source = "git+https://github.com/girlbossceo/ruwuma?rev=c76e2873c1593a3308d4ba3e0e4a1db65acf8536#c76e2873c1593a3308d4ba3e0e4a1db65acf8536"
dependencies = [ dependencies = [
"headers", "headers",
"http 1.1.0", "http",
"http-auth", "http-auth",
"ruma-common", "ruma-common",
"thiserror", "thiserror",
@@ -3519,7 +3413,7 @@ version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c90802b38c899a2c9e557dff25ad186362eddf755d5f5244001b172dd03bead" checksum = "6c90802b38c899a2c9e557dff25ad186362eddf755d5f5244001b172dd03bead"
dependencies = [ dependencies = [
"http 1.1.0", "http",
"pin-project", "pin-project",
"sentry-core", "sentry-core",
"tower-layer", "tower-layer",
@@ -3583,7 +3477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5" checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"indexmap 2.2.6", "indexmap 2.3.0",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
@@ -3591,11 +3485,12 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.120" version = "1.0.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]
@@ -3647,7 +3542,7 @@ version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [ dependencies = [
"indexmap 2.2.6", "indexmap 2.3.0",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
@@ -3714,9 +3609,9 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-mio" name = "signal-hook-mio"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [ dependencies = [
"libc", "libc",
"mio 0.8.11", "mio 0.8.11",
@@ -4055,9 +3950,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.39.1" version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -4071,16 +3966,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "tokio-io-timeout"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
dependencies = [
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.4.0" version = "2.4.0"
@@ -4127,9 +4012,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-socks" name = "tokio-socks"
version = "0.5.1" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
dependencies = [ dependencies = [
"either", "either",
"futures-util", "futures-util",
@@ -4163,21 +4048,21 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.16" version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_edit 0.22.17", "toml_edit 0.22.20",
] ]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.7" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -4188,43 +4073,46 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [ dependencies = [
"indexmap 2.2.6", "indexmap 2.3.0",
"toml_datetime", "toml_datetime",
"winnow 0.5.40", "winnow 0.5.40",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.17" version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [ dependencies = [
"indexmap 2.2.6", "indexmap 2.3.0",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"winnow 0.6.16", "winnow 0.6.18",
] ]
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.11.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" checksum = "38659f4a91aba8598d27821589f5db7dddd94601e7a01b1e485a50e5484c7401"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
"axum 0.6.20", "axum",
"base64 0.21.7", "base64 0.22.1",
"bytes", "bytes",
"h2 0.3.26", "h2",
"http 0.2.12", "http",
"http-body 0.4.6", "http-body",
"hyper 0.14.30", "http-body-util",
"hyper",
"hyper-timeout", "hyper-timeout",
"hyper-util",
"percent-encoding", "percent-encoding",
"pin-project", "pin-project",
"prost", "prost",
"socket2",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tower", "tower",
@@ -4264,8 +4152,8 @@ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
@@ -4881,9 +4769,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.16" version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -4925,6 +4813,27 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.8.1" version = "1.8.1"
+27 -3
View File
@@ -33,6 +33,7 @@ version = "0.2.8"
[workspace.dependencies.cargo_toml] [workspace.dependencies.cargo_toml]
version = "0.20" version = "0.20"
default-features = false
features = ["features"] features = ["features"]
[workspace.dependencies.toml] [workspace.dependencies.toml]
@@ -96,6 +97,7 @@ features = ["typed-header", "tracing"]
[workspace.dependencies.axum-server] [workspace.dependencies.axum-server]
version = "0.6.0" version = "0.6.0"
default-features = false
features = ["tls-rustls"] features = ["tls-rustls"]
[workspace.dependencies.axum-client-ip] [workspace.dependencies.axum-client-ip]
@@ -103,10 +105,12 @@ version = "0.6.0"
[workspace.dependencies.tower] [workspace.dependencies.tower]
version = "0.4.13" version = "0.4.13"
default-features = false
features = ["util"] features = ["util"]
[workspace.dependencies.tower-http] [workspace.dependencies.tower-http]
version = "0.5.2" version = "0.5.2"
default-features = false
features = [ features = [
"add-extension", "add-extension",
"cors", "cors",
@@ -129,10 +133,12 @@ features = [
[workspace.dependencies.serde] [workspace.dependencies.serde]
version = "1.0.204" version = "1.0.204"
default-features = false
features = ["rc"] features = ["rc"]
[workspace.dependencies.serde_json] [workspace.dependencies.serde_json]
version = "1.0.120" version = "1.0.121"
default-features = false
features = ["raw_value"] features = ["raw_value"]
# Used for appservice registration files # Used for appservice registration files
@@ -200,7 +206,8 @@ version = "0.3.30"
default-features = false default-features = false
[workspace.dependencies.tokio] [workspace.dependencies.tokio]
version = "1.39.1" version = "1.39.2"
default-features = false
features = [ features = [
"fs", "fs",
"net", "net",
@@ -221,6 +228,7 @@ version = "0.8.5"
# Validating urls in config, was already a transitive dependency # Validating urls in config, was already a transitive dependency
[workspace.dependencies.url] [workspace.dependencies.url]
version = "2.5.0" version = "2.5.0"
default-features = false
features = ["serde"] features = ["serde"]
# standard date and time tools # standard date and time tools
@@ -231,6 +239,7 @@ default-features = false
[workspace.dependencies.hyper] [workspace.dependencies.hyper]
version = "1.4.1" version = "1.4.1"
default-features = false
features = [ features = [
"server", "server",
"http1", "http1",
@@ -239,6 +248,7 @@ features = [
[workspace.dependencies.hyper-util] [workspace.dependencies.hyper-util]
version = "0.1.6" version = "0.1.6"
default-features = false
features = [ features = [
"client", "client",
"server-auto", "server-auto",
@@ -250,11 +260,13 @@ features = [
# to support multiple variations of setting a config option # to support multiple variations of setting a config option
[workspace.dependencies.either] [workspace.dependencies.either]
version = "1.11.0" version = "1.11.0"
default-features = false
features = ["serde"] features = ["serde"]
# Used for reading the configuration from conduwuit.toml & environment variables # Used for reading the configuration from conduwuit.toml & environment variables
[workspace.dependencies.figment] [workspace.dependencies.figment]
version = "0.10.18" version = "0.10.18"
default-features = false
features = ["env", "toml"] features = ["env", "toml"]
[workspace.dependencies.hickory-resolver] [workspace.dependencies.hickory-resolver]
@@ -396,10 +408,11 @@ default-features = false
features = ["use_std"] features = ["use_std"]
[workspace.dependencies.console-subscriber] [workspace.dependencies.console-subscriber]
version = "0.3" version = "0.4"
[workspace.dependencies.nix] [workspace.dependencies.nix]
version = "0.29.0" version = "0.29.0"
default-features = false
features = ["resource"] features = ["resource"]
[workspace.dependencies.sd-notify] [workspace.dependencies.sd-notify]
@@ -427,6 +440,7 @@ version = "0.1"
[workspace.dependencies.syn] [workspace.dependencies.syn]
version = "2.0.72" version = "2.0.72"
default-features = false
features = ["full", "extra-traits"] features = ["full", "extra-traits"]
[workspace.dependencies.quote] [workspace.dependencies.quote]
@@ -555,7 +569,17 @@ lto = "fat"
[profile.release-max-perf.build-override] [profile.release-max-perf.build-override]
inherits = "release-max-perf" inherits = "release-max-perf"
opt-level = 0 opt-level = 0
codegen-units = 32
#rustflags = [ #rustflags = [
# '-Crelocation-model=pic',
# '-Ctarget-feature=-crt-static',
# '-Clink-arg=-Wl,--no-gc-sections',
#]
[profile.release-max-perf.package.conduit_macros]
inherits = "release-max-perf.build-override"
#rustflags = [
# '-Crelocation-model=pic',
# '-Ctarget-feature=-crt-static', # '-Ctarget-feature=-crt-static',
#] #]
+7 -11
View File
@@ -7,7 +7,7 @@ use std::{
use api::client::validate_and_add_event_id; use api::client::validate_and_add_event_id;
use conduit::{ use conduit::{
debug, info, log, debug, debug_error, err, info, log,
log::{capture, Capture}, log::{capture, Capture},
utils, warn, Error, PduEvent, Result, utils, warn, Error, PduEvent, Result,
}; };
@@ -229,8 +229,7 @@ pub(super) async fn get_remote_pdu(
debug!("Attempting to fetch homeserver signing keys for {server}"); debug!("Attempting to fetch homeserver signing keys for {server}");
self.services self.services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map) .fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
.await .await
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
@@ -443,8 +442,7 @@ pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
self.services self.services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys([&value], &pub_key_map) .fetch_required_signing_keys([&value], &pub_key_map)
.await?; .await?;
@@ -562,8 +560,7 @@ pub(super) async fn force_set_room_state_from_server(
info!("Fetching required signing keys for all the state events we got"); info!("Fetching required signing keys for all the state events we got");
self.services self.services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map) .fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
.await?; .await?;
@@ -578,8 +575,8 @@ pub(super) async fn force_set_room_state_from_server(
}; };
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| { let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
warn!("Invalid PDU in fetching remote room state PDUs response: {} {:?}", e, value); debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
Error::BadServerResponse("Invalid PDU in send_join response.") err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
})?; })?;
self.services self.services
@@ -682,8 +679,7 @@ pub(super) async fn get_verify_keys(
let signature_ids: Vec<String> = Vec::new(); let signature_ids: Vec<String> = Vec::new();
let keys = self let keys = self
.services .services
.rooms .server_keys
.event_handler
.fetch_signing_keys_for_server(&server_name, signature_ids) .fetch_signing_keys_for_server(&server_name, signature_ids)
.await?; .await?;
-2
View File
@@ -47,7 +47,6 @@ hmac.workspace = true
http.workspace = true http.workspace = true
http-body-util.workspace = true http-body-util.workspace = true
hyper.workspace = true hyper.workspace = true
image.workspace = true
ipaddress.workspace = true ipaddress.workspace = true
jsonwebtoken.workspace = true jsonwebtoken.workspace = true
log.workspace = true log.workspace = true
@@ -60,7 +59,6 @@ serde.workspace = true
sha-1.workspace = true sha-1.workspace = true
tokio.workspace = true tokio.workspace = true
tracing.workspace = true tracing.workspace = true
webpage.workspace = true
[lints] [lints]
workspace = true workspace = true
+2 -3
View File
@@ -4,7 +4,7 @@ use std::{
}; };
use axum::extract::State; use axum::extract::State;
use conduit::{debug, utils, utils::math::continue_exponential_backoff_secs, Error, Result}; use conduit::{utils, utils::math::continue_exponential_backoff_secs, Err, Error, Result};
use futures_util::{stream::FuturesUnordered, StreamExt}; use futures_util::{stream::FuturesUnordered, StreamExt};
use ruma::{ use ruma::{
api::{ api::{
@@ -362,8 +362,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
const MIN: u64 = 5 * 60; const MIN: u64 = 5 * 60;
const MAX: u64 = 60 * 60 * 24; const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) { if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
debug!("Backing off query from {server:?}"); return (server, Err!(BadServerResponse("bad query from {server:?}, still backing off")));
return (server, Err(Error::BadServerResponse("bad query, still backing off")));
} }
} }
+19 -254
View File
@@ -1,21 +1,18 @@
#![allow(deprecated)] #![allow(deprecated)]
use std::{io::Cursor, time::Duration}; use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::{ use conduit::{
debug, debug_warn, error, debug_warn, err, error,
utils::{ utils::{
self, self,
content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename}, content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename},
math::ruma_from_usize, math::ruma_from_usize,
}, },
warn, Error, Result, warn, Err, Error, Result,
}; };
use image::io::Reader as ImgReader;
use ipaddress::IPAddress;
use reqwest::Url;
use ruma::api::client::{ use ruma::api::client::{
error::{ErrorKind, RetryAfter}, error::{ErrorKind, RetryAfter},
media::{ media::{
@@ -24,16 +21,12 @@ use ruma::api::client::{
}, },
}; };
use service::{ use service::{
media::{FileMeta, UrlPreviewData}, media::{FileMeta, MXC_LENGTH},
Services, Services,
}; };
use webpage::HTML;
use crate::{Ruma, RumaResponse}; use crate::{Ruma, RumaResponse};
/// generated MXC ID (`media-id`) length
const MXC_LENGTH: usize = 32;
/// Cache control for immutable objects /// Cache control for immutable objects
const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable"; const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable";
@@ -76,12 +69,13 @@ pub(crate) async fn get_media_preview_route(
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 url = &body.url; let url = &body.url;
if !url_preview_allowed(&services, url) { if !services.media.url_preview_allowed(url) {
warn!(%sender_user, "URL is not allowed to be previewed: {url}"); return Err!(Request(Forbidden(
return Err(Error::BadRequest(ErrorKind::forbidden(), "URL is not allowed to be previewed")); warn!(%sender_user, %url, "URL is not allowed to be previewed")
)));
} }
match get_url_preview(&services, url).await { match services.media.get_url_preview(url).await {
Ok(preview) => { Ok(preview) => {
let res = serde_json::value::to_raw_value(&preview).map_err(|e| { let res = serde_json::value::to_raw_value(&preview).map_err(|e| {
error!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}"); error!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}");
@@ -97,7 +91,6 @@ pub(crate) async fn get_media_preview_route(
}, },
Err(e) => { Err(e) => {
warn!(%sender_user, "Failed to generate a URL preview: {e}"); warn!(%sender_user, "Failed to generate a URL preview: {e}");
// there doesn't seem to be an agreed-upon error code in the spec. // there doesn't seem to be an agreed-upon error code in the spec.
// the only response codes in the preview_url spec page are 200 and 429. // the only response codes in the preview_url spec page are 200 and 429.
Err(Error::BadRequest( Err(Error::BadRequest(
@@ -229,10 +222,7 @@ pub(crate) async fn get_content_route(
body.timeout_ms, body.timeout_ms,
) )
.await .await
.map_err(|e| { .map_err(|e| err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))))?;
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Error::BadRequest(ErrorKind::NotFound, "Remote media error.")
})?;
let content_disposition = Some(make_content_disposition( let content_disposition = Some(make_content_disposition(
&response.content_type, &response.content_type,
@@ -248,7 +238,7 @@ pub(crate) async fn get_content_route(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()), cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
}) })
} else { } else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")) Err!(Request(NotFound("Media not found.")))
} }
} }
@@ -335,13 +325,10 @@ pub(crate) async fn get_content_as_filename_route(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
}) })
}, },
Err(e) => { Err(e) => Err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))),
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Err(Error::BadRequest(ErrorKind::NotFound, "Remote media error."))
},
} }
} else { } else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")) Err!(Request(NotFound("Media not found.")))
} }
} }
@@ -392,10 +379,10 @@ pub(crate) async fn get_content_thumbnail_route(
&mxc, &mxc,
body.width body.width
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?, .map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?,
body.height body.height
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Height is invalid."))?, .map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?,
) )
.await? .await?
{ {
@@ -418,7 +405,7 @@ pub(crate) async fn get_content_thumbnail_route(
// we'll lie to the client and say the blocked server's media was not found and // we'll lie to the client and say the blocked server's media was not found and
// log. the client has no way of telling anyways so this is a security bonus. // log. the client has no way of telling anyways so this is a security bonus.
debug_warn!("Received request for media `{}` on blocklisted server", mxc); debug_warn!("Received request for media `{}` on blocklisted server", mxc);
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")); return Err!(Request(NotFound("Media not found.")));
} }
match services match services
@@ -467,13 +454,10 @@ pub(crate) async fn get_content_thumbnail_route(
content_disposition, content_disposition,
}) })
}, },
Err(e) => { Err(e) => Err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))),
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Err(Error::BadRequest(ErrorKind::NotFound, "Remote media error."))
},
} }
} else { } else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")) Err!(Request(NotFound("Media not found.")))
} }
} }
@@ -511,7 +495,7 @@ async fn get_remote_content(
// we'll lie to the client and say the blocked server's media was not found and // we'll lie to the client and say the blocked server's media was not found and
// log. the client has no way of telling anyways so this is a security bonus. // log. the client has no way of telling anyways so this is a security bonus.
debug_warn!("Received request for media `{mxc}` on blocklisted server"); debug_warn!("Received request for media `{mxc}` on blocklisted server");
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")); return Err!(Request(NotFound("Media not found.")));
} }
let content_response = services let content_response = services
@@ -553,222 +537,3 @@ async fn get_remote_content(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()), cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
}) })
} }
async fn download_image(services: &Services, client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
let image = client.get(url).send().await?.bytes().await?;
let mxc = format!("mxc://{}/{}", services.globals.server_name(), utils::random_string(MXC_LENGTH));
services
.media
.create(None, &mxc, None, None, &image)
.await?;
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
Err(_) => (None, None),
Ok(reader) => match reader.into_dimensions() {
Err(_) => (None, None),
Ok((width, height)) => (Some(width), Some(height)),
},
};
Ok(UrlPreviewData {
image: Some(mxc),
image_size: Some(image.len()),
image_width: width,
image_height: height,
..Default::default()
})
}
async fn download_html(services: &Services, client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
let mut response = client.get(url).send().await?;
let mut bytes: Vec<u8> = Vec::new();
while let Some(chunk) = response.chunk().await? {
bytes.extend_from_slice(&chunk);
if bytes.len() > services.globals.url_preview_max_spider_size() {
debug!(
"Response body from URL {} exceeds url_preview_max_spider_size ({}), not processing the rest of the \
response body and assuming our necessary data is in this range.",
url,
services.globals.url_preview_max_spider_size()
);
break;
}
}
let body = String::from_utf8_lossy(&bytes);
let Ok(html) = HTML::from_string(body.to_string(), Some(url.to_owned())) else {
return Err(Error::BadRequest(ErrorKind::Unknown, "Failed to parse HTML"));
};
let mut data = match html.opengraph.images.first() {
None => UrlPreviewData::default(),
Some(obj) => download_image(services, client, &obj.url).await?,
};
let props = html.opengraph.properties;
/* use OpenGraph title/description, but fall back to HTML if not available */
data.title = props.get("title").cloned().or(html.title);
data.description = props.get("description").cloned().or(html.description);
Ok(data)
}
async fn request_url_preview(services: &Services, url: &str) -> Result<UrlPreviewData> {
if let Ok(ip) = IPAddress::parse(url) {
if !services.globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
}
}
let client = &services.client.url_preview;
let response = client.head(url).send().await?;
if let Some(remote_addr) = response.remote_addr() {
if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) {
if !services.globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
}
}
}
let Some(content_type) = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|x| x.to_str().ok())
else {
return Err(Error::BadRequest(ErrorKind::Unknown, "Unknown Content-Type"));
};
let data = match content_type {
html if html.starts_with("text/html") => download_html(services, client, url).await?,
img if img.starts_with("image/") => download_image(services, client, url).await?,
_ => return Err(Error::BadRequest(ErrorKind::Unknown, "Unsupported Content-Type")),
};
services.media.set_url_preview(url, &data).await?;
Ok(data)
}
async fn get_url_preview(services: &Services, url: &str) -> Result<UrlPreviewData> {
if let Some(preview) = services.media.get_url_preview(url).await {
return Ok(preview);
}
// ensure that only one request is made per URL
let _request_lock = services.media.url_preview_mutex.lock(url).await;
match services.media.get_url_preview(url).await {
Some(preview) => Ok(preview),
None => request_url_preview(services, url).await,
}
}
fn url_preview_allowed(services: &Services, url_str: &str) -> bool {
let url: Url = match Url::parse(url_str) {
Ok(u) => u,
Err(e) => {
warn!("Failed to parse URL from a str: {}", e);
return false;
},
};
if ["http", "https"]
.iter()
.all(|&scheme| scheme != url.scheme().to_lowercase())
{
debug!("Ignoring non-HTTP/HTTPS URL to preview: {}", url);
return false;
}
let host = match url.host_str() {
None => {
debug!("Ignoring URL preview for a URL that does not have a host (?): {}", url);
return false;
},
Some(h) => h.to_owned(),
};
let allowlist_domain_contains = services.globals.url_preview_domain_contains_allowlist();
let allowlist_domain_explicit = services.globals.url_preview_domain_explicit_allowlist();
let denylist_domain_explicit = services.globals.url_preview_domain_explicit_denylist();
let allowlist_url_contains = services.globals.url_preview_url_contains_allowlist();
if allowlist_domain_contains.contains(&"*".to_owned())
|| allowlist_domain_explicit.contains(&"*".to_owned())
|| allowlist_url_contains.contains(&"*".to_owned())
{
debug!("Config key contains * which is allowing all URL previews. Allowing URL {}", url);
return true;
}
if !host.is_empty() {
if denylist_domain_explicit.contains(&host) {
debug!(
"Host {} is not allowed by url_preview_domain_explicit_denylist (check 1/4)",
&host
);
return false;
}
if allowlist_domain_explicit.contains(&host) {
debug!("Host {} is allowed by url_preview_domain_explicit_allowlist (check 2/4)", &host);
return true;
}
if allowlist_domain_contains
.iter()
.any(|domain_s| domain_s.contains(&host.clone()))
{
debug!("Host {} is allowed by url_preview_domain_contains_allowlist (check 3/4)", &host);
return true;
}
if allowlist_url_contains
.iter()
.any(|url_s| url.to_string().contains(&url_s.to_string()))
{
debug!("URL {} is allowed by url_preview_url_contains_allowlist (check 4/4)", &host);
return true;
}
// check root domain if available and if user has root domain checks
if services.globals.url_preview_check_root_domain() {
debug!("Checking root domain");
match host.split_once('.') {
None => return false,
Some((_, root_domain)) => {
if denylist_domain_explicit.contains(&root_domain.to_owned()) {
debug!(
"Root domain {} is not allowed by url_preview_domain_explicit_denylist (check 1/3)",
&root_domain
);
return true;
}
if allowlist_domain_explicit.contains(&root_domain.to_owned()) {
debug!(
"Root domain {} is allowed by url_preview_domain_explicit_allowlist (check 2/3)",
&root_domain
);
return true;
}
if allowlist_domain_contains
.iter()
.any(|domain_s| domain_s.contains(&root_domain.to_owned()))
{
debug!(
"Root domain {} is allowed by url_preview_domain_contains_allowlist (check 3/3)",
&root_domain
);
return true;
}
},
}
}
}
false
}
+24 -29
View File
@@ -8,11 +8,11 @@ use std::{
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::{ use conduit::{
debug, debug_warn, error, info, debug, debug_error, debug_warn, err, error, info,
pdu::{gen_event_id_canonical_json, PduBuilder}, pdu::{gen_event_id_canonical_json, PduBuilder},
trace, utils, trace, utils,
utils::math::continue_exponential_backoff_secs, utils::math::continue_exponential_backoff_secs,
warn, Error, PduEvent, Result, warn, Err, Error, PduEvent, Result,
}; };
use ruma::{ use ruma::{
api::{ api::{
@@ -705,11 +705,11 @@ async fn join_room_by_id_helper_remote(
{ {
room_version room_version
}, },
_ => return Err(Error::BadServerResponse("Room version is not supported")), _ => return Err!(BadServerResponse("Room version is not supported")),
}; };
let mut join_event_stub: CanonicalJsonObject = serde_json::from_str(make_join_response.event.get()) let mut join_event_stub: CanonicalJsonObject = serde_json::from_str(make_join_response.event.get())
.map_err(|_| Error::BadServerResponse("Invalid make_join event json received from server."))?; .map_err(|e| err!(BadServerResponse("Invalid make_join event json received from server: {e:?}")))?;
let join_authorized_via_users_server = join_event_stub let join_authorized_via_users_server = join_event_stub
.get("content") .get("content")
@@ -876,15 +876,14 @@ async fn join_room_by_id_helper_remote(
info!("Parsing join event"); info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(event_id, join_event.clone()) let parsed_join_pdu = PduEvent::from_id_val(event_id, join_event.clone())
.map_err(|_| Error::BadServerResponse("Invalid join event PDU."))?; .map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?;
let mut state = HashMap::new(); let mut state = HashMap::new();
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
info!("Fetching join signing keys"); info!("Fetching join signing keys");
services services
.rooms .server_keys
.event_handler
.fetch_join_signing_keys(&send_join_response, &room_version_id, &pub_key_map) .fetch_join_signing_keys(&send_join_response, &room_version_id, &pub_key_map)
.await?; .await?;
@@ -900,8 +899,8 @@ async fn join_room_by_id_helper_remote(
}; };
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| { let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
warn!("Invalid PDU in send_join response: {} {:?}", e, value); debug_warn!("Invalid PDU in send_join response: {value:#?}");
Error::BadServerResponse("Invalid PDU in send_join response.") err!(BadServerResponse("Invalid PDU in send_join response: {e:?}"))
})?; })?;
services.rooms.outlier.add_pdu_outlier(&event_id, &value)?; services.rooms.outlier.add_pdu_outlier(&event_id, &value)?;
@@ -1123,10 +1122,10 @@ async fn join_room_by_id_helper_local(
{ {
room_version_id room_version_id
}, },
_ => return Err(Error::BadServerResponse("Room version is not supported")), _ => return Err!(BadServerResponse("Room version is not supported")),
}; };
let mut join_event_stub: CanonicalJsonObject = serde_json::from_str(make_join_response.event.get()) let mut join_event_stub: CanonicalJsonObject = serde_json::from_str(make_join_response.event.get())
.map_err(|_| Error::BadServerResponse("Invalid make_join event json received from server."))?; .map_err(|e| err!(BadServerResponse("Invalid make_join event json received from server: {e:?}")))?;
let join_authorized_via_users_server = join_event_stub let join_authorized_via_users_server = join_event_stub
.get("content") .get("content")
.map(|s| { .map(|s| {
@@ -1230,8 +1229,7 @@ async fn join_room_by_id_helper_local(
drop(state_lock); drop(state_lock);
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
services services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys([&signed_value], &pub_key_map) .fetch_required_signing_keys([&signed_value], &pub_key_map)
.await?; .await?;
services services
@@ -1252,7 +1250,7 @@ async fn join_room_by_id_helper_local(
async fn make_join_request( async fn make_join_request(
services: &Services, sender_user: &UserId, room_id: &RoomId, servers: &[OwnedServerName], services: &Services, sender_user: &UserId, room_id: &RoomId, servers: &[OwnedServerName],
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> { ) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
let mut make_join_response_and_server = Err(Error::BadServerResponse("No server available to assist in joining.")); let mut make_join_response_and_server = Err!(BadServerResponse("No server available to assist in joining."));
let mut make_join_counter: u16 = 0; let mut make_join_counter: u16 = 0;
let mut incompatible_room_version_count: u8 = 0; let mut incompatible_room_version_count: u8 = 0;
@@ -1293,8 +1291,7 @@ async fn make_join_request(
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or M_UNSUPPORTED_ROOM_VERSION, \ "15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or M_UNSUPPORTED_ROOM_VERSION, \
assuming that Conduwuit does not support the room {room_id}: {e}" assuming that Conduwuit does not support the room {room_id}: {e}"
); );
make_join_response_and_server = make_join_response_and_server = Err!(BadServerResponse("Room version is not supported by Conduwuit"));
Err(Error::BadServerResponse("Room version is not supported by Conduwuit"));
return make_join_response_and_server; return make_join_response_and_server;
} }
@@ -1302,8 +1299,7 @@ async fn make_join_request(
warn!( warn!(
"50 servers failed to provide valid make_join response, assuming no server can assist in joining." "50 servers failed to provide valid make_join response, assuming no server can assist in joining."
); );
make_join_response_and_server = make_join_response_and_server = Err!(BadServerResponse("No server available to assist in joining."));
Err(Error::BadServerResponse("No server available to assist in joining."));
return make_join_response_and_server; return make_join_response_and_server;
} }
} }
@@ -1323,8 +1319,8 @@ pub async fn validate_and_add_event_id(
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>, pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
) -> Result<(OwnedEventId, CanonicalJsonObject)> { ) -> Result<(OwnedEventId, CanonicalJsonObject)> {
let mut value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| { let mut value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
error!("Invalid PDU in server response: {:?}: {:?}", pdu, e); debug_error!("Invalid PDU in server response: {pdu:#?}");
Error::BadServerResponse("Invalid PDU in server response") err!(BadServerResponse("Invalid PDU in server response: {e:?}"))
})?; })?;
let event_id = EventId::parse(format!( let event_id = EventId::parse(format!(
"${}", "${}",
@@ -1360,15 +1356,15 @@ pub async fn validate_and_add_event_id(
const MIN: u64 = 60 * 5; const MIN: u64 = 60 * 5;
const MAX: u64 = 60 * 60 * 24; const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) { if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
debug!("Backing off from {event_id}"); return Err!(BadServerResponse("bad event {event_id:?}, still backing off"));
return Err(Error::BadServerResponse("bad event, still backing off"));
} }
} }
if let Err(e) = ruma::signatures::verify_event(&*pub_key_map.read().await, &value, room_version) { if let Err(e) = ruma::signatures::verify_event(&*pub_key_map.read().await, &value, room_version) {
warn!("Event {} failed verification {:?} {}", event_id, pdu, e); debug_error!("Event {event_id} failed verification {pdu:#?}");
let e = Err!(BadServerResponse(debug_error!("Event {event_id} failed verification: {e:?}")));
back_off(event_id).await; back_off(event_id).await;
return Err(Error::BadServerResponse("Event failed verification.")); return e;
} }
value.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.as_str().to_owned())); value.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.as_str().to_owned()));
@@ -1474,8 +1470,7 @@ pub(crate) async fn invite_helper(
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Origin field is invalid."))?; .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Origin field is invalid."))?;
services services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys([&value], &pub_key_map) .fetch_required_signing_keys([&value], &pub_key_map)
.await?; .await?;
@@ -1651,7 +1646,7 @@ pub async fn leave_room(services: &Services, user_id: &UserId, room_id: &RoomId,
} }
async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<()> { async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<()> {
let mut make_leave_response_and_server = Err(Error::BadServerResponse("No server available to assist in leaving.")); let mut make_leave_response_and_server = Err!(BadServerResponse("No server available to assist in leaving."));
let invite_state = services let invite_state = services
.rooms .rooms
@@ -1708,11 +1703,11 @@ async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &Room
{ {
version version
}, },
_ => return Err(Error::BadServerResponse("Room version is not supported")), _ => return Err!(BadServerResponse("Room version is not supported")),
}; };
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(make_leave_response.event.get()) let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(make_leave_response.event.get())
.map_err(|_| Error::BadServerResponse("Invalid make_leave event json received from server."))?; .map_err(|e| err!(BadServerResponse("Invalid make_leave event json received from server: {e:?}")))?;
// TODO: Is origin needed? // TODO: Is origin needed?
leave_event_stub.insert( leave_event_stub.insert(
+1 -2
View File
@@ -10,8 +10,7 @@ extern crate conduit_service as service;
pub(crate) use conduit::{debug_info, pdu::PduEvent, utils, Error, Result}; pub(crate) use conduit::{debug_info, pdu::PduEvent, utils, Error, Result};
pub(crate) use service::services; pub(crate) use service::services;
pub use crate::router::State; pub(crate) use self::router::{Ruma, RumaResponse, State};
pub(crate) use crate::router::{Ruma, RumaResponse};
conduit::mod_ctor! {} conduit::mod_ctor! {}
conduit::mod_dtor! {} conduit::mod_dtor! {}
+2 -5
View File
@@ -3,8 +3,7 @@ mod auth;
mod handler; mod handler;
mod request; mod request;
mod response; mod response;
pub mod state;
use std::sync::Arc;
use axum::{ use axum::{
response::IntoResponse, response::IntoResponse,
@@ -15,11 +14,9 @@ use conduit::{err, Server};
use http::Uri; use http::Uri;
use self::handler::RouterExt; use self::handler::RouterExt;
pub(super) use self::{args::Args as Ruma, response::RumaResponse}; pub(super) use self::{args::Args as Ruma, response::RumaResponse, state::State};
use crate::{client, server}; use crate::{client, server};
pub type State = Arc<service::Services>;
pub fn build(router: Router<State>, server: &Server) -> Router<State> { pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config; let config = &server.config;
let router = router let router = router
+1 -2
View File
@@ -250,8 +250,7 @@ async fn auth_server(
}; };
let keys_result = services let keys_result = services
.rooms .server_keys
.event_handler
.fetch_signing_keys_for_server(origin, vec![x_matrix.key.to_string()]) .fetch_signing_keys_for_server(origin, vec![x_matrix.key.to_string()])
.await; .await;
+79
View File
@@ -0,0 +1,79 @@
use std::{ops::Deref, sync::Arc};
use conduit_service::Services;
#[derive(Clone, Copy)]
pub struct State {
services: *const Services,
}
pub struct Guard {
services: Arc<Services>,
}
pub fn create(services: Arc<Services>) -> (State, Guard) {
let state = State {
services: Arc::into_raw(services.clone()),
};
let guard = Guard {
services,
};
(state, guard)
}
impl Drop for Guard {
fn drop(&mut self) {
let ptr = Arc::as_ptr(&self.services);
// SAFETY: Parity with Arc::into_raw() called in create(). This revivifies the
// Arc lost to State so it can be dropped, otherwise Services will leak.
let arc = unsafe { Arc::from_raw(ptr) };
debug_assert!(
Arc::strong_count(&arc) > 1,
"Services usually has more than one reference and is not dropped here"
);
}
}
impl Deref for State {
type Target = Services;
fn deref(&self) -> &Self::Target {
deref(&self.services).expect("dereferenced Services pointer in State must not be null")
}
}
/// SAFETY: State is a thin wrapper containing a raw const pointer to Services
/// in lieu of an Arc. Services is internally threadsafe. If State contains
/// additional fields this notice should be reevaluated.
unsafe impl Send for State {}
/// SAFETY: State is a thin wrapper containing a raw const pointer to Services
/// in lieu of an Arc. Services is internally threadsafe. If State contains
/// additional fields this notice should be reevaluated.
unsafe impl Sync for State {}
fn deref(services: &*const Services) -> Option<&Services> {
// SAFETY: We replaced Arc<Services> with *const Services in State. This is
// worth about 10 clones (20 reference count updates) for each request handled.
// Though this is not an incredibly large quantity, it's woefully unnecessary
// given the context as explained below; though it is not currently known to be
// a performance bottleneck, the front-line position justifies preempting it.
//
// Services is created prior to the axum/tower stack and Router, and prior
// to serving any requests through the handlers in this crate. It is then
// dropped only after all requests have completed, the listening sockets
// have been closed, axum/tower has been dropped. Thus Services is
// expected to live at least as long as any instance of State, making the
// constant updates to the prior Arc unnecessary to keep Services alive.
//
// Nevertheless if it is possible to accomplish this by annotating State
// with a lifetime to hold a reference (and be aware I have made a
// significant effort trying to make this work) this unsafety may not be
// necessary. It is either very difficult or impossible to get a
// lifetime'ed reference through Router / RumaHandler; though it is
// possible to pass a reference through axum's `with_state()` in trivial
// configurations as the only requirement of a State is Clone.
unsafe { services.as_ref() }
}
+1 -2
View File
@@ -105,8 +105,7 @@ async fn handle_pdus(
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
if !parsed_pdus.is_empty() { if !parsed_pdus.is_empty() {
services services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys(parsed_pdus.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map) .fetch_required_signing_keys(parsed_pdus.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
.await .await
.unwrap_or_else(|e| warn!("Could not fetch all signatures for PDUs from {origin}: {e:?}")); .unwrap_or_else(|e| warn!("Could not fetch all signatures for PDUs from {origin}: {e:?}"));
+1 -2
View File
@@ -138,8 +138,7 @@ async fn create_join_event(
} }
services services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys([&value], &pub_key_map) .fetch_required_signing_keys([&value], &pub_key_map)
.await?; .await?;
+1 -2
View File
@@ -150,8 +150,7 @@ async fn create_leave_event(
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "origin is not a server name."))?; .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "origin is not a server name."))?;
services services
.rooms .server_keys
.event_handler
.fetch_required_signing_keys([&value], &pub_key_map) .fetch_required_signing_keys([&value], &pub_key_map)
.await?; .await?;
+1 -1
View File
@@ -287,7 +287,7 @@ pub struct Config {
#[serde(default = "true_fn")] #[serde(default = "true_fn")]
pub media_startup_check: bool, pub media_startup_check: bool,
#[serde(default = "true_fn")] #[serde(default)]
pub media_compat_file_link: bool, pub media_compat_file_link: bool,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
pub prevent_media_downloads_from: Vec<OwnedServerName>, pub prevent_media_downloads_from: Vec<OwnedServerName>,
+5 -1
View File
@@ -14,7 +14,7 @@ pub use crate::utils::debug::*;
#[macro_export] #[macro_export]
macro_rules! debug_event { macro_rules! debug_event {
( $level:expr, $($x:tt)+ ) => { ( $level:expr, $($x:tt)+ ) => {
if cfg!(debug_assertions) && cfg!(not(feature = "dev_release_log_level")) { if $crate::debug::logging() {
::tracing::event!( $level, $($x)+ ) ::tracing::event!( $level, $($x)+ )
} else { } else {
::tracing::debug!( $($x)+ ) ::tracing::debug!( $($x)+ )
@@ -88,3 +88,7 @@ pub fn panic_str(p: &Box<dyn Any + Send>) -> &'static str { p.downcast_ref::<&st
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn type_name<T>(_: &T) -> &'static str { std::any::type_name::<T>() } pub fn type_name<T>(_: &T) -> &'static str { std::any::type_name::<T>() }
#[must_use]
#[inline]
pub const fn logging() -> bool { cfg!(debug_assertions) && cfg!(not(feature = "dev_release_log_level")) }
+114 -25
View File
@@ -41,60 +41,149 @@ macro_rules! Err {
#[macro_export] #[macro_export]
macro_rules! err { macro_rules! err {
(Config($item:literal, $($args:expr),*)) => {{ (Request(Forbidden($level:ident!($($args:tt)+)))) => {{
$crate::error!(config = %$item, $($args),*); let mut buf = String::new();
$crate::error::Error::Config($item, $crate::format_maybe!($($args),*))
}};
(Request(Forbidden($level:ident!($($args:expr),*)))) => {{
$crate::$level!($($args),*);
$crate::error::Error::Request( $crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::forbidden(), ::ruma::api::client::error::ErrorKind::forbidden(),
$crate::format_maybe!($($args),*), $crate::err_log!(buf, $level, $($args)+),
::http::StatusCode::BAD_REQUEST ::http::StatusCode::BAD_REQUEST
) )
}}; }};
(Request(Forbidden($($args:expr),*))) => { (Request(Forbidden($($args:tt)+))) => {
$crate::error::Error::Request( $crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::forbidden(), ::ruma::api::client::error::ErrorKind::forbidden(),
$crate::format_maybe!($($args),*), $crate::format_maybe!($($args)+),
::http::StatusCode::BAD_REQUEST ::http::StatusCode::BAD_REQUEST
) )
}; };
(Request($variant:ident($level:ident!($($args:expr),*)))) => {{ (Request($variant:ident($level:ident!($($args:tt)+)))) => {{
$crate::$level!($($args),*); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::$variant, ::ruma::api::client::error::ErrorKind::$variant,
$crate::format_maybe!($($args),*), $crate::err_log!(buf, $level, $($args)+),
::http::StatusCode::BAD_REQUEST ::http::StatusCode::BAD_REQUEST
) )
}}; }};
(Request($variant:ident($($args:expr),*))) => { (Request($variant:ident($($args:tt)+))) => {
$crate::error::Error::Request( $crate::error::Error::Request(
::ruma::api::client::error::ErrorKind::$variant, ::ruma::api::client::error::ErrorKind::$variant,
$crate::format_maybe!($($args),*), $crate::format_maybe!($($args)+),
::http::StatusCode::BAD_REQUEST ::http::StatusCode::BAD_REQUEST
) )
}; };
($variant:ident($level:ident!($($args:expr),*))) => {{ (Config($item:literal, $($args:tt)+)) => {{
$crate::$level!($($args),*); let mut buf = String::new();
$crate::error::Error::$variant($crate::format_maybe!($($args),*)) $crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+))
}}; }};
($variant:ident($($args:expr),*)) => { ($variant:ident($level:ident!($($args:tt)+))) => {{
$crate::error::Error::$variant($crate::format_maybe!($($args),*)) let mut buf = String::new();
$crate::error::Error::$variant($crate::err_log!(buf, $level, $($args)+))
}};
($variant:ident($($args:tt)+)) => {
$crate::error::Error::$variant($crate::format_maybe!($($args)+))
}; };
($level:ident!($($args:expr),*)) => {{ ($level:ident!($($args:tt)+)) => {{
$crate::$level!($($args),*); let mut buf = String::new();
$crate::error::Error::Err($crate::format_maybe!($($args),*)) $crate::error::Error::Err($crate::err_log!(buf, $level, $($args)+))
}}; }};
($($args:expr),*) => { ($($args:tt)+) => {
$crate::error::Error::Err($crate::format_maybe!($($args),*)) $crate::error::Error::Err($crate::format_maybe!($($args)+))
};
}
/// A trinity of integration between tracing, logging, and Error. This is a
/// customization of tracing::event! with the primary purpose of sharing the
/// error string, fieldset parsing and formatting. An added benefit is that we
/// can share the same callsite metadata for the source of our Error and the
/// associated logging and tracing event dispatches.
#[macro_export]
macro_rules! err_log {
($out:ident, $level:ident, $($fields:tt)+) => {{
use std::{fmt, fmt::Write};
use ::tracing::{
callsite, callsite2, level_enabled, metadata, valueset, Callsite, Event, __macro_support,
__tracing_log,
field::{Field, ValueSet, Visit},
Level,
};
const LEVEL: Level = $crate::err_lev!($level);
static __CALLSITE: callsite::DefaultCallsite = callsite2! {
name: std::concat! {
"event ",
std::file!(),
":",
std::line!(),
},
kind: metadata::Kind::EVENT,
target: std::module_path!(),
level: LEVEL,
fields: $($fields)+,
};
let visit = &mut |vs: ValueSet<'_>| {
struct Visitor<'a>(&'a mut String);
impl Visit for Visitor<'_> {
fn record_debug(&mut self, field: &Field, val: &dyn fmt::Debug) {
if field.name() == "message" {
write!(self.0, "{:?}", val).expect("stream error");
} else {
write!(self.0, " {}={:?}", field.name(), val).expect("stream error");
}
}
}
let meta = __CALLSITE.metadata();
let enabled = level_enabled!(LEVEL) && {
let interest = __CALLSITE.interest();
!interest.is_never() && __macro_support::__is_enabled(meta, interest)
};
if enabled {
Event::dispatch(meta, &vs);
}
__tracing_log!(LEVEL, __CALLSITE, &vs);
vs.record(&mut Visitor(&mut $out));
};
(visit)(valueset!(__CALLSITE.metadata().fields(), $($fields)+));
($out).into()
}}
}
#[macro_export]
macro_rules! err_lev {
(debug_warn) => {
if $crate::debug::logging() {
::tracing::Level::WARN
} else {
::tracing::Level::DEBUG
}
};
(debug_error) => {
if $crate::debug::logging() {
::tracing::Level::ERROR
} else {
::tracing::Level::DEBUG
}
};
(warn) => {
::tracing::Level::WARN
};
(error) => {
::tracing::Level::ERROR
}; };
} }
+3 -4
View File
@@ -5,8 +5,7 @@ mod response;
use std::{any::Any, borrow::Cow, convert::Infallible, fmt}; use std::{any::Any, borrow::Cow, convert::Infallible, fmt};
pub use log::*; pub use self::log::*;
use crate::error; use crate::error;
#[derive(thiserror::Error)] #[derive(thiserror::Error)]
@@ -87,13 +86,13 @@ pub enum Error {
// conduwuit // conduwuit
#[error("Arithmetic operation failed: {0}")] #[error("Arithmetic operation failed: {0}")]
Arithmetic(Cow<'static, str>), Arithmetic(Cow<'static, str>),
#[error("{0}")]
BadServerResponse(Cow<'static, str>),
#[error("There was a problem with the '{0}' directive in your configuration: {1}")] #[error("There was a problem with the '{0}' directive in your configuration: {1}")]
Config(&'static str, Cow<'static, str>), Config(&'static str, Cow<'static, str>),
#[error("{0}")] #[error("{0}")]
Database(Cow<'static, str>), Database(Cow<'static, str>),
#[error("{0}")] #[error("{0}")]
BadServerResponse(&'static str),
#[error("{0}")]
Conflict(&'static str), // This is only needed for when a room alias already exists Conflict(&'static str), // This is only needed for when a room alias already exists
// unique / untyped // unique / untyped
+1 -1
View File
@@ -16,7 +16,7 @@ pub use error::Error;
pub use info::{rustc_flags_capture, version, version::version}; pub use info::{rustc_flags_capture, version, version::version};
pub use pdu::{PduBuilder, PduCount, PduEvent}; pub use pdu::{PduBuilder, PduCount, PduEvent};
pub use server::Server; pub use server::Server;
pub use utils::{ctor, dtor}; pub use utils::{ctor, dtor, implement};
pub use crate as conduit_core; pub use crate as conduit_core;
+3 -5
View File
@@ -23,7 +23,7 @@ use serde_json::{
value::{to_raw_value, RawValue as RawJsonValue}, value::{to_raw_value, RawValue as RawJsonValue},
}; };
use crate::{warn, Error}; use crate::{err, warn, Error};
#[derive(Deserialize)] #[derive(Deserialize)]
struct ExtractRedactedBecause { struct ExtractRedactedBecause {
@@ -396,10 +396,8 @@ impl Ord for PduEvent {
pub fn gen_event_id_canonical_json( pub fn gen_event_id_canonical_json(
pdu: &RawJsonValue, room_version_id: &RoomVersionId, pdu: &RawJsonValue, room_version_id: &RoomVersionId,
) -> crate::Result<(OwnedEventId, CanonicalJsonObject)> { ) -> crate::Result<(OwnedEventId, CanonicalJsonObject)> {
let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| { let value: CanonicalJsonObject = serde_json::from_str(pdu.get())
warn!("Error parsing incoming event {:?}: {:?}", pdu, e); .map_err(|e| err!(BadServerResponse(warn!("Error parsing incoming event: {e:?}"))))?;
Error::BadServerResponse("Invalid PDU in server response")
})?;
let event_id = format!( let event_id = format!(
"${}", "${}",
+1
View File
@@ -17,6 +17,7 @@ use std::cmp::{self, Ordering};
pub use ::ctor::{ctor, dtor}; pub use ::ctor::{ctor, dtor};
pub use bytes::{increment, u64_from_bytes, u64_from_u8, u64_from_u8x8}; pub use bytes::{increment, u64_from_bytes, u64_from_u8, u64_from_u8x8};
pub use conduit_macros::implement;
pub use debug::slice_truncated as debug_slice_truncated; pub use debug::slice_truncated as debug_slice_truncated;
pub use hash::calculate_hash; pub use hash::calculate_hash;
pub use html::Escape as HtmlEscape; pub use html::Escape as HtmlEscape;
+7 -3
View File
@@ -8,12 +8,12 @@ pub const EMPTY: &str = "";
/// arguments are provided the first is assumed to be a format string. /// arguments are provided the first is assumed to be a format string.
#[macro_export] #[macro_export]
macro_rules! format_maybe { macro_rules! format_maybe {
($s:literal) => { ($s:literal $(,)?) => {
if $crate::is_format!($s) { std::format!($s).into() } else { $s.into() } if $crate::is_format!($s) { std::format!($s).into() } else { $s.into() }
}; };
($($args:expr),*) => { ($s:literal, $($args:tt)+) => {
std::format!($($args),*).into() std::format!($s, $($args)+).into()
}; };
} }
@@ -24,6 +24,10 @@ macro_rules! is_format {
($s:literal) => { ($s:literal) => {
::const_str::contains!($s, "{") && ::const_str::contains!($s, "}") ::const_str::contains!($s, "{") && ::const_str::contains!($s, "}")
}; };
($($s:tt)+) => {
false
};
} }
#[inline] #[inline]
+6 -4
View File
@@ -6,6 +6,7 @@ use axum::{
}; };
use axum_client_ip::SecureClientIpSource; use axum_client_ip::SecureClientIpSource;
use conduit::{error, Result, Server}; use conduit::{error, Result, Server};
use conduit_api::router::state::Guard;
use conduit_service::Services; use conduit_service::Services;
use http::{ use http::{
header::{self, HeaderName}, header::{self, HeaderName},
@@ -35,7 +36,7 @@ const CONDUWUIT_CSP: &[&str] = &[
const CONDUWUIT_PERMISSIONS_POLICY: &[&str] = &["interest-cohort=()", "browsing-topics=()"]; const CONDUWUIT_PERMISSIONS_POLICY: &[&str] = &["interest-cohort=()", "browsing-topics=()"];
pub(crate) fn build(services: &Arc<Services>) -> Result<Router> { pub(crate) fn build(services: &Arc<Services>) -> Result<(Router, Guard)> {
let server = &services.server; let server = &services.server;
let layers = ServiceBuilder::new(); let layers = ServiceBuilder::new();
@@ -47,7 +48,7 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<Router> {
let layers = layers let layers = layers
.sensitive_headers([header::AUTHORIZATION]) .sensitive_headers([header::AUTHORIZATION])
.layer(axum::middleware::from_fn_with_state(Arc::clone(server), request::spawn)) .layer(axum::middleware::from_fn_with_state(Arc::clone(services), request::spawn))
.layer( .layer(
TraceLayer::new_for_http() TraceLayer::new_for_http()
.make_span_with(tracing_span::<_>) .make_span_with(tracing_span::<_>)
@@ -55,7 +56,7 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<Router> {
.on_request(DefaultOnRequest::new().level(Level::TRACE)) .on_request(DefaultOnRequest::new().level(Level::TRACE))
.on_response(DefaultOnResponse::new().level(Level::DEBUG)), .on_response(DefaultOnResponse::new().level(Level::DEBUG)),
) )
.layer(axum::middleware::from_fn_with_state(Arc::clone(server), request::handle)) .layer(axum::middleware::from_fn_with_state(Arc::clone(services), request::handle))
.layer(SecureClientIpSource::ConnectInfo.into_extension()) .layer(SecureClientIpSource::ConnectInfo.into_extension())
.layer(SetResponseHeaderLayer::if_not_present( .layer(SetResponseHeaderLayer::if_not_present(
HeaderName::from_static("origin-agent-cluster"), // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster HeaderName::from_static("origin-agent-cluster"), // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster
@@ -85,7 +86,8 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<Router> {
.layer(body_limit_layer(server)) .layer(body_limit_layer(server))
.layer(CatchPanicLayer::custom(catch_panic)); .layer(CatchPanicLayer::custom(catch_panic));
Ok(router::build(services).layer(layers)) let (router, guard) = router::build(services);
Ok((router.layer(layers), guard))
} }
#[cfg(any(feature = "zstd_compression", feature = "gzip_compression", feature = "brotli_compression"))] #[cfg(any(feature = "zstd_compression", feature = "gzip_compression", feature = "brotli_compression"))]
+6 -3
View File
@@ -4,13 +4,15 @@ use axum::{
extract::State, extract::State,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use conduit::{debug, debug_error, debug_warn, defer, err, error, trace, Result, Server}; use conduit::{debug, debug_error, debug_warn, defer, err, error, trace, Result};
use conduit_service::Services;
use http::{Method, StatusCode, Uri}; use http::{Method, StatusCode, Uri};
#[tracing::instrument(skip_all, level = "debug")] #[tracing::instrument(skip_all, level = "debug")]
pub(crate) async fn spawn( pub(crate) async fn spawn(
State(server): State<Arc<Server>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next, State(services): State<Arc<Services>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next,
) -> Result<Response, StatusCode> { ) -> Result<Response, StatusCode> {
let server = &services.server;
if !server.running() { if !server.running() {
debug_warn!("unavailable pending shutdown"); debug_warn!("unavailable pending shutdown");
return Err(StatusCode::SERVICE_UNAVAILABLE); return Err(StatusCode::SERVICE_UNAVAILABLE);
@@ -34,8 +36,9 @@ pub(crate) async fn spawn(
#[tracing::instrument(skip_all, name = "handle")] #[tracing::instrument(skip_all, name = "handle")]
pub(crate) async fn handle( pub(crate) async fn handle(
State(server): State<Arc<Server>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next, State(services): State<Arc<Services>>, req: http::Request<axum::body::Body>, next: axum::middleware::Next,
) -> Result<Response, StatusCode> { ) -> Result<Response, StatusCode> {
let server = &services.server;
if !server.running() { if !server.running() {
debug_warn!( debug_warn!(
method = %req.method(), method = %req.method(),
+8 -7
View File
@@ -2,19 +2,20 @@ use std::sync::Arc;
use axum::{response::IntoResponse, routing::get, Router}; use axum::{response::IntoResponse, routing::get, Router};
use conduit::Error; use conduit::Error;
use conduit_api::State; use conduit_api::router::{state, state::Guard};
use conduit_service::Services; use conduit_service::Services;
use http::{StatusCode, Uri}; use http::{StatusCode, Uri};
use ruma::api::client::error::ErrorKind; use ruma::api::client::error::ErrorKind;
pub(crate) fn build(services: &Arc<Services>) -> Router { pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
let router = Router::<State>::new(); let router = Router::<state::State>::new();
let state = services.clone(); let (state, guard) = state::create(services.clone());
let router = conduit_api::router::build(router, &services.server)
conduit_api::router::build(router, &services.server)
.route("/", get(it_works)) .route("/", get(it_works))
.fallback(not_found) .fallback(not_found)
.with_state(state) .with_state(state);
(router, guard)
} }
async fn not_found(_uri: Uri) -> impl IntoResponse { async fn not_found(_uri: Uri) -> impl IntoResponse {
+1 -1
View File
@@ -18,7 +18,7 @@ pub(super) async fn serve(
let server = &services.server; let server = &services.server;
let config = &server.config; let config = &server.config;
let addrs = config.get_bind_addrs(); let addrs = config.get_bind_addrs();
let app = layers::build(&services)?; let (app, _guard) = layers::build(&services)?;
if cfg!(unix) && config.unix_socket_path.is_some() { if cfg!(unix) && config.unix_socket_path.is_some() {
unix::serve(server, app, shutdown).await unix::serve(server, app, shutdown).await
+1
View File
@@ -69,6 +69,7 @@ termimad.optional = true
tokio.workspace = true tokio.workspace = true
tracing.workspace = true tracing.workspace = true
url.workspace = true url.workspace = true
webpage.workspace = true
[lints] [lints]
workspace = true workspace = true
+29 -1
View File
@@ -743,6 +743,7 @@ async fn db_lt_13(services: &Services) -> Result<()> {
/// again. /// again.
async fn migrate_sha256_media(services: &Services) -> Result<()> { async fn migrate_sha256_media(services: &Services) -> Result<()> {
let db = &services.db; let db = &services.db;
let config = &services.server.config;
warn!("Migrating legacy base64 file names to sha256 file names"); warn!("Migrating legacy base64 file names to sha256 file names");
let mediaid_file = &db["mediaid_file"]; let mediaid_file = &db["mediaid_file"];
@@ -759,9 +760,11 @@ async fn migrate_sha256_media(services: &Services) -> Result<()> {
for (old_path, path) in changes { for (old_path, path) in changes {
if old_path.exists() { if old_path.exists() {
tokio::fs::rename(&old_path, &path).await?; tokio::fs::rename(&old_path, &path).await?;
if config.media_compat_file_link {
tokio::fs::symlink(&path, &old_path).await?; tokio::fs::symlink(&path, &old_path).await?;
} }
} }
}
// Apply fix from when sha256_media was backward-incompat and bumped the schema // Apply fix from when sha256_media was backward-incompat and bumped the schema
// version from 13 to 14. For users satisfying these conditions we can go back. // version from 13 to 14. For users satisfying these conditions we can go back.
@@ -822,8 +825,14 @@ async fn handle_media_check(
let (mediaid_file, mediaid_user) = dbs; let (mediaid_file, mediaid_user) = dbs;
let old_exists = files.contains(old_path);
let new_exists = files.contains(new_path); let new_exists = files.contains(new_path);
let old_exists = files.contains(old_path);
let old_is_symlink = || async {
tokio::fs::symlink_metadata(old_path)
.await
.map_or(false, |md| md.is_symlink())
};
if !old_exists && !new_exists { if !old_exists && !new_exists {
error!( error!(
media_id = ?encode_key(key), ?new_path, ?old_path, media_id = ?encode_key(key), ?new_path, ?old_path,
@@ -849,10 +858,29 @@ async fn handle_media_check(
"Legacy media found without sha256 migration. Fixing..." "Legacy media found without sha256 migration. Fixing..."
); );
debug_assert!(
old_is_symlink().await,
"Legacy media not expected to be a symlink without an existing sha256 migration."
);
tokio::fs::rename(&old_path, &new_path).await?; tokio::fs::rename(&old_path, &new_path).await?;
tokio::fs::symlink(&new_path, &old_path).await?; tokio::fs::symlink(&new_path, &old_path).await?;
} }
if !config.media_compat_file_link && old_exists && old_is_symlink().await {
debug_warn!(
media_id = ?encode_key(key), ?new_path, ?old_path,
"Legacy link found but compat disabled. Cleansing symlink..."
);
debug_assert!(
new_exists,
"sha256 migration into new file expected prior to cleaning legacy symlink here."
);
tokio::fs::remove_file(&old_path).await?;
}
Ok(()) Ok(())
} }
+1 -1
View File
@@ -4,7 +4,7 @@ use conduit::{debug, debug_info, utils::string_from_bytes, Error, Result};
use database::{Database, Map}; use database::{Database, Map};
use ruma::api::client::error::ErrorKind; use ruma::api::client::error::ErrorKind;
use crate::media::UrlPreviewData; use super::preview::UrlPreviewData;
pub(crate) struct Data { pub(crate) struct Data {
mediaid_file: Arc<Map>, mediaid_file: Arc<Map>,
+20 -42
View File
@@ -1,4 +1,5 @@
mod data; mod data;
mod preview;
mod tests; mod tests;
mod thumbnail; mod thumbnail;
@@ -6,16 +7,15 @@ use std::{path::PathBuf, sync::Arc, time::SystemTime};
use async_trait::async_trait; use async_trait::async_trait;
use base64::{engine::general_purpose, Engine as _}; use base64::{engine::general_purpose, Engine as _};
use conduit::{debug, debug_error, err, error, utils, utils::MutexMap, Err, Result, Server}; use conduit::{debug, debug_error, err, error, trace, utils, utils::MutexMap, Err, Result, Server};
use data::{Data, Metadata}; use data::{Data, Metadata};
use ruma::{OwnedMxcUri, OwnedUserId}; use ruma::{OwnedMxcUri, OwnedUserId};
use serde::Serialize;
use tokio::{ use tokio::{
fs, fs,
io::{AsyncReadExt, AsyncWriteExt, BufReader}, io::{AsyncReadExt, AsyncWriteExt, BufReader},
}; };
use crate::{globals, Dep}; use crate::{client, globals, Dep};
#[derive(Debug)] #[derive(Debug)]
pub struct FileMeta { pub struct FileMeta {
@@ -24,43 +24,32 @@ pub struct FileMeta {
pub content_disposition: Option<String>, pub content_disposition: Option<String>,
} }
#[derive(Serialize, Default)]
pub struct UrlPreviewData {
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:title"))]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:description"))]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:image"))]
pub image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "matrix:image:size"))]
pub image_size: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:image:width"))]
pub image_width: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:image:height"))]
pub image_height: Option<u32>,
}
pub struct Service { pub struct Service {
services: Services, url_preview_mutex: MutexMap<String, ()>,
pub(crate) db: Data, pub(crate) db: Data,
pub url_preview_mutex: MutexMap<String, ()>, services: Services,
} }
struct Services { struct Services {
server: Arc<Server>, server: Arc<Server>,
client: Dep<client::Service>,
globals: Dep<globals::Service>, globals: Dep<globals::Service>,
} }
/// generated MXC ID (`media-id`) length
pub const MXC_LENGTH: usize = 32;
#[async_trait] #[async_trait]
impl crate::Service for Service { impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> { fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self { Ok(Arc::new(Self {
url_preview_mutex: MutexMap::new(),
db: Data::new(args.db),
services: Services { services: Services {
server: args.server.clone(), server: args.server.clone(),
client: args.depend::<client::Service>("client"),
globals: args.depend::<globals::Service>("globals"), globals: args.depend::<globals::Service>("globals"),
}, },
db: Data::new(args.db),
url_preview_mutex: MutexMap::new(),
})) }))
} }
@@ -99,10 +88,15 @@ impl Service {
pub async fn delete(&self, mxc: &str) -> Result<()> { pub async fn delete(&self, mxc: &str) -> Result<()> {
if let Ok(keys) = self.db.search_mxc_metadata_prefix(mxc) { if let Ok(keys) = self.db.search_mxc_metadata_prefix(mxc) {
for key in keys { for key in keys {
self.remove_media_file(&key).await?; trace!(?mxc, ?key, "Deleting from filesystem");
if let Err(e) = self.remove_media_file(&key).await {
error!(?mxc, ?key, "Failed to remove media file: {e}");
}
debug!("Deleting MXC {mxc} from database"); trace!(?mxc, ?key, "Deleting from database");
self.db.delete_file_mxc(mxc)?; if let Err(e) = self.db.delete_file_mxc(mxc) {
error!(?mxc, ?key, "Failed to remove media from database: {e}");
}
} }
Ok(()) Ok(())
@@ -224,22 +218,6 @@ impl Service {
Ok(deletion_count) Ok(deletion_count)
} }
pub async fn get_url_preview(&self, url: &str) -> Option<UrlPreviewData> { self.db.get_url_preview(url) }
/// TODO: use this?
#[allow(dead_code)]
pub async fn remove_url_preview(&self, url: &str) -> Result<()> {
// TODO: also remove the downloaded image
self.db.remove_url_preview(url)
}
pub async fn set_url_preview(&self, url: &str, data: &UrlPreviewData) -> Result<()> {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("valid system time");
self.db.set_url_preview(url, data, now)
}
pub async fn create_media_dir(&self) -> Result<()> { pub async fn create_media_dir(&self) -> Result<()> {
let dir = self.get_media_dir(); let dir = self.get_media_dir();
Ok(fs::create_dir_all(dir).await?) Ok(fs::create_dir_all(dir).await?)
+274
View File
@@ -0,0 +1,274 @@
use std::{io::Cursor, time::SystemTime};
use conduit::{debug, utils, warn, Err, Result};
use conduit_core::implement;
use image::ImageReader as ImgReader;
use ipaddress::IPAddress;
use serde::Serialize;
use url::Url;
use webpage::HTML;
use super::{Service, MXC_LENGTH};
#[derive(Serialize, Default)]
pub struct UrlPreviewData {
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:title"))]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:description"))]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:image"))]
pub image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "matrix:image:size"))]
pub image_size: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:image:width"))]
pub image_width: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none", rename(serialize = "og:image:height"))]
pub image_height: Option<u32>,
}
#[implement(Service)]
pub async fn remove_url_preview(&self, url: &str) -> Result<()> {
// TODO: also remove the downloaded image
self.db.remove_url_preview(url)
}
#[implement(Service)]
pub async fn set_url_preview(&self, url: &str, data: &UrlPreviewData) -> Result<()> {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("valid system time");
self.db.set_url_preview(url, data, now)
}
#[implement(Service)]
pub async fn download_image(&self, url: &str) -> Result<UrlPreviewData> {
let client = &self.services.client.url_preview;
let image = client.get(url).send().await?.bytes().await?;
let mxc = format!(
"mxc://{}/{}",
self.services.globals.server_name(),
utils::random_string(MXC_LENGTH)
);
self.create(None, &mxc, None, None, &image).await?;
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
Err(_) => (None, None),
Ok(reader) => match reader.into_dimensions() {
Err(_) => (None, None),
Ok((width, height)) => (Some(width), Some(height)),
},
};
Ok(UrlPreviewData {
image: Some(mxc),
image_size: Some(image.len()),
image_width: width,
image_height: height,
..Default::default()
})
}
#[implement(Service)]
pub async fn get_url_preview(&self, url: &str) -> Result<UrlPreviewData> {
if let Some(preview) = self.db.get_url_preview(url) {
return Ok(preview);
}
// ensure that only one request is made per URL
let _request_lock = self.url_preview_mutex.lock(url).await;
match self.db.get_url_preview(url) {
Some(preview) => Ok(preview),
None => self.request_url_preview(url).await,
}
}
#[implement(Service)]
async fn request_url_preview(&self, url: &str) -> Result<UrlPreviewData> {
if let Ok(ip) = IPAddress::parse(url) {
if !self.services.globals.valid_cidr_range(&ip) {
return Err!(BadServerResponse("Requesting from this address is forbidden"));
}
}
let client = &self.services.client.url_preview;
let response = client.head(url).send().await?;
if let Some(remote_addr) = response.remote_addr() {
if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) {
if !self.services.globals.valid_cidr_range(&ip) {
return Err!(BadServerResponse("Requesting from this address is forbidden"));
}
}
}
let Some(content_type) = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|x| x.to_str().ok())
else {
return Err!(Request(Unknown("Unknown Content-Type")));
};
let data = match content_type {
html if html.starts_with("text/html") => self.download_html(url).await?,
img if img.starts_with("image/") => self.download_image(url).await?,
_ => return Err!(Request(Unknown("Unsupported Content-Type"))),
};
self.set_url_preview(url, &data).await?;
Ok(data)
}
#[implement(Service)]
async fn download_html(&self, url: &str) -> Result<UrlPreviewData> {
let client = &self.services.client.url_preview;
let mut response = client.get(url).send().await?;
let mut bytes: Vec<u8> = Vec::new();
while let Some(chunk) = response.chunk().await? {
bytes.extend_from_slice(&chunk);
if bytes.len() > self.services.globals.url_preview_max_spider_size() {
debug!(
"Response body from URL {} exceeds url_preview_max_spider_size ({}), not processing the rest of the \
response body and assuming our necessary data is in this range.",
url,
self.services.globals.url_preview_max_spider_size()
);
break;
}
}
let body = String::from_utf8_lossy(&bytes);
let Ok(html) = HTML::from_string(body.to_string(), Some(url.to_owned())) else {
return Err!(Request(Unknown("Failed to parse HTML")));
};
let mut data = match html.opengraph.images.first() {
None => UrlPreviewData::default(),
Some(obj) => self.download_image(&obj.url).await?,
};
let props = html.opengraph.properties;
/* use OpenGraph title/description, but fall back to HTML if not available */
data.title = props.get("title").cloned().or(html.title);
data.description = props.get("description").cloned().or(html.description);
Ok(data)
}
#[implement(Service)]
pub fn url_preview_allowed(&self, url_str: &str) -> bool {
let url: Url = match Url::parse(url_str) {
Ok(u) => u,
Err(e) => {
warn!("Failed to parse URL from a str: {}", e);
return false;
},
};
if ["http", "https"]
.iter()
.all(|&scheme| scheme != url.scheme().to_lowercase())
{
debug!("Ignoring non-HTTP/HTTPS URL to preview: {}", url);
return false;
}
let host = match url.host_str() {
None => {
debug!("Ignoring URL preview for a URL that does not have a host (?): {}", url);
return false;
},
Some(h) => h.to_owned(),
};
let allowlist_domain_contains = self
.services
.globals
.url_preview_domain_contains_allowlist();
let allowlist_domain_explicit = self
.services
.globals
.url_preview_domain_explicit_allowlist();
let denylist_domain_explicit = self.services.globals.url_preview_domain_explicit_denylist();
let allowlist_url_contains = self.services.globals.url_preview_url_contains_allowlist();
if allowlist_domain_contains.contains(&"*".to_owned())
|| allowlist_domain_explicit.contains(&"*".to_owned())
|| allowlist_url_contains.contains(&"*".to_owned())
{
debug!("Config key contains * which is allowing all URL previews. Allowing URL {}", url);
return true;
}
if !host.is_empty() {
if denylist_domain_explicit.contains(&host) {
debug!(
"Host {} is not allowed by url_preview_domain_explicit_denylist (check 1/4)",
&host
);
return false;
}
if allowlist_domain_explicit.contains(&host) {
debug!("Host {} is allowed by url_preview_domain_explicit_allowlist (check 2/4)", &host);
return true;
}
if allowlist_domain_contains
.iter()
.any(|domain_s| domain_s.contains(&host.clone()))
{
debug!("Host {} is allowed by url_preview_domain_contains_allowlist (check 3/4)", &host);
return true;
}
if allowlist_url_contains
.iter()
.any(|url_s| url.to_string().contains(&url_s.to_string()))
{
debug!("URL {} is allowed by url_preview_url_contains_allowlist (check 4/4)", &host);
return true;
}
// check root domain if available and if user has root domain checks
if self.services.globals.url_preview_check_root_domain() {
debug!("Checking root domain");
match host.split_once('.') {
None => return false,
Some((_, root_domain)) => {
if denylist_domain_explicit.contains(&root_domain.to_owned()) {
debug!(
"Root domain {} is not allowed by url_preview_domain_explicit_denylist (check 1/3)",
&root_domain
);
return true;
}
if allowlist_domain_explicit.contains(&root_domain.to_owned()) {
debug!(
"Root domain {} is allowed by url_preview_domain_explicit_allowlist (check 2/3)",
&root_domain
);
return true;
}
if allowlist_domain_contains
.iter()
.any(|domain_s| domain_s.contains(&root_domain.to_owned()))
{
debug!(
"Root domain {} is allowed by url_preview_domain_contains_allowlist (check 3/3)",
&root_domain
);
return true;
}
},
}
}
}
false
}
+1
View File
@@ -18,6 +18,7 @@ pub mod pusher;
pub mod resolver; pub mod resolver;
pub mod rooms; pub mod rooms;
pub mod sending; pub mod sending;
pub mod server_keys;
pub mod transaction_ids; pub mod transaction_ids;
pub mod uiaa; pub mod uiaa;
pub mod updates; pub mod updates;
+16 -17
View File
@@ -3,7 +3,7 @@ mod data;
use std::{fmt::Debug, mem, sync::Arc}; use std::{fmt::Debug, mem, sync::Arc};
use bytes::BytesMut; use bytes::BytesMut;
use conduit::{debug_info, info, trace, utils::string_from_bytes, warn, Error, PduEvent, Result}; use conduit::{debug_error, err, trace, utils::string_from_bytes, warn, Err, PduEvent, Result};
use ipaddress::IPAddress; use ipaddress::IPAddress;
use ruma::{ use ruma::{
api::{ api::{
@@ -84,8 +84,9 @@ impl Service {
let http_request = request let http_request = request
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::IfRequired(""), &VERSIONS) .try_into_http_request::<BytesMut>(&dest, SendAccessToken::IfRequired(""), &VERSIONS)
.map_err(|e| { .map_err(|e| {
warn!("Failed to find destination {dest} for push gateway: {e}"); err!(BadServerResponse(warn!(
Error::BadServerResponse("Invalid push gateway destination") "Failed to find destination {dest} for push gateway: {e}"
)))
})? })?
.map(BytesMut::freeze); .map(BytesMut::freeze);
@@ -95,7 +96,7 @@ impl Service {
trace!("Checking request URL for IP"); trace!("Checking request URL for IP");
if let Ok(ip) = IPAddress::parse(url_host) { if let Ok(ip) = IPAddress::parse(url_host) {
if !self.services.globals.valid_cidr_range(&ip) { if !self.services.globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Not allowed to send requests to this IP")); return Err!(BadServerResponse("Not allowed to send requests to this IP"));
} }
} }
} }
@@ -110,7 +111,7 @@ impl Service {
if let Some(remote_addr) = response.remote_addr() { if let Some(remote_addr) = response.remote_addr() {
if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) { if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) {
if !self.services.globals.valid_cidr_range(&ip) { if !self.services.globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Not allowed to send requests to this IP")); return Err!(BadServerResponse("Not allowed to send requests to this IP"));
} }
} }
} }
@@ -129,10 +130,10 @@ impl Service {
let body = response.bytes().await?; // TODO: handle timeout let body = response.bytes().await?; // TODO: handle timeout
if !status.is_success() { if !status.is_success() {
info!("Push gateway {dest} returned unsuccessful HTTP response ({status})"); debug_error!("Push gateway response body: {:?}", string_from_bytes(&body));
debug_info!("Push gateway response body: {:?}", string_from_bytes(&body)); return Err!(BadServerResponse(error!(
"Push gateway {dest} returned unsuccessful HTTP response: {status}"
return Err(Error::BadServerResponse("Push gateway returned unsuccessful response")); )));
} }
let response = T::IncomingResponse::try_from_http_response( let response = T::IncomingResponse::try_from_http_response(
@@ -140,13 +141,11 @@ impl Service {
.body(body) .body(body)
.expect("reqwest body is valid http body"), .expect("reqwest body is valid http body"),
); );
response.map_err(|e| { response
warn!("Push gateway {dest} returned invalid response bytes: {e}"); .map_err(|e| err!(BadServerResponse(error!("Push gateway {dest} returned invalid response: {e}"))))
Error::BadServerResponse("Push gateway returned bad/invalid response")
})
}, },
Err(e) => { Err(e) => {
warn!("Could not send request to pusher {dest}: {e}"); debug_error!("Could not send request to pusher {dest}: {e}");
Err(e.into()) Err(e.into())
}, },
} }
@@ -165,7 +164,7 @@ impl Service {
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")? .room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")?
.map(|ev| { .map(|ev| {
serde_json::from_str(ev.content.get()) serde_json::from_str(ev.content.get())
.map_err(|_| Error::bad_database("invalid m.room.power_levels event")) .map_err(|e| err!(Database("invalid m.room.power_levels event: {e:?}")))
}) })
.transpose()? .transpose()?
.unwrap_or_default(); .unwrap_or_default();
@@ -181,8 +180,8 @@ impl Service {
}; };
if notify.is_some() { if notify.is_some() {
return Err(Error::bad_database( return Err!(Database(
r#"Malformed pushrule contains more than one of these actions: ["dont_notify", "notify", "coalesce"]"#, r#"Malformed pushrule contains more than one of these actions: ["dont_notify", "notify", "coalesce"]"#
)); ));
} }
+4 -6
View File
@@ -4,7 +4,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use conduit::{debug, debug_error, debug_info, debug_warn, trace, Err, Error, Result}; use conduit::{debug, debug_error, debug_info, debug_warn, err, trace, Err, Result};
use hickory_resolver::{error::ResolveError, lookup::SrvLookup}; use hickory_resolver::{error::ResolveError, lookup::SrvLookup};
use ipaddress::IPAddress; use ipaddress::IPAddress;
use ruma::ServerName; use ruma::ServerName;
@@ -329,10 +329,8 @@ impl super::Service {
dest.is_ip_literal() || !IPAddress::is_valid(dest.host()), dest.is_ip_literal() || !IPAddress::is_valid(dest.host()),
"Destination is not an IP literal." "Destination is not an IP literal."
); );
let ip = IPAddress::parse(dest.host()).map_err(|e| { let ip = IPAddress::parse(dest.host())
debug_error!("Failed to parse IP literal from string: {}", e); .map_err(|e| err!(BadServerResponse(debug_error!("Failed to parse IP literal from string: {e}"))))?;
Error::BadServerResponse("Invalid IP address")
})?;
self.validate_ip(&ip)?; self.validate_ip(&ip)?;
@@ -341,7 +339,7 @@ impl super::Service {
pub(crate) fn validate_ip(&self, ip: &IPAddress) -> Result<()> { pub(crate) fn validate_ip(&self, ip: &IPAddress) -> Result<()> {
if !self.services.globals.valid_cidr_range(ip) { if !self.services.globals.valid_cidr_range(ip) {
return Err(Error::BadServerResponse("Not allowed to send requests to this IP")); return Err!(BadServerResponse("Not allowed to send requests to this IP"));
} }
Ok(()) Ok(())
+6 -3
View File
@@ -1,5 +1,4 @@
mod parse_incoming_pdu; mod parse_incoming_pdu;
mod signing_keys;
use std::{ use std::{
collections::{hash_map, BTreeMap, HashMap, HashSet}, collections::{hash_map, BTreeMap, HashMap, HashSet},
@@ -35,7 +34,7 @@ use ruma::{
use tokio::sync::RwLock; use tokio::sync::RwLock;
use super::state_compressor::CompressedStateEvent; use super::state_compressor::CompressedStateEvent;
use crate::{globals, rooms, sending, Dep}; use crate::{globals, rooms, sending, server_keys, Dep};
pub struct Service { pub struct Service {
services: Services, services: Services,
@@ -50,6 +49,7 @@ struct Services {
metadata: Dep<rooms::metadata::Service>, metadata: Dep<rooms::metadata::Service>,
outlier: Dep<rooms::outlier::Service>, outlier: Dep<rooms::outlier::Service>,
pdu_metadata: Dep<rooms::pdu_metadata::Service>, pdu_metadata: Dep<rooms::pdu_metadata::Service>,
server_keys: Dep<server_keys::Service>,
short: Dep<rooms::short::Service>, short: Dep<rooms::short::Service>,
state: Dep<rooms::state::Service>, state: Dep<rooms::state::Service>,
state_accessor: Dep<rooms::state_accessor::Service>, state_accessor: Dep<rooms::state_accessor::Service>,
@@ -77,6 +77,7 @@ impl crate::Service for Service {
auth_chain: args.depend::<rooms::auth_chain::Service>("rooms::auth_chain"), auth_chain: args.depend::<rooms::auth_chain::Service>("rooms::auth_chain"),
metadata: args.depend::<rooms::metadata::Service>("rooms::metadata"), metadata: args.depend::<rooms::metadata::Service>("rooms::metadata"),
outlier: args.depend::<rooms::outlier::Service>("rooms::outlier"), outlier: args.depend::<rooms::outlier::Service>("rooms::outlier"),
server_keys: args.depend::<server_keys::Service>("server_keys"),
pdu_metadata: args.depend::<rooms::pdu_metadata::Service>("rooms::pdu_metadata"), pdu_metadata: args.depend::<rooms::pdu_metadata::Service>("rooms::pdu_metadata"),
short: args.depend::<rooms::short::Service>("rooms::short"), short: args.depend::<rooms::short::Service>("rooms::short"),
state: args.depend::<rooms::state::Service>("rooms::state"), state: args.depend::<rooms::state::Service>("rooms::state"),
@@ -1187,7 +1188,9 @@ impl Service {
// We go through all the signatures we see on the PDUs and their unresolved // We go through all the signatures we see on the PDUs and their unresolved
// dependencies and fetch the corresponding signing keys // dependencies and fetch the corresponding signing keys
self.fetch_required_signing_keys( self.services
.server_keys
.fetch_required_signing_keys(
events_with_auth_events events_with_auth_events
.iter() .iter()
.flat_map(|(_id, _local_pdu, events)| events) .flat_map(|(_id, _local_pdu, events)| events)
@@ -1,18 +1,18 @@
use conduit::{pdu::gen_event_id_canonical_json, warn, Err, Error, Result}; use conduit::{debug_warn, err, pdu::gen_event_id_canonical_json, Err, Result};
use ruma::{api::client::error::ErrorKind, CanonicalJsonObject, OwnedEventId, OwnedRoomId, RoomId}; use ruma::{CanonicalJsonObject, OwnedEventId, OwnedRoomId, RoomId};
use serde_json::value::RawValue as RawJsonValue; use serde_json::value::RawValue as RawJsonValue;
impl super::Service { impl super::Service {
pub fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result<(OwnedEventId, CanonicalJsonObject, OwnedRoomId)> { pub fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result<(OwnedEventId, CanonicalJsonObject, OwnedRoomId)> {
let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| { let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
warn!("Error parsing incoming event {pdu:?}: {e:?}"); debug_warn!("Error parsing incoming event {pdu:#?}");
Error::BadServerResponse("Invalid PDU in server response") err!(BadServerResponse("Error parsing incoming event {e:?}"))
})?; })?;
let room_id: OwnedRoomId = value let room_id: OwnedRoomId = value
.get("room_id") .get("room_id")
.and_then(|id| RoomId::parse(id.as_str()?).ok()) .and_then(|id| RoomId::parse(id.as_str()?).ok())
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid room id in pdu"))?; .ok_or(err!(Request(InvalidParam("Invalid room id in pdu"))))?;
let Ok(room_version_id) = self.services.state.get_room_version(&room_id) else { let Ok(room_version_id) = self.services.state.get_room_version(&room_id) else {
return Err!("Server is not in room {room_id}"); return Err!("Server is not in room {room_id}");
+4 -2
View File
@@ -41,7 +41,7 @@ use tokio::sync::RwLock;
use self::data::Data; use self::data::Data;
use crate::{ use crate::{
account_data, admin, appservice, appservice::NamespaceRegex, globals, pusher, rooms, account_data, admin, appservice, appservice::NamespaceRegex, globals, pusher, rooms,
rooms::state_compressor::CompressedStateEvent, sending, Dep, rooms::state_compressor::CompressedStateEvent, sending, server_keys, Dep,
}; };
// Update Relationships // Update Relationships
@@ -86,6 +86,7 @@ struct Services {
pdu_metadata: Dep<rooms::pdu_metadata::Service>, pdu_metadata: Dep<rooms::pdu_metadata::Service>,
read_receipt: Dep<rooms::read_receipt::Service>, read_receipt: Dep<rooms::read_receipt::Service>,
sending: Dep<sending::Service>, sending: Dep<sending::Service>,
server_keys: Dep<server_keys::Service>,
user: Dep<rooms::user::Service>, user: Dep<rooms::user::Service>,
pusher: Dep<pusher::Service>, pusher: Dep<pusher::Service>,
threads: Dep<rooms::threads::Service>, threads: Dep<rooms::threads::Service>,
@@ -114,6 +115,7 @@ impl crate::Service for Service {
pdu_metadata: args.depend::<rooms::pdu_metadata::Service>("rooms::pdu_metadata"), pdu_metadata: args.depend::<rooms::pdu_metadata::Service>("rooms::pdu_metadata"),
read_receipt: args.depend::<rooms::read_receipt::Service>("rooms::read_receipt"), read_receipt: args.depend::<rooms::read_receipt::Service>("rooms::read_receipt"),
sending: args.depend::<sending::Service>("sending"), sending: args.depend::<sending::Service>("sending"),
server_keys: args.depend::<server_keys::Service>("server_keys"),
user: args.depend::<rooms::user::Service>("rooms::user"), user: args.depend::<rooms::user::Service>("rooms::user"),
pusher: args.depend::<pusher::Service>("pusher"), pusher: args.depend::<pusher::Service>("pusher"),
threads: args.depend::<rooms::threads::Service>("rooms::threads"), threads: args.depend::<rooms::threads::Service>("rooms::threads"),
@@ -1181,7 +1183,7 @@ impl Service {
} }
self.services self.services
.event_handler .server_keys
.fetch_required_signing_keys([&value], pub_key_map) .fetch_required_signing_keys([&value], pub_key_map)
.await?; .await?;
+9 -12
View File
@@ -1,7 +1,7 @@
use std::{fmt::Debug, mem}; use std::{fmt::Debug, mem};
use bytes::BytesMut; use bytes::BytesMut;
use conduit::{debug_error, trace, utils, warn, Error, Result}; use conduit::{debug_error, err, trace, utils, warn, Err, Result};
use reqwest::Client; use reqwest::Client;
use ruma::api::{appservice::Registration, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken}; use ruma::api::{appservice::Registration, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken};
@@ -26,10 +26,7 @@ where
let hs_token = registration.hs_token.as_str(); let hs_token = registration.hs_token.as_str();
let mut http_request = request let mut http_request = request
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::IfRequired(hs_token), &VERSIONS) .try_into_http_request::<BytesMut>(&dest, SendAccessToken::IfRequired(hs_token), &VERSIONS)
.map_err(|e| { .map_err(|e| err!(BadServerResponse(warn!("Failed to find destination {dest}: {e}"))))?
warn!("Failed to find destination {dest}: {e}");
Error::BadServerResponse("Invalid appservice destination")
})?
.map(BytesMut::freeze); .map(BytesMut::freeze);
let mut parts = http_request.uri().clone().into_parts(); let mut parts = http_request.uri().clone().into_parts();
@@ -69,13 +66,11 @@ where
let body = response.bytes().await?; // TODO: handle timeout let body = response.bytes().await?; // TODO: handle timeout
if !status.is_success() { if !status.is_success() {
warn!( debug_error!("Appservice response bytes: {:?}", utils::string_from_bytes(&body));
return Err!(BadServerResponse(error!(
"Appservice \"{}\" returned unsuccessful HTTP response {status} at {dest}", "Appservice \"{}\" returned unsuccessful HTTP response {status} at {dest}",
registration.id registration.id
); )));
debug_error!("Appservice response bytes: {:?}", utils::string_from_bytes(&body));
return Err(Error::BadServerResponse("Appservice returned unsuccessful HTTP response"));
} }
let response = T::IncomingResponse::try_from_http_response( let response = T::IncomingResponse::try_from_http_response(
@@ -85,7 +80,9 @@ where
); );
response.map(Some).map_err(|e| { response.map(Some).map_err(|e| {
warn!("Appservice \"{}\" returned invalid response bytes {dest}: {e}", registration.id); err!(BadServerResponse(error!(
Error::BadServerResponse("Appservice returned bad/invalid response") "Appservice \"{}\" returned invalid response bytes {dest}: {e}",
registration.id
)))
}) })
} }
-343
View File
@@ -1,343 +0,0 @@
use std::{
fmt::Debug,
net::{IpAddr, SocketAddr},
};
use conduit::{debug, debug_error, debug_info, debug_warn, trace, Err, Error, Result};
use hickory_resolver::{error::ResolveError, lookup::SrvLookup};
use ipaddress::IPAddress;
use ruma::ServerName;
use crate::{
resolver::{add_port_to_hostname, get_ip_with_port, CachedDest, CachedOverride, FedDest},
services,
};
#[derive(Clone, Debug)]
pub(crate) struct ActualDest {
pub(crate) dest: FedDest,
pub(crate) host: String,
pub(crate) string: String,
pub(crate) cached: bool,
}
#[tracing::instrument(skip_all, name = "resolve")]
pub(crate) async fn get_actual_dest(server_name: &ServerName) -> Result<ActualDest> {
let cached;
let cached_result = services().resolver.get_cached_destination(server_name);
let CachedDest {
dest,
host,
..
} = if let Some(result) = cached_result {
cached = true;
result
} else {
cached = false;
validate_dest(server_name)?;
resolve_actual_dest(server_name, true).await?
};
let string = dest.clone().into_https_string();
Ok(ActualDest {
dest,
host,
string,
cached,
})
}
/// Returns: `actual_destination`, host header
/// Implemented according to the specification at <https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names>
/// Numbers in comments below refer to bullet points in linked section of
/// specification
#[tracing::instrument(skip_all, name = "actual")]
pub async fn resolve_actual_dest(dest: &ServerName, cache: bool) -> Result<CachedDest> {
trace!("Finding actual destination for {dest}");
let mut host = dest.as_str().to_owned();
let actual_dest = match get_ip_with_port(dest.as_str()) {
Some(host_port) => actual_dest_1(host_port)?,
None => {
if let Some(pos) = dest.as_str().find(':') {
actual_dest_2(dest, cache, pos).await?
} else if let Some(delegated) = request_well_known(dest.as_str()).await? {
actual_dest_3(&mut host, cache, delegated).await?
} else if let Some(overrider) = query_srv_record(dest.as_str()).await? {
actual_dest_4(&host, cache, overrider).await?
} else {
actual_dest_5(dest, cache).await?
}
},
};
// 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
let host = if let Ok(addr) = host.parse::<SocketAddr>() {
FedDest::Literal(addr)
} else if let Ok(addr) = host.parse::<IpAddr>() {
FedDest::Named(addr.to_string(), ":8448".to_owned())
} else if let Some(pos) = host.find(':') {
let (host, port) = host.split_at(pos);
FedDest::Named(host.to_owned(), port.to_owned())
} else {
FedDest::Named(host, ":8448".to_owned())
};
debug!("Actual destination: {actual_dest:?} hostname: {host:?}");
Ok(CachedDest {
dest: actual_dest,
host: host.into_uri_string(),
expire: CachedDest::default_expire(),
})
}
fn actual_dest_1(host_port: FedDest) -> Result<FedDest> {
debug!("1: IP literal with provided or default port");
Ok(host_port)
}
async fn actual_dest_2(dest: &ServerName, cache: bool, pos: usize) -> Result<FedDest> {
debug!("2: Hostname with included port");
let (host, port) = dest.as_str().split_at(pos);
conditional_query_and_cache_override(host, host, port.parse::<u16>().unwrap_or(8448), cache).await?;
Ok(FedDest::Named(host.to_owned(), port.to_owned()))
}
async fn actual_dest_3(host: &mut String, cache: bool, delegated: String) -> Result<FedDest> {
debug!("3: A .well-known file is available");
*host = add_port_to_hostname(&delegated).into_uri_string();
match get_ip_with_port(&delegated) {
Some(host_and_port) => actual_dest_3_1(host_and_port),
None => {
if let Some(pos) = delegated.find(':') {
actual_dest_3_2(cache, delegated, pos).await
} else {
trace!("Delegated hostname has no port in this branch");
if let Some(overrider) = query_srv_record(&delegated).await? {
actual_dest_3_3(cache, delegated, overrider).await
} else {
actual_dest_3_4(cache, delegated).await
}
}
},
}
}
fn actual_dest_3_1(host_and_port: FedDest) -> Result<FedDest> {
debug!("3.1: IP literal in .well-known file");
Ok(host_and_port)
}
async fn actual_dest_3_2(cache: bool, delegated: String, pos: usize) -> Result<FedDest> {
debug!("3.2: Hostname with port in .well-known file");
let (host, port) = delegated.split_at(pos);
conditional_query_and_cache_override(host, host, port.parse::<u16>().unwrap_or(8448), cache).await?;
Ok(FedDest::Named(host.to_owned(), port.to_owned()))
}
async fn actual_dest_3_3(cache: bool, delegated: String, overrider: FedDest) -> Result<FedDest> {
debug!("3.3: SRV lookup successful");
let force_port = overrider.port();
conditional_query_and_cache_override(&delegated, &overrider.hostname(), force_port.unwrap_or(8448), cache).await?;
if let Some(port) = force_port {
Ok(FedDest::Named(delegated, format!(":{port}")))
} else {
Ok(add_port_to_hostname(&delegated))
}
}
async fn actual_dest_3_4(cache: bool, delegated: String) -> Result<FedDest> {
debug!("3.4: No SRV records, just use the hostname from .well-known");
conditional_query_and_cache_override(&delegated, &delegated, 8448, cache).await?;
Ok(add_port_to_hostname(&delegated))
}
async fn actual_dest_4(host: &str, cache: bool, overrider: FedDest) -> Result<FedDest> {
debug!("4: No .well-known; SRV record found");
let force_port = overrider.port();
conditional_query_and_cache_override(host, &overrider.hostname(), force_port.unwrap_or(8448), cache).await?;
if let Some(port) = force_port {
Ok(FedDest::Named(host.to_owned(), format!(":{port}")))
} else {
Ok(add_port_to_hostname(host))
}
}
async fn actual_dest_5(dest: &ServerName, cache: bool) -> Result<FedDest> {
debug!("5: No SRV record found");
conditional_query_and_cache_override(dest.as_str(), dest.as_str(), 8448, cache).await?;
Ok(add_port_to_hostname(dest.as_str()))
}
#[tracing::instrument(skip_all, name = "well-known")]
async fn request_well_known(dest: &str) -> Result<Option<String>> {
trace!("Requesting well known for {dest}");
if !services().resolver.has_cached_override(dest) {
query_and_cache_override(dest, dest, 8448).await?;
}
let response = services()
.client
.well_known
.get(&format!("https://{dest}/.well-known/matrix/server"))
.send()
.await;
trace!("response: {:?}", response);
if let Err(e) = &response {
debug!("error: {e:?}");
return Ok(None);
}
let response = response?;
if !response.status().is_success() {
debug!("response not 2XX");
return Ok(None);
}
let text = response.text().await?;
trace!("response text: {:?}", text);
if text.len() >= 12288 {
debug_warn!("response contains junk");
return Ok(None);
}
let body: serde_json::Value = serde_json::from_str(&text).unwrap_or_default();
let m_server = body
.get("m.server")
.unwrap_or(&serde_json::Value::Null)
.as_str()
.unwrap_or_default();
if ruma::identifiers_validation::server_name::validate(m_server).is_err() {
debug_error!("response content missing or invalid");
return Ok(None);
}
debug_info!("{:?} found at {:?}", dest, m_server);
Ok(Some(m_server.to_owned()))
}
#[inline]
async fn conditional_query_and_cache_override(overname: &str, hostname: &str, port: u16, cache: bool) -> Result<()> {
if cache {
query_and_cache_override(overname, hostname, port).await
} else {
Ok(())
}
}
#[tracing::instrument(skip_all, name = "ip")]
async fn query_and_cache_override(overname: &'_ str, hostname: &'_ str, port: u16) -> Result<()> {
match services()
.resolver
.resolver
.lookup_ip(hostname.to_owned())
.await
{
Err(e) => handle_resolve_error(&e),
Ok(override_ip) => {
if hostname != overname {
debug_info!("{overname:?} overriden by {hostname:?}");
}
services().resolver.set_cached_override(
overname.to_owned(),
CachedOverride {
ips: override_ip.iter().collect(),
port,
expire: CachedOverride::default_expire(),
},
);
Ok(())
},
}
}
#[tracing::instrument(skip_all, name = "srv")]
async fn query_srv_record(hostname: &'_ str) -> Result<Option<FedDest>> {
fn handle_successful_srv(srv: &SrvLookup) -> Option<FedDest> {
srv.iter().next().map(|result| {
FedDest::Named(
result.target().to_string().trim_end_matches('.').to_owned(),
format!(":{}", result.port()),
)
})
}
async fn lookup_srv(hostname: &str) -> Result<SrvLookup, ResolveError> {
debug!("querying SRV for {:?}", hostname);
let hostname = hostname.trim_end_matches('.');
services()
.resolver
.resolver
.srv_lookup(hostname.to_owned())
.await
}
let hostnames = [format!("_matrix-fed._tcp.{hostname}."), format!("_matrix._tcp.{hostname}.")];
for hostname in hostnames {
match lookup_srv(&hostname).await {
Ok(result) => return Ok(handle_successful_srv(&result)),
Err(e) => handle_resolve_error(&e)?,
}
}
Ok(None)
}
#[allow(clippy::single_match_else)]
fn handle_resolve_error(e: &ResolveError) -> Result<()> {
use hickory_resolver::error::ResolveErrorKind;
match *e.kind() {
ResolveErrorKind::NoRecordsFound {
..
} => {
// Raise to debug_warn if we can find out the result wasn't from cache
debug!("{e}");
Ok(())
},
_ => Err!(error!("DNS {e}")),
}
}
fn validate_dest(dest: &ServerName) -> Result<()> {
if dest == services().globals.server_name() {
return Err!("Won't send federation request to ourselves");
}
if dest.is_ip_literal() || IPAddress::is_valid(dest.host()) {
validate_dest_ip_literal(dest)?;
}
Ok(())
}
fn validate_dest_ip_literal(dest: &ServerName) -> Result<()> {
trace!("Destination is an IP literal, checking against IP range denylist.",);
debug_assert!(
dest.is_ip_literal() || !IPAddress::is_valid(dest.host()),
"Destination is not an IP literal."
);
let ip = IPAddress::parse(dest.host()).map_err(|e| {
debug_error!("Failed to parse IP literal from string: {}", e);
Error::BadServerResponse("Invalid IP address")
})?;
validate_ip(&ip)?;
Ok(())
}
pub(crate) fn validate_ip(ip: &IPAddress) -> Result<()> {
if !services().globals.valid_cidr_range(ip) {
return Err(Error::BadServerResponse("Not allowed to send requests to this IP"));
}
Ok(())
}
+3 -6
View File
@@ -1,7 +1,7 @@
use std::{fmt::Debug, mem}; use std::{fmt::Debug, mem};
use conduit::{ use conduit::{
debug, debug_error, debug_warn, error::inspect_debug_log, trace, utils::string::EMPTY, Err, Error, Result, debug, debug_error, debug_warn, err, error::inspect_debug_log, trace, utils::string::EMPTY, Err, Error, Result,
}; };
use http::{header::AUTHORIZATION, HeaderValue}; use http::{header::AUTHORIZATION, HeaderValue};
use ipaddress::IPAddress; use ipaddress::IPAddress;
@@ -62,7 +62,7 @@ impl super::Service {
trace!("Preparing request"); trace!("Preparing request");
let mut http_request = req let mut http_request = req
.try_into_http_request::<Vec<u8>>(&actual.string, SATIR, &VERSIONS) .try_into_http_request::<Vec<u8>>(&actual.string, SATIR, &VERSIONS)
.map_err(|_| Error::BadServerResponse("Invalid destination"))?; .map_err(|e| err!(BadServerResponse("Invalid destination: {e:?}")))?;
sign_request::<T>(&self.services.globals, dest, &mut http_request); sign_request::<T>(&self.services.globals, dest, &mut http_request);
@@ -139,10 +139,7 @@ where
); );
} }
match response { response.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
Err(_) => Err(Error::BadServerResponse("Server returned bad 200 response.")),
Ok(response) => Ok(response),
}
} }
fn handle_error<T>( fn handle_error<T>(
@@ -1,9 +1,10 @@
use std::{ use std::{
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
sync::Arc,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use conduit::{debug, error, info, trace, warn, Error, Result}; use conduit::{debug, debug_error, debug_warn, err, error, info, trace, warn, Err, Result};
use futures_util::{stream::FuturesUnordered, StreamExt}; use futures_util::{stream::FuturesUnordered, StreamExt};
use ruma::{ use ruma::{
api::federation::{ api::federation::{
@@ -21,7 +22,31 @@ use ruma::{
use serde_json::value::RawValue as RawJsonValue; use serde_json::value::RawValue as RawJsonValue;
use tokio::sync::{RwLock, RwLockWriteGuard}; use tokio::sync::{RwLock, RwLockWriteGuard};
impl super::Service { use crate::{globals, sending, Dep};
pub struct Service {
services: Services,
}
struct Services {
globals: Dep<globals::Service>,
sending: Dep<sending::Service>,
}
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
services: Services {
globals: args.depend::<globals::Service>("globals"),
sending: args.depend::<sending::Service>("sending"),
},
}))
}
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}
impl Service {
pub async fn fetch_required_signing_keys<'a, E>( pub async fn fetch_required_signing_keys<'a, E>(
&'a self, events: E, pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>, &'a self, events: E, pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
) -> Result<()> ) -> Result<()>
@@ -32,13 +57,13 @@ impl super::Service {
for event in events { for event in events {
for (signature_server, signature) in event for (signature_server, signature) in event
.get("signatures") .get("signatures")
.ok_or(Error::BadServerResponse("No signatures in server response pdu."))? .ok_or(err!(BadServerResponse("No signatures in server response pdu.")))?
.as_object() .as_object()
.ok_or(Error::BadServerResponse("Invalid signatures object in server response pdu."))? .ok_or(err!(BadServerResponse("Invalid signatures object in server response pdu.")))?
{ {
let signature_object = signature.as_object().ok_or(Error::BadServerResponse( let signature_object = signature.as_object().ok_or(err!(BadServerResponse(
"Invalid signatures content object in server response pdu.", "Invalid signatures content object in server response pdu.",
))?; )))?;
for signature_id in signature_object.keys() { for signature_id in signature_object.keys() {
server_key_ids server_key_ids
@@ -69,10 +94,12 @@ impl super::Service {
.map(|(signature_server, signature_ids)| async { .map(|(signature_server, signature_ids)| async {
let fetch_res = self let fetch_res = self
.fetch_signing_keys_for_server( .fetch_signing_keys_for_server(
signature_server.as_str().try_into().map_err(|_| { signature_server.as_str().try_into().map_err(|e| {
( (
signature_server.clone(), signature_server.clone(),
Error::BadServerResponse("Invalid servername in signatures of server response pdu."), err!(BadServerResponse(
"Invalid servername in signatures of server response pdu: {e:?}"
)),
) )
})?, })?,
signature_ids.into_iter().collect(), // HashSet to Vec signature_ids.into_iter().collect(), // HashSet to Vec
@@ -82,7 +109,9 @@ impl super::Service {
match fetch_res { match fetch_res {
Ok(keys) => Ok((signature_server, keys)), Ok(keys) => Ok((signature_server, keys)),
Err(e) => { Err(e) => {
warn!("Signature verification failed: Could not fetch signing key for {signature_server}: {e}",); debug_error!(
"Signature verification failed: Could not fetch signing key for {signature_server}: {e}",
);
Err((signature_server, e)) Err((signature_server, e))
}, },
} }
@@ -98,7 +127,7 @@ impl super::Service {
.insert(signature_server.clone(), keys); .insert(signature_server.clone(), keys);
}, },
Err((signature_server, e)) => { Err((signature_server, e)) => {
warn!("Failed to fetch keys for {}: {:?}", signature_server, e); debug_warn!("Failed to fetch keys for {signature_server}: {e:?}");
}, },
} }
} }
@@ -116,35 +145,37 @@ impl super::Service {
pub_key_map: &mut RwLockWriteGuard<'_, BTreeMap<String, BTreeMap<String, Base64>>>, pub_key_map: &mut RwLockWriteGuard<'_, BTreeMap<String, BTreeMap<String, Base64>>>,
) -> Result<()> { ) -> Result<()> {
let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| { let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
error!("Invalid PDU in server response: {:?}: {:?}", pdu, e); debug_error!("Invalid PDU in server response: {pdu:#?}");
Error::BadServerResponse("Invalid PDU in server response") err!(BadServerResponse(error!("Invalid PDU in server response: {e:?}")))
})?; })?;
let signatures = value let signatures = value
.get("signatures") .get("signatures")
.ok_or(Error::BadServerResponse("No signatures in server response pdu."))? .ok_or(err!(BadServerResponse("No signatures in server response pdu.")))?
.as_object() .as_object()
.ok_or(Error::BadServerResponse("Invalid signatures object in server response pdu."))?; .ok_or(err!(BadServerResponse("Invalid signatures object in server response pdu.")))?;
for (signature_server, signature) in signatures { for (signature_server, signature) in signatures {
let signature_object = signature.as_object().ok_or(Error::BadServerResponse( let signature_object = signature.as_object().ok_or(err!(BadServerResponse(
"Invalid signatures content object in server response pdu.", "Invalid signatures content object in server response pdu.",
))?; )))?;
let signature_ids = signature_object.keys().cloned().collect::<Vec<_>>(); let signature_ids = signature_object.keys().cloned().collect::<Vec<_>>();
let contains_all_ids = let contains_all_ids =
|keys: &BTreeMap<String, Base64>| signature_ids.iter().all(|id| keys.contains_key(id)); |keys: &BTreeMap<String, Base64>| signature_ids.iter().all(|id| keys.contains_key(id));
let origin = <&ServerName>::try_from(signature_server.as_str()) let origin = <&ServerName>::try_from(signature_server.as_str()).map_err(|e| {
.map_err(|_| Error::BadServerResponse("Invalid servername in signatures of server response pdu."))?; err!(BadServerResponse(
"Invalid servername in signatures of server response pdu: {e:?}"
))
})?;
if servers.contains_key(origin) || pub_key_map.contains_key(origin.as_str()) { if servers.contains_key(origin) || pub_key_map.contains_key(origin.as_str()) {
continue; continue;
} }
debug!("Loading signing keys for {}", origin); debug!("Loading signing keys for {origin}");
let result: BTreeMap<_, _> = self let result: BTreeMap<_, _> = self
.services .services
.globals .globals
@@ -154,7 +185,7 @@ impl super::Service {
.collect(); .collect();
if !contains_all_ids(&result) { if !contains_all_ids(&result) {
debug!("Signing key not loaded for {}", origin); debug_warn!("Signing key not loaded for {origin}");
servers.insert(origin.to_owned(), BTreeMap::new()); servers.insert(origin.to_owned(), BTreeMap::new());
} }
@@ -171,7 +202,7 @@ impl super::Service {
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>, pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
) -> Result<()> { ) -> Result<()> {
for server in self.services.globals.trusted_servers() { for server in self.services.globals.trusted_servers() {
debug!("Asking batch signing keys from trusted server {}", server); debug!("Asking batch signing keys from trusted server {server}");
match self match self
.services .services
.sending .sending
@@ -184,14 +215,16 @@ impl super::Service {
.await .await
{ {
Ok(keys) => { Ok(keys) => {
debug!("Got signing keys: {:?}", keys); debug!("Got signing keys: {keys:?}");
let mut pkm = pub_key_map.write().await; let mut pkm = pub_key_map.write().await;
for k in keys.server_keys { for k in keys.server_keys {
let k = match k.deserialize() { let k = match k.deserialize() {
Ok(key) => key, Ok(key) => key,
Err(e) => { Err(e) => {
warn!("Received error {e} while fetching keys from trusted server {server}"); warn!(
warn!("{}", k.into_json()); "Received error {e} while fetching keys from trusted server {server}: {:#?}",
k.into_json()
);
continue; continue;
}, },
}; };
@@ -211,13 +244,10 @@ impl super::Service {
pkm.insert(k.server_name.to_string(), result); pkm.insert(k.server_name.to_string(), result);
} }
}, },
Err(e) => { Err(e) => error!(
warn!(
"Failed sending batched key request to trusted key server {server} for the remote servers \ "Failed sending batched key request to trusted key server {server} for the remote servers \
{:?}: {e}", {servers:?}: {e}"
servers ),
);
},
} }
} }
@@ -453,7 +483,6 @@ impl super::Service {
} }
} else { } else {
info!("query_trusted_key_servers_first is set to false, querying {origin} first"); info!("query_trusted_key_servers_first is set to false, querying {origin} first");
debug!("Asking {origin} for their signing keys over federation"); debug!("Asking {origin} for their signing keys over federation");
if let Some(server_key) = self if let Some(server_key) = self
.services .services
@@ -511,7 +540,7 @@ impl super::Service {
.filter_map(|e| e.deserialize().ok()) .filter_map(|e| e.deserialize().ok())
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) { }) {
debug!("Got signing keys: {:?}", server_keys); debug!("Got signing keys: {server_keys:?}");
for k in server_keys { for k in server_keys {
self.services self.services
.globals .globals
@@ -536,7 +565,6 @@ impl super::Service {
} }
} }
warn!("Failed to find public key for server: {origin}"); Err!(BadServerResponse(warn!("Failed to find public key for server {origin:?}")))
Err(Error::BadServerResponse("Failed to find public key for server"))
} }
} }
+3 -1
View File
@@ -12,7 +12,7 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
account_data, admin, appservice, client, emergency, globals, key_backups, account_data, admin, appservice, client, emergency, globals, key_backups,
manager::Manager, manager::Manager,
media, presence, pusher, resolver, rooms, sending, service, media, presence, pusher, resolver, rooms, sending, server_keys, service,
service::{Args, Map, Service}, service::{Args, Map, Service},
transaction_ids, uiaa, updates, users, transaction_ids, uiaa, updates, users,
}; };
@@ -31,6 +31,7 @@ pub struct Services {
pub resolver: Arc<resolver::Service>, pub resolver: Arc<resolver::Service>,
pub rooms: rooms::Service, pub rooms: rooms::Service,
pub sending: Arc<sending::Service>, pub sending: Arc<sending::Service>,
pub server_keys: Arc<server_keys::Service>,
pub transaction_ids: Arc<transaction_ids::Service>, pub transaction_ids: Arc<transaction_ids::Service>,
pub uiaa: Arc<uiaa::Service>, pub uiaa: Arc<uiaa::Service>,
pub updates: Arc<updates::Service>, pub updates: Arc<updates::Service>,
@@ -94,6 +95,7 @@ impl Services {
user: build!(rooms::user::Service), user: build!(rooms::user::Service),
}, },
sending: build!(sending::Service), sending: build!(sending::Service),
server_keys: build!(server_keys::Service),
transaction_ids: build!(transaction_ids::Service), transaction_ids: build!(transaction_ids::Service),
uiaa: build!(uiaa::Service), uiaa: build!(uiaa::Service),
updates: build!(updates::Service), updates: build!(updates::Service),