diff --git a/Cargo.lock b/Cargo.lock index 2c972c79..7db84a63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,7 +79,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -109,6 +109,45 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dbc3a507a82b17ba0d98f6ce8fd6954ea0c8152e98009d36a40d8dcc8ce078a" +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "assign" version = "1.1.1" @@ -128,9 +167,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" +checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" dependencies = [ "brotli", "flate2", @@ -150,7 +189,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -172,7 +211,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -183,7 +222,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -355,11 +394,11 @@ dependencies = [ "hyper", "hyper-util", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.23.26", + "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tower-service", ] @@ -374,9 +413,9 @@ dependencies = [ "http", "http-body-util", "pin-project", - "rustls", + "rustls 0.23.26", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tokio-util", "tower-layer", "tower-service", @@ -434,7 +473,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn", + "syn 2.0.101", "which", ] @@ -453,7 +492,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn", + "syn 2.0.101", ] [[package]] @@ -509,9 +548,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "cf19e729cdbd51af9a397fb9ef8ac8378007b797f8273cfbfdf45dcaa316167b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -520,9 +559,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -592,9 +631,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.19" +version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "jobserver", "libc", @@ -690,7 +729,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -789,6 +828,16 @@ dependencies = [ "crossterm", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -862,7 +911,7 @@ dependencies = [ "proc-macro2", "quote", "strict", - "syn", + "syn 2.0.101", ] [[package]] @@ -970,7 +1019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -997,7 +1046,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1038,6 +1087,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.4.0" @@ -1066,7 +1129,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1118,7 +1181,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1282,6 +1345,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1329,7 +1393,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1387,9 +1451,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -1579,7 +1643,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.9.1", - "ring", + "ring 0.17.14", "serde", "thiserror 2.0.12", "tinyvec", @@ -1671,7 +1735,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1766,11 +1830,11 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls", - "rustls-native-certs", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tower-service", "webpki-roots", ] @@ -1922,7 +1986,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2026,7 +2090,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2178,7 +2242,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 2.0.101", ] [[package]] @@ -2193,6 +2257,43 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lber" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a" +dependencies = [ + "bytes", + "nom", +] + +[[package]] +name = "ldap3" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166199a8207874a275144c8a94ff6eed5fcbf5c52303e4d9b4d53a0c7ac76554" +dependencies = [ + "async-trait", + "bytes", + "futures", + "futures-util", + "lazy_static", + "lber", + "log", + "nom", + "percent-encoding", + "ring 0.16.20", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.24.1", + "tokio-stream", + "tokio-util", + "url", + "x509-parser", +] + [[package]] name = "lebe" version = "0.5.2" @@ -2398,9 +2499,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minicbor" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb9d59e79ad66121ab441a0d1950890906a41e01ae14145ecf8401aa8894f50" +checksum = "8a309f581ade7597820083bc275075c4c6986e57e53f8d26f88507cfefc8c987" dependencies = [ "minicbor-derive", ] @@ -2413,7 +2514,7 @@ checksum = "a9882ef5c56df184b8ffc107fc6c61e33ee3a654b021961d790a78571bb9d67a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2579,7 +2680,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2641,6 +2742,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -2821,7 +2931,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2885,7 +2995,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2963,7 +3073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.101", ] [[package]] @@ -2992,7 +3102,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "version_check", "yansi", ] @@ -3013,7 +3123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -3036,7 +3146,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -3093,7 +3203,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls", + "rustls 0.23.26", "socket2", "thiserror 2.0.12", "tokio", @@ -3103,16 +3213,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" dependencies = [ "bytes", "getrandom 0.3.2", "rand 0.9.1", - "ring", + "ring 0.17.14", "rustc-hash 2.1.1", - "rustls", + "rustls 0.23.26", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -3197,7 +3307,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -3360,16 +3470,16 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", - "rustls-native-certs", - "rustls-pemfile", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tokio-socks", "tokio-util", "tower 0.5.2", @@ -3396,6 +3506,21 @@ version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + [[package]] name = "ring" version = "0.17.14" @@ -3404,9 +3529,9 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -3474,7 +3599,7 @@ dependencies = [ "base64 0.22.1", "bytes", "form_urlencoded", - "getrandom 0.2.15", + "getrandom 0.2.16", "http", "indexmap 2.9.0", "js_int", @@ -3574,7 +3699,7 @@ dependencies = [ "quote", "ruma-identifiers-validation", "serde", - "syn", + "syn 2.0.101", "toml", ] @@ -3659,6 +3784,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.44" @@ -3672,6 +3806,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.26" @@ -3681,13 +3827,25 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring", + "ring 0.17.14", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.1", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -3697,7 +3855,16 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", ] [[package]] @@ -3718,6 +3885,16 @@ dependencies = [ "web-time 1.1.0", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + [[package]] name = "rustls-webpki" version = "0.103.1" @@ -3725,9 +3902,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "aws-lc-rs", - "ring", + "ring 0.17.14", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3787,6 +3964,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + [[package]] name = "sd-notify" version = "0.4.5" @@ -3796,6 +3983,19 @@ dependencies = [ "libc", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -3803,7 +4003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -3833,7 +4033,7 @@ checksum = "255914a8e53822abd946e2ce8baa41d4cded6b8e938913b7f7b9da5b7ab44335" dependencies = [ "httpdate", "reqwest", - "rustls", + "rustls 0.23.26", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -3977,7 +4177,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4194,6 +4394,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.7.3" @@ -4258,9 +4464,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -4276,6 +4493,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -4284,7 +4513,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4375,7 +4604,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4386,7 +4615,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4542,7 +4771,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4557,13 +4786,23 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls", + "rustls 0.23.26", "tokio", ] @@ -4592,9 +4831,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -4605,9 +4844,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" dependencies = [ "serde", "serde_spanned", @@ -4617,26 +4856,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28391a4201ba7eb1984cfeb6862c0b3ea2cfe23332298967c749dddc0d6cd976" + [[package]] name = "tonic" version = "0.12.3" @@ -4754,7 +5000,7 @@ source = "git+https://github.com/matrix-construct/tracing?rev=1e64095a8051a1adf0 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -4937,6 +5183,7 @@ dependencies = [ "http-body-util", "ipaddress", "itertools 0.14.0", + "ldap3", "libc", "libloading", "log", @@ -4946,7 +5193,7 @@ dependencies = [ "rand 0.8.5", "regex", "reqwest", - "ring", + "ring 0.17.14", "ruma", "sanitize-filename", "serde", @@ -4994,7 +5241,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5014,7 +5261,7 @@ dependencies = [ "hyper-util", "log", "ruma", - "rustls", + "rustls 0.23.26", "sd-notify", "sentry", "sentry-tower", @@ -5046,6 +5293,7 @@ dependencies = [ "image", "ipaddress", "itertools 0.14.0", + "ldap3", "log", "loole", "lru-cache", @@ -5136,12 +5384,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -5157,7 +5417,7 @@ dependencies = [ "base64 0.22.1", "log", "once_cell", - "rustls", + "rustls 0.23.26", "rustls-pki-types", "url", "webpki-roots", @@ -5290,7 +5550,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -5325,7 +5585,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5383,9 +5643,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "29aad86cec885cafd03e8305fd727c418e970a521322c91688414d5b8efba16b" dependencies = [ "rustls-pki-types", ] @@ -5473,7 +5733,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5484,7 +5744,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5755,9 +6015,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" dependencies = [ "memchr", ] @@ -5793,6 +6053,23 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xml5ever" version = "0.18.1" @@ -5830,28 +6107,28 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.101", + "synstructure 0.13.1", ] [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -5871,8 +6148,8 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.101", + "synstructure 0.13.1", ] [[package]] @@ -5900,7 +6177,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ea192512..6d56360b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -549,6 +549,11 @@ features = ["serde"] version = "2.0.1" default-features = false +[workspace.dependencies.ldap3] +version = "0.11" +default-features = false +features = ["sync", "tls-rustls"] + # # Patches # diff --git a/src/admin/query/users.rs b/src/admin/query/users.rs index 1b6b534a..76049fd5 100644 --- a/src/admin/query/users.rs +++ b/src/admin/query/users.rs @@ -94,6 +94,39 @@ pub(crate) enum UsersCommand { user_a: OwnedUserId, user_b: OwnedUserId, }, + + SearchLdap { + user_id: OwnedUserId, + }, + + AuthLdap { + user_dn: String, + password: String, + }, +} + +#[admin_command] +async fn auth_ldap(&self, user_dn: String, password: String) -> Result { + let timer = tokio::time::Instant::now(); + let result = self + .services + .users + .auth_ldap(&user_dn, &password) + .await; + let query_time = timer.elapsed(); + + self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```")) + .await +} + +#[admin_command] +async fn search_ldap(&self, user_id: OwnedUserId) -> Result { + let timer = tokio::time::Instant::now(); + let result = self.services.users.search_ldap(&user_id).await; + let query_time = timer.elapsed(); + + self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```")) + .await } #[admin_command] diff --git a/src/api/client/account.rs b/src/api/client/account.rs index cbb0c48b..dbefb70f 100644 --- a/src/api/client/account.rs +++ b/src/api/client/account.rs @@ -372,7 +372,10 @@ pub(crate) async fn register_route( let password = if is_guest { None } else { body.password.as_deref() }; // Create user - services.users.create(&user_id, password, None).await?; + services + .users + .create(&user_id, password, None) + .await?; // Default to pretty displayname let mut displayname = user_id.localpart().to_owned(); diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index 9c65a243..dec76448 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -90,7 +90,10 @@ pub(crate) async fn get_displayname_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None, None).await?; + services + .users + .create(&body.user_id, None, None) + .await?; } services @@ -193,7 +196,10 @@ pub(crate) async fn get_avatar_url_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None, None).await?; + services + .users + .create(&body.user_id, None, None) + .await?; } services @@ -255,7 +261,10 @@ pub(crate) async fn get_profile_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None, None).await?; + services + .users + .create(&body.user_id, None, None) + .await?; } services diff --git a/src/api/client/session.rs b/src/api/client/session.rs index 14293f43..c1907e9e 100644 --- a/src/api/client/session.rs +++ b/src/api/client/session.rs @@ -2,9 +2,9 @@ use std::time::Duration; use axum::extract::State; use axum_client_ip::InsecureClientIp; -use futures::StreamExt; +use futures::{StreamExt, TryFutureExt}; use ruma::{ - UserId, + OwnedUserId, UserId, api::client::{ session::{ get_login_token, @@ -22,10 +22,10 @@ use ruma::{ }, }; use tuwunel_core::{ - Err, Error, Result, debug, err, info, utils, - utils::{ReadyExt, hash}, + Err, Error, Result, debug, debug_error, err, info, utils, + utils::{hash, stream::ReadyExt}, }; -use tuwunel_service::uiaa::SESSION_ID_LENGTH; +use tuwunel_service::{Services, uiaa::SESSION_ID_LENGTH}; use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH}; use crate::Ruma; @@ -49,6 +49,82 @@ pub(crate) async fn get_login_types_route( ])) } +/// Authenticates the given user by its ID and its password. +/// +/// Returns the user ID if successful, and an error otherwise. +async fn password_login( + services: &Services, + user_id: &UserId, + lowercased_user_id: &UserId, + password: &str, +) -> Result { + let (hash, user_id) = services + .users + .password_hash(user_id) + .map_ok(|hash| (hash, user_id)) + .or_else(|_| { + services + .users + .password_hash(lowercased_user_id) + .map_ok(|hash| (hash, lowercased_user_id)) + }) + .await?; + + if hash.is_empty() { + return Err!(Request(UserDeactivated("The user has been deactivated"))); + } + + hash::verify_password(password, &hash) + .inspect_err(|e| debug_error!("{e}")) + .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; + + Ok(user_id.to_owned()) +} + +/// Authenticates the given user through the configured LDAP server. +/// +/// Creates the user if the user is found in the LDAP and do not already have an +/// account. +async fn ldap_login( + services: &Services, + user_id: &UserId, + lowercased_user_id: &UserId, + password: &str, +) -> Result { + debug!("Searching user in LDAP"); + + let dns = services.users.search_ldap(user_id).await?; + + if dns.len() >= 2 { + return Err!(Ldap("LDAP search returned two or more results")); + } + + let Some(user_dn) = dns.first() else { + return password_login(services, user_id, lowercased_user_id, password).await; + }; + + // LDAP users are automatically created on first login attempt. This is a very + // common feature that can be seen on many services using a LDAP provider for + // their users (synapse, Nextcloud, Jellyfin, ...). + // + // LDAP users are crated with a dummy password but non empty because an empty + // password is reserved for deactivated accounts. The tuwunel password field + // will never be read to login a LDAP user so it's not an issue. + if !services.users.exists(lowercased_user_id).await { + debug!("Creating user {lowercased_user_id} from LDAP"); + services + .users + .create(lowercased_user_id, Some("*"), Some("ldap")) + .await?; + } + + services + .users + .auth_ldap(user_dn, password) + .await + .map(|()| lowercased_user_id.to_owned()) +} + /// # `POST /_matrix/client/v3/login` /// /// Authenticates the user and returns an access token it can use in subsequent @@ -107,43 +183,10 @@ pub(crate) async fn login_route( return Err!(Request(Unknown("User ID does not belong to this homeserver"))); } - // first try the username as-is - let hash = services - .users - .password_hash(&user_id) - .await - .inspect_err(|e| debug!("{e}")); - - match hash { - | Ok(hash) => { - if hash.is_empty() { - return Err!(Request(UserDeactivated("The user has been deactivated"))); - } - - hash::verify_password(password, &hash) - .inspect_err(|e| debug!("{e}")) - .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; - - user_id - }, - | Err(_e) => { - let hash_lowercased_user_id = services - .users - .password_hash(&lowercased_user_id) - .await - .inspect_err(|e| debug!("{e}")) - .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; - - if hash_lowercased_user_id.is_empty() { - return Err!(Request(UserDeactivated("The user has been deactivated"))); - } - - hash::verify_password(password, &hash_lowercased_user_id) - .inspect_err(|e| debug!("{e}")) - .map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?; - - lowercased_user_id - }, + if services.config.ldap.enable { + ldap_login(&services, &user_id, &lowercased_user_id, password).await? + } else { + password_login(&services, &user_id, &lowercased_user_id, password).await? } }, | login::v3::LoginInfo::Token(login::v3::Token { token }) => { diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index c0aad378..3b69bb46 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -306,7 +306,10 @@ pub(crate) async fn get_timezone_key_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None, None).await?; + services + .users + .create(&body.user_id, None, None) + .await?; } services @@ -366,7 +369,10 @@ pub(crate) async fn get_profile_key_route( .await { if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None, None).await?; + services + .users + .create(&body.user_id, None, None) + .await?; } services diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 363e6bfc..e6dc2fc4 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -78,6 +78,7 @@ http-body-util.workspace = true http.workspace = true ipaddress.workspace = true itertools.workspace = true +ldap3.workspace = true libc.workspace = true libloading.workspace = true libloading.optional = true diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index f80fb80f..0bb1044f 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -52,7 +52,7 @@ use crate::{Result, err, error::Error, utils::sys}; ### For more information, see: ### https://tuwunel.chat/configuration.html "#, - ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates" + ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates ldap" )] pub struct Config { /// The server_name is the pretty name of this server. It is used as a @@ -1804,6 +1804,17 @@ pub struct Config { // external structure; separate section #[serde(default)] pub blurhashing: BlurhashConfig, + + #[cfg(not(doctest))] + /// Examples: + /// + /// - No LDAP login (default): + /// + /// ldap = "none" + /// + /// default: "none" + pub ldap: LdapConfig, + #[serde(flatten)] #[allow(clippy::zero_sized_map_values)] // this is a catchall, the map shouldn't be zero at runtime @@ -1885,6 +1896,75 @@ pub struct BlurhashConfig { pub blurhash_max_raw_size: u64, } +#[derive(Clone, Debug, Deserialize)] +#[config_example_generator(filename = "tuwunel-example.toml", section = "global.ldap")] +pub struct LdapConfig { + /// Whether to enable LDAP login. + /// + /// example: "true" + #[serde(default)] + pub enable: bool, + + /// URI of the LDAP server. + /// + /// example: "ldap://ldap.example.com:389" + #[serde(deserialize_with = "crate::utils::deserialize_from_str")] + pub uri: Url, + + /// Whether to use StartTLS to bind to the LDAP server. + /// + /// example: true + #[serde(default)] + pub start_tls: bool, + + /// Root of the searches. + /// + /// example: "ou=users,dc=example,dc=org" + #[serde(default)] + pub base_dn: String, + + /// Bind DN if anonymous search is not enabled. + /// + /// example: "cn=ldap-reader,dc=example,dc=org" + #[serde(default)] + pub bind_dn: Option, + + /// Path to a file on the system that contains the password for the + /// `bind_dn`. + /// + /// The server must be able to access the file, and it must not be empty. + #[serde(default)] + pub bind_password_file: Option, + + /// Search filter to limit user searches. + /// + /// example: "(&(objectClass=person)(memberOf=matrix))" + /// + /// default: "(objectClass=*)" + #[serde(default = "default_ldap_search_filter")] + pub filter: String, + + /// Attribute to use to uniquely identify the user. + /// + /// example: "uid" or "cn" + /// + /// default: "uid" + #[serde(default = "default_ldap_uid_attribute")] + pub uid_attribute: String, + + /// Attribute containing the mail of the user. + /// + /// example: "mail" + #[serde(default = "default_ldap_mail_attribute")] + pub mail_attribute: String, + + /// Attribute containing the distinguished name of the user. + /// + /// example: "givenName" or "sn" + #[serde(default = "default_ldap_name_attribute")] + pub name_attribute: String, +} + #[derive(Deserialize, Clone, Debug)] #[serde(transparent)] struct ListeningPort { @@ -2267,10 +2347,16 @@ fn default_sender_shutdown_timeout() -> u64 { 5 } // blurhashing defaults recommended by https://blurha.sh/ // 2^25 -pub(super) fn default_blurhash_max_raw_size() -> u64 { 33_554_432 } +fn default_blurhash_max_raw_size() -> u64 { 33_554_432 } -pub(super) fn default_blurhash_x_component() -> u32 { 4 } +fn default_blurhash_x_component() -> u32 { 4 } -pub(super) fn default_blurhash_y_component() -> u32 { 3 } +fn default_blurhash_y_component() -> u32 { 3 } -// end recommended & blurhashing defaults +fn default_ldap_search_filter() -> String { "(objectClass=*)".to_owned() } + +fn default_ldap_uid_attribute() -> String { String::from("uid") } + +fn default_ldap_mail_attribute() -> String { String::from("mail") } + +fn default_ldap_name_attribute() -> String { String::from("name") } diff --git a/src/core/error/mod.rs b/src/core/error/mod.rs index 5c869249..6b977651 100644 --- a/src/core/error/mod.rs +++ b/src/core/error/mod.rs @@ -110,6 +110,8 @@ pub enum Error { InconsistentRoomState(&'static str, ruma::OwnedRoomId), #[error(transparent)] IntoHttp(#[from] ruma::api::error::IntoHttpError), + #[error("{0}")] + Ldap(Cow<'static, str>), #[error(transparent)] Mxc(#[from] ruma::MxcUriError), #[error(transparent)] diff --git a/src/service/Cargo.toml b/src/service/Cargo.toml index 5f400dd5..20eac916 100644 --- a/src/service/Cargo.toml +++ b/src/service/Cargo.toml @@ -87,6 +87,7 @@ image.workspace = true image.optional = true ipaddress.workspace = true itertools.workspace = true +ldap3.workspace = true log.workspace = true loole.workspace = true lru-cache.workspace = true diff --git a/src/service/admin/create.rs b/src/service/admin/create.rs index 324f66d2..0b752a45 100644 --- a/src/service/admin/create.rs +++ b/src/service/admin/create.rs @@ -38,7 +38,10 @@ pub async fn create_admin_room(services: &Services) -> Result { // Create a user for the server let server_user = services.globals.server_user.as_ref(); - services.users.create(server_user, None, None).await?; + services + .users + .create(server_user, None, None) + .await?; let create_content = { use RoomVersionId::*; diff --git a/src/service/rooms/state_cache/mod.rs b/src/service/rooms/state_cache/mod.rs index c453dca3..0952be61 100644 --- a/src/service/rooms/state_cache/mod.rs +++ b/src/service/rooms/state_cache/mod.rs @@ -133,7 +133,10 @@ impl Service { #[allow(clippy::collapsible_if)] if !self.services.globals.user_is_local(user_id) { if !self.services.users.exists(user_id).await { - self.services.users.create(user_id, None, None).await?; + self.services + .users + .create(user_id, None, None) + .await?; } /* diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index 8bb6742d..2d13dad5 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -1,6 +1,8 @@ use std::{collections::BTreeMap, mem, sync::Arc}; use futures::{Stream, StreamExt, TryFutureExt}; +use itertools::Itertools; +use ldap3::{LdapConnAsync, Scope, SearchEntry}; use ruma::{ DeviceId, KeyId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OneTimeKeyId, OneTimeKeyName, OwnedDeviceId, OwnedKeyId, OwnedMxcUri, OwnedUserId, RoomId, UInt, UserId, @@ -13,8 +15,8 @@ use ruma::{ }; use serde_json::json; use tuwunel_core::{ - Err, Error, Result, Server, at, debug_warn, err, trace, - utils::{self, ReadyExt, stream::TryIgnore, string::Unquoted}, + Err, Error, Result, Server, at, debug, debug_warn, err, error, trace, + utils::{self, ReadyExt, result::LogErr, stream::TryIgnore, string::Unquoted}, }; use tuwunel_database::{Deserialized, Ignore, Interfix, Json, Map}; @@ -123,6 +125,10 @@ impl Service { } /// Create a new user account on this homeserver. + /// + /// User origin is by default "password" (meaning that it will login using + /// its user_id/password). Users with other origins (currently only "ldap" + /// is available) have special login processes. #[inline] pub async fn create( &self, @@ -222,7 +228,11 @@ impl Service { /// Returns the origin of the user (password/LDAP/...). pub async fn origin(&self, user_id: &UserId) -> Result { - self.db.userid_origin.get(user_id).await.deserialized() + self.db + .userid_origin + .get(user_id) + .await + .deserialized() } /// Returns the password hash for the given user. @@ -236,13 +246,17 @@ impl Service { /// Hash and set the user's password to the Argon2 hash pub async fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> { + // Cannot change the password of a LDAP user. There are two special cases : + // - a `None` password can be used to deactivate a LDAP user + // - a "*" password is used as the default password of an active LDAP user if self .db .userid_origin .get(user_id) .await .deserialized::()? - == "ldap" + == "ldap" && password.is_some() + && password != Some("*") { Err!(Request(InvalidParam("Cannot change password of a LDAP user"))) } else { @@ -1163,6 +1177,96 @@ impl Service { self.db.useridprofilekey_value.del(key); } } + + pub async fn search_ldap(&self, user_id: &UserId) -> Result> { + let config = &self.services.server.config.ldap; + let (conn, mut ldap) = LdapConnAsync::new(config.uri.as_str()) + .await + .map_err(|e| err!(Ldap(error!(?user_id, "LDAP connection setup error: {e}"))))?; + + let driver = self.services.server.runtime().spawn(async move { + match conn.drive().await { + | Err(e) => error!("LDAP connection error: {e}"), + | Ok(()) => debug!("LDAP connection completed."), + } + }); + + match (&config.bind_dn, &config.bind_password_file) { + | (Some(bind_dn), Some(bind_password_file)) => { + let bind_pw = String::from_utf8(std::fs::read(bind_password_file)?)?; + ldap.simple_bind(bind_dn, bind_pw.trim()) + .await + .and_then(ldap3::LdapResult::success) + .map_err(|e| err!(Ldap(error!("LDAP bind error: {e}"))))?; + }, + | (..) => {}, + } + + let attr = [&config.uid_attribute, &config.name_attribute]; + + let filter = &config.filter; + + let (entries, _result) = ldap + .search(&config.base_dn, Scope::Subtree, filter, &attr) + .await + .and_then(ldap3::SearchResult::success) + .inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Search")) + .map_err(|e| err!(Ldap(error!(?attr, ?filter, "LDAP search error: {e}"))))?; + + let localpart = user_id.localpart().to_owned(); + let lowercased_localpart = localpart.to_lowercase(); + let dns = entries + .into_iter() + .filter_map(|entry| { + let search_entry = SearchEntry::construct(entry); + debug!(?search_entry, "LDAP search entry"); + search_entry + .attrs + .get(&config.uid_attribute) + .into_iter() + .chain(search_entry.attrs.get(&config.name_attribute)) + .any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart)) + .then_some(search_entry.dn) + }) + .collect_vec(); + + ldap.unbind() + .await + .map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?; + + driver.await.log_err().ok(); + + Ok(dns) + } + + pub async fn auth_ldap(&self, user_dn: &str, password: &str) -> Result { + let config = &self.services.server.config.ldap; + let (conn, mut ldap) = LdapConnAsync::new(config.uri.as_str()) + .await + .map_err(|e| err!(Ldap(error!(?user_dn, "LDAP connection setup error: {e}"))))?; + + let driver = self.services.server.runtime().spawn(async move { + match conn.drive().await { + | Err(e) => error!("LDAP connection error: {e}"), + | Ok(()) => debug!("LDAP connection completed."), + } + }); + + ldap.simple_bind(user_dn, password) + .await + .and_then(ldap3::LdapResult::success) + .map_err(|e| { + err!(Request(Forbidden(debug_error!("LDAP authentication error: {e}")))) + })?; + + ldap.unbind() + .await + .map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?; + + driver.await.log_err().ok(); + + Ok(()) + } } pub fn parse_master_key( diff --git a/tuwunel-example.toml b/tuwunel-example.toml index 29e71cfb..4a6c222e 100644 --- a/tuwunel-example.toml +++ b/tuwunel-example.toml @@ -1626,3 +1626,66 @@ # is 33.55MB. Setting it to 0 disables blurhashing. # #blurhash_max_raw_size = 33554432 + +[global.ldap] + +# Whether to enable LDAP login. +# +# example: "true" +# +#enable = false + +# URI of the LDAP server. +# +# example: "ldap://ldap.example.com:389" +# +#uri = + +# Whether to use StartTLS to bind to the LDAP server. +# +# example: true +# +#start_tls = false + +# Root of the searches. +# +# example: "ou=users,dc=example,dc=org" +# +#base_dn = false + +# Bind DN if anonymous search is not enabled. +# +# example: "cn=ldap-reader,dc=example,dc=org" +# +#bind_dn = false + +# Path to a file on the system that contains the password for the +# `bind_dn`. +# +# The server must be able to access the file, and it must not be empty. +# +#bind_password_file = false + +# Search filter to limit user searches. +# +# example: "(&(objectClass=person)(memberOf=matrix))" +# +#filter = "(objectClass=*)" + +# Attribute to use to uniquely identify the user. +# +# example: "uid" or "cn" +# +#uid_attribute = "uid" + +# Attribute containing the mail of the user. +# +# example: "mail" +# +#mail_attribute = + +# Attribute containing the distinguished name of the user. +# +# example: "givenName" or "sn" +# +#name_attribute =