Skip to main content

sspi/
kdc.rs

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    // https://web.mit.edu/kerberos/krb5-current/doc/user/user_config/kerberos.html#environment-variables
37
38    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}