mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-24 07:45:09 +00:00
fix(sqlserver): use legacy encryption for fallback
This commit is contained in:
@@ -1023,8 +1023,10 @@ function isSqlServerLegacyUnencryptedMode(params: string | undefined): boolean {
|
||||
if (!normalized) return false;
|
||||
const parsed = new URLSearchParams(normalized);
|
||||
for (const [key, value] of parsed.entries()) {
|
||||
if (key.trim().toLowerCase() === "sqlserverencryption") {
|
||||
return ["disabled", "disable", "false", "0", "off"].includes(value.trim().toLowerCase());
|
||||
const normalizedKey = key.trim().toLowerCase();
|
||||
if (normalizedKey === "sqlserverencryption" || normalizedKey === "encrypt") {
|
||||
// Accept JDBC-style `encrypt=false` from imported SQL Server URLs as the same opt-in.
|
||||
if (["disabled", "disable", "false", "0", "off", "no"].includes(value.trim().toLowerCase())) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -1032,7 +1034,13 @@ function isSqlServerLegacyUnencryptedMode(params: string | undefined): boolean {
|
||||
|
||||
function setSqlServerLegacyUnencryptedMode(params: string | undefined, enabled: boolean): string {
|
||||
const normalized = (params || "").trim().replace(/^\?/, "").replace(/;/g, "&");
|
||||
return setUrlParam(normalized, "sqlserverEncryption", enabled ? "disabled" : "");
|
||||
const parsed = new URLSearchParams(normalized);
|
||||
for (const key of Array.from(parsed.keys())) {
|
||||
const normalizedKey = key.trim().toLowerCase();
|
||||
if (normalizedKey === "sqlserverencryption" || normalizedKey === "encrypt") parsed.delete(key);
|
||||
}
|
||||
if (enabled) parsed.set("sqlserverEncryption", "disabled");
|
||||
return parsed.toString();
|
||||
}
|
||||
|
||||
function isSqlServerTlsHandshakeFailure(message: string): boolean {
|
||||
|
||||
@@ -305,9 +305,9 @@ export default {
|
||||
testing: "Testing...",
|
||||
copyTestResult: "Copy test result",
|
||||
sqlServerLegacyUnencryptedMode: "Legacy compatibility mode",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Use SQL Server legacy TLS 1.0 unencrypted connection",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Use SQL Server legacy unencrypted connection",
|
||||
sqlServerLegacyUnencryptedModeEnabled: "SQL Server legacy compatibility mode is enabled.",
|
||||
sqlServerLegacyUnencryptedModeHint: "Applies to unencrypted transport or login-only encryption. It will still fail if the server requires TLS 1.0 encryption.\nUse it only on trusted networks, VPNs, or SSH tunnels",
|
||||
sqlServerLegacyUnencryptedModeHint: "Applies to unencrypted transport or login-only encryption. It will still fail if the server requires encrypted transport that the embedded driver cannot negotiate.\nUse it only on trusted networks, VPNs, or SSH tunnels",
|
||||
saveAndConnect: "Save & Connect",
|
||||
save: "Save",
|
||||
editTitle: "Edit Connection",
|
||||
|
||||
@@ -307,9 +307,9 @@ export default withEnglishFallback({
|
||||
testing: "Probando...",
|
||||
copyTestResult: "Copiar resultado de prueba",
|
||||
sqlServerLegacyUnencryptedMode: "Modo de compatibilidad heredado",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Usar conexión SQL Server heredada TLS 1.0 sin cifrar",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Usar conexión SQL Server heredada sin cifrar",
|
||||
sqlServerLegacyUnencryptedModeEnabled: "El modo de compatibilidad heredado de SQL Server está activado.",
|
||||
sqlServerLegacyUnencryptedModeHint: "Aplica a transporte sin cifrar o cifrado solo en el login. Seguirá fallando si el servidor requiere cifrado TLS 1.0.\nÚsalo solo en redes de confianza, VPN o túneles SSH",
|
||||
sqlServerLegacyUnencryptedModeHint: "Aplica a transporte sin cifrar o cifrado solo en el login. Seguirá fallando si el servidor requiere transporte cifrado que el controlador integrado no puede negociar.\nÚsalo solo en redes de confianza, VPN o túneles SSH",
|
||||
saveAndConnect: "Guardar y conectar",
|
||||
save: "Guardar",
|
||||
editTitle: "Editar conexión",
|
||||
|
||||
@@ -306,9 +306,9 @@ export default withEnglishFallback({
|
||||
testing: "Verifica in corso...",
|
||||
copyTestResult: "Copia risultato test",
|
||||
sqlServerLegacyUnencryptedMode: "Modalità compatibilità legacy",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Usa connessione non crittografata legacy TLS 1.0 SQL Server",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Usa connessione non crittografata legacy SQL Server",
|
||||
sqlServerLegacyUnencryptedModeEnabled: "La modalità compatibilità legacy di SQL Server è abilitata.",
|
||||
sqlServerLegacyUnencryptedModeHint: "Si applica al trasporto non crittografato o alla crittografia solo per il login. Fallirà comunque se il server richiede la crittografia TLS 1.0.\nUsala solo su reti attendibili, VPN o tunnel SSH",
|
||||
sqlServerLegacyUnencryptedModeHint: "Si applica al trasporto non crittografato o alla crittografia solo per il login. Fallirà comunque se il server richiede un trasporto crittografato che il driver integrato non può negoziare.\nUsala solo su reti attendibili, VPN o tunnel SSH",
|
||||
saveAndConnect: "Salva & Connetti",
|
||||
save: "Salva",
|
||||
editTitle: "Modifica Connessione",
|
||||
|
||||
@@ -300,9 +300,9 @@ export default withEnglishFallback({
|
||||
testing: "テスト中...",
|
||||
copyTestResult: "テスト結果をコピー",
|
||||
sqlServerLegacyUnencryptedMode: "レガシー互換モード",
|
||||
sqlServerLegacyUnencryptedModeEnable: "SQL Server レガシー TLS 1.0 非暗号化接続を使用",
|
||||
sqlServerLegacyUnencryptedModeEnable: "SQL Server レガシー非暗号化接続を使用",
|
||||
sqlServerLegacyUnencryptedModeEnabled: "SQL Server レガシー互換モードが有効です。",
|
||||
sqlServerLegacyUnencryptedModeHint: "非暗号化またはログイン時のみ暗号化する場合に適用されます。サーバーが TLS 1.0 暗号化を要求する場合は失敗します。\n信頼できるネットワーク、VPN、SSH トンネルでのみ使用してください",
|
||||
sqlServerLegacyUnencryptedModeHint: "非暗号化またはログイン時のみ暗号化する場合に適用されます。サーバーが内蔵ドライバーでネゴシエートできない暗号化通信を要求する場合は失敗します。\n信頼できるネットワーク、VPN、SSH トンネルでのみ使用してください",
|
||||
saveAndConnect: "保存して接続",
|
||||
save: "保存",
|
||||
editTitle: "接続を編集",
|
||||
|
||||
@@ -307,9 +307,9 @@ export default withEnglishFallback({
|
||||
testing: "Testando...",
|
||||
copyTestResult: "Copiar resultado do teste",
|
||||
sqlServerLegacyUnencryptedMode: "Modo de compatibilidade legado",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Usar conexão SQL Server legada TLS 1.0 não criptografada",
|
||||
sqlServerLegacyUnencryptedModeEnable: "Usar conexão SQL Server legada não criptografada",
|
||||
sqlServerLegacyUnencryptedModeEnabled: "O modo de compatibilidade legado do SQL Server está ativado.",
|
||||
sqlServerLegacyUnencryptedModeHint: "Aplica-se a transporte não criptografado ou criptografia apenas no login. Ainda falhará se o servidor exigir criptografia TLS 1.0.\nUse apenas em redes confiáveis, VPNs ou túneis SSH",
|
||||
sqlServerLegacyUnencryptedModeHint: "Aplica-se a transporte não criptografado ou criptografia apenas no login. Ainda falhará se o servidor exigir transporte criptografado que o driver integrado não consegue negociar.\nUse apenas em redes confiáveis, VPNs ou túneis SSH",
|
||||
saveAndConnect: "Salvar e Conectar",
|
||||
save: "Salvar",
|
||||
editTitle: "Editar Conexão",
|
||||
|
||||
@@ -307,9 +307,9 @@ export default withEnglishFallback({
|
||||
testing: "测试中...",
|
||||
copyTestResult: "复制测试结果",
|
||||
sqlServerLegacyUnencryptedMode: "旧版兼容模式",
|
||||
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 旧版TLS 1.0非加密连接",
|
||||
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 旧版非加密连接",
|
||||
sqlServerLegacyUnencryptedModeEnabled: "已启用 SQL Server 旧版兼容模式。",
|
||||
sqlServerLegacyUnencryptedModeHint: "适用于非加密或仅登录阶段加密的场景。若服务端强制 TLS 1.0 加密,此模式仍会失败\n请仅在可信内网、VPN 或 SSH 隧道中使用",
|
||||
sqlServerLegacyUnencryptedModeHint: "适用于非加密或仅登录阶段加密的场景。若服务端强制使用内置驱动无法协商的加密传输,此模式仍会失败\n请仅在可信内网、VPN 或 SSH 隧道中使用",
|
||||
saveAndConnect: "保存并连接",
|
||||
save: "保存",
|
||||
editTitle: "编辑连接",
|
||||
|
||||
@@ -307,9 +307,9 @@ export default withEnglishFallback({
|
||||
testing: "測試中……",
|
||||
copyTestResult: "複製測試結果",
|
||||
sqlServerLegacyUnencryptedMode: "舊版相容模式",
|
||||
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 舊版 TLS 1.0 非加密連線",
|
||||
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 舊版非加密連線",
|
||||
sqlServerLegacyUnencryptedModeEnabled: "已啟用 SQL Server 舊版相容模式。",
|
||||
sqlServerLegacyUnencryptedModeHint: "適用於非加密或僅登入階段加密的場景。若伺服器強制 TLS 1.0 加密,此模式仍會失敗\n請僅在可信內網、VPN 或 SSH 隧道中使用",
|
||||
sqlServerLegacyUnencryptedModeHint: "適用於非加密或僅登入階段加密的場景。若伺服器強制使用內建驅動無法協商的加密傳輸,此模式仍會失敗\n請僅在可信內網、VPN 或 SSH 隧道中使用",
|
||||
saveAndConnect: "儲存並連線",
|
||||
save: "儲存",
|
||||
editTitle: "編輯連線",
|
||||
|
||||
@@ -17,6 +17,8 @@ use tokio_util::sync::CancellationToken;
|
||||
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;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -55,23 +57,24 @@ pub async fn connect(
|
||||
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, tiberius::EncryptionLevel::NotSupported, timeout)
|
||||
.await
|
||||
.map_err(|plain_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 configuration. \
|
||||
If you are connecting to SQL Server 2008/2008 R2 or another legacy instance, \
|
||||
"{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 \
|
||||
if the server requires TLS 1.0 encryption. Only use this mode on trusted networks, VPNs, \
|
||||
if the server requires encrypted transport that the embedded driver cannot negotiate. \
|
||||
Only use this mode on trusted networks, VPNs, \
|
||||
or SSH tunnels.\n\n\
|
||||
Automatic unencrypted fallback also failed: {plain_error}"
|
||||
)
|
||||
Automatic legacy unencrypted fallback also failed: {plain_error}"
|
||||
)
|
||||
} else {
|
||||
plain_error
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,8 +85,10 @@ fn sqlserver_legacy_encryption_disabled(url_params: Option<&str>) -> bool {
|
||||
};
|
||||
|
||||
params.trim_start_matches('?').split(['&', ';']).filter_map(|pair| pair.split_once('=')).any(|(key, value)| {
|
||||
key.trim().eq_ignore_ascii_case("sqlserverEncryption")
|
||||
&& matches!(value.trim().to_ascii_lowercase().as_str(), "disabled" | "disable" | "false" | "0" | "off")
|
||||
let key = key.trim();
|
||||
let value = value.trim().to_ascii_lowercase();
|
||||
let disabled = matches!(value.as_str(), "disabled" | "disable" | "false" | "0" | "off" | "no");
|
||||
(key.eq_ignore_ascii_case("sqlserverEncryption") || key.eq_ignore_ascii_case("encrypt")) && disabled
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1860,12 +1865,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqlserver_legacy_encryption_flag_is_opt_in() {
|
||||
fn sqlserver_legacy_encryption_flag_accepts_dbx_and_jdbc_params() {
|
||||
assert!(!super::sqlserver_legacy_encryption_disabled(None));
|
||||
assert!(!super::sqlserver_legacy_encryption_disabled(Some("encrypt=false")));
|
||||
assert!(!super::sqlserver_legacy_encryption_disabled(Some("encrypt=true")));
|
||||
assert!(super::sqlserver_legacy_encryption_disabled(Some("sqlserverEncryption=disabled")));
|
||||
assert!(super::sqlserver_legacy_encryption_disabled(Some("applicationName=dbx;sqlserverEncryption=off")));
|
||||
assert!(super::sqlserver_legacy_encryption_disabled(Some("?sqlserverEncryption=false&applicationName=dbx")));
|
||||
assert!(super::sqlserver_legacy_encryption_disabled(Some("applicationName=dbx;encrypt=false")));
|
||||
assert!(super::sqlserver_legacy_encryption_disabled(Some("?Encrypt=0&applicationName=dbx")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1873,6 +1880,15 @@ mod tests {
|
||||
assert_eq!(super::SQLSERVER_LEGACY_ENCRYPTION_LEVEL, tiberius::EncryptionLevel::Off);
|
||||
}
|
||||
|
||||
#[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"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqlserver_tls_handshake_error_detection_matches_legacy_hint_cases() {
|
||||
assert!(super::is_sqlserver_tls_handshake_error(
|
||||
|
||||
Reference in New Issue
Block a user