1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use std::fmt::Debug;
use std::str::FromStr;

use url::Url;

use crate::kdc::detect_kdc_url;
use crate::negotiate::{NegotiatedProtocol, ProtocolConfig};
use crate::{Kerberos, Result};

#[derive(Clone, Debug)]
pub struct KerberosConfig {
    /// KDC URL
    ///
    /// Depending on the scheme it is expected to be either:
    /// - a (Kerberos) KDC address (e.g.: tcp://domain:88, udp://domain:88), or
    /// - a KDC _Proxy_ URL (e.g.: https://gateway.devolutions.net/jet/KdcProxy?token=<…>)
    ///
    /// That is, when the scheme is `http` or `https`, the KDC Proxy Protocol ([KKDCP]) will be
    /// used on top of the Kerberos protocol, wrapping the messages.
    /// Otherwise, the scheme must be either `tcp` or `udp`, and the KDC protocol will be used
    /// in order to communicate with the KDC server directly.
    ///
    /// [KKDCP]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kkdcp/5bcebb8d-b747-4ee5-9453-428aec1c5c38
    pub kdc_url: Option<Url>,
    /// Computer name, or "workstation name", of the client machine performing the authentication attempt
    ///
    /// This is also referred to as the "Source Workstation", i.e.: the name of the computer attempting to logon.
    pub client_computer_name: Option<String>,
}

impl ProtocolConfig for KerberosConfig {
    fn new_client(&self) -> Result<NegotiatedProtocol> {
        Ok(NegotiatedProtocol::Kerberos(Kerberos::new_client_from_config(
            Clone::clone(self),
        )?))
    }

    fn box_clone(&self) -> Box<dyn ProtocolConfig> {
        Box::new(Clone::clone(self))
    }
}

pub fn parse_kdc_url(kdc_url: &str) -> Option<Url> {
    if !kdc_url.contains("://") {
        Url::from_str(&format!("tcp://{kdc_url}")).ok()
    } else {
        Url::from_str(kdc_url).ok()
    }
}

impl KerberosConfig {
    pub fn new(kdc_url: &str, client_computer_name: String) -> Self {
        let kdc_url = parse_kdc_url(kdc_url);

        Self {
            kdc_url,
            client_computer_name: Some(client_computer_name),
        }
    }

    pub fn get_kdc_url(self, domain: &str) -> Option<Url> {
        if let Some(kdc_url) = self.kdc_url {
            Some(kdc_url)
        } else {
            detect_kdc_url(domain)
        }
    }

    pub fn from_kdc_url(url: &str) -> Self {
        let kdc_url = parse_kdc_url(url);

        Self {
            kdc_url,
            client_computer_name: None,
        }
    }
}