use log::{debug, error, trace, warn};
use smb2_client::{SmbClient, SmbError};
pub use smb2_client::msg::DirEntry;
const OBJECT_PATH_NOT_FOUND: u32 = 0xC000_003A;
pub enum SmbAuth<'a> {
Password(&'a str),
Hash(&'a [u8; 16]),
Kerberos { gss_blob: &'a [u8], session_key: &'a [u8; 16] },
}
pub fn ipc_unc(host: &str) -> String {
format!(r"\\{host}\IPC$")
}
pub fn sysvol_unc(host: &str) -> String {
format!(r"\\{host}\SYSVOL")
}
pub async fn connect_authenticated(
host: &str,
domain: &str,
user: &str,
auth: SmbAuth<'_>,
) -> anyhow::Result<SmbClient> {
let target = format!("{host}:445");
trace!("[{host}] SMB connecting to {target}");
let mut smb = SmbClient::connect(&target).await.map_err(|e| {
error!("[{host}] SMB connect failed: {e}");
anyhow::anyhow!("connect: {e}")
})?;
debug!("[{host}] SMB connected, negotiate done");
match auth {
SmbAuth::Hash(nt) => {
trace!("[{host}] SMB SESSION_SETUP as {domain}\\{user} (pass the hash)");
smb.login_hash(host, domain, user, nt).await.map_err(|e| {
error!("[{host}] SMB auth failed for {domain}\\{user} (pass the hash): {e}");
anyhow::anyhow!("auth(PTH): {e}")
})?;
}
SmbAuth::Password(password) => {
trace!("[{host}] SMB SESSION_SETUP as {domain}\\{user} (password)");
smb.login(host, domain, user, password).await.map_err(|e| {
error!("[{host}] SMB auth failed for {domain}\\{user}: {e}");
anyhow::anyhow!("auth: {e}")
})?;
}
SmbAuth::Kerberos { gss_blob, session_key } => {
trace!("[{host}] SMB SESSION_SETUP with Kerberos (pass the ticket)");
smb.login_kerberos(gss_blob, session_key).await.map_err(|e| {
error!("[{host}] SMB Kerberos auth failed: {e}");
anyhow::anyhow!("auth(krb): {e}")
})?;
}
}
debug!("[{host}] SMB authenticated as {domain}\\{user}");
Ok(smb)
}
pub async fn tree_connect(smb: &mut SmbClient, host: &str, unc: &str) -> anyhow::Result<()> {
trace!("[{host}] SMB tree connect {unc}");
smb.tree_connect(unc).await.map_err(|e| {
error!("[{host}] SMB tree connect to {unc} failed: {e}");
anyhow::anyhow!("tree connect {unc}: {e}")
})?;
debug!("[{host}] tree connected: {unc}");
Ok(())
}
pub async fn connect_ipc(
host: &str,
domain: &str,
user: &str,
auth: SmbAuth<'_>,
) -> anyhow::Result<SmbClient> {
let mut smb = connect_authenticated(host, domain, user, auth).await?;
tree_connect(&mut smb, host, &ipc_unc(host)).await?;
debug!("[{host}] IPC$ ready, RPC pipes available");
Ok(smb)
}
pub async fn connect_sysvol(
host: &str,
domain: &str,
user: &str,
auth: SmbAuth<'_>,
) -> anyhow::Result<SmbClient> {
let mut smb = connect_authenticated(host, domain, user, auth).await?;
tree_connect(&mut smb, host, &sysvol_unc(host)).await?;
debug!("[{host}] SYSVOL ready, GPO files readable");
Ok(smb)
}
pub async fn open_rpc_pipe(
smb: &mut SmbClient,
host: &str,
pipe: &str,
) -> anyhow::Result<[u8; 16]> {
trace!("[{host}] opening RPC pipe {pipe}");
match smb.open_pipe(pipe).await {
Ok(file_id) => {
debug!("[{host}] RPC pipe {pipe} opened");
Ok(file_id)
}
Err(e) => {
warn!("[{host}] cannot open RPC pipe {pipe}: {e}");
Err(anyhow::anyhow!("{pipe}: {e}"))
}
}
}
pub async fn list_dir(smb: &mut SmbClient, host: &str, path: &str) -> anyhow::Result<Vec<DirEntry>> {
let shown = if path.is_empty() { r"\" } else { path };
trace!("[{host}] SMB list dir {shown}");
smb.list_directory(path).await.map_err(|e| {
warn!("[{host}] cannot list {shown}: {e}");
anyhow::anyhow!("list {shown}: {e}")
})
}
pub async fn try_read_file(
smb: &mut SmbClient,
host: &str,
path: &str,
) -> anyhow::Result<Option<Vec<u8>>> {
use smb2_client::status;
trace!("[{host}] SMB read file {path}");
match smb.read_file(path).await {
Ok(bytes) => Ok(Some(bytes)),
Err(SmbError::Status(code, _))
if code == status::OBJECT_NAME_NOT_FOUND || code == OBJECT_PATH_NOT_FOUND =>
{
trace!("[{host}] {path} not present, skipping");
Ok(None)
}
Err(e) => {
warn!("[{host}] cannot read {path}: {e}");
Err(anyhow::anyhow!("read {path}: {e}"))
}
}
}
pub fn nt_hash_from_str(raw: &str) -> Option<[u8; 16]> {
let nt = raw.trim().rsplit(':').next().unwrap_or(raw).trim();
if nt.len() != 32 || !nt.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let mut out = [0u8; 16];
for (i, byte) in out.iter_mut().enumerate() {
*byte = u8::from_str_radix(&nt[i * 2..i * 2 + 2], 16).ok()?;
}
Some(out)
}