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)]
13pub struct Config {
33 pub(crate) host: Option<String>,
34 pub(crate) port: Option<u16>,
35 pub(crate) database: Option<String>,
36 pub(crate) instance_name: Option<String>,
37 pub(crate) application_name: Option<String>,
38 pub(crate) encryption: EncryptionLevel,
39 pub(crate) trust: TrustConfig,
40 pub(crate) auth: AuthMethod,
41 pub(crate) readonly: bool,
42 pub(crate) packet_size: Option<u32>,
43 pub(crate) hostname_in_certificate: Option<String>,
44 pub(crate) client_name: Option<String>,
45 pub(crate) multi_subnet_failover: bool,
46 #[cfg(any(
47 feature = "rustls",
48 feature = "native-tls",
49 feature = "vendored-openssl"
50 ))]
51 pub(crate) client_cert: Option<ClientCertificate>,
52}
53
54#[derive(Clone, Debug)]
55pub(crate) enum TrustConfig {
56 #[allow(dead_code)]
57 CaCertificateLocation(PathBuf),
58 TrustAll,
59 Default,
60}
61
62#[cfg(any(
70 feature = "rustls",
71 feature = "native-tls",
72 feature = "vendored-openssl"
73))]
74#[derive(Clone, Debug)]
75pub(crate) struct ClientCertificate {
76 pub(crate) source: ClientCertSource,
77}
78
79#[cfg(any(
80 feature = "rustls",
81 feature = "native-tls",
82 feature = "vendored-openssl"
83))]
84#[derive(Clone)]
85pub(crate) enum ClientCertSource {
86 CertAndKey { cert: PathBuf, key: PathBuf },
91 #[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
94 Pkcs12 {
95 path: PathBuf,
96 password: zeroize::Zeroizing<String>,
97 },
98}
99
100#[cfg(any(
102 feature = "rustls",
103 feature = "native-tls",
104 feature = "vendored-openssl"
105))]
106impl std::fmt::Debug for ClientCertSource {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 match self {
109 ClientCertSource::CertAndKey { cert, key } => f
110 .debug_struct("CertAndKey")
111 .field("cert", cert)
112 .field("key", key)
113 .finish(),
114 #[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
115 ClientCertSource::Pkcs12 { path, .. } => f
116 .debug_struct("Pkcs12")
117 .field("path", path)
118 .field("password", &"<redacted>")
119 .finish(),
120 }
121 }
122}
123
124impl Default for Config {
125 fn default() -> Self {
126 Self {
127 host: None,
128 port: None,
129 database: None,
130 instance_name: None,
131 application_name: None,
132 #[cfg(any(
133 feature = "rustls",
134 feature = "native-tls",
135 feature = "vendored-openssl"
136 ))]
137 encryption: EncryptionLevel::Required,
138 #[cfg(not(any(
139 feature = "rustls",
140 feature = "native-tls",
141 feature = "vendored-openssl"
142 )))]
143 encryption: EncryptionLevel::NotSupported,
144 trust: TrustConfig::Default,
145 auth: AuthMethod::None,
146 readonly: false,
147 packet_size: None,
148 hostname_in_certificate: None,
149 client_name: None,
150 multi_subnet_failover: false,
151 #[cfg(any(
152 feature = "rustls",
153 feature = "native-tls",
154 feature = "vendored-openssl"
155 ))]
156 client_cert: None,
157 }
158 }
159}
160
161impl Config {
162 pub fn new() -> Self {
164 Self::default()
165 }
166
167 pub fn builder() -> ConfigBuilder {
189 ConfigBuilder {
190 inner: Self::default(),
191 }
192 }
193
194 pub fn host(&mut self, host: impl ToString) {
198 self.host = Some(host.to_string());
199 }
200
201 pub fn port(&mut self, port: u16) {
205 self.port = Some(port);
206 }
207
208 pub fn database(&mut self, database: impl ToString) {
212 self.database = Some(database.to_string())
213 }
214
215 pub fn instance_name(&mut self, name: impl ToString) {
223 self.instance_name = Some(name.to_string());
224 }
225
226 pub fn application_name(&mut self, name: impl ToString) {
231 self.application_name = Some(name.to_string());
232 }
233
234 pub fn packet_size(&mut self, size: u32) {
242 self.packet_size = Some(size);
243 }
244
245 pub fn get_packet_size(&self) -> Option<u32> {
247 self.packet_size
248 }
249
250 pub fn encryption(&mut self, encryption: EncryptionLevel) {
255 self.encryption = encryption;
256 }
257
258 pub fn trust_cert(&mut self) {
269 if let TrustConfig::CaCertificateLocation(_) = &self.trust {
270 panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
271 }
272 self.trust = TrustConfig::TrustAll;
273 }
274
275 pub fn trust_cert_ca(&mut self, path: impl ToString) {
285 if let TrustConfig::TrustAll = &self.trust {
286 panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
287 } else {
288 self.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string()))
289 }
290 }
291
292 pub fn hostname_in_certificate(&mut self, hostname: impl ToString) {
303 self.hostname_in_certificate = Some(hostname.to_string());
304 }
305
306 pub fn client_name(&mut self, name: impl ToString) {
311 self.client_name = Some(name.to_string());
312 }
313
314 pub fn authentication(&mut self, auth: AuthMethod) {
318 self.auth = auth;
319 }
320
321 pub fn readonly(&mut self, readnoly: bool) {
325 self.readonly = readnoly;
326 }
327
328 pub fn multi_subnet_failover(&mut self, multi_subnet_failover: bool) {
338 self.multi_subnet_failover = multi_subnet_failover;
339 }
340
341 pub fn get_multi_subnet_failover(&self) -> bool {
343 self.multi_subnet_failover
344 }
345
346 #[cfg(any(
372 feature = "rustls",
373 feature = "native-tls",
374 feature = "vendored-openssl"
375 ))]
376 #[cfg_attr(
377 docsrs,
378 doc(cfg(any(
379 feature = "rustls",
380 feature = "native-tls",
381 feature = "vendored-openssl"
382 )))
383 )]
384 pub fn client_certificate(&mut self, cert: impl Into<PathBuf>, key: impl Into<PathBuf>) {
385 self.client_cert = Some(ClientCertificate {
386 source: ClientCertSource::CertAndKey {
387 cert: cert.into(),
388 key: key.into(),
389 },
390 });
391 }
392
393 #[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
406 #[cfg_attr(
407 docsrs,
408 doc(cfg(any(feature = "native-tls", feature = "vendored-openssl")))
409 )]
410 pub fn client_certificate_pkcs12(
411 &mut self,
412 path: impl Into<PathBuf>,
413 password: impl Into<String>,
414 ) {
415 self.client_cert = Some(ClientCertificate {
416 source: ClientCertSource::Pkcs12 {
417 path: path.into(),
418 password: zeroize::Zeroizing::new(password.into()),
419 },
420 });
421 }
422
423 #[cfg(any(
424 feature = "rustls",
425 feature = "native-tls",
426 feature = "vendored-openssl"
427 ))]
428 pub(crate) fn get_client_certificate(&self) -> Option<&ClientCertificate> {
429 self.client_cert.as_ref()
430 }
431
432 pub(crate) fn get_host(&self) -> &str {
433 self.host
434 .as_deref()
435 .filter(|v| v != &".")
436 .unwrap_or("localhost")
437 }
438
439 #[cfg(any(
440 feature = "rustls",
441 feature = "native-tls",
442 feature = "vendored-openssl"
443 ))]
444 pub(crate) fn get_hostname_in_certificate(&self) -> &str {
445 self.hostname_in_certificate
446 .as_deref()
447 .unwrap_or_else(|| self.get_host())
448 }
449
450 pub(crate) fn get_port(&self) -> u16 {
451 match (self.port, self.instance_name.as_ref()) {
452 (Some(port), _) => port,
454 (None, Some(_)) => 1434,
457 (None, None) => 1433,
459 }
460 }
461
462 pub fn get_addr(&self) -> String {
464 format!("{}:{}", self.get_host(), self.get_port())
465 }
466
467 pub fn from_ado_string(s: &str) -> crate::Result<Self> {
490 let ado: AdoNetConfig = s.parse()?;
491 Self::from_config_string(ado)
492 }
493
494 pub fn from_jdbc_string(s: &str) -> crate::Result<Self> {
501 let jdbc: JdbcConfig = s.parse()?;
502 Self::from_config_string(jdbc)
503 }
504
505 fn from_config_string(s: impl ConfigString) -> crate::Result<Self> {
506 let mut builder = Self::new();
507
508 let server = s.server()?;
509
510 if let Some(host) = server.host {
511 builder.host(host);
512 }
513
514 if let Some(port) = server.port {
515 builder.port(port);
516 }
517
518 if let Some(instance) = server.instance {
519 builder.instance_name(instance);
520 }
521
522 builder.authentication(s.authentication()?);
523
524 if let Some(database) = s.database() {
525 builder.database(database);
526 }
527
528 if let Some(name) = s.application_name() {
529 builder.application_name(name);
530 }
531
532 if s.trust_cert()? {
533 builder.trust_cert();
534 }
535
536 if let Some(ca) = s.trust_cert_ca() {
537 builder.trust_cert_ca(ca);
538 }
539
540 if let Some(hostname_in_cert) = s.hostname_in_certificate() {
541 builder.hostname_in_certificate(hostname_in_cert);
542 }
543
544 builder.encryption(s.encrypt()?);
545
546 builder.readonly(s.readonly());
547
548 if let Some(client_name) = s.client_name() {
549 builder.client_name(client_name);
550 }
551 builder.multi_subnet_failover(s.multi_subnet_failover()?);
552
553 Ok(builder)
554 }
555}
556
557#[derive(Clone, Debug)]
581pub struct ConfigBuilder {
582 inner: Config,
583}
584
585impl ConfigBuilder {
586 pub fn host(mut self, host: impl ToString) -> Self {
590 self.inner.host = Some(host.to_string());
591 self
592 }
593
594 pub fn port(mut self, port: u16) -> Self {
598 self.inner.port = Some(port);
599 self
600 }
601
602 pub fn database(mut self, database: impl ToString) -> Self {
606 self.inner.database = Some(database.to_string());
607 self
608 }
609
610 pub fn instance_name(mut self, name: impl ToString) -> Self {
618 self.inner.instance_name = Some(name.to_string());
619 self
620 }
621
622 pub fn application_name(mut self, name: impl ToString) -> Self {
627 self.inner.application_name = Some(name.to_string());
628 self
629 }
630
631 pub fn encryption(mut self, encryption: EncryptionLevel) -> Self {
636 self.inner.encryption = encryption;
637 self
638 }
639
640 pub fn trust_cert(mut self) -> Self {
651 if let TrustConfig::CaCertificateLocation(_) = &self.inner.trust {
652 panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
653 }
654 self.inner.trust = TrustConfig::TrustAll;
655 self
656 }
657
658 pub fn trust_cert_ca(mut self, path: impl ToString) -> Self {
668 if let TrustConfig::TrustAll = &self.inner.trust {
669 panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
670 } else {
671 self.inner.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string()))
672 }
673 self
674 }
675
676 pub fn authentication(mut self, auth: AuthMethod) -> Self {
680 self.inner.auth = auth;
681 self
682 }
683
684 pub fn readonly(mut self, readonly: bool) -> Self {
688 self.inner.readonly = readonly;
689 self
690 }
691
692 #[cfg(any(
696 feature = "rustls",
697 feature = "native-tls",
698 feature = "vendored-openssl"
699 ))]
700 #[cfg_attr(
701 docsrs,
702 doc(cfg(any(
703 feature = "rustls",
704 feature = "native-tls",
705 feature = "vendored-openssl"
706 )))
707 )]
708 pub fn client_certificate(mut self, cert: impl Into<PathBuf>, key: impl Into<PathBuf>) -> Self {
709 self.inner.client_certificate(cert, key);
710 self
711 }
712
713 #[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
718 #[cfg_attr(
719 docsrs,
720 doc(cfg(any(feature = "native-tls", feature = "vendored-openssl")))
721 )]
722 pub fn client_certificate_pkcs12(
723 mut self,
724 path: impl Into<PathBuf>,
725 password: impl Into<String>,
726 ) -> Self {
727 self.inner.client_certificate_pkcs12(path, password);
728 self
729 }
730
731 pub fn build(self) -> Config {
735 self.inner
736 }
737}
738
739impl From<Config> for ConfigBuilder {
740 fn from(config: Config) -> Self {
741 ConfigBuilder { inner: config }
742 }
743}
744
745impl From<ConfigBuilder> for Config {
746 fn from(builder: ConfigBuilder) -> Self {
747 builder.inner
748 }
749}
750
751pub(crate) struct ServerDefinition {
752 host: Option<String>,
753 port: Option<u16>,
754 instance: Option<String>,
755}
756
757pub(crate) trait ConfigString {
758 fn dict(&self) -> &HashMap<String, String>;
759
760 fn server(&self) -> crate::Result<ServerDefinition>;
761
762 fn authentication(&self) -> crate::Result<AuthMethod> {
763 let user = self
764 .dict()
765 .get("uid")
766 .or_else(|| self.dict().get("username"))
767 .or_else(|| self.dict().get("user"))
768 .or_else(|| self.dict().get("user id"))
769 .map(|s| s.as_str());
770
771 let pw = self
772 .dict()
773 .get("password")
774 .or_else(|| self.dict().get("pwd"))
775 .map(|s| s.as_str());
776
777 match self
778 .dict()
779 .get("integratedsecurity")
780 .or_else(|| self.dict().get("integrated security"))
781 {
782 #[cfg(all(windows, feature = "winauth"))]
783 Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => match (user, pw)
784 {
785 (None, None) => Ok(AuthMethod::Integrated),
786 _ => Ok(AuthMethod::windows(user.unwrap_or(""), pw.unwrap_or(""))),
787 },
788 #[cfg(all(unix, feature = "sspi-rs"))]
793 Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => {
794 match (user, pw) {
795 (Some(user), Some(pw)) => Ok(AuthMethod::windows(user, pw)),
796 #[cfg(feature = "integrated-auth-gssapi")]
797 (None, None) => Ok(AuthMethod::Integrated),
798 _ => Ok(AuthMethod::windows(user.unwrap_or(""), pw.unwrap_or(""))),
799 }
800 }
801 #[cfg(all(
802 feature = "integrated-auth-gssapi",
803 not(all(unix, feature = "sspi-rs"))
804 ))]
805 Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => {
806 Ok(AuthMethod::Integrated)
807 }
808 _ => Ok(AuthMethod::sql_server(user.unwrap_or(""), pw.unwrap_or(""))),
809 }
810 }
811
812 fn database(&self) -> Option<String> {
813 self.dict()
814 .get("database")
815 .or_else(|| self.dict().get("initial catalog"))
816 .or_else(|| self.dict().get("databasename"))
817 .map(|db| db.to_string())
818 }
819
820 fn application_name(&self) -> Option<String> {
821 self.dict()
822 .get("application name")
823 .or_else(|| self.dict().get("applicationname"))
824 .map(|name| name.to_string())
825 }
826
827 fn trust_cert(&self) -> crate::Result<bool> {
828 self.dict()
829 .get("trustservercertificate")
830 .map(Self::parse_bool)
831 .unwrap_or(Ok(false))
832 }
833
834 fn trust_cert_ca(&self) -> Option<String> {
835 self.dict()
836 .get("trustservercertificateca")
837 .map(|ca| ca.to_string())
838 }
839
840 fn hostname_in_certificate(&self) -> Option<String> {
841 self.dict()
842 .get("hostnameincertificate")
843 .or_else(|| self.dict().get("hostname in certificate"))
844 .map(|host| host.to_string())
845 }
846
847 fn client_name(&self) -> Option<String> {
848 self.dict()
849 .get("workstationid")
850 .or_else(|| self.dict().get("workstation id"))
851 .map(|name| name.to_string())
852 }
853
854 #[cfg(any(
855 feature = "rustls",
856 feature = "native-tls",
857 feature = "vendored-openssl"
858 ))]
859 fn encrypt(&self) -> crate::Result<EncryptionLevel> {
860 self.dict()
861 .get("encrypt")
862 .map(|val| match Self::parse_bool(val) {
863 Ok(true) => Ok(EncryptionLevel::Required),
864 Ok(false) => Ok(EncryptionLevel::Off),
865 Err(_) if val == "DANGER_PLAINTEXT" => Ok(EncryptionLevel::NotSupported),
866 Err(_) if val.eq_ignore_ascii_case("strict") && cfg!(feature = "tds80") => {
867 Ok(EncryptionLevel::Strict)
868 }
869 Err(_) if val.eq_ignore_ascii_case("strict") => Err(crate::Error::Conversion(
870 "encrypt=strict requires the crate's `tds80` feature to be enabled".into(),
871 )),
872 Err(e) => Err(e),
873 })
874 .unwrap_or(Ok(EncryptionLevel::Required))
880 }
881
882 #[cfg(not(any(
883 feature = "rustls",
884 feature = "native-tls",
885 feature = "vendored-openssl"
886 )))]
887 fn encrypt(&self) -> crate::Result<EncryptionLevel> {
888 Ok(EncryptionLevel::NotSupported)
889 }
890
891 fn parse_bool<T: AsRef<str>>(v: T) -> crate::Result<bool> {
892 match v.as_ref().trim().to_lowercase().as_str() {
893 "true" | "yes" => Ok(true),
894 "false" | "no" => Ok(false),
895 _ => Err(crate::Error::Conversion(
896 "Connection string: Not a valid boolean".into(),
897 )),
898 }
899 }
900
901 fn readonly(&self) -> bool {
902 self.dict()
903 .get("applicationintent")
904 .filter(|val| val.trim().eq_ignore_ascii_case("ReadOnly"))
905 .is_some()
906 }
907
908 fn multi_subnet_failover(&self) -> crate::Result<bool> {
909 self.dict()
910 .get("multisubnetfailover")
911 .map(Self::parse_bool)
912 .unwrap_or(Ok(false))
913 }
914}
915
916#[cfg(test)]
917mod tests {
918 use super::*;
919
920 #[test]
921 fn config_builder_constructs_config() {
922 let config = Config::builder()
923 .host("db.example.com")
924 .port(4433)
925 .database("northwind")
926 .application_name("my-app")
927 .authentication(AuthMethod::sql_server("SA", "secret"))
928 .readonly(true)
929 .build();
930
931 assert_eq!("db.example.com", config.get_host());
932 assert_eq!(4433, config.get_port());
933 assert_eq!("db.example.com:4433", config.get_addr());
934 assert_eq!(Some("northwind"), config.database.as_deref());
935 assert_eq!(Some("my-app"), config.application_name.as_deref());
936 assert!(config.readonly);
937 assert!(matches!(config.auth, AuthMethod::SqlServer(_)));
938 assert!(matches!(config.trust, TrustConfig::Default));
939 }
940
941 #[test]
942 fn config_builder_roundtrips_via_from() {
943 let config = Config::builder().host("localhost").port(1433).build();
944 let builder: ConfigBuilder = config.into();
945 let config = builder.database("master").build();
946
947 assert_eq!("localhost:1433", config.get_addr());
948 assert_eq!(Some("master"), config.database.as_deref());
949 }
950
951 #[test]
952 fn config_from_builder_carries_builder_settings() {
953 let config: Config = Config::builder().host("db.internal").port(2020).into();
955 assert_eq!("db.internal", config.get_host());
956 assert_eq!(2020, config.get_port());
957 }
958
959 #[test]
960 fn get_packet_size_reflects_the_set_value() {
961 let mut config = Config::new();
962 assert_eq!(config.get_packet_size(), None);
963 config.packet_size(8192);
964 assert_eq!(config.get_packet_size(), Some(8192));
965 }
966
967 #[test]
968 fn from_jdbc_string_parses_host_and_port() {
969 let config =
970 Config::from_jdbc_string("jdbc:sqlserver://db.example.com:2345").expect("valid jdbc");
971 assert_eq!("db.example.com", config.get_host());
972 assert_eq!(2345, config.get_port());
973 }
974
975 #[cfg(any(
976 feature = "rustls",
977 feature = "native-tls",
978 feature = "vendored-openssl"
979 ))]
980 #[test]
981 fn get_hostname_in_certificate_falls_back_to_host() {
982 let mut config = Config::new();
983 config.host("real.host");
984 assert_eq!(config.get_hostname_in_certificate(), "real.host");
986 config.hostname_in_certificate("cert.host");
988 assert_eq!(config.get_hostname_in_certificate(), "cert.host");
989 }
990
991 #[cfg(any(
992 feature = "rustls",
993 feature = "native-tls",
994 feature = "vendored-openssl"
995 ))]
996 #[test]
997 fn client_certificate_sets_cert_and_key_source() {
998 let mut config = Config::new();
999 assert!(config.get_client_certificate().is_none());
1000
1001 config.client_certificate("/tmp/client.pem", "/tmp/client.key");
1002
1003 let cert = config
1004 .get_client_certificate()
1005 .expect("client certificate should be set");
1006 match &cert.source {
1007 ClientCertSource::CertAndKey { cert, key } => {
1008 assert_eq!(cert, &PathBuf::from("/tmp/client.pem"));
1009 assert_eq!(key, &PathBuf::from("/tmp/client.key"));
1010 }
1011 #[allow(unreachable_patterns)]
1012 other => panic!("expected CertAndKey source, got {other:?}"),
1013 }
1014 }
1015
1016 #[cfg(any(
1017 feature = "rustls",
1018 feature = "native-tls",
1019 feature = "vendored-openssl"
1020 ))]
1021 #[test]
1022 fn config_builder_sets_client_certificate() {
1023 let config = Config::builder()
1024 .host("localhost")
1025 .client_certificate("cert.der", "key.der")
1026 .build();
1027
1028 match &config
1029 .get_client_certificate()
1030 .expect("client certificate should be set")
1031 .source
1032 {
1033 ClientCertSource::CertAndKey { cert, key } => {
1034 assert_eq!(cert, &PathBuf::from("cert.der"));
1035 assert_eq!(key, &PathBuf::from("key.der"));
1036 }
1037 #[allow(unreachable_patterns)]
1038 other => panic!("expected CertAndKey source, got {other:?}"),
1039 }
1040 }
1041
1042 #[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
1043 #[test]
1044 fn client_certificate_pkcs12_sets_bundle_source() {
1045 let mut config = Config::new();
1046 config.client_certificate_pkcs12("/tmp/identity.pfx", "s3cr3t");
1047
1048 match &config
1049 .get_client_certificate()
1050 .expect("client certificate should be set")
1051 .source
1052 {
1053 ClientCertSource::Pkcs12 { path, password } => {
1054 assert_eq!(path, &PathBuf::from("/tmp/identity.pfx"));
1055 assert_eq!(password.as_str(), "s3cr3t");
1056 }
1057 other => panic!("expected Pkcs12 source, got {other:?}"),
1058 }
1059 }
1060
1061 #[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
1062 #[test]
1063 fn client_certificate_debug_redacts_pkcs12_password() {
1064 let mut config = Config::new();
1065 config.client_certificate_pkcs12("/tmp/identity.pfx", "topsecret");
1066
1067 let dbg = format!("{:?}", config.get_client_certificate().unwrap());
1068 assert!(dbg.contains("<redacted>"));
1069 assert!(!dbg.contains("topsecret"));
1070 }
1071
1072 #[cfg(all(unix, feature = "sspi-rs"))]
1073 #[test]
1074 fn ado_integrated_security_sspi_with_credentials_uses_windows_ntlm() {
1075 let config = Config::from_ado_string(
1076 "server=tcp:localhost,1433;IntegratedSecurity=SSPI;uid=DOMAIN\\user;pwd=secret",
1077 )
1078 .unwrap();
1079
1080 match config.auth {
1081 AuthMethod::Windows(auth) => {
1082 assert_eq!("user", auth.user);
1083 assert_eq!(Some("DOMAIN"), auth.domain.as_deref());
1084 }
1085 other => panic!("expected Windows NTLM auth, got {other:?}"),
1086 }
1087 }
1088
1089 #[test]
1090 fn config_direct_setters_populate_fields() {
1091 let mut config = Config::new();
1092 config.database("northwind");
1093 config.instance_name("SQLEXPRESS");
1094 config.client_name("workstation-7");
1095
1096 assert_eq!(Some("northwind"), config.database.as_deref());
1097 assert_eq!(Some("SQLEXPRESS"), config.instance_name.as_deref());
1098 assert_eq!(Some("workstation-7"), config.client_name.as_deref());
1099 }
1100
1101 #[test]
1102 fn get_port_defaults_without_port_or_instance() {
1103 let config = Config::new();
1105 assert_eq!(1433, config.get_port());
1106 }
1107
1108 #[test]
1109 fn get_port_uses_sql_browser_port_for_named_instance() {
1110 let mut config = Config::new();
1112 config.instance_name("SQLEXPRESS");
1113 assert_eq!(1434, config.get_port());
1114 }
1115
1116 #[test]
1117 #[should_panic(expected = "mutual exclusive")]
1118 fn trust_cert_after_trust_cert_ca_panics() {
1119 let mut config = Config::new();
1120 config.trust_cert_ca("/tmp/ca.crt");
1121 config.trust_cert();
1122 }
1123
1124 #[test]
1125 #[should_panic(expected = "mutual exclusive")]
1126 fn trust_cert_ca_after_trust_cert_panics() {
1127 let mut config = Config::new();
1128 config.trust_cert();
1129 config.trust_cert_ca("/tmp/ca.crt");
1130 }
1131
1132 #[test]
1133 fn trust_cert_ca_sets_ca_location() {
1134 let mut config = Config::new();
1135 config.trust_cert_ca("/tmp/ca.crt");
1136 assert!(matches!(
1137 config.trust,
1138 TrustConfig::CaCertificateLocation(_)
1139 ));
1140 }
1141
1142 #[test]
1143 fn config_builder_covers_all_setters() {
1144 let config = Config::builder()
1145 .host("localhost")
1146 .instance_name("SQLEXPRESS")
1147 .encryption(EncryptionLevel::Off)
1148 .trust_cert_ca("/tmp/ca.crt")
1149 .build();
1150
1151 assert_eq!(Some("SQLEXPRESS"), config.instance_name.as_deref());
1152 assert!(matches!(config.encryption, EncryptionLevel::Off));
1153 assert!(matches!(
1154 config.trust,
1155 TrustConfig::CaCertificateLocation(_)
1156 ));
1157 }
1158
1159 #[test]
1160 fn config_builder_trust_cert_sets_trust_all() {
1161 let config = Config::builder().trust_cert().build();
1162 assert!(matches!(config.trust, TrustConfig::TrustAll));
1163 }
1164
1165 #[test]
1166 #[should_panic(expected = "mutual exclusive")]
1167 fn config_builder_trust_cert_after_ca_panics() {
1168 Config::builder().trust_cert_ca("/tmp/ca.crt").trust_cert();
1169 }
1170
1171 #[test]
1172 #[should_panic(expected = "mutual exclusive")]
1173 fn config_builder_trust_cert_ca_after_trust_cert_panics() {
1174 Config::builder().trust_cert().trust_cert_ca("/tmp/ca.crt");
1175 }
1176
1177 #[test]
1178 fn from_ado_string_populates_optional_fields() {
1179 let config = Config::from_ado_string(
1180 "server=tcp:my-server.com\\SQLEXPRESS;database=northwind;\
1181 HostNameInCertificate=cert.host;WorkstationID=ws-1",
1182 )
1183 .expect("valid ado string");
1184
1185 assert_eq!("my-server.com", config.get_host());
1186 assert_eq!(Some("SQLEXPRESS"), config.instance_name.as_deref());
1187 assert_eq!(Some("northwind"), config.database.as_deref());
1188 assert_eq!(Some("cert.host"), config.hostname_in_certificate.as_deref());
1189 assert_eq!(Some("ws-1"), config.client_name.as_deref());
1190 }
1191
1192 #[cfg(any(
1193 feature = "rustls",
1194 feature = "native-tls",
1195 feature = "vendored-openssl"
1196 ))]
1197 #[test]
1198 fn client_cert_source_debug_formats_cert_and_key() {
1199 let mut config = Config::new();
1200 config.client_certificate("/tmp/client.pem", "/tmp/client.key");
1201
1202 let dbg = format!("{:?}", config.get_client_certificate().unwrap().source);
1203 assert!(dbg.contains("CertAndKey"));
1204 assert!(dbg.contains("client.pem"));
1205 assert!(dbg.contains("client.key"));
1206 }
1207
1208 #[cfg(any(feature = "native-tls", feature = "vendored-openssl"))]
1209 #[test]
1210 fn config_builder_sets_pkcs12_client_certificate() {
1211 let config = Config::builder()
1212 .client_certificate_pkcs12("/tmp/identity.pfx", "s3cr3t")
1213 .build();
1214
1215 match &config
1216 .get_client_certificate()
1217 .expect("client certificate should be set")
1218 .source
1219 {
1220 ClientCertSource::Pkcs12 { path, password } => {
1221 assert_eq!(path, &PathBuf::from("/tmp/identity.pfx"));
1222 assert_eq!(password.as_str(), "s3cr3t");
1223 }
1224 other => panic!("expected Pkcs12 source, got {other:?}"),
1225 }
1226 }
1227
1228 #[cfg(all(unix, feature = "sspi-rs"))]
1229 #[test]
1230 fn ado_integrated_security_sspi_with_partial_credentials_uses_windows() {
1231 let config = Config::from_ado_string(
1233 "server=tcp:localhost,1433;IntegratedSecurity=SSPI;uid=onlyuser",
1234 )
1235 .unwrap();
1236
1237 match config.auth {
1238 AuthMethod::Windows(auth) => {
1239 assert_eq!("onlyuser", auth.user);
1240 }
1241 other => panic!("expected Windows auth, got {other:?}"),
1242 }
1243 }
1244}