Skip to content

Sync fork with upstream tiberius 0.13.0 - #3

Merged
maksimu merged 159 commits into
mainfrom
sync/upstream-0.13
Sep 28, 2026
Merged

maksimu merged 159 commits into
mainfrom
sync/upstream-0.13

Conversation

@maksimu

@maksimu maksimu commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Brings the fork up to upstream tiberius-rs/tiberius 0.13.0 (35605c9, 155 commits ahead of our base), with all three of our fixes on top. No consumer is affected until it moves its pin. keeperdb pins 781fb71, keeper-pam-connections pins f012b79, and PR #2's merge is 150cc7f; all three stay reachable from main through the merge commits.

The fixes are rewritten to fit upstream's new code rather than copied. 89bce70 loads the Windows ROOT store for vendored OpenSSL; upstream's reworked trust code has the same gap, so the fix now applies in the whole validation path. 5fe1f41 keeps winauth's pure-Rust NTLMv2 on Linux/macOS. Upstream added a separate sspi-rs path for this, and it wins when both features are on. PR #2 (optional encryption accepts ENCRYPT_NOT_SUP, for the Gateway proxy port forward) comes in via 6158e8c. Its match arm applies unchanged, and its tests now unwrap the Result that 0.13's negotiated_encryption returns. The old version-alignment commit is dropped because upstream's version matches crates.io again.

Verified: cargo check is clean for keeperdb's feature set, the defaults, all, rustls+sspi-rs and a no-TLS build, and a Windows cross-check (x86_64-pc-windows-gnu) passes. Lib tests pass (1019 with all, 985 with keeperdb's features, including PR #2's tests). CI on this PR also ran upstream's full integration matrix against live SQL Server 2017, 2019, 2022 and Azure SQL Edge, and all 37 checks passed. The macOS and Windows lanes are skipped here, so NTLM from Linux/macOS and the Windows root store still need a manual test. Consumers moving to it need tiberius 0.13 plus a bb8-tiberius that accepts 0.13, since every published release requires ^0.12.

jakewimmer and others added 30 commits September 2, 2026 21:08
Migrate the macOS runner to macos-26-intel and add docker/setup-docker-action
so Docker is available for SQL Server. Replace all manual cargo cache steps
with Swatinem/rust-cache and add a sanitization step that replaces commas
with + to keep matrix variants isolated.

(cherry picked from commit 66030d2)
TlsPreloginWrapper and Header::set_type are only used on TLS-enabled
builds; mark them #[allow(dead_code)] so --no-default-features builds
with chrono/time pass under -D warnings (the -D dead-code CI failure
flagged on tiberius-rs#438). No behavior change; TLS builds unaffected.
Restore mssql:mssql ownership using COPY --chown=mssql, which the builder
resolves to a numeric uid:uid and applies without needing a named group.
A shell 'chown mssql:mssql' fails on the azure-sql-edge and 2019 base
images: they define the mssql user but no named mssql group (only 2022
does). The --chmod on the cert COPYs still requires BuildKit, so those
stay split into RUN chmod. Ownership issue caught by @aqrln in review.
The modernized clippy step gated on -D warnings, but the ~25 pre-existing
baseline lints it flags are fixed downstream in the feature stack, where
the strict gate is re-added atomically with those fixes. Drop -D warnings
here so this CI-unblock PR is green without weakening the eventual gate.
macOS GitHub runners have no Linux Docker daemon, so 'docker compose up
mssql-*' fails at container start and the macOS integration jobs can
never pass. Reduce the macOS lane to compile + 'cargo test --lib' across
both feature sets, keeping real macOS compile coverage while the
server-dependent integration tests run on the Linux and Windows lanes.
The workflow called prisma/.github reusable workflows (secret_detection,
code_scanning) with `secrets: inherit`. Those live in the prisma org and
can't be resolved from tiberius-rs, so PR Code Security failed at startup
on every PR to main.

Replace them with a portable secret scan (gitleaks, free for public
repos, no license needed). Drop the CodeQL code-scanning job: CodeQL has
no Rust support, so it never scanned this crate — Rust security/quality
is already covered by cargo-deny and the clippy gate.
gitleaks-action refuses to run on repos under a GitHub organization
without a paid license key ("[tiberius-rs] is an organization. License
key is required."), so the Secret Detection job failed at startup.

The gitleaks binary itself is MIT-licensed and free. Install a pinned
release, verify its SHA-256, and run `gitleaks git` over the full
history fetched by checkout. No license, no token, no org gating.
gitleaks flagged 4 private keys under docker/certs/ — the self-signed
TLS material that brings up the local SQL Server container the
integration tests connect to over TLS. They are throwaway test
fixtures committed upstream in 2022, not production secrets.

Add a .gitleaks.toml that keeps the full default rule set and allowlists
only that fixture path, so the rest of the tree and history stays
scanned. Verified locally: 0 leaks with the config, 4 without.
- gitleaks.toml: trim the header comment; drop the misleading "upstream 2022" note
- pr-code-security.yml: remove the CodeQL-removal comment (history narration)
Upgrade tokio-rustls to 0.26, rustls to 0.23, and rustls-native-certs to
0.8 to resolve RUSTSEC-2024-0421 and RUSTSEC-2025-0010.

Migrate the TLS stream to the rustls 0.23 API. Switch the crypto provider
to aws-lc-rs via builder_with_provider to avoid the dual-provider conflict
that tokio-rustls 0.26 introduces when ring is also in the dependency graph.

Pin to TLS 1.2 to prevent TLS 1.3 KeyUpdate messages from triggering
UnexpectedEof on the macOS CI runner.

(cherry picked from commit d46e4c0)
azure_core 0.20.0 switched from reqwest 0.11 to reqwest 0.12, which
pulls in rustls 0.23 and rustls-webpki 0.103.13. Bumping azure_identity
to 0.20.0 closes RUSTSEC-2026-0098, 0099, and 0104 in the dev build
without any changes to the production stack.

client_credentials_flow::perform now takes &str for the client secret.
Updated aad-auth.rs to pass raw env var strings and dropped the oauth2
ClientId/ClientSecret wrappers. Also bump reqwest 0.11 -> 0.12 and
oauth2 4.2.3 -> 5.0 in dev-dependencies to match.

Remove .cargo/audit.toml - the suppressions are no longer needed.

(cherry picked from commit 0e90db7)
- with_native_roots: load native roots best-effort (rustls-native-certs 0.8) —
  DEBUG-log per-cert errors and keep what loads instead of .expect() panicking;
  empty result still trips the existing assert!
- drop the redundant type annotation on the PEM cert collect (keep turbofish)
- remove the now-unused oauth2 dev-dependency
- trim two review-flagged comments
Two tests written before #[test_on_runtimes] existed and never updated.

cyrillic_collations_should_work previously created a dedicated database with
a Cyrillic default collation, requiring an admin connection and DROP DATABASE
at teardown. The DROP raced against open connections on macOS/rustls CI,
causing flaky failures. Replace with a session-local temp table using
column-level COLLATE clauses. The code path under test (COLMETADATA collation
-> encoding_rs decode) is identical.

application_name_should_be_set_correctly needed the application name set
before connecting. Add APP_NAME_CONN_STR embedding it in the connection
string so the macro-generated harness connects with it set.

(cherry picked from commit 6247684)
- Add deny.toml: fails on any vulnerability/yanked crate in the built
  graph; documents justified ignores for advisories that are provably
  dev-dependency-only or reachable solely via the opt-in
  sql-browser-async-std feature (not part of the default shipped lib).
- Replace the broken prisma-org PR Code Security workflow (which fails at
  startup on the fork) with a self-contained Security audit workflow that
  runs cargo-deny on push/PR and weekly.
- Bump dev-deps env_logger 0.9->0.11 and indicatif 0.17->0.18, dropping the
  unmaintained atty/number_prefix transitives from the test graph.

cargo deny check advisories bans sources: advisories ok, bans ok, sources ok.

(cherry picked from commit 1ce85b7)
Direct fixes for long-standing reported issues (regression tests added,
128 lib tests pass):

- tiberius-rs#211: bounds-check usize column index (try_get returns Err, not panic)
- tiberius-rs#382: match raw-identifier column names (r#type -> SQL 'type')
- tiberius-rs#418: correct swapped old/new in EnvChange Display (Database, PacketSize)
- tiberius-rs#281: lower chatty per-connection/token logs from INFO to DEBUG
- tiberius-rs#263: convert SQL smallint (I16/Intn) into i32 via FromSql
- tiberius-rs#424/tiberius-rs#425: return Error instead of panicking on unexpected server input
  in the TDS decoder (incl. negotiated_encryption)
- tiberius-rs#305: error at connect time when encryption is required but no TLS
  feature is compiled in
- tiberius-rs#316: fix multiply-overflow panic decoding dates before 1900
- tiberius-rs#358/tiberius-rs#352: coerce numerics into Money/SmallMoney and strings into
  NText/Text columns during bulk insert
- tiberius-rs#348: send the ReadOnly intent flag in LOGIN7 when ApplicationIntent=ReadOnly

(cherry picked from commit 34e5e55)
- Resolve 20 pre-existing clippy lints under `cargo clippy --features=all
  -- -D warnings` (legacy numeric methods/constants, unused/elidable
  lifetimes, redundant closure, doc list indentation, format specifier,
  derivable Default via #[default]); narrow justified #[allow] for the
  uint_enum! cast and the tds::time module inception.
- rustfmt the files touched by the backlog fixes.

dev green gate: fmt --check clean, build ok, clippy -D warnings ok,
cargo-deny ok, 128 lib tests pass.

(cherry picked from commit edda463)
The linux test lane started SQL Server and ran the suite without waiting
for it to accept logins — SQL binds 1433 before the SA login/databases
finish initializing, so tests raced startup and failed sporadically
(scattered across DB versions and feature sets, the signature of a race).
Gate on an authenticated SELECT 1 from a throwaway mssql-tools container,
which works uniformly across the full server images and azure-sql-edge.
- security.yml: shorten the cargo-deny step comment
- deny.toml: trim the header preamble (per-entry reasons kept)
- connection.rs: drop the no-TLS comment duplicating the helper doc
- row.rs: QueryIdx for usize via then_some
…oundary

The upper/lower range checks compared the rounded f64 scaled value against
i64::MAX/i32::MAX cast to f64. i64::MAX is not representable in f64 (it rounds
up to 2^63), so a value one hundredth-of-a-cent past money's max passed the
check and then saturated on the 'as i64' cast, silently writing i64::MAX.
Cast the rounded value to i128 first and range-check there (exact for every
in-range magnitude), rejecting the 2^63 boundary correctly. Factor the shared
range-check + word-emit into put_scaled(), and name the scale (4) and factor
(1e4) as consts.
encode_us_varchar wrote the UTF-16 code-unit count into a u16 length field
with no bound, so a table-name part longer than 65535 units would wrap (or
panic in debug), corrupting the wire. NumParts was written as 'parts.len() as
u8', wrapping for 256+ parts. Both now return Error::BulkInput instead.
Now that row.encode can fail (e.g. an out-of-range money value), a mid-row
failure left the Row-token byte plus earlier columns in the send buffer; those
partial bytes would be flushed on the next successful send/finalize and desync
the bulk stream. Snapshot the buffer length before encoding and truncate back
to it on error so the stream stays in sync.
The test_bulk_type! cases only assert the inserted row count. Add focused
tests that bulk-insert a known value and read it back, asserting the exact
value survived (including a numeric magnitude beyond f64's 2^53 exact range).
Requires a live SQL Server; compiles locally, runs in CI.
…undtrip

Adds server-free mock-reader unit tests for two already-fixed
panic->Error::Protocol conversions that had no coverage:

- column_data::int::decode: invalid Intn length (e.g. 3) returns
  Error::Protocol instead of hitting the unimplemented!() branch.
- TokenFeatureExtAck::decode: invalid FedAuth data length and an
  unsupported feature id both return Error::Protocol; the module had
  no #[cfg(test)] mod at all before this.

Also adds bulk_ntext_value_roundtrips mirroring the existing
bulk_text_value_roundtrips, exercising NTEXT bulk-insert/read-back
with a UTF-16-heavy string. Server-gated via test_on_runtimes; not run
locally.
A NOT-NULL money column arrives from the server as TypeInfo::FixedLen(Money)
(8-byte) and smallmoney as FixedLen(Money4) (4-byte), but the bulk row-encode
match only had arms for the nullable MONEYN (VarLenSized) form. NOT-NULL money
columns fell through to the BulkInput catch-all:

  invalid data type, expecting Some(FixedLen(Money)) but found F64(Some(...))

Add FixedLen(Money)/FixedLen(Money4) arms for both ColumnData::F64 and
ColumnData::Numeric. FixedLen types carry the raw fixed-width bytes with NO
length prefix (mirroring fixed_len::decode and the sibling Float8/Datetime
FixedLen arms), so add money::encode_fixed / encode_numeric_fixed wrappers that
share the existing scaling and range-check logic but omit the length byte.

(cherry picked from commit 32e9d80)
MattJackson and others added 29 commits September 23, 2026 18:44
…s lane

- integration-linux-next referenced a `mssql-2025` docker-compose service
  that does not exist (compose only defines 2022/2019/2017/azure-sql-edge),
  so the lane could only ever fail and produced false red signal on qa.
  Removed until a real SQL Server 2025 image + compose service is added.
- The macOS `--features=all` matrix entry pulls in native-tls, which on
  macOS is Apple Secure Transport and cannot complete the SQL Server TLS
  handshake; it was a permanent soft-fail. Removed it (rustls and
  vendored-openssl already provide macOS TLS-backend coverage) and documented
  why, so the macOS lane is now genuinely green rather than perpetually red.
…per poll

The PLP / TEXT / NTEXT / IMAGE value decoders read their payload one byte
(or one u16) per `poll_read`, turning an N-byte column value into N async
state-machine polls on every row. Add a packet-aware `read_bytes_into`
primitive to `SqlReadBytes` that fills a buffer by copying the largest
available contiguous slice each iteration — O(packets) instead of O(bytes) —
while preserving the existing `MAX_PREALLOC` windowed-reservation cap so a
lying server length can't force a large up-front allocation. Route every LOB
value decoder through it; decode output is byte-for-byte identical, locked by
the existing round-trip tests plus new packet-boundary tests.

Also drop a duplicate packet-header decode in `PacketCodec::decode` (the
length is now peeked inline) and move the `length < HEADER_BYTES` guard ahead
of consuming the header so a malformed short packet is rejected without
draining bytes.
…rrupting

Server-controlled value bytes could previously panic the connection task or
silently produce wrong data. Harden every value decode/encode path:

- numeric: reconstruct the 12/16-byte magnitude with checked arithmetic and
  reject magnitudes past i128::MAX; use `unsigned_abs` on encode (no i128::MIN
  panic); validate scale/precision (0..=38) at decode before constructing.
- time (chrono + time backends): a SmallDateTime minute field >= 1440 no
  longer panics (chrono) or silently wraps to the wrong wall-clock time (time)
  — both return a protocol error; reject DATETIME2 scale > 9 and out-of-range
  day/offset values; surface out-of-range chrono dates as errors, not asserts.
- sql_variant: checked 16-byte magnitude arithmetic; cross-checked prop/data
  lengths.
- add the length-validation guard the sibling decoders already have to the
  `time`/`guid` column paths; bound-check the interpolated numeric scale;
  guard the XML blob-length multiply against overflow.

`numeric`/`guid` magnitudes also switch to the bulk `read_bytes_into` reader.
Each fix ships with a red-before-green unit test.
Make token decode desync-proof and token encode overflow-proof, and align a
few spots with MS-TDS:

- decode: ERROR/INFO/LOGINACK now read exactly their declared Length into a
  bounded buffer (a Length/content mismatch is a clean error, not a stream
  desync); reject odd-length ORDER, out-of-range ENVCHANGE packet sizes, and
  COLINFO entries that overrun their token; strict (non-lossy) UTF-16.
- encode: replace four hand-rolled B_VARCHAR length counters (which wrapped a
  u8 past 255 and corrupted the wire) and the truncating XML/UDT length
  prefixes with shared, bound-checked `encode_b_varchar`/`encode_us_varchar`
  helpers; de-duplicate the ALL_HEADERS block.
- negotiation: apply the LOGINACK-negotiated TDS version to the connection
  context so the version-dependent DONE/ERROR field widths are actually
  reached; fail (don't downgrade) when Required encryption is declined;
  exhaustive TokenType dispatch; preserve a trailing empty result set in the
  command stream.

Adds server-free coverage for the TokenStream state machine (via a test-only
mock connection) and the token length-mismatch paths.
- SQL identifiers: one shared strict validator for the bulk table/column and
  TVP `db_type` paths (which are interpolated into a batch because T-SQL can't
  parameterize identifiers). It rejects statement-breakers (`;`, quotes, `--`,
  `=`), top-level spaces, and delimiter-adjacent token splicing (`[t]UNION(..)`,
  `foo(1)UNION(..)`) while keeping multi-part names and parameterized types
  working. Documented as defense-in-depth, not a substitute for trust.
- credentials: store the AAD token in `Zeroizing`; surface a GSSAPI error
  instead of unwrapping.
- connection integrity: guard the bulk-load write path and
  `send_sensitive_login` with the poisoned-connection check so a
  cancelled/dropped write can't be followed by a silently-desynced reuse; roll
  back a partial bulk row on encode failure (now tested).
- TLS: role-specific cert/key file-extension checks; named SSRP constants.
`FieldAttr::parse` panicked inside the proc-macro on a malformed or duplicate
`#[colname]` attribute, giving users a raw macro panic instead of a normal
compiler diagnostic. Return `syn::Error`s pointed at the offending span, like
the rest of the `TableValueRow` derive already does.
- add a 0.13.0 CHANGELOG section and refresh the README feature table (drop the
  removed sql-browser-async-std, add winauth/sspi-rs/serde).
- add `# Errors`/`# Panics` sections to the public fallible/panicking APIs and
  correct the chrono `DateTime` type-mapping doc tables.
… lane

- declare `rust-version = 1.88` so the MSRV is enforced by cargo, not just CI.
- add a `[licenses]` allow-list and run `cargo deny check ... licenses`.
- add one Linux smoke lane exercising chrono + time + the decimal crates
  together, which per-feature-isolated lanes couldn't catch.
Adds the `secrecy` crate (minimal, default features) so in-memory
credentials can be stored as `SecretString`, which bundles
zeroize-on-drop, redacted `Debug`, and `expose_secret()`-gated access.
Migrate every in-memory credential-storage site from
zeroize::Zeroizing<String> to secrecy::SecretString, which combines
zeroize-on-drop, a redacted Debug ([REDACTED]), and expose_secret()-gated
plaintext access so a secret can no longer be read or logged by accident.

Sites migrated:
- client/auth.rs: SqlServerAuth.password, WindowsAuth.password and
  AuthMethod::AADToken now hold SecretString. Hand-written redacting Debug
  impls are dropped in favour of derive(Debug); PartialEq/Eq are
  hand-written (SecretString has no PartialEq) so the public
  AuthMethod: Eq bound and its equality behaviour are preserved.
- client/config.rs: ClientCertSource::Pkcs12.password now holds
  SecretString; its manual redacting Debug is replaced by derive(Debug).
- tds/codec/login.rs: LoginMessage.password and FedAuthExt.fed_auth_token
  now hold SecretString, exposed only where their bytes are written into
  the LOGIN7 buffer. FedAuthExt loses its lifetime (all fields now owned).
  Test-only PartialEq/Eq are hand-written for the encode/decode
  round-trips. The Zeroizing<Box<[u8]>> encoded buffer is left unchanged.
- client/connection.rs: the SqlServer, AADToken and unix/windows Windows
  credential handoffs expose the secret only at the boundary where an
  external API needs plaintext.
- tls_stream/{native_tls,opentls}: expose the PKCS#12 password only for
  the from_pkcs12 decryption call.

Freed-plaintext-leak fix: SecretString::from(String) routes through
String::into_boxed_str(), which reallocates and frees the source buffer
WITHOUT zeroizing when the string has spare capacity, leaving a
recoverable plaintext copy in freed heap. A pub(crate) helper
`secret_from_string` in auth.rs (reachable via `pub(crate) mod auth`)
copies the bytes into an exact-sized Box<str> and wipes the original
before wrapping. Every String-to-SecretString conversion routes through
it. The redaction marker is unified on secrecy's [REDACTED].

Public constructors keep their impl Into<String>/ToString signatures and
wrap into SecretString internally.

Tests: adapt auth/config/login tests to expose_secret; add tests
asserting Debug never leaks the secret for the SqlServer, Windows, AAD,
connection-password, PKCS#12, login-password and fed-auth-token paths and
shows [REDACTED]; that expose_secret() yields the original value; that
secret_from_string preserves the value for the spare-capacity shape; and
a compile-time proof that the stored credential type is ZeroizeOnDrop.
Adds SqlBulkCopyOptions (TABLOCK, CHECK_CONSTRAINTS, KEEP_NULLS,
FIRE_TRIGGERS, KEEP_IDENTITY) and ORDER hints via
Client::bulk_insert_with_options; identity columns are retained only
when KEEP_IDENTITY is set. Closes tiberius-rs#302.

Co-authored-by: Adrian Ehrsam <adrian.ehrsam@bmsuisse.ch>
Config::lossy_utf16_decoding replaces invalid UTF-16 in NVARCHAR/NCHAR
and NTEXT values with U+FFFD; strict decoding remains the default and
framing guards stay unconditional. Closes tiberius-rs#325.

Co-authored-by: Skyler <1156263951@qq.com>
…ius-rs#290, tiberius-rs#330)

Introduces a two-axis trust model (root source + additive extra CAs +
bypass): trust_cert_ca now accumulates and accepts multi-cert files,
new trust_cert_ca_bundle takes in-memory PEM/DER bundles, and new
trust_webpki_roots (rustls-webpki-roots feature) uses bundled Mozilla
roots. Cert loading is unified across all three TLS backends. Closes tiberius-rs#290, tiberius-rs#330.

Co-authored-by: main() <main@ehvag.de>
Co-authored-by: zlepper <rhdh@digizuite.com>
When compiled without any TLS backend (none of the rustls, native-tls or
vendored-openssl features), the connection-string parser silently ignored
an explicit encryption request: the no-TLS ConfigString::encrypt() returned
Ok(EncryptionLevel::NotSupported) unconditionally. As a result encrypt=true
(or strict) resolved to NotSupported, the check_tls_backend_available guard
never fired, and traffic went out in plaintext with no signal to the user.

Classify the encrypt token the same way the with-TLS parser does and turn an
explicit encryption-on request (true/yes/strict) into a clear Error::Tls at
parse time, matching the wording and variant of check_tls_backend_available.
Opting out (false/no/DANGER_PLAINTEXT) and an omitted keyword still resolve
to NotSupported, so existing no-TLS users are unaffected. Unrecognized tokens
(e.g. mandatory) keep the with-TLS parser's bad-boolean error for consistency.

Adds no-TLS-gated unit tests for both the ADO.NET and JDBC parsers covering
the on/strict/mandatory error cases and the off/plaintext/missing ok cases.
Follow-up to the s10 code audit. Comment-only cleanups, no code or test
behavior changes:

- Drop dangling "Rule N" cross-references (21 sites across the TLS
  backends, certs.rs and config.rs). They referenced a numbered rule
  list that exists in no shipped source or doc; each comment already
  states its own invariant, so the numbering only added drift risk.
- Remove the duplicated `azure-sql-edge on macOS` debugging anecdote
  from the two handshake-timeout doc comments (connection.rs, config.rs)
  in favour of the general failure class (a server that accepts TCP then
  stalls mid-handshake).
- Rewrite a bulk-identifier test comment that narrated a prior bug
  ("used to slip through saturating_sub") to state the current invariant.
- Replace inline "pre-0.13 ..." version asides in the trust-config and
  timeout docs with plain descriptions of current behavior; the release
  migration notes already live in CHANGELOG.md.
…erius-rs#313)

The `connection-string` 0.2.0 crate is unmaintained and diverges from
ADO.NET: its ADO tokenizer requires a `;` after a value, so a value that
itself contains `=` (for example a base64 or otherwise generated password
with `=` padding, `Password=Zm9vYmFy==`) fails to parse with
"Key-value pairs must be separated by a `;`" — the exact symptom reported
in tiberius-rs#313. Reproduced across the full set of ASCII special characters, not
just `=`.

Parse ADO.NET connection strings in-house instead (a new hand-written
tokenizer, not a fork of the crate), as a documented superset of ADO.NET:

- split each pair on the FIRST `=`, so a value may contain further `=`
  (matching ADO.NET and fixing the base64-password case);
- `'…'` / `"…"` quoting with `''` / `""` doubling to embed the quote;
- whitespace preserved inside quotes, trimmed when unquoted (ADO.NET);
- `{…}` brace quoting kept as an explicit extension (not ADO.NET);
- ASCII only; a `==` in the key position is a literal `=`;
- duplicate keys are last-wins.

JDBC parsing continues to use the crate for now. Errors keep the existing
actionable quoting hint.

Tests:
- parser-level unit tests over keys, pairs, quoting, braces, errors, and an
  exhaustive sweep of every printable-ASCII character round-tripping through
  single and double quotes and (where legal) unquoted;
- auth-level matrix in `ado_net.rs` over every special char via both quote
  styles, doubled-quote embedding, the tiberius-rs#313 base64 cases, and the ambiguous
  inputs that must error;
- a server-gated end-to-end test (`tests/special_char_password.rs`) that
  creates a login with a `=`-padded / `;` / braced password and authenticates
  as it, covering the whole parse -> LOGIN7 -> auth path in the CI
  integration lanes.

Updates two special-character tests from tiberius-rs#333 to the ADO.NET-aligned
behavior (whitespace preserved in quotes; trailing junk after a braced value
is rejected rather than folded in).
`connect_with_full_encryption` (and its `ENCRYPTED_CONN_STR`) use
`encrypt=true`, which since tiberius-rs#305 is a hard error on a build with no TLS
backend rather than silently degrading to plaintext. Gate both behind a TLS
feature so the `--no-default-features` integration lanes pass; full-encryption
coverage still runs in the rustls / native-tls / vendored-openssl lanes.
Client::bulk_insert* declared each column of the INSERT BULK column
list as `[name] type` without a collation. SQL Server reads the bulk
data of a char, varchar or text column declared that way in the
database default collation and converts it to the column's collation,
so text bulk-loaded into a column whose collation differs from the
database default was stored corrupted. In a CP1252 database,
"Привет" bulk-loaded into a Cyrillic_General_CI_AS column was stored
as "I?eaao", and every byte 0x80-0xFF of text bulk-loaded into a
SQL_Latin1_General_CP437_BIN or SQL_1xCompat_CP850_CI_AS column was
stored as a different byte: CP437 "é" (0x82) became "," (0x2C).

The INSERT BULK column list now declares char, varchar, text, nchar,
nvarchar and ntext columns with ` COLLATE <name>`, as SqlClient's
SqlBulkCopy does. The collation names come from
`EXEC <catalog>..sp_tablecollations_100 N'<schema>.<table>'`, run in
the batch of the column metadata query, so bulk_insert takes no extra
round trip. It runs in tempdb for a # temp table and in the catalog the
table name gives otherwise; before SQL Server 2008 the procedure is
sp_tablecollations_90. Columns are matched to their collation by name.
A collation name that is not ASCII letters, digits and `_` fails the
bulk insert with Error::Protocol instead of reaching the statement.
* fix(collation): compose lossy decoding with legacy code pages
The vendored OpenSSL discovers roots via openssl-probe, which only
checks Unix filesystem paths — on Windows it finds nothing, so
TrustConfig::Default validates against an empty trust store and every
strict-validation handshake fails with 'unable to get local issuer
certificate' (e.g. Azure SQL with AAD token auth).

Load the Windows ROOT certificate store (current-user view, a
composite that includes the local-machine store) into the connector
via schannel, the same crate rustls-native-certs uses for this.
Scoped to cfg(windows) + the vendored-openssl feature + the Default
trust branch; TrustAll, CaCertificateLocation, other TLS backends,
and non-Windows targets are unchanged.
…rgets

Re-applies the fork's cross-platform NTLM change (40c5e67) on top of
upstream 0.13. The winauth crate's NtlmV2Client is pure Rust; only its
`windows` SSPI module is Windows-specific. Upstream 0.13 added a
separate Unix path via sspi-rs, but keeperdb-proxy found sspi left an
unusable session against real SQL Server while raw winauth NTLMv2
works, so keep winauth as the NTLM client on every platform.

- Cargo.toml: winauth is a cross-platform optional dependency
- AuthMethod::Windows / WindowsAuth / AuthMethod::windows(): available
  with feature = "winauth" on any target (or unix + sspi-rs, as before)
- SSPI helpers (Context::spn, LoginMessage::integrated_security,
  TokenSspi::new, flush_sspi): widened to include feature = "winauth"
- The winauth Windows arm is used unless unix + sspi-rs is enabled, in
  which case upstream's sspi-rs arm is kept
- AuthMethod::Integrated is unchanged: SSPI on Windows, GSSAPI on Unix
Keeps the fork's history (the 781fb71 and f012b79 revs pinned by keeperdb and
keeper-pam-connections) reachable from main. The tree is the sync branch: the
fork's two fixes are re-applied on upstream 0.13 in 89bce70 and 5fe1f41.
Brings PR #2 (acaae1f): optional encryption accepts an ENCRYPT_NOT_SUP
response, so KeeperDB can reach SQL Server through the Gateway's
keeperdb-proxy port forward.

Upstream 0.13 made negotiated_encryption return a Result, so PR #2's two
tests now unwrap it. The match arm itself applies unchanged, ahead of
upstream's Required and Strict arms.
@maksimu
maksimu merged commit 33fb406 into main Sep 28, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.