use adhammer_collector::Collector;
use adhammer_core::sid::Sid;
use anyhow::{Context, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TargetKind {
Sid,
Dn,
Sam,
}
pub(crate) fn classify(s: &str) -> TargetKind {
if s.starts_with("S-1-") {
TargetKind::Sid
} else if s.contains('=') && s.split(',').any(|c| c.trim_start().starts_with("DC=")) {
TargetKind::Dn
} else {
TargetKind::Sam
}
}
pub(crate) async fn to_sid(c: &mut Collector, s: &str) -> Result<Sid> {
match classify(s) {
TargetKind::Sid => Sid::parse(s).context(format!("bad SID literal: {s}")),
TargetKind::Sam | TargetKind::Dn => c
.resolve_sid(s)
.await
.with_context(|| format!("resolve --target {s:?} to SID")),
}
}
pub(crate) async fn to_dn(c: &mut Collector, s: &str) -> Result<String> {
match classify(s) {
TargetKind::Dn => Ok(s.to_string()),
TargetKind::Sam | TargetKind::Sid => c
.resolve_dn(s)
.await
.with_context(|| format!("resolve --target {s:?} to DN")),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sid_prefix_detected() {
assert_eq!(classify("S-1-5-21-1-2-3-1000"), TargetKind::Sid);
assert_eq!(classify("S-1-5-32-544"), TargetKind::Sid);
}
#[test]
fn dn_with_dc_components_detected() {
assert_eq!(
classify("CN=Alice,OU=Users,DC=corp,DC=local"),
TargetKind::Dn
);
assert_eq!(
classify("CN=DC01$,OU=Domain Controllers,DC=lab,DC=local"),
TargetKind::Dn
);
}
#[test]
fn bare_sam_falls_through() {
assert_eq!(classify("alice"), TargetKind::Sam);
assert_eq!(classify("DC01$"), TargetKind::Sam);
assert_eq!(classify("svc_sql"), TargetKind::Sam);
}
#[test]
fn cn_only_without_dc_is_not_a_dn() {
assert_eq!(classify("CN=foo"), TargetKind::Sam);
}
#[test]
fn ambiguous_looking_sid_gate() {
assert_eq!(classify("S-2-5-21-1-2-3"), TargetKind::Sam);
assert_eq!(classify("S-foo"), TargetKind::Sam);
}
}