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    TrustAll,
42    Default,
43}
44
45impl Default for Config {
46    fn default() -> Self {
47        Self {
48            host: None,
49            port: None,
50            database: None,
51            instance_name: None,
52            application_name: None,
53            #[cfg(any(
54                feature = "rustls",
55                feature = "native-tls",
56                feature = "vendored-openssl"
57            ))]
58            encryption: EncryptionLevel::Required,
59            #[cfg(not(any(
60                feature = "rustls",
61                feature = "native-tls",
62                feature = "vendored-openssl"
63            )))]
64            encryption: EncryptionLevel::NotSupported,
65            trust: TrustConfig::Default,
66            auth: AuthMethod::None,
67            readonly: false,
68        }
69    }
70}
71
72impl Config {
73    /// Create a new `Config` with the default settings.
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    /// A host or ip address to connect to.
79    ///
80    /// - Defaults to `localhost`.
81    pub fn host(&mut self, host: impl ToString) {
82        self.host = Some(host.to_string());
83    }
84
85    /// The server port.
86    ///
87    /// - Defaults to `1433`.
88    pub fn port(&mut self, port: u16) {
89        self.port = Some(port);
90    }
91
92    /// The database to connect to.
93    ///
94    /// - Defaults to `master`.
95    pub fn database(&mut self, database: impl ToString) {
96        self.database = Some(database.to_string())
97    }
98
99    /// The instance name as defined in the SQL Browser. Only available on
100    /// Windows platforms.
101    ///
102    /// If specified, the port is replaced with the value returned from the
103    /// browser.
104    ///
105    /// - Defaults to no name specified.
106    pub fn instance_name(&mut self, name: impl ToString) {
107        self.instance_name = Some(name.to_string());
108    }
109
110    /// Sets the application name to the connection, queryable with the
111    /// `APP_NAME()` command.
112    ///
113    /// - Defaults to no name specified.
114    pub fn application_name(&mut self, name: impl ToString) {
115        self.application_name = Some(name.to_string());
116    }
117
118    /// Set the preferred encryption level.
119    ///
120    /// - With `tls` feature, defaults to `Required`.
121    /// - Without `tls` feature, defaults to `NotSupported`.
122    pub fn encryption(&mut self, encryption: EncryptionLevel) {
123        self.encryption = encryption;
124    }
125
126    /// If set, the server certificate will not be validated and it is accepted
127    /// as-is.
128    ///
129    /// On production setting, the certificate should be added to the local key
130    /// storage (or use `trust_cert_ca` instead), using this setting is potentially dangerous.
131    ///
132    /// # Panics
133    /// Will panic in case `trust_cert_ca` was called before.
134    ///
135    /// - Defaults to `default`, meaning server certificate is validated against system-truststore.
136    pub fn trust_cert(&mut self) {
137        if let TrustConfig::CaCertificateLocation(_) = &self.trust {
138            panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
139        }
140        self.trust = TrustConfig::TrustAll;
141    }
142
143    /// If set, the server certificate will be validated against the given CA certificate in
144    /// in addition to the system-truststore.
145    /// Useful when using self-signed certificates on the server without having to disable the
146    /// trust-chain.
147    ///
148    /// # Panics
149    /// Will panic in case `trust_cert` was called before.
150    ///
151    /// - Defaults to validating the server certificate is validated against system's certificate storage.
152    pub fn trust_cert_ca(&mut self, path: impl ToString) {
153        if let TrustConfig::TrustAll = &self.trust {
154            panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
155        } else {
156            self.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string()))
157        }
158    }
159
160    /// Sets the authentication method.
161    ///
162    /// - Defaults to `None`.
163    pub fn authentication(&mut self, auth: AuthMethod) {
164        self.auth = auth;
165    }
166
167    /// Sets ApplicationIntent readonly.
168    ///
169    /// - Defaults to `false`.
170    pub fn readonly(&mut self, readnoly: bool) {
171        self.readonly = readnoly;
172    }
173
174    pub(crate) fn get_host(&self) -> &str {
175        self.host
176            .as_deref()
177            .filter(|v| v != &".")
178            .unwrap_or("localhost")
179    }
180
181    pub(crate) fn get_port(&self) -> u16 {
182        match (self.port, self.instance_name.as_ref()) {
183            // A user-defined port, we must use that.
184            (Some(port), _) => port,
185            // If using a named instance, we'll give the default port of SQL
186            // Browser.
187            (None, Some(_)) => 1434,
188            // Otherwise the defaulting to the default SQL Server port.
189            (None, None) => 1433,
190        }
191    }
192
193    /// Get the host address including port
194    pub fn get_addr(&self) -> String {
195        format!("{}:{}", self.get_host(), self.get_port())
196    }
197
198    /// Creates a new `Config` from an [ADO.NET connection string].
199    ///
200    /// # Supported parameters
201    ///
202    /// All parameter keys are handled case-insensitive.
203    ///
204    /// |Parameter|Allowed values|Description|
205    /// |--------|--------|--------|
206    /// |`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`|
207    /// |`IntegratedSecurity`|`true`,`false`,`yes`,`no`|Toggle between Windows/Kerberos authentication and SQL authentication.|
208    /// |`uid`,`username`,`user`,`user id`|`<string>`|The SQL Server login account.|
209    /// |`password`,`pwd`|`<string>`|The password for the SQL Server account logging on.|
210    /// |`database`|`<string>`|The name of the database.|
211    /// |`TrustServerCertificate`|`true`,`false`,`yes`,`no`|Specifies whether the driver trusts the server certificate when connecting using TLS. Cannot be used toghether with `TrustServerCertificateCA`|
212    /// |`TrustServerCertificateCA`|`<path>`|Path to a `pem`, `crt` or `der` certificate file. Cannot be used together with `TrustServerCertificate`|
213    /// |`encrypt`|`true`,`false`,`yes`,`no`,`DANGER_PLAINTEXT`|Specifies whether the driver uses TLS to encrypt communication.|
214    /// |`Application Name`, `ApplicationName`|`<string>`|Sets the application name for the connection.|
215    ///
216    /// [ADO.NET connection string]: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings
217    pub fn from_ado_string(s: &str) -> crate::Result<Self> {
218        let ado: AdoNetConfig = s.parse()?;
219        Self::from_config_string(ado)
220    }
221
222    /// Creates a new `Config` from a [JDBC connection string].
223    ///
224    /// See [`from_ado_string`] method for supported parameters.
225    ///
226    /// [JDBC connection string]: https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver15
227    /// [`from_ado_string`]: #method.from_ado_string
228    pub fn from_jdbc_string(s: &str) -> crate::Result<Self> {
229        let jdbc: JdbcConfig = s.parse()?;
230        Self::from_config_string(jdbc)
231    }
232
233    fn from_config_string(s: impl ConfigString) -> crate::Result<Self> {
234        let mut builder = Self::new();
235
236        let server = s.server()?;
237
238        if let Some(host) = server.host {
239            builder.host(host);
240        }
241
242        if let Some(port) = server.port {
243            builder.port(port);
244        }
245
246        if let Some(instance) = server.instance {
247            builder.instance_name(instance);
248        }
249
250        builder.authentication(s.authentication()?);
251
252        if let Some(database) = s.database() {
253            builder.database(database);
254        }
255
256        if let Some(name) = s.application_name() {
257            builder.application_name(name);
258        }
259
260        if s.trust_cert()? {
261            builder.trust_cert();
262        }
263
264        if let Some(ca) = s.trust_cert_ca() {
265            builder.trust_cert_ca(ca);
266        }
267
268        builder.encryption(s.encrypt()?);
269
270        builder.readonly(s.readonly());
271
272        Ok(builder)
273    }
274}
275
276pub(crate) struct ServerDefinition {
277    host: Option<String>,
278    port: Option<u16>,
279    instance: Option<String>,
280}
281
282pub(crate) trait ConfigString {
283    fn dict(&self) -> &HashMap<String, String>;
284
285    fn server(&self) -> crate::Result<ServerDefinition>;
286
287    fn authentication(&self) -> crate::Result<AuthMethod> {
288        let user = self
289            .dict()
290            .get("uid")
291            .or_else(|| self.dict().get("username"))
292            .or_else(|| self.dict().get("user"))
293            .or_else(|| self.dict().get("user id"))
294            .map(|s| s.as_str());
295
296        let pw = self
297            .dict()
298            .get("password")
299            .or_else(|| self.dict().get("pwd"))
300            .map(|s| s.as_str());
301
302        match self
303            .dict()
304            .get("integratedsecurity")
305            .or_else(|| self.dict().get("integrated security"))
306        {
307            #[cfg(all(windows, feature = "winauth"))]
308            Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => match (user, pw)
309            {
310                (None, None) => Ok(AuthMethod::Integrated),
311                _ => Ok(AuthMethod::windows(user.unwrap_or(""), pw.unwrap_or(""))),
312            },
313            #[cfg(feature = "integrated-auth-gssapi")]
314            Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => {
315                Ok(AuthMethod::Integrated)
316            }
317            _ => Ok(AuthMethod::sql_server(user.unwrap_or(""), pw.unwrap_or(""))),
318        }
319    }
320
321    fn database(&self) -> Option<String> {
322        self.dict()
323            .get("database")
324            .or_else(|| self.dict().get("initial catalog"))
325            .or_else(|| self.dict().get("databasename"))
326            .map(|db| db.to_string())
327    }
328
329    fn application_name(&self) -> Option<String> {
330        self.dict()
331            .get("application name")
332            .or_else(|| self.dict().get("applicationname"))
333            .map(|name| name.to_string())
334    }
335
336    fn trust_cert(&self) -> crate::Result<bool> {
337        self.dict()
338            .get("trustservercertificate")
339            .map(Self::parse_bool)
340            .unwrap_or(Ok(false))
341    }
342
343    fn trust_cert_ca(&self) -> Option<String> {
344        self.dict()
345            .get("trustservercertificateca")
346            .map(|ca| ca.to_string())
347    }
348
349    #[cfg(any(
350        feature = "rustls",
351        feature = "native-tls",
352        feature = "vendored-openssl"
353    ))]
354    fn encrypt(&self) -> crate::Result<EncryptionLevel> {
355        self.dict()
356            .get("encrypt")
357            .map(|val| match Self::parse_bool(val) {
358                Ok(true) => Ok(EncryptionLevel::Required),
359                Ok(false) => Ok(EncryptionLevel::Off),
360                Err(_) if val == "DANGER_PLAINTEXT" => Ok(EncryptionLevel::NotSupported),
361                Err(e) => Err(e),
362            })
363            .unwrap_or(Ok(EncryptionLevel::Off))
364    }
365
366    #[cfg(not(any(
367        feature = "rustls",
368        feature = "native-tls",
369        feature = "vendored-openssl"
370    )))]
371    fn encrypt(&self) -> crate::Result<EncryptionLevel> {
372        Ok(EncryptionLevel::NotSupported)
373    }
374
375    fn parse_bool<T: AsRef<str>>(v: T) -> crate::Result<bool> {
376        match v.as_ref().trim().to_lowercase().as_str() {
377            "true" | "yes" => Ok(true),
378            "false" | "no" => Ok(false),
379            _ => Err(crate::Error::Conversion(
380                "Connection string: Not a valid boolean".into(),
381            )),
382        }
383    }
384
385    fn readonly(&self) -> bool {
386        self.dict()
387            .get("applicationintent")
388            .filter(|val| *val == "ReadOnly")
389            .is_some()
390    }
391}