tiberius/client/
config.rs1mod ado_net;
2mod jdbc;
3
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7use super::AuthMethod;
8use crate::EncryptionLevel;
9use ado_net::*;
10use jdbc::*;
11
12#[derive(Clone, Debug)]
13pub struct Config {
26 pub(crate) host: Option<String>,
27 pub(crate) port: Option<u16>,
28 pub(crate) database: Option<String>,
29 pub(crate) instance_name: Option<String>,
30 pub(crate) application_name: Option<String>,
31 pub(crate) encryption: EncryptionLevel,
32 pub(crate) trust: TrustConfig,
33 pub(crate) auth: AuthMethod,
34 pub(crate) readonly: bool,
35}
36
37#[derive(Clone, Debug)]
38pub(crate) enum TrustConfig {
39 #[allow(dead_code)]
40 CaCertificateLocation(PathBuf),
41 #[allow(dead_code)]
42 CaCertificatePem(String),
43 TrustAll,
44 Default,
45}
46
47impl Default for Config {
48 fn default() -> Self {
49 Self {
50 host: None,
51 port: None,
52 database: None,
53 instance_name: None,
54 application_name: None,
55 #[cfg(any(
56 feature = "rustls",
57 feature = "native-tls",
58 feature = "vendored-openssl"
59 ))]
60 encryption: EncryptionLevel::Required,
61 #[cfg(not(any(
62 feature = "rustls",
63 feature = "native-tls",
64 feature = "vendored-openssl"
65 )))]
66 encryption: EncryptionLevel::NotSupported,
67 trust: TrustConfig::Default,
68 auth: AuthMethod::None,
69 readonly: false,
70 }
71 }
72}
73
74impl Config {
75 pub fn new() -> Self {
77 Self::default()
78 }
79
80 pub fn host(&mut self, host: impl ToString) {
84 self.host = Some(host.to_string());
85 }
86
87 pub fn port(&mut self, port: u16) {
91 self.port = Some(port);
92 }
93
94 pub fn database(&mut self, database: impl ToString) {
98 self.database = Some(database.to_string())
99 }
100
101 pub fn instance_name(&mut self, name: impl ToString) {
109 self.instance_name = Some(name.to_string());
110 }
111
112 pub fn application_name(&mut self, name: impl ToString) {
117 self.application_name = Some(name.to_string());
118 }
119
120 pub fn encryption(&mut self, encryption: EncryptionLevel) {
125 self.encryption = encryption;
126 }
127
128 pub fn trust_cert(&mut self) {
139 self.trust = match &self.trust {
140 TrustConfig::Default | TrustConfig::TrustAll => TrustConfig::TrustAll,
141 TrustConfig::CaCertificatePem(_) => {
142 panic!("'trust_cert_ca_pem' and 'trust_cert' are mutual exclusive! Only use one.")
143 }
144 TrustConfig::CaCertificateLocation(_) => {
145 panic!("'trust_cert_ca' and 'trust_cert' are mutual exclusive! Only use one.")
146 }
147 };
148 }
149
150 pub fn trust_cert_ca(&mut self, path: impl ToString) {
160 self.trust = match &self.trust {
161 TrustConfig::Default | TrustConfig::CaCertificateLocation(_) => {
162 TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string()))
163 }
164 TrustConfig::CaCertificatePem(_) => {
165 panic!("'trust_cert_ca_pem' and 'trust_cert_ca' are mutual exclusive! Only use one.")
166 }
167 TrustConfig::TrustAll => {
168 panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
169 }
170 };
171 }
172
173 pub fn trust_cert_ca_pem(&mut self, cert: impl ToString) {
183 self.trust = match &self.trust {
184 TrustConfig::Default | TrustConfig::CaCertificatePem(_) => {
185 TrustConfig::CaCertificatePem(cert.to_string())
186 }
187 TrustConfig::CaCertificateLocation(_) => {
188 panic!("'trust_cert_ca' and 'trust_cert_ca_pem' are mutual exclusive! Only use one.")
189 }
190 TrustConfig::TrustAll => {
191 panic!("'trust_cert' and 'trust_cert_ca_pem' are mutual exclusive! Only use one.")
192 }
193 };
194 }
195
196 pub fn authentication(&mut self, auth: AuthMethod) {
200 self.auth = auth;
201 }
202
203 pub fn readonly(&mut self, readnoly: bool) {
207 self.readonly = readnoly;
208 }
209
210 pub(crate) fn get_host(&self) -> &str {
211 self.host
212 .as_deref()
213 .filter(|v| v != &".")
214 .unwrap_or("localhost")
215 }
216
217 pub(crate) fn get_port(&self) -> u16 {
218 match (self.port, self.instance_name.as_ref()) {
219 (Some(port), _) => port,
221 (None, Some(_)) => 1434,
224 (None, None) => 1433,
226 }
227 }
228
229 pub fn get_addr(&self) -> String {
231 format!("{}:{}", self.get_host(), self.get_port())
232 }
233
234 pub fn from_ado_string(s: &str) -> crate::Result<Self> {
254 let ado: AdoNetConfig = s.parse()?;
255 Self::from_config_string(ado)
256 }
257
258 pub fn from_jdbc_string(s: &str) -> crate::Result<Self> {
265 let jdbc: JdbcConfig = s.parse()?;
266 Self::from_config_string(jdbc)
267 }
268
269 fn from_config_string(s: impl ConfigString) -> crate::Result<Self> {
270 let mut builder = Self::new();
271
272 let server = s.server()?;
273
274 if let Some(host) = server.host {
275 builder.host(host);
276 }
277
278 if let Some(port) = server.port {
279 builder.port(port);
280 }
281
282 if let Some(instance) = server.instance {
283 builder.instance_name(instance);
284 }
285
286 builder.authentication(s.authentication()?);
287
288 if let Some(database) = s.database() {
289 builder.database(database);
290 }
291
292 if let Some(name) = s.application_name() {
293 builder.application_name(name);
294 }
295
296 if s.trust_cert()? {
297 builder.trust_cert();
298 }
299
300 if let Some(ca) = s.trust_cert_ca() {
301 builder.trust_cert_ca(ca);
302 }
303
304 builder.encryption(s.encrypt()?);
305
306 builder.readonly(s.readonly());
307
308 Ok(builder)
309 }
310}
311
312pub(crate) struct ServerDefinition {
313 host: Option<String>,
314 port: Option<u16>,
315 instance: Option<String>,
316}
317
318pub(crate) trait ConfigString {
319 fn dict(&self) -> &HashMap<String, String>;
320
321 fn server(&self) -> crate::Result<ServerDefinition>;
322
323 fn authentication(&self) -> crate::Result<AuthMethod> {
324 let user = self
325 .dict()
326 .get("uid")
327 .or_else(|| self.dict().get("username"))
328 .or_else(|| self.dict().get("user"))
329 .or_else(|| self.dict().get("user id"))
330 .map(|s| s.as_str());
331
332 let pw = self
333 .dict()
334 .get("password")
335 .or_else(|| self.dict().get("pwd"))
336 .map(|s| s.as_str());
337
338 match self
339 .dict()
340 .get("integratedsecurity")
341 .or_else(|| self.dict().get("integrated security"))
342 {
343 #[cfg(all(windows, feature = "winauth"))]
344 Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => match (user, pw)
345 {
346 (None, None) => Ok(AuthMethod::Integrated),
347 _ => Ok(AuthMethod::windows(user.unwrap_or(""), pw.unwrap_or(""))),
348 },
349 #[cfg(feature = "integrated-auth-gssapi")]
350 Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => {
351 Ok(AuthMethod::Integrated)
352 }
353 _ => Ok(AuthMethod::sql_server(user.unwrap_or(""), pw.unwrap_or(""))),
354 }
355 }
356
357 fn database(&self) -> Option<String> {
358 self.dict()
359 .get("database")
360 .or_else(|| self.dict().get("initial catalog"))
361 .or_else(|| self.dict().get("databasename"))
362 .map(|db| db.to_string())
363 }
364
365 fn application_name(&self) -> Option<String> {
366 self.dict()
367 .get("application name")
368 .or_else(|| self.dict().get("applicationname"))
369 .map(|name| name.to_string())
370 }
371
372 fn trust_cert(&self) -> crate::Result<bool> {
373 self.dict()
374 .get("trustservercertificate")
375 .map(Self::parse_bool)
376 .unwrap_or(Ok(false))
377 }
378
379 fn trust_cert_ca(&self) -> Option<String> {
380 self.dict()
381 .get("trustservercertificateca")
382 .map(|ca| ca.to_string())
383 }
384
385 #[cfg(any(
386 feature = "rustls",
387 feature = "native-tls",
388 feature = "vendored-openssl"
389 ))]
390 fn encrypt(&self) -> crate::Result<EncryptionLevel> {
391 self.dict()
392 .get("encrypt")
393 .map(|val| match Self::parse_bool(val) {
394 Ok(true) => Ok(EncryptionLevel::Required),
395 Ok(false) => Ok(EncryptionLevel::Off),
396 Err(_) if val == "DANGER_PLAINTEXT" => Ok(EncryptionLevel::NotSupported),
397 Err(e) => Err(e),
398 })
399 .unwrap_or(Ok(EncryptionLevel::Off))
400 }
401
402 #[cfg(not(any(
403 feature = "rustls",
404 feature = "native-tls",
405 feature = "vendored-openssl"
406 )))]
407 fn encrypt(&self) -> crate::Result<EncryptionLevel> {
408 Ok(EncryptionLevel::NotSupported)
409 }
410
411 fn parse_bool<T: AsRef<str>>(v: T) -> crate::Result<bool> {
412 match v.as_ref().trim().to_lowercase().as_str() {
413 "true" | "yes" => Ok(true),
414 "false" | "no" => Ok(false),
415 _ => Err(crate::Error::Conversion(
416 "Connection string: Not a valid boolean".into(),
417 )),
418 }
419 }
420
421 fn readonly(&self) -> bool {
422 self.dict()
423 .get("applicationintent")
424 .filter(|val| *val == "ReadOnly")
425 .is_some()
426 }
427}