use std::fs::DirBuilder;
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};
use crate::ccache::ArmorCache;
use crate::error::Result;
use crate::exec::{self, Tools};
use crate::identity::MachineIdentity;
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct JoinStatus {
pub configured_realms: Vec<String>,
pub keytab_principal: Option<String>,
pub keytab_error: Option<String>,
pub credential_valid: Option<bool>,
pub credential_error: Option<String>,
pub sssd_status: Option<String>,
}
impl JoinStatus {
pub fn is_configured(&self) -> bool {
!self.configured_realms.is_empty()
}
pub fn is_properly_joined(&self) -> bool {
self.credential_valid == Some(true)
}
}
pub fn check(tools: &Tools, keytab: &Path, realm: Option<&str>, deep: bool) -> JoinStatus {
let mut status = JoinStatus::default();
if let Ok(out) = exec::run_ok(&tools.realm, ["list", "--name-only"]) {
status.configured_realms = out
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.map(String::from)
.collect();
}
let discovered = match realm {
Some(r) => MachineIdentity::discover_in_realm(tools, keytab, r),
None => MachineIdentity::discover(tools, keytab),
};
let identity = match discovered {
Ok(id) => {
status.keytab_principal = Some(id.principal.clone());
Some(id)
}
Err(e) => {
status.keytab_error = Some(e.to_string());
None
}
};
if deep {
match identity {
Some(ref id) => match verify_credential(tools, id) {
Ok(()) => status.credential_valid = Some(true),
Err(e) => {
status.credential_valid = Some(false);
status.credential_error = Some(e.to_string());
}
},
None => {
status.credential_error =
Some("skipped: machine credential not readable".to_string());
}
}
}
if let Some(realm) = status.configured_realms.first() {
if let Ok(out) = exec::run_ok(&tools.sssctl, ["domain-status", "--", realm]) {
let trimmed = out.trim();
if !trimmed.is_empty() {
status.sssd_status = Some(trimmed.to_string());
}
}
}
status
}
pub fn verify_credential(tools: &Tools, identity: &MachineIdentity) -> Result<()> {
let dir = private_scratch_dir()?;
let cache = ArmorCache::new(dir.join("verify.ccache"));
let outcome = cache.mint(tools, identity);
cache.destroy(tools);
let _ = std::fs::remove_dir_all(&dir);
outcome
}
fn private_scratch_dir() -> Result<PathBuf> {
let base = Path::new("/tmp");
let pid = std::process::id();
for attempt in 0u32.. {
let dir = base.join(format!("machine-krb-verify-{pid}-{attempt}"));
match DirBuilder::new().mode(0o700).create(&dir) {
Ok(()) => return Ok(dir),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(e) => return Err(e.into()),
}
}
unreachable!("u32 attempt space exhausted");
}