use crate::ndr::{NdrDecoder, NdrEncoder};
use crate::transport::SmbPipe;
use crate::{Result, Syntax};
use smb2_client::SmbClient;
pub fn srvsvc_syntax() -> Syntax {
Syntax::new("4b324fc8-1670-01d3-1278-5a47bf6ee188", 3, 0)
}
pub mod opnum {
pub const NETR_SESSION_ENUM: u16 = 12;
}
const SESSION_LEVEL_10: u32 = 10;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Session {
pub client: String,
pub user: String,
}
pub fn encode_session_enum() -> Vec<u8> {
let mut e = NdrEncoder::new();
e.null_ptr(); e.null_ptr(); e.null_ptr();
e.u32(SESSION_LEVEL_10); e.u32(SESSION_LEVEL_10); e.referent(); e.u32(0); e.null_ptr();
e.u32(0xFFFF_FFFF); e.null_ptr(); e.into_bytes()
}
pub fn decode_session_enum(stub: &[u8]) -> Result<(Vec<Session>, u32, u32)> {
let mut d = NdrDecoder::new(stub);
let _level = d.u32()?; let _tag = d.u32()?; let container_ref = d.u32()?; let mut sessions = Vec::new();
if container_ref != 0 {
let entries_read = d.u32()? as usize;
let buffer_ref = d.u32()?;
if buffer_ref != 0 {
let _max = d.u32()?; let mut refs = Vec::with_capacity(entries_read);
for _ in 0..entries_read {
let cname_ref = d.u32()?;
let user_ref = d.u32()?;
let _time = d.u32()?;
let _idle = d.u32()?;
refs.push((cname_ref, user_ref));
}
for (cname_ref, user_ref) in refs {
let client = if cname_ref != 0 {
d.conformant_varying_wstr()?
} else {
String::new()
};
let user = if user_ref != 0 {
d.conformant_varying_wstr()?
} else {
String::new()
};
sessions.push(Session { client, user });
}
}
}
let total_entries = d.u32().unwrap_or(sessions.len() as u32);
let resume_ref = d.u32().unwrap_or(0);
if resume_ref != 0 {
let _resume = d.u32();
}
let ret = d.u32().unwrap_or(0);
Ok((sessions, total_entries, ret))
}
pub struct SrvsvcClient<'a> {
pipe: SmbPipe<'a>,
}
impl<'a> SrvsvcClient<'a> {
pub async fn bind(client: &'a mut SmbClient, file_id: [u8; 16]) -> Result<Self> {
let mut pipe = SmbPipe::new(client, file_id);
pipe.bind(srvsvc_syntax()).await?;
Ok(SrvsvcClient { pipe })
}
pub async fn enum_sessions(&mut self) -> Result<(Vec<Session>, u32)> {
let resp = self
.pipe
.call(opnum::NETR_SESSION_ENUM, &encode_session_enum())
.await?;
let (sessions, _total, ret) = decode_session_enum(&resp)?;
Ok((sessions, ret))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_selects_level_10_with_null_names() {
let stub = encode_session_enum();
assert_eq!(&stub[0..4], &0u32.to_le_bytes());
assert_eq!(&stub[4..8], &0u32.to_le_bytes());
assert_eq!(&stub[8..12], &0u32.to_le_bytes());
assert_eq!(u32::from_le_bytes(stub[12..16].try_into().unwrap()), 10); assert_eq!(u32::from_le_bytes(stub[16..20].try_into().unwrap()), 10); }
#[test]
fn decodes_one_session_from_a_handbuilt_reply() {
fn wstr(out: &mut Vec<u8>, s: &str) {
let units: Vec<u16> = s.encode_utf16().chain(std::iter::once(0)).collect();
out.extend_from_slice(&(units.len() as u32).to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&(units.len() as u32).to_le_bytes()); for u in units {
out.extend_from_slice(&u.to_le_bytes());
}
while out.len() % 4 != 0 {
out.push(0);
}
}
let mut r = Vec::new();
r.extend_from_slice(&10u32.to_le_bytes()); r.extend_from_slice(&10u32.to_le_bytes()); r.extend_from_slice(&0x2_0000u32.to_le_bytes()); r.extend_from_slice(&1u32.to_le_bytes()); r.extend_from_slice(&0x2_0004u32.to_le_bytes()); r.extend_from_slice(&1u32.to_le_bytes()); r.extend_from_slice(&0x2_0008u32.to_le_bytes()); r.extend_from_slice(&0x2_000cu32.to_le_bytes()); r.extend_from_slice(&123u32.to_le_bytes()); r.extend_from_slice(&4u32.to_le_bytes()); wstr(&mut r, "WKSTN01"); wstr(&mut r, "administrator"); r.extend_from_slice(&1u32.to_le_bytes()); r.extend_from_slice(&0u32.to_le_bytes()); r.extend_from_slice(&0u32.to_le_bytes());
let (sessions, total, ret) = decode_session_enum(&r).unwrap();
assert_eq!(ret, 0);
assert_eq!(total, 1);
assert_eq!(
sessions,
vec![Session {
client: "WKSTN01".into(),
user: "administrator".into()
}]
);
}
#[test]
fn empty_reply_is_not_a_panic() {
for cut in 0..24 {
let _ = decode_session_enum(&vec![0u8; cut]);
}
}
}