1cfg_if::cfg_if! {
2 if #[cfg(windows)] {
3 use windows_registry::LOCAL_MACHINE;
4 }
5}
6
7use std::env;
8#[cfg(not(target_os = "windows"))]
9use std::path::Path;
10use std::str::FromStr;
11
12use url::Url;
13
14use crate::dns::detect_kdc_hosts_from_dns;
15#[cfg(not(target_os = "windows"))]
16use crate::krb::Krb5Conf;
17
18#[cfg(target_os = "windows")]
19#[instrument(level = "debug", ret)]
20pub(crate) fn detect_kdc_hosts_from_system(domain: &str) -> Vec<String> {
21 let domain_upper = domain.to_uppercase();
22 let hklm = LOCAL_MACHINE;
23 let domains_key_path = "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos\\Domains";
24 let domain_key_path = format!("{}\\{}", domains_key_path, &domain_upper);
25 if let Ok(domain_key) = hklm.open(domain_key_path) {
26 let kdc_names: Vec<String> = domain_key.get_multi_string("KdcNames").unwrap_or_default();
27 kdc_names.iter().map(|x| format!("tcp://{x}:88")).collect()
28 } else {
29 Vec::new()
30 }
31}
32
33#[cfg(not(target_os = "windows"))]
34#[instrument(level = "debug", ret)]
35pub(crate) fn detect_kdc_hosts_from_system(domain: &str) -> Vec<String> {
36 let krb5_config = env::var("KRB5_CONFIG").unwrap_or_else(|_| "/etc/krb5.conf:/usr/local/etc/krb5.conf".to_string());
39 let krb5_conf_paths = krb5_config.split(':').map(Path::new).collect::<Vec<&Path>>();
40
41 for krb5_conf_path in krb5_conf_paths {
42 if krb5_conf_path.exists()
43 && let Some(krb5_conf) = Krb5Conf::new_from_file(krb5_conf_path)
44 && let Some(kdc) = krb5_conf.get_value(vec!["realms", domain, "kdc"])
45 {
46 let kdc_url = format!("tcp://{}", kdc.as_str());
47 return vec![kdc_url];
48 }
49 }
50
51 Vec::new()
52}
53
54#[instrument(ret, level = "debug")]
55pub(crate) fn detect_kdc_hosts(domain: &str) -> Vec<String> {
56 if let Ok(kdc_url) = env::var(format!("SSPI_KDC_URL_{domain}")) {
57 return vec![kdc_url];
58 }
59
60 if let Ok(kdc_url) = env::var("SSPI_KDC_URL") {
61 return vec![kdc_url];
62 }
63
64 let kdc_hosts = detect_kdc_hosts_from_system(domain);
65
66 if !kdc_hosts.is_empty() {
67 return kdc_hosts;
68 }
69
70 detect_kdc_hosts_from_dns(domain)
71}
72
73pub fn detect_kdc_host(domain: &str) -> Option<String> {
74 let kdc_hosts = detect_kdc_hosts(domain);
75 if !kdc_hosts.is_empty() {
76 Some(kdc_hosts.first().unwrap().to_string())
77 } else {
78 None
79 }
80}
81
82pub fn detect_kdc_url(domain: &str) -> Option<Url> {
83 let kdc_host = detect_kdc_host(domain)?;
84 Url::from_str(&kdc_host).ok()
85}
86
87#[cfg(test)]
88mod tests {
89 use super::detect_kdc_hosts;
90 #[test]
91 fn test_detect_kdc() {
92 if let Ok(domain) = std::env::var("TEST_KERBEROS_REALM") {
93 println!("Finding KDC for {} domain", &domain);
94 let kdc_hosts = detect_kdc_hosts(&domain);
95 if let Some(kdc_host) = kdc_hosts.first() {
96 println!("KDC server: {kdc_host}");
97 } else {
98 println!("No KDC server found!");
99 }
100 }
101 }
102}