fix(sqlserver): preserve legacy encryption fallback
This commit is contained in:
parent
f23894d816
commit
0747134dc2
|
|
@ -18,8 +18,14 @@ pub type SqlServerClient = Client<Compat<TcpStream>>;
|
|||
pub const SQLSERVER_DRIVER_PANIC_ERROR_PREFIX: &str = "SQL Server driver panic:";
|
||||
const SIMPLE_QUERY_MODULE_KEYWORDS: &[&str] = &["FUNCTION", "PROC", "PROCEDURE", "TRIGGER", "VIEW"];
|
||||
// Match JDBC/tiberius `encrypt=false`: encrypt only login, then drop back to raw TDS.
|
||||
// `NotSupported` sends ENCRYPT_NOT_SUP and is rejected by some legacy SQL Server setups.
|
||||
const SQLSERVER_LEGACY_ENCRYPTION_LEVEL: tiberius::EncryptionLevel = tiberius::EncryptionLevel::Off;
|
||||
// Some very old SQL Server setups only accepted DBX <= 0.5.48 because the fallback
|
||||
// advertised no encryption support at all. Keep it as the last-resort compatibility path.
|
||||
const SQLSERVER_UNSUPPORTED_ENCRYPTION_LEVEL: tiberius::EncryptionLevel = tiberius::EncryptionLevel::NotSupported;
|
||||
const SQLSERVER_LEGACY_ENCRYPTION_FALLBACKS: [(&str, tiberius::EncryptionLevel); 2] = [
|
||||
("login-only encryption", SQLSERVER_LEGACY_ENCRYPTION_LEVEL),
|
||||
("no-encryption compatibility fallback", SQLSERVER_UNSUPPORTED_ENCRYPTION_LEVEL),
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct SqlServerEndpoint<'a> {
|
||||
|
|
@ -51,17 +57,17 @@ pub async fn connect(
|
|||
timeout: Duration,
|
||||
) -> Result<SqlServerClient, String> {
|
||||
if sqlserver_legacy_encryption_disabled(url_params) {
|
||||
return try_connect(host, port, user, pass, database, SQLSERVER_LEGACY_ENCRYPTION_LEVEL, timeout).await;
|
||||
return try_connect_legacy_sqlserver_encryption(host, port, user, pass, database, timeout).await;
|
||||
}
|
||||
|
||||
match try_connect(host, port, user, pass, database, tiberius::EncryptionLevel::Required, timeout).await {
|
||||
Ok(client) => Ok(client),
|
||||
Err(encrypted_error) => {
|
||||
try_connect(host, port, user, pass, database, SQLSERVER_LEGACY_ENCRYPTION_LEVEL, timeout).await.map_err(
|
||||
|plain_error| {
|
||||
if is_sqlserver_tls_handshake_error(&encrypted_error) {
|
||||
format!(
|
||||
"{encrypted_error}\n\nThis may be caused by an old SQL Server TLS/encryption configuration. \
|
||||
Err(encrypted_error) => try_connect_legacy_sqlserver_encryption(host, port, user, pass, database, timeout)
|
||||
.await
|
||||
.map_err(|plain_error| {
|
||||
if is_sqlserver_tls_handshake_error(&encrypted_error) {
|
||||
format!(
|
||||
"{encrypted_error}\n\nThis may be caused by an old SQL Server TLS/encryption configuration. \
|
||||
If you are connecting to SQL Server 2008/2008 R2/2012 or another legacy instance, \
|
||||
try SQL Server legacy unencrypted mode. It behaves like encrypt=false and only helps \
|
||||
when the server allows unencrypted transport or login-only encryption. It will still fail \
|
||||
|
|
@ -69,14 +75,31 @@ pub async fn connect(
|
|||
Only use this mode on trusted networks, VPNs, \
|
||||
or SSH tunnels.\n\n\
|
||||
Automatic legacy unencrypted fallback also failed: {plain_error}"
|
||||
)
|
||||
} else {
|
||||
plain_error
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
} else {
|
||||
plain_error
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_connect_legacy_sqlserver_encryption(
|
||||
host: &str,
|
||||
port: u16,
|
||||
user: &str,
|
||||
pass: &str,
|
||||
database: Option<&str>,
|
||||
timeout: Duration,
|
||||
) -> Result<SqlServerClient, String> {
|
||||
let mut errors = Vec::new();
|
||||
for (label, encryption) in SQLSERVER_LEGACY_ENCRYPTION_FALLBACKS {
|
||||
match try_connect(host, port, user, pass, database, encryption, timeout).await {
|
||||
Ok(client) => return Ok(client),
|
||||
Err(error) => errors.push(format!("{label} failed: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
Err(errors.join("\n"))
|
||||
}
|
||||
|
||||
fn sqlserver_legacy_encryption_disabled(url_params: Option<&str>) -> bool {
|
||||
|
|
@ -1876,17 +1899,15 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn sqlserver_legacy_encryption_mode_matches_jdbc_encrypt_false_semantics() {
|
||||
fn sqlserver_legacy_encryption_modes_cover_jdbc_and_no_encryption_fallback() {
|
||||
assert_eq!(super::SQLSERVER_LEGACY_ENCRYPTION_LEVEL, tiberius::EncryptionLevel::Off);
|
||||
assert_eq!(super::SQLSERVER_UNSUPPORTED_ENCRYPTION_LEVEL, tiberius::EncryptionLevel::NotSupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqlserver_automatic_fallback_uses_legacy_encryption_mode() {
|
||||
let source = include_str!("sqlserver.rs");
|
||||
let connect = source.split("pub async fn connect").nth(1).unwrap();
|
||||
let connect = connect.split("fn sqlserver_legacy_encryption_disabled").next().unwrap();
|
||||
assert!(connect.contains("database, SQLSERVER_LEGACY_ENCRYPTION_LEVEL, timeout"));
|
||||
assert!(!connect.contains("EncryptionLevel::NotSupported, timeout"));
|
||||
fn sqlserver_automatic_fallback_preserves_v48_no_encryption_compatibility() {
|
||||
let levels = super::SQLSERVER_LEGACY_ENCRYPTION_FALLBACKS.map(|(_, encryption)| encryption);
|
||||
assert_eq!(levels, [tiberius::EncryptionLevel::Off, tiberius::EncryptionLevel::NotSupported]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue