diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f21f605..2f998c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -412,4 +412,10 @@ jobs: # not enable it (`cargo tree -i rkyv` has no path), so no rkyv code # is compiled into the shipped binary. Re-check whenever # rust_decimal is upgraded or its enabled features change. - ignore: RUSTSEC-2026-0235 + # RUSTSEC-2023-0071 affects rsa 0.10.0-rc.18 (Marvin timing + # side channel, no patched release). sspi depends on picky, which + # depends on rsa, with no feature gate. The NTLM path never + # performs RSA private-key operations, so the timing leak is not + # reachable from this plugin. Re-check when sspi drops picky or + # rsa ships a fix. + ignore: RUSTSEC-2026-0235,RUSTSEC-2023-0071 diff --git a/CHANGELOG.md b/CHANGELOG.md index ba2627b..da6e56e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- NTLM authentication with `DOMAIN\user` and password on every platform, + including Linux and macOS (`extra.ntlm`, "Use NTLM" checkbox). + +### Changed + +- Upgrade Microsoft's `mssql-tiberius-bridge` to 0.3.0 and `mssql-tds` to 0.4.0. + ## [1.0.0-beta.4] - 2026-10-25 ### Changed diff --git a/Cargo.lock b/Cargo.lock index 95c3aaf..09af646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aes" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fc36d7440d015b45468ba08a21e80e056ebbd014c611c6298bb7503e04bd1" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.7.8" @@ -53,7 +64,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -67,6 +78,36 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "async-dnssd" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d49ffe175ab45bbfd74b548313d9d7cdfff27161a94b007b52eeeb5f9aaa15e" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-util", + "libc", + "log", + "pin-utils", + "pkg-config", + "tokio", + "winapi", +] + +[[package]] +name = "async-recursion" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -106,12 +147,24 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bigdecimal" version = "0.4.10" @@ -125,6 +178,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.1" @@ -143,6 +202,24 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -204,6 +281,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + [[package]] name = "byteorder" version = "1.5.0" @@ -216,6 +299,15 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.4.0" @@ -273,6 +365,28 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "core-foundation" version = "0.10.1" @@ -289,6 +403,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.3.0" @@ -304,6 +424,92 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c035cb64cba03dad2fb921f3e218785e8cf9a41790da5eb3a1400f62fa2be11" +dependencies = [ + "cpubits", + "ctutils", + "getrandom 0.4.3", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-primes" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" +dependencies = [ + "crypto-bigint", + "rand_core 0.10.1", +] + +[[package]] +name = "ctutils" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261af4dc7a06ea8a527a8717d8cbba3417530d6ea14e1edb3de6b67806be474f" +dependencies = [ + "cmov", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rand_core 0.10.1", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "data-encoding" version = "2.11.0" @@ -347,6 +553,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae" +[[package]] +name = "der" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -367,6 +584,27 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +[[package]] +name = "des" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "ctutils", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -390,6 +628,67 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ecdsa" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.1", + "sha2", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900f37e7e18d74a2347389af6cd69e3a30d5cc9e697f40004865679aa75e9c7c" +dependencies = [ + "base16ct", + "crypto-bigint", + "crypto-common", + "digest", + "ff", + "group", + "hkdf", + "hybrid-array", + "pem-rfc7468", + "pkcs8", + "rand_core 0.10.1", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -421,6 +720,22 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -448,6 +763,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "funty" version = "2.0.0" @@ -542,6 +866,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -553,6 +887,20 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -561,10 +909,21 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.1", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "half" version = "2.7.1" @@ -603,6 +962,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest", +] + [[package]] name = "hostname" version = "0.4.2" @@ -614,6 +997,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -638,6 +1032,110 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" + +[[package]] +name = "icu_properties" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" + +[[package]] +name = "icu_provider" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -657,6 +1155,16 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a580c13f0ffc335ef42dd3f6b1a41b726051c5e1656d9a3aa6fddb3c5dfbbf9" +dependencies = [ + "block-padding", + "hybrid-array", +] + [[package]] name = "itoa" version = "1.0.18" @@ -674,6 +1182,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" +dependencies = [ + "cfg-if", + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -698,6 +1216,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "lock_api" version = "0.4.14" @@ -713,6 +1237,25 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "md4" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd76fb0fd6b2e4be62a73f8e0858ca97f81babcb1af322dcaca196f735f17f80" +dependencies = [ + "digest", +] + [[package]] name = "memchr" version = "2.8.3" @@ -744,7 +1287,7 @@ checksum = "812c3993a5f6d98e14da9d8ab0bf9f9f536e49c18240f34af9bae301e4d35ae8" dependencies = [ "async-trait", "bigdecimal", - "bitflags", + "bitflags 2.13.1", "block2", "byteorder", "bytes", @@ -768,7 +1311,7 @@ dependencies = [ "tracing", "uuid", "winapi", - "windows", + "windows 0.58.0", "windows-sys 0.59.0", "x509-parser", "zeroize", @@ -836,6 +1379,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -879,6 +1433,15 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "oid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" +dependencies = [ + "serde", +] + [[package]] name = "oid-registry" version = "0.8.1" @@ -900,7 +1463,7 @@ version = "0.10.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4de8111e94a56adca7ebfe6c365b6d42f99deb69b74b6becacd4204e2f193aa2" dependencies = [ - "bitflags", + "bitflags 2.13.1", "cfg-if", "foreign-types", "libc", @@ -947,6 +1510,47 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p256" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad64cc32c2dc466317c12ee5853e61f159f9eab1fe7efade0395dc2e7b43449" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -970,18 +1574,179 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "picky" +version = "7.0.0-rc.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcf32b73bfc151bc07c3fbf35e729ad3078d48c581f8656c0f62a529fbf681c" +dependencies = [ + "base64", + "crypto-bigint", + "crypto-common", + "digest", + "ed25519-dalek", + "hex", + "inout", + "md-5", + "p256", + "p384", + "p521", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "pkcs1", + "rand 0.10.2", + "rand_core 0.10.1", + "rsa", + "serde", + "sha1", + "sha2", + "sha3", + "thiserror", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "picky-asn1" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e56b8955035e2593f7f7e7f8d30c9c7a0ce5a5e31cc529f8f087a9e0437526" +dependencies = [ + "oid", + "serde", + "serde_bytes", + "time", + "zeroize", +] + +[[package]] +name = "picky-asn1-der" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4467118a2c640f4040aea27d01293620984b0b3f3ef0c53577c3bbf6bab4256" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98dd311a5e525d1200365273a509b9d07bccf3481aaa901047fdca19830ca721" +dependencies = [ + "base64", + "crypto-bigint", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", + "widestring", + "zeroize", +] + +[[package]] +name = "picky-krb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9306c2a07e8bfd1faa0985b76bc7b86ab98f2b9a1433fdfa4c9a116b6e4c8a3d" +dependencies = [ + "aes", + "block-padding", + "byteorder", + "cbc", + "cipher", + "crypto-bigint", + "des", + "hmac", + "inout", + "oid", + "pbkdf2", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "rand 0.10.2", + "rand_core 0.10.1", + "serde", + "sha1", + "thiserror", + "uuid", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bee6c73da26345c729282832b60b0363cf3dd9f4bfd81d8551b7a1c889a113" + +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1003,6 +1768,33 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a65843dfefbafd3c879c683306959a6de478443ffe9c9adf02f5976432402d7" +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint", + "crypto-common", + "ff", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" +dependencies = [ + "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -1050,6 +1842,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1115,16 +1913,26 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.1", ] [[package]] name = "rend" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rfc6979" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" dependencies = [ - "bytecheck", + "crypto-bigint", + "hmac", ] [[package]] @@ -1156,6 +1964,24 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rsa" +version = "0.10.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" +dependencies = [ + "const-oid", + "crypto-bigint", + "crypto-primes", + "digest", + "pkcs1", + "pkcs8", + "rand_core 0.10.1", + "signature", + "spki", + "zeroize", +] + [[package]] name = "rust_decimal" version = "1.42.1" @@ -1173,6 +1999,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -1188,7 +2023,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", @@ -1222,13 +2057,27 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct", + "ctutils", + "der", + "hybrid-array", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.1", "core-foundation", "core-foundation-sys", "libc", @@ -1245,6 +2094,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.229" @@ -1255,6 +2110,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -1288,6 +2153,49 @@ dependencies = [ "zmij", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" @@ -1304,6 +2212,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest", + "rand_core 0.10.1", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -1342,6 +2260,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + [[package]] name = "sqlserver-plugin" version = "1.0.0-beta.4" @@ -1354,12 +2288,71 @@ dependencies = [ "mssql-tiberius-bridge", "once_cell", "openssl", + "rand 0.8.7", "serde", "serde_json", + "sspi", + "tokio", + "uuid", +] + +[[package]] +name = "sspi" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3fa5e1405884e88be309bd801760caa84c654be746fe4261ab407496263692" +dependencies = [ + "async-dnssd", + "async-recursion", + "bitflags 2.13.1", + "bytemuck", + "byteorder", + "cfg-if", + "crypto-bigint", + "crypto-mac", + "futures", + "getrandom 0.3.4", + "hmac", + "md-5", + "md4", + "num-derive", + "num-traits", + "oid", + "picky", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", + "pkcs1", + "rand 0.10.2", + "rand_core 0.10.1", + "rsa", + "serde", + "sha1", + "sha2", + "time", "tokio", + "tracing", + "url", "uuid", + "widestring", + "windows 0.62.2", + "windows-registry", + "zeroize", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1404,6 +2397,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "tap" version = "1.0.1" @@ -1417,7 +2421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1450,6 +2454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -1473,6 +2478,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.12.0" @@ -1606,12 +2621,45 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0" +dependencies = [ + "utf_types", +] + +[[package]] +name = "utf_types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6" + [[package]] name = "uuid" version = "1.29.0" @@ -1621,6 +2669,7 @@ dependencies = [ "getrandom 0.4.3", "js-sys", "rand 0.10.2", + "serde_core", "wasm-bindgen", ] @@ -1642,6 +2691,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.126" @@ -1698,6 +2756,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -1730,6 +2794,27 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.58.0" @@ -1756,6 +2841,17 @@ dependencies = [ "windows-strings 0.5.1", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -1806,6 +2902,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.2.0" @@ -1912,6 +3029,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2017,6 +3143,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wnaf" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" +dependencies = [ + "ff", + "group", + "hybrid-array", + "primefield", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + [[package]] name = "wyz" version = "0.5.1" @@ -2026,6 +3176,17 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e8131a03190127fb2263afc72b322ecadae46b6ff8c6f399ff5d02f5559af6" +dependencies = [ + "curve25519-dalek", + "rand_core 0.10.1", + "zeroize", +] + [[package]] name = "x509-parser" version = "0.18.1" @@ -2043,6 +3204,29 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", + "synstructure 0.14.0", +] + [[package]] name = "zerocopy" version = "0.8.55" @@ -2063,6 +3247,27 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", + "synstructure 0.14.0", +] + [[package]] name = "zeroize" version = "1.9.1" @@ -2083,6 +3288,39 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 05cafb7..3468d91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ mssql-tds = "=0.4.0" once_cell = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" +# Pure-Rust NTLM client for `extra.ntlm`; mssql-tds only has Kerberos on Unix. +sspi = { version = "0.23", default-features = false } tokio = { version = "1", features = ["full"] } uuid = "1" @@ -32,6 +34,10 @@ uuid = "1" # targets keep the platform TLS stack selected by native-tls. openssl = { version = "0.10", features = ["vendored"] } +[dev-dependencies] +# Random test secrets; the tests must not hardcode passwords. +rand = "0.8" + [profile.release] lto = true codegen-units = 1 diff --git a/README.md b/README.md index e21d5b2..8e17e9a 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ This plugin enables Tabularis to connect to SQL Server instances, providing sche | `host` | `localhost` | Yes unless using `connection_string` | SQL Server hostname or IP address | | `port` | `1433` | No | TDS port | | `database` | — | Yes unless using `connection_string` | Database the pool connects to | -| `username` | `sa` | Yes, unless integrated authentication is enabled or `connection_string` is used | SQL-authenticated login | +| `username` | `sa` | Yes, unless integrated authentication is enabled or `connection_string` is used | SQL-authenticated login, or `DOMAIN\user` / `user@domain` with NTLM | | `password` | — | If required by the server | Login password; redacted from connection errors | | `ssl_mode` | `prefer` | No | `disable`, `prefer`, `require`, or `verify-full` | | `ssl_ca` | — | No | Rejected; strict TLS uses the system trust store | @@ -137,6 +137,19 @@ fails at runtime if `libgssapi_krb5` (package `libgssapi-krb5-2` on Debian/Ubuntu, `krb5-libs` on RHEL/Alpine) is missing, or without a valid Kerberos ticket (`kinit`) and `/etc/krb5.conf`. +### NTLM authentication + +The second checkbox, "Use NTLM", writes `extra.ntlm = "true"` and keeps the +username/password inputs. Enter the username as `DOMAIN\user` or +`user@domain`. The plugin then authenticates with NTLM through the pure-Rust +[`sspi`](https://crates.io/crates/sspi) crate on every platform, so Linux and +macOS need no Kerberos setup or system library. It plugs into mssql-tds' +`SecurityContextProvider` hook ([microsoft/mssql-rs#707](https://github.com/microsoft/mssql-rs/issues/707)). + +NTLM cannot be combined with integrated authentication, and it requires a +username. Channel bindings (Extended Protection) are not sent, so servers +that require Extended Protection reject it. + A connection string may be combined with discrete fields. Values explicitly present in the string are authoritative, while discrete fields fill only fields the string omits. Repeating the same value is allowed; contradictory @@ -372,7 +385,7 @@ remaining pools. ## Known Limitations -- SQL authentication and Windows/Kerberos integrated authentication (`integrated_auth`) are supported; Azure AD authentication is follow-up work. +- SQL authentication, Windows/Kerberos integrated authentication (`integrated_auth`) and NTLM (`ntlm`) are supported; Azure AD authentication is follow-up work. - Primary-key membership changes are disabled: the single-column alteration API cannot safely preserve composite PKs and referencing foreign keys. - Custom CA files are rejected explicitly; strict verification uses the system trust store. - SQL Server has indexed views, not materialized views. Indexed views are maintained synchronously and have no refresh operation, so `get_materialized_views`, `get_materialized_view_columns`, `get_materialized_view_definition`, and `refresh_materialized_view` deliberately return `-32601` rather than pretending the features are equivalent. diff --git a/src/common.rs b/src/common.rs index ced5c70..0c7ac9b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -70,3 +70,15 @@ pub fn returns_result_set(query: &str) -> bool { | "CALL" ) } + +/// A fresh random password for tests. Tests must not hardcode secrets. +#[cfg(test)] +pub(crate) fn random_secret() -> String { + use rand::distributions::Alphanumeric; + use rand::{thread_rng, Rng}; + thread_rng() + .sample_iter(&Alphanumeric) + .take(24) + .map(char::from) + .collect() +} diff --git a/src/connection.rs b/src/connection.rs index 73d19a1..822d05c 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -49,6 +49,12 @@ pub fn resolve_connection_params(params: &ConnectionParams) -> Result Result")); } } diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 6cf10d3..1319c0f 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -11,6 +11,7 @@ pub mod explain; pub mod extract; pub mod helpers; pub mod introspection; +pub mod ntlm; pub mod ops; pub mod pool; pub mod query_templates; diff --git a/src/driver/ntlm.rs b/src/driver/ntlm.rs new file mode 100644 index 0000000..ffb0be5 --- /dev/null +++ b/src/driver/ntlm.rs @@ -0,0 +1,201 @@ +//! NTLM with explicit `DOMAIN\user` credentials, on every platform. +//! +//! mssql-tds' built-in integrated auth is Kerberos-only on Linux/macOS +//! (GSSAPI). This plugs Devolutions' pure-Rust `sspi` NTLM client into its +//! `SecurityContextProvider` hook instead, so no system library is needed. +//! Channel bindings (Extended Protection) are not sent. + +use mssql_tds::security::{ + IntegratedAuthConfig, SecurityContext, SecurityContextProvider, SecurityError, SspiAuthToken, +}; +use sspi::{ + AuthIdentity, AuthIdentityBuffers, BufferType, ClientRequestFlags, CredentialUse, + DataRepresentation, Ntlm, SecurityBuffer, SecurityStatus, Sspi, SspiImpl, Username, +}; + +pub struct NtlmProvider { + identity: AuthIdentity, +} + +impl NtlmProvider { + /// `username` is `DOMAIN\user` or `user@domain`. + pub fn new(username: &str, password: &str) -> Result { + let username = Username::parse(username) + .map_err(|error| format!("invalid NTLM username {username:?}: {error}"))?; + Ok(Self { + identity: AuthIdentity { + username, + password: password.to_string().into(), + }, + }) + } +} + +impl SecurityContextProvider for NtlmProvider { + fn create_context( + &self, + _config: &IntegratedAuthConfig, + server: &str, + port: u16, + ) -> Result, SecurityError> { + let mut ntlm = Ntlm::new(); + let credentials = ntlm + .acquire_credentials_handle() + .with_credential_use(CredentialUse::Outbound) + .with_auth_data(&self.identity) + .execute(&mut ntlm) + .map_err(|error| SecurityError::AcquireCredentialsFailed { + code: error.error_type as u32, + message: error.to_string(), + })? + .credentials_handle; + Ok(Box::new(NtlmContext { + ntlm, + credentials, + spn: format!("MSSQLSvc/{server}:{port}"), + complete: false, + })) + } +} + +struct NtlmContext { + ntlm: Ntlm, + credentials: Option, + spn: String, + complete: bool, +} + +impl SecurityContext for NtlmContext { + fn package_name(&self) -> &str { + "NTLM" + } + + fn generate_token( + &mut self, + server_token: Option<&[u8]>, + ) -> Result { + let failed = |error: sspi::Error| SecurityError::InitContextFailed { + code: error.error_type as u32, + message: error.to_string(), + }; + let mut input = vec![SecurityBuffer::new( + server_token.unwrap_or_default().to_vec(), + BufferType::Token, + )]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; + let mut builder = self + .ntlm + .initialize_security_context() + .with_credentials_handle(&mut self.credentials) + .with_context_requirements(ClientRequestFlags::ALLOCATE_MEMORY) + .with_target_data_representation(DataRepresentation::Native) + .with_target_name(&self.spn) + .with_output(&mut output); + if server_token.is_some() { + builder = builder.with_input(&mut input); + } + let status = self + .ntlm + .initialize_security_context_impl(&mut builder) + .map_err(failed)? + .resolve_to_result() + .map_err(failed)? + .status; + if matches!( + status, + SecurityStatus::CompleteAndContinue | SecurityStatus::CompleteNeeded + ) { + self.ntlm.complete_auth_token(&mut output).map_err(failed)?; + } + self.complete = !matches!( + status, + SecurityStatus::ContinueNeeded | SecurityStatus::CompleteAndContinue + ); + let data = output.pop().map(|buffer| buffer.buffer).unwrap_or_default(); + Ok(SspiAuthToken::new(data, self.complete)) + } + + fn is_complete(&self) -> bool { + self.complete + } + + fn spn(&self) -> &str { + &self.spn + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::random_secret; + use sspi::ServerRequestFlags; + + /// Runs the client against sspi's NTLM server, as SQL Server would. + fn handshake(user: &str, client_password: &str, server_password: &str) -> sspi::Result<()> { + let client = NtlmProvider::new(user, client_password).expect("username parses"); + let mut client = client + .create_context(&IntegratedAuthConfig::new(), "db.example.com", 1433) + .expect("context"); + + let mut server = Ntlm::new(); + let server_identity = AuthIdentity { + username: Username::parse(user).expect("username parses"), + password: server_password.to_string().into(), + }; + let mut server_credentials = server + .acquire_credentials_handle() + .with_credential_use(CredentialUse::Inbound) + .with_auth_data(&server_identity) + .execute(&mut server)? + .credentials_handle; + + let mut token = client.generate_token(None).expect("negotiate"); + assert!(!token.is_complete); + loop { + let mut input = vec![SecurityBuffer::new(token.data, BufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; + let builder = server + .accept_security_context() + .with_credentials_handle(&mut server_credentials) + .with_context_requirements(ServerRequestFlags::ALLOCATE_MEMORY) + .with_target_data_representation(DataRepresentation::Native) + .with_input(&mut input) + .with_output(&mut output); + let status = server + .accept_security_context_impl(builder)? + .resolve_to_result()? + .status; + if matches!( + status, + SecurityStatus::CompleteAndContinue | SecurityStatus::CompleteNeeded + ) { + server.complete_auth_token(&mut output)?; + } + if status != SecurityStatus::ContinueNeeded { + assert!(client.is_complete()); + return Ok(()); + } + token = client + .generate_token(Some(&output[0].buffer)) + .expect("authenticate"); + } + } + + #[test] + fn completes_a_handshake_with_matching_credentials() { + let secret = random_secret(); + handshake(r"CONTOSO\alice", &secret, &secret).expect("server accepts"); + } + + #[test] + fn a_wrong_password_is_rejected() { + let (client_secret, server_secret) = (random_secret(), random_secret()); + assert_ne!(client_secret, server_secret); + assert!(handshake(r"CONTOSO\alice", &client_secret, &server_secret).is_err()); + } + + #[test] + fn rejects_an_unparseable_username() { + assert!(NtlmProvider::new(r"CONTOSO\alice\bob", "x").is_err()); + } +} diff --git a/src/driver/pool.rs b/src/driver/pool.rs index 87e4931..cc7db88 100644 --- a/src/driver/pool.rs +++ b/src/driver/pool.rs @@ -11,10 +11,12 @@ //! requests encrypted local-development-compatible connections. use std::future::Future; +use std::sync::Arc; use std::time::Duration; use crate::connection::{custom_ca_error, resolve_connection_params}; use crate::driver::error::{bridge_error_requires_discard, format_bridge_error}; +use crate::driver::ntlm::NtlmProvider; use crate::models::ConnectionParams; use crate::settings::PluginSettings; use deadpool::managed::{Manager, Metrics, RecycleError, RecycleResult}; @@ -194,8 +196,8 @@ impl Manager for BridgeManager { /// Build a `mssql_tiberius_bridge::Config` from Tabularis `ConnectionParams`. /// /// Consumes the shared connection fields used by current Tabularis drivers. -/// Uses `AuthMethod::Integrated` when `integrated_auth` is set, else -/// username/password. TLS maps `ssl_mode` onto the bridge's encryption policy. +/// Uses `AuthMethod::Integrated` when `integrated_auth` is set, the same with +/// an [`NtlmProvider`] when `ntlm` is set, else username/password. TLS maps `ssl_mode` onto the bridge's encryption policy. pub fn build_config( params: &ConnectionParams, settings: &PluginSettings, @@ -207,6 +209,13 @@ pub fn build_config( cfg.database(params.database.primary()); if params.integrated_auth { cfg.authentication(AuthMethod::Integrated); + } else if params.ntlm { + let provider = NtlmProvider::new( + params.username.as_deref().unwrap_or(""), + params.password.as_deref().unwrap_or(""), + )?; + cfg.authentication(AuthMethod::Integrated) + .security_context_provider(Arc::new(provider)); } else { cfg.authentication(AuthMethod::sql_server( params.username.as_deref().unwrap_or("sa"), diff --git a/src/driver/pool/tests.rs b/src/driver/pool/tests.rs index 5055431..f75581e 100644 --- a/src/driver/pool/tests.rs +++ b/src/driver/pool/tests.rs @@ -1,4 +1,5 @@ use super::*; +use crate::common::random_secret; use crate::models::{ConnectionParams, DatabaseSelection}; use crate::settings::PluginSettings; @@ -8,7 +9,7 @@ fn base_params(host: Option<&str>, port: Option, db: &str) -> ConnectionPar host: host.map(String::from), port, username: Some("sa".into()), - password: Some("Strong!Pass123".into()), + password: Some(random_secret()), database: DatabaseSelection::Single(db.into()), ..Default::default() } @@ -61,6 +62,23 @@ fn build_config_accepts_integrated_auth_without_credentials() { assert!(build_config(¶ms, &PluginSettings::default()).is_ok()); } +#[test] +fn build_config_accepts_ntlm_credentials() { + let mut params = base_params(Some("localhost"), Some(1433), "master"); + params.username = Some(r"CONTOSO\alice".into()); + params.ntlm = true; + assert!(build_config(¶ms, &PluginSettings::default()).is_ok()); +} + +#[test] +fn build_config_rejects_a_malformed_ntlm_username() { + let mut params = base_params(Some("localhost"), Some(1433), "master"); + params.username = Some(r"CONTOSO\alice\bob".into()); + params.ntlm = true; + let error = build_config(¶ms, &PluginSettings::default()).unwrap_err(); + assert!(error.contains("NTLM username"), "{error}"); +} + #[test] fn manager_is_clone_send_sync() { fn assert_send() {} diff --git a/src/models.rs b/src/models.rs index 466a876..ca924f5 100644 --- a/src/models.rs +++ b/src/models.rs @@ -56,6 +56,9 @@ pub struct ConnectionParams { /// Windows/Kerberos auth (SSPI/GSSAPI). Set via `extra["integrated_auth"]` /// or `Integrated Security=True` in `connection_string`; excludes username/password. pub integrated_auth: bool, + /// NTLM with `username` as `DOMAIN\user` (or `user@domain`) and `password`, + /// on every platform. Set via `extra["ntlm"]`; excludes `integrated_auth`. + pub ntlm: bool, /// Opaque plugin-specific fields forwarded verbatim by the host. pub extra: HashMap, /// URL or ADO.NET/ODBC keyword connection string. It is parsed and diff --git a/src/pool_manager.rs b/src/pool_manager.rs index 062c713..f7be8c5 100644 --- a/src/pool_manager.rs +++ b/src/pool_manager.rs @@ -29,6 +29,8 @@ fn build_connection_key(params: &ConnectionParams) -> String { let ssl_mode = params.ssl_mode.as_deref().unwrap_or("prefer"); let auth = if params.integrated_auth { "integrated".to_string() + } else if params.ntlm { + format!("ntlm:{}", params.username.as_deref().unwrap_or("")) } else { format!("sql:{}", params.username.as_deref().unwrap_or("")) }; @@ -174,6 +176,15 @@ mod tests { ); } + #[test] + fn ntlm_is_part_of_the_key_for_the_same_username() { + let sql_auth = params(Some("ss045-key")); + let mut ntlm = sql_auth.clone(); + ntlm.ntlm = true; + + assert_ne!(build_connection_key(&sql_auth), build_connection_key(&ntlm)); + } + #[test] fn database_is_part_of_the_key_and_identical_params_are_stable() { let master = params(Some("ss045-key")); diff --git a/src/rpc.rs b/src/rpc.rs index 926a423..8bec0eb 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -412,7 +412,7 @@ mod tests { host: Some("localhost".into()), port: Some(1433), username: Some("sa".into()), - password: Some("test-password".into()), + password: Some(crate::common::random_secret()), database: crate::models::DatabaseSelection::Single("master".into()), connection_id: Some("shutdown-rpc-test".into()), ..Default::default() diff --git a/ui/src/IntegratedAuthToggle.test.tsx b/ui/src/IntegratedAuthToggle.test.tsx index add12f4..9566946 100644 --- a/ui/src/IntegratedAuthToggle.test.tsx +++ b/ui/src/IntegratedAuthToggle.test.tsx @@ -8,9 +8,10 @@ import IntegratedAuthToggle from "./IntegratedAuthToggle"; function mount(extra: Record) { const setCredentialFieldsHidden = vi.fn(); + const setExtraField = vi.fn(); const container = document.createElement("div"); document.body.appendChild(container); - const context = { extra, setExtraField: vi.fn(), setCredentialFieldsHidden }; + const context = { extra, setExtraField, setCredentialFieldsHidden }; act(() => { createRoot(container).render( ) { />, ); }); - const checkbox = container.querySelector("input")!; - return { checkbox, setCredentialFieldsHidden }; + const inputs = container.querySelectorAll("input"); + const checkbox = inputs[0]; + const ntlmCheckbox = inputs[1]; + return { checkbox, ntlmCheckbox, setCredentialFieldsHidden, setExtraField }; } test("hides credential fields for a saved connection without a click", () => { @@ -34,3 +37,24 @@ test("leaves credential fields visible when the flag is unset", () => { expect(checkbox.checked).toBe(false); expect(setCredentialFieldsHidden).toHaveBeenCalledWith(false); }); + +test("NTLM keeps credential fields visible for DOMAIN\\user and password", () => { + const { checkbox, ntlmCheckbox, setCredentialFieldsHidden } = mount({ ntlm: "true" }); + expect(ntlmCheckbox.checked).toBe(true); + expect(checkbox.checked).toBe(false); + expect(setCredentialFieldsHidden).toHaveBeenCalledWith(false); +}); + +test("checking NTLM clears Windows Authentication", () => { + const { ntlmCheckbox, setExtraField } = mount({ integrated_auth: "true" }); + act(() => ntlmCheckbox.click()); + expect(setExtraField).toHaveBeenCalledWith("ntlm", "true"); + expect(setExtraField).toHaveBeenCalledWith("integrated_auth", ""); +}); + +test("checking Windows Authentication clears NTLM", () => { + const { checkbox, setExtraField } = mount({ ntlm: "true" }); + act(() => checkbox.click()); + expect(setExtraField).toHaveBeenCalledWith("integrated_auth", "true"); + expect(setExtraField).toHaveBeenCalledWith("ntlm", ""); +}); diff --git a/ui/src/IntegratedAuthToggle.tsx b/ui/src/IntegratedAuthToggle.tsx index 55f92cc..54370af 100644 --- a/ui/src/IntegratedAuthToggle.tsx +++ b/ui/src/IntegratedAuthToggle.tsx @@ -9,27 +9,53 @@ interface ExtraFieldsContext { setCredentialFieldsHidden?: (hidden: boolean) => void; } +type AuthMode = "integrated_auth" | "ntlm"; + +interface ModeCheckboxProps { + label: string; + checked: boolean; + onToggle: (on: boolean) => void; +} + +function ModeCheckbox({ label, checked, onToggle }: ModeCheckboxProps) { + return ( + + ); +} + export default function IntegratedAuthToggle({ context }: SlotComponentProps) { const c = context as ExtraFieldsContext; - const checked = c.extra?.integrated_auth === "true"; + const integratedAuth = c.extra?.integrated_auth === "true"; + const ntlm = c.extra?.ntlm === "true"; const setCredentialFieldsHidden = c.setCredentialFieldsHidden; // Also runs on mount: a saved connection arrives with extra.integrated_auth // already set and never fires onChange. useEffect(() => { - setCredentialFieldsHidden?.(checked); - }, [checked, setCredentialFieldsHidden]); + setCredentialFieldsHidden?.(integratedAuth); + }, [integratedAuth, setCredentialFieldsHidden]); + + // The two modes exclude each other; the backend rejects both at once. + const selectMode = (mode: AuthMode, other: AuthMode, on: boolean) => { + c.setExtraField?.(mode, on ? "true" : ""); + if (on) c.setExtraField?.(other, ""); + }; return ( -