tiberius/client/
config.rs

1mod 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)]
13/// The `Config` struct contains all configuration information
14/// required for connecting to the database with a [`Client`]. It also provides
15/// the server address when connecting to a `TcpStream` via the
16/// [`get_addr`] method.
17///
18/// When using an [ADO.NET connection string], it can be
19/// constructed using the [`from_ado_string`] function.
20///
21/// [`Client`]: struct.Client.html
22/// [ADO.NET connection string]: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings
23/// [`from_ado_string`]: struct.Config.html#method.from_ado_string
24/// [`get_addr`]: struct.Config.html#method.get_addr
25pub 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    /// Create a new `Config` with the default settings.
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    /// A host or ip address to connect to.
81    ///
82    /// - Defaults to `localhost`.
83    pub fn host(&mut self, host: impl ToString) {
84        self.host = Some(host.to_string());
85    }
86
87    /// The server port.
88    ///
89    /// - Defaults to `1433`.
90    pub fn port(&mut self, port: u16) {
91        self.port = Some(port);
92    }
93
94    /// The database to connect to.
95    ///
96    /// - Defaults to `master`.
97    pub fn database(&mut self, database: impl ToString) {
98        self.database = Some(database.to_string())
99    }
100
101    /// The instance name as defined in the SQL Browser. Only available on
102    /// Windows platforms.
103    ///
104    /// If specified, the port is replaced with the value returned from the
105    /// browser.
106    ///
107    /// - Defaults to no name specified.
108    pub fn instance_name(&mut self, name: impl ToString) {
109        self.instance_name = Some(name.to_string());
110    }
111
112    /// Sets the application name to the connection, queryable with the
113    /// `APP_NAME()` command.
114    ///
115    /// - Defaults to no name specified.
116    pub fn application_name(&mut self, name: impl ToString) {
117        self.application_name = Some(name.to_string());
118    }
119
120    /// Set the preferred encryption level.
121    ///
122    /// - With `tls` feature, defaults to `Required`.
123    /// - Without `tls` feature, defaults to `NotSupported`.
124    pub fn encryption(&mut self, encryption: EncryptionLevel) {
125        self.encryption = encryption;
126    }
127
128    /// If set, the server certificate will not be validated and it is accepted
129    /// as-is.
130    ///
131    /// On production setting, the certificate should be added to the local key
132    /// storage (or use `trust_cert_ca` instead), using this setting is potentially dangerous.
133    ///
134    /// # Panics
135    /// Will panic in case `trust_cert_ca` or `trust_cert_ca_pem` was called before.
136    ///
137    /// - Defaults to `default`, meaning server certificate is validated against system-truststore.
138    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    /// If set, the server certificate will be validated against the given CA certificate in
151    /// in addition to the system-truststore.
152    /// Useful when using self-signed certificates on the server without having to disable the
153    /// trust-chain.
154    ///
155    /// # Panics
156    /// Will panic in case `trust_cert` or `trust_cert_ca_pem` was called before.
157    ///
158    /// - Defaults to validating the server certificate is validated against system's certificate storage.
159    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    /// If set, the server certificate will be validated against the given CA certificate in
174    /// in addition to the system-truststore.
175    /// Useful when using self-signed certificates on the server without having to disable the
176    /// trust-chain.
177    ///
178    /// # Panics
179    /// Will panic in case `trust_cert` or `trust_cert_ca` was called before.
180    ///
181    /// - Defaults to validating the server certificate is validated against system's certificate storage.
182    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    /// Sets the authentication method.
197    ///
198    /// - Defaults to `None`.
199    pub fn authentication(&mut self, auth: AuthMethod) {
200        self.auth = auth;
201    }
202
203    /// Sets ApplicationIntent readonly.
204    ///
205    /// - Defaults to `false`.
206    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            // A user-defined port, we must use that.
220            (Some(port), _) => port,
221            // If using a named instance, we'll give the default port of SQL
222            // Browser.
223            (None, Some(_)) => 1434,
224            // Otherwise the defaulting to the default SQL Server port.
225            (None, None) => 1433,
226        }
227    }
228
229    /// Get the host address including port
230    pub fn get_addr(&self) -> String {
231        format!("{}:{}", self.get_host(), self.get_port())
232    }
233
234    /// Creates a new `Config` from an [ADO.NET connection string].
235    ///
236    /// # Supported parameters
237    ///
238    /// All parameter keys are handled case-insensitive.
239    ///
240    /// |Parameter|Allowed values|Description|
241    /// |--------|--------|--------|
242    /// |`server`|`<string>`|The name or network address of the instance of SQL Server to which to connect. The port number can be specified after the server name. The correct form of this parameter is either `tcp:host,port` or `tcp:host\\instance`|
243    /// |`IntegratedSecurity`|`true`,`false`,`yes`,`no`|Toggle between Windows/Kerberos authentication and SQL authentication.|
244    /// |`uid`,`username`,`user`,`user id`|`<string>`|The SQL Server login account.|
245    /// |`password`,`pwd`|`<string>`|The password for the SQL Server account logging on.|
246    /// |`database`|`<string>`|The name of the database.|
247    /// |`TrustServerCertificate`|`true`,`false`,`yes`,`no`|Specifies whether the driver trusts the server certificate when connecting using TLS. Cannot be used toghether with `TrustServerCertificateCA`|
248    /// |`TrustServerCertificateCA`|`<path>`|Path to a `pem`, `crt` or `der` certificate file. Cannot be used together with `TrustServerCertificate`|
249    /// |`encrypt`|`true`,`false`,`yes`,`no`,`DANGER_PLAINTEXT`|Specifies whether the driver uses TLS to encrypt communication.|
250    /// |`Application Name`, `ApplicationName`|`<string>`|Sets the application name for the connection.|
251    ///
252    /// [ADO.NET connection string]: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings
253    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    /// Creates a new `Config` from a [JDBC connection string].
259    ///
260    /// See [`from_ado_string`] method for supported parameters.
261    ///
262    /// [JDBC connection string]: https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver15
263    /// [`from_ado_string`]: #method.from_ado_string
264    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}