pub const HEADER_LEN: usize = 64;
pub const PROTOCOL_ID: [u8; 4] = *b"\xFESMB";
pub const OFF_PROTOCOL_ID: usize = 0;
pub const OFF_STRUCTURE_SIZE: usize = 4;
pub const OFF_CREDIT_CHARGE: usize = 6;
pub const OFF_STATUS: usize = 8;
pub const OFF_COMMAND: usize = 12;
pub const OFF_CREDITS: usize = 14;
pub const OFF_FLAGS: usize = 16;
pub const OFF_NEXT_COMMAND: usize = 20;
pub const OFF_MESSAGE_ID: usize = 24;
pub const OFF_RESERVED: usize = 32;
pub const OFF_TREE_ID: usize = 36;
pub const OFF_SESSION_ID: usize = 40;
pub const OFF_SIGNATURE: usize = 48;
pub const FLAGS_SERVER_TO_REDIR: u32 = 0x0000_0001;
pub const FLAGS_ASYNC_COMMAND: u32 = 0x0000_0002;
pub const FLAGS_RELATED_OPERATIONS: u32 = 0x0000_0004;
pub const FLAGS_SIGNED: u32 = 0x0000_0008;
pub mod command {
pub const NEGOTIATE: u16 = 0x0000;
pub const SESSION_SETUP: u16 = 0x0001;
pub const LOGOFF: u16 = 0x0002;
pub const TREE_CONNECT: u16 = 0x0003;
pub const TREE_DISCONNECT: u16 = 0x0004;
pub const CREATE: u16 = 0x0005;
pub const CLOSE: u16 = 0x0006;
pub const FLUSH: u16 = 0x0007;
pub const READ: u16 = 0x0008;
pub const WRITE: u16 = 0x0009;
pub const LOCK: u16 = 0x000A;
pub const IOCTL: u16 = 0x000B;
pub const CANCEL: u16 = 0x000C;
pub const ECHO: u16 = 0x000D;
pub const QUERY_DIRECTORY: u16 = 0x000E;
pub const CHANGE_NOTIFY: u16 = 0x000F;
pub const QUERY_INFO: u16 = 0x0010;
pub const SET_INFO: u16 = 0x0011;
pub const OPLOCK_BREAK: u16 = 0x0012;
}
pub mod status {
pub const SUCCESS: u32 = 0x0000_0000;
pub const INVALID_INFO_CLASS: u32 = 0xC000_0003;
pub const INVALID_PARAMETER: u32 = 0xC000_000D;
pub const INVALID_DEVICE_REQUEST: u32 = 0xC000_0010;
pub const END_OF_FILE: u32 = 0xC000_0011;
pub const MORE_PROCESSING_REQUIRED: u32 = 0xC000_0016;
pub const ACCESS_DENIED: u32 = 0xC000_0022;
pub const DATA_ERROR: u32 = 0xC000_003E;
pub const LOGON_FAILURE: u32 = 0xC000_006D;
pub const INSUFFICIENT_RESOURCES: u32 = 0xC000_009A;
pub const NOT_SUPPORTED: u32 = 0xC000_00BB;
pub const NETWORK_NAME_DELETED: u32 = 0xC000_00C9;
pub const INTERNAL_ERROR: u32 = 0xC000_00E5;
pub const TOO_MANY_OPENED_FILES: u32 = 0xC000_011F;
pub const FILE_CLOSED: u32 = 0xC000_0128;
pub const USER_SESSION_DELETED: u32 = 0xC000_0203;
pub const NO_MORE_FILES: u32 = 0x8000_0006;
}
pub const NBSS_SESSION_MESSAGE: u8 = 0x00;
pub const NBSS_SESSION_REQUEST: u8 = 0x81;
pub const NBSS_POSITIVE_RESPONSE: u8 = 0x82;
pub const NBSS_KEEPALIVE: u8 = 0x85;
pub const NBSS_MAX_LEN: usize = 0x00FF_FFFF;
pub fn frame(message: &[u8]) -> Vec<u8> {
assert!(
message.len() <= NBSS_MAX_LEN,
"an SMB2 message longer than 24 bits cannot be framed"
);
let len = message.len() as u32;
let mut out = Vec::with_capacity(4 + message.len());
out.push(NBSS_SESSION_MESSAGE);
out.extend_from_slice(&len.to_be_bytes()[1..4]);
out.extend_from_slice(message);
out
}
pub fn parse_frame_header(header: [u8; 4]) -> (u8, usize) {
let len = u32::from_be_bytes([0, header[1], header[2], header[3]]) as usize;
(header[0], len)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RequestHeader {
pub credit_charge: u16,
pub command: u16,
pub credit_request: u16,
pub flags: u32,
pub next_command: u32,
pub message_id: u64,
pub tree_id: u32,
pub session_id: u64,
}
impl RequestHeader {
pub fn parse(msg: &[u8]) -> Option<Self> {
if msg.len() < HEADER_LEN || msg[OFF_PROTOCOL_ID..OFF_PROTOCOL_ID + 4] != PROTOCOL_ID {
return None;
}
Some(Self {
credit_charge: le16(msg, OFF_CREDIT_CHARGE),
command: le16(msg, OFF_COMMAND),
credit_request: le16(msg, OFF_CREDITS),
flags: le32(msg, OFF_FLAGS),
next_command: le32(msg, OFF_NEXT_COMMAND),
message_id: le64(msg, OFF_MESSAGE_ID),
tree_id: le32(msg, OFF_TREE_ID),
session_id: le64(msg, OFF_SESSION_ID),
})
}
pub fn is_related(&self) -> bool {
self.flags & FLAGS_RELATED_OPERATIONS != 0
}
}
pub const MAX_CREDIT_GRANT: u16 = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResponseHeader {
pub command: u16,
pub status: u32,
pub message_id: u64,
pub credit_charge: u16,
pub credits: u16,
pub flags: u32,
pub next_command: u32,
pub tree_id: u32,
pub session_id: u64,
}
impl ResponseHeader {
pub fn for_request(req: &RequestHeader, status: u32) -> Self {
Self {
command: req.command,
status,
message_id: req.message_id,
credit_charge: req.credit_charge,
credits: req.credit_request.clamp(1, MAX_CREDIT_GRANT),
flags: req.flags & FLAGS_RELATED_OPERATIONS,
next_command: 0,
tree_id: req.tree_id,
session_id: req.session_id,
}
}
pub fn with_status(mut self, status: u32) -> Self {
self.status = status;
self
}
pub fn with_session_id(mut self, session_id: u64) -> Self {
self.session_id = session_id;
self
}
pub fn with_tree_id(mut self, tree_id: u32) -> Self {
self.tree_id = tree_id;
self
}
pub fn encode(&self) -> [u8; HEADER_LEN] {
let mut h = [0u8; HEADER_LEN];
h[OFF_PROTOCOL_ID..OFF_PROTOCOL_ID + 4].copy_from_slice(&PROTOCOL_ID);
h[OFF_STRUCTURE_SIZE..OFF_STRUCTURE_SIZE + 2].copy_from_slice(&64u16.to_le_bytes());
h[OFF_CREDIT_CHARGE..OFF_CREDIT_CHARGE + 2]
.copy_from_slice(&self.credit_charge.to_le_bytes());
h[OFF_STATUS..OFF_STATUS + 4].copy_from_slice(&self.status.to_le_bytes());
h[OFF_COMMAND..OFF_COMMAND + 2].copy_from_slice(&self.command.to_le_bytes());
h[OFF_CREDITS..OFF_CREDITS + 2].copy_from_slice(&self.credits.to_le_bytes());
let flags = self.flags | FLAGS_SERVER_TO_REDIR;
h[OFF_FLAGS..OFF_FLAGS + 4].copy_from_slice(&flags.to_le_bytes());
h[OFF_NEXT_COMMAND..OFF_NEXT_COMMAND + 4].copy_from_slice(&self.next_command.to_le_bytes());
h[OFF_MESSAGE_ID..OFF_MESSAGE_ID + 8].copy_from_slice(&self.message_id.to_le_bytes());
h[OFF_TREE_ID..OFF_TREE_ID + 4].copy_from_slice(&self.tree_id.to_le_bytes());
h[OFF_SESSION_ID..OFF_SESSION_ID + 8].copy_from_slice(&self.session_id.to_le_bytes());
h
}
fn message(&self, body: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(HEADER_LEN + body.len());
out.extend_from_slice(&self.encode());
out.extend_from_slice(body);
out
}
}
pub fn chain(responses: Vec<Vec<u8>>) -> Vec<u8> {
let count = responses.len();
let mut out = Vec::new();
for (i, mut response) in responses.into_iter().enumerate() {
if i + 1 < count {
while response.len() % 8 != 0 {
response.push(0);
}
let next = response.len() as u32;
response[OFF_NEXT_COMMAND..OFF_NEXT_COMMAND + 4].copy_from_slice(&next.to_le_bytes());
}
out.extend_from_slice(&response);
}
out
}
pub fn error_response(hdr: &ResponseHeader) -> Vec<u8> {
let mut body = Vec::with_capacity(9);
body.extend_from_slice(&9u16.to_le_bytes()); body.push(0); body.push(0); body.extend_from_slice(&0u32.to_le_bytes()); body.push(0); hdr.message(&body)
}
pub fn empty_response(hdr: &ResponseHeader) -> Vec<u8> {
hdr.message(&[4, 0, 0, 0])
}
#[derive(Debug, Clone)]
pub struct NegotiateParams {
pub dialect: u16,
pub server_guid: [u8; 16],
pub capabilities: u32,
pub max_transact_size: u32,
pub max_read_size: u32,
pub max_write_size: u32,
pub system_time: u64,
pub security_blob: Vec<u8>,
}
pub fn negotiate_response(hdr: &ResponseHeader, p: &NegotiateParams) -> Vec<u8> {
let mut body = Vec::with_capacity(64 + p.security_blob.len());
body.extend_from_slice(&65u16.to_le_bytes()); body.extend_from_slice(&0x0001u16.to_le_bytes()); body.extend_from_slice(&p.dialect.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.extend_from_slice(&p.server_guid); body.extend_from_slice(&p.capabilities.to_le_bytes());
body.extend_from_slice(&p.max_transact_size.to_le_bytes());
body.extend_from_slice(&p.max_read_size.to_le_bytes());
body.extend_from_slice(&p.max_write_size.to_le_bytes());
body.extend_from_slice(&p.system_time.to_le_bytes()); body.extend_from_slice(&0u64.to_le_bytes()); let (offset, len) = if p.security_blob.is_empty() {
(0u16, 0u16)
} else {
((HEADER_LEN + 64) as u16, p.security_blob.len() as u16)
};
body.extend_from_slice(&offset.to_le_bytes()); body.extend_from_slice(&len.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); debug_assert_eq!(body.len(), 64);
if p.security_blob.is_empty() {
body.push(0); } else {
body.extend_from_slice(&p.security_blob);
}
hdr.message(&body)
}
pub const SESSION_FLAG_IS_GUEST: u16 = 0x0001;
pub const SESSION_FLAG_IS_NULL: u16 = 0x0002;
pub fn session_setup_response(hdr: &ResponseHeader, session_flags: u16, blob: &[u8]) -> Vec<u8> {
let mut body = Vec::with_capacity(8 + blob.len().max(1));
body.extend_from_slice(&9u16.to_le_bytes()); body.extend_from_slice(&session_flags.to_le_bytes());
let offset = if blob.is_empty() {
0u16
} else {
(HEADER_LEN + 8) as u16
};
body.extend_from_slice(&offset.to_le_bytes()); body.extend_from_slice(&(blob.len() as u16).to_le_bytes()); if blob.is_empty() {
body.push(0);
} else {
body.extend_from_slice(blob);
}
hdr.message(&body)
}
pub const SHARE_TYPE_DISK: u8 = 0x01;
pub const SHARE_TYPE_PIPE: u8 = 0x02;
pub fn tree_connect_response(hdr: &ResponseHeader, share_type: u8, maximal_access: u32) -> Vec<u8> {
let mut body = Vec::with_capacity(16);
body.extend_from_slice(&16u16.to_le_bytes()); body.push(share_type);
body.push(0); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&maximal_access.to_le_bytes()); hdr.message(&body)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FileMeta {
pub is_directory: bool,
pub size: u64,
pub time: u64,
pub file_index: u64,
}
pub const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x0000_0010;
pub const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x0000_0020;
pub const FILE_ATTRIBUTE_NORMAL: u32 = 0x0000_0080;
impl FileMeta {
pub fn attributes(&self) -> u32 {
if self.is_directory {
FILE_ATTRIBUTE_DIRECTORY
} else {
FILE_ATTRIBUTE_NORMAL
}
}
pub fn allocation_size(&self) -> u64 {
self.size.div_ceil(4096) * 4096
}
fn times(&self, out: &mut Vec<u8>) {
for _ in 0..4 {
out.extend_from_slice(&self.time.to_le_bytes());
}
}
}
pub const FILE_OPENED: u32 = 0x0000_0001;
pub const FILE_CREATED: u32 = 0x0000_0002;
pub fn create_response(
hdr: &ResponseHeader,
file_id: &[u8; 16],
meta: &FileMeta,
create_action: u32,
) -> Vec<u8> {
let mut body = Vec::with_capacity(89);
body.extend_from_slice(&89u16.to_le_bytes()); body.push(0); body.push(0); body.extend_from_slice(&create_action.to_le_bytes());
meta.times(&mut body); body.extend_from_slice(&meta.allocation_size().to_le_bytes());
body.extend_from_slice(&meta.size.to_le_bytes()); body.extend_from_slice(&meta.attributes().to_le_bytes());
body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(file_id);
body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.push(0); hdr.message(&body)
}
pub const CLOSE_FLAG_POSTQUERY_ATTRIB: u16 = 0x0001;
pub fn close_response(hdr: &ResponseHeader, meta: Option<&FileMeta>) -> Vec<u8> {
let mut body = Vec::with_capacity(60);
body.extend_from_slice(&60u16.to_le_bytes()); match meta {
Some(meta) => {
body.extend_from_slice(&CLOSE_FLAG_POSTQUERY_ATTRIB.to_le_bytes());
body.extend_from_slice(&0u32.to_le_bytes()); meta.times(&mut body);
body.extend_from_slice(&meta.allocation_size().to_le_bytes());
body.extend_from_slice(&meta.size.to_le_bytes());
body.extend_from_slice(&meta.attributes().to_le_bytes());
}
None => body.resize(60, 0),
}
hdr.message(&body)
}
pub const READ_DATA_OFFSET: u8 = (HEADER_LEN + 16) as u8;
pub fn read_response(hdr: &ResponseHeader, data: &[u8]) -> Vec<u8> {
let mut body = Vec::with_capacity(16 + data.len());
body.extend_from_slice(&17u16.to_le_bytes()); body.push(READ_DATA_OFFSET); body.push(0); body.extend_from_slice(&(data.len() as u32).to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(data);
if data.is_empty() {
body.push(0);
}
hdr.message(&body)
}
pub fn write_response(hdr: &ResponseHeader, count: u32) -> Vec<u8> {
let mut body = Vec::with_capacity(17);
body.extend_from_slice(&17u16.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.extend_from_slice(&count.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.push(0); hdr.message(&body)
}
fn output_buffer_response(hdr: &ResponseHeader, buffer: &[u8]) -> Vec<u8> {
let mut body = Vec::with_capacity(8 + buffer.len().max(1));
body.extend_from_slice(&9u16.to_le_bytes()); body.extend_from_slice(&((HEADER_LEN + 8) as u16).to_le_bytes()); body.extend_from_slice(&(buffer.len() as u32).to_le_bytes()); if buffer.is_empty() {
body.push(0);
} else {
body.extend_from_slice(buffer);
}
hdr.message(&body)
}
pub fn query_info_response(hdr: &ResponseHeader, buffer: &[u8]) -> Vec<u8> {
output_buffer_response(hdr, buffer)
}
pub fn query_directory_response(hdr: &ResponseHeader, entries: &[u8]) -> Vec<u8> {
output_buffer_response(hdr, entries)
}
pub const INFO_FILE: u8 = 0x01;
pub const INFO_FILESYSTEM: u8 = 0x02;
pub const INFO_SECURITY: u8 = 0x03;
pub const INFO_QUOTA: u8 = 0x04;
pub mod file_class {
pub const DIRECTORY: u8 = 1;
pub const FULL_DIRECTORY: u8 = 2;
pub const BOTH_DIRECTORY: u8 = 3;
pub const BASIC: u8 = 4;
pub const STANDARD: u8 = 5;
pub const INTERNAL: u8 = 6;
pub const EA: u8 = 7;
pub const ACCESS: u8 = 8;
pub const NAMES: u8 = 12;
pub const POSITION: u8 = 14;
pub const MODE: u8 = 16;
pub const ALIGNMENT: u8 = 17;
pub const ALL: u8 = 18;
pub const STREAM: u8 = 22;
pub const NETWORK_OPEN: u8 = 34;
pub const ATTRIBUTE_TAG: u8 = 35;
pub const ID_BOTH_DIRECTORY: u8 = 37;
pub const ID_FULL_DIRECTORY: u8 = 38;
}
pub mod fs_class {
pub const VOLUME: u8 = 1;
pub const SIZE: u8 = 3;
pub const DEVICE: u8 = 4;
pub const ATTRIBUTE: u8 = 5;
pub const FULL_SIZE: u8 = 7;
pub const SECTOR_SIZE: u8 = 11;
}
pub fn file_class_needs_size(class: u8) -> bool {
matches!(
class,
file_class::STANDARD | file_class::ALL | file_class::NETWORK_OPEN | file_class::STREAM
)
}
pub fn file_info(class: u8, meta: &FileMeta) -> Option<Vec<u8>> {
let mut out = Vec::new();
match class {
file_class::BASIC => basic_info(meta, &mut out),
file_class::STANDARD => standard_info(meta, &mut out),
file_class::INTERNAL => out.extend_from_slice(&meta.file_index.to_le_bytes()),
file_class::EA => out.extend_from_slice(&0u32.to_le_bytes()),
file_class::ACCESS => out.extend_from_slice(&0x001F_01FFu32.to_le_bytes()),
file_class::POSITION => out.extend_from_slice(&0u64.to_le_bytes()),
file_class::MODE => out.extend_from_slice(&0u32.to_le_bytes()),
file_class::ALIGNMENT => out.extend_from_slice(&0u32.to_le_bytes()),
file_class::ALL => {
basic_info(meta, &mut out); standard_info(meta, &mut out); out.extend_from_slice(&meta.file_index.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0x001F_01FFu32.to_le_bytes()); out.extend_from_slice(&0u64.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); }
file_class::STREAM => {
if meta.is_directory {
return Some(Vec::new());
}
let name: Vec<u8> = utf16le("::$DATA");
out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&(name.len() as u32).to_le_bytes());
out.extend_from_slice(&meta.size.to_le_bytes());
out.extend_from_slice(&meta.allocation_size().to_le_bytes());
out.extend_from_slice(&name);
}
file_class::NETWORK_OPEN => {
meta.times(&mut out);
out.extend_from_slice(&meta.allocation_size().to_le_bytes());
out.extend_from_slice(&meta.size.to_le_bytes());
out.extend_from_slice(&meta.attributes().to_le_bytes());
out.extend_from_slice(&0u32.to_le_bytes()); }
file_class::ATTRIBUTE_TAG => {
out.extend_from_slice(&meta.attributes().to_le_bytes());
out.extend_from_slice(&0u32.to_le_bytes()); }
_ => return None,
}
Some(out)
}
fn basic_info(meta: &FileMeta, out: &mut Vec<u8>) {
meta.times(out);
out.extend_from_slice(&meta.attributes().to_le_bytes());
out.extend_from_slice(&0u32.to_le_bytes()); }
fn standard_info(meta: &FileMeta, out: &mut Vec<u8>) {
out.extend_from_slice(&meta.allocation_size().to_le_bytes());
out.extend_from_slice(&meta.size.to_le_bytes()); out.extend_from_slice(&1u32.to_le_bytes()); out.push(0); out.push(meta.is_directory as u8); out.extend_from_slice(&0u16.to_le_bytes()); }
const FS_TOTAL_UNITS: u64 = 262_144;
const FS_FREE_UNITS: u64 = 131_072;
const FS_SECTORS_PER_UNIT: u32 = 8;
const FS_BYTES_PER_SECTOR: u32 = 512;
const FS_SERIAL: u32 = 0x4E47_5342;
pub fn fs_info(class: u8, volume_label: &str, created: u64) -> Option<Vec<u8>> {
let mut out = Vec::new();
match class {
fs_class::VOLUME => {
let label = utf16le(volume_label);
out.extend_from_slice(&created.to_le_bytes()); out.extend_from_slice(&FS_SERIAL.to_le_bytes());
out.extend_from_slice(&(label.len() as u32).to_le_bytes());
out.push(0); out.push(0); out.extend_from_slice(&label);
}
fs_class::SIZE => {
out.extend_from_slice(&FS_TOTAL_UNITS.to_le_bytes());
out.extend_from_slice(&FS_FREE_UNITS.to_le_bytes());
out.extend_from_slice(&FS_SECTORS_PER_UNIT.to_le_bytes());
out.extend_from_slice(&FS_BYTES_PER_SECTOR.to_le_bytes());
}
fs_class::DEVICE => {
out.extend_from_slice(&0x0000_0007u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); }
fs_class::ATTRIBUTE => {
let name = utf16le("NTFS");
out.extend_from_slice(&0x0000_0007u32.to_le_bytes());
out.extend_from_slice(&255u32.to_le_bytes()); out.extend_from_slice(&(name.len() as u32).to_le_bytes());
out.extend_from_slice(&name);
}
fs_class::FULL_SIZE => {
out.extend_from_slice(&FS_TOTAL_UNITS.to_le_bytes());
out.extend_from_slice(&FS_FREE_UNITS.to_le_bytes()); out.extend_from_slice(&FS_FREE_UNITS.to_le_bytes()); out.extend_from_slice(&FS_SECTORS_PER_UNIT.to_le_bytes());
out.extend_from_slice(&FS_BYTES_PER_SECTOR.to_le_bytes());
}
fs_class::SECTOR_SIZE => {
let sector = FS_BYTES_PER_SECTOR;
out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); }
_ => return None,
}
Some(out)
}
pub fn directory_class_supported(class: u8) -> bool {
matches!(
class,
file_class::DIRECTORY
| file_class::FULL_DIRECTORY
| file_class::BOTH_DIRECTORY
| file_class::NAMES
| file_class::ID_BOTH_DIRECTORY
| file_class::ID_FULL_DIRECTORY
)
}
pub fn directory_entry(class: u8, name: &str, meta: &FileMeta) -> Vec<u8> {
let name = utf16le(name);
let mut e = Vec::with_capacity(104 + name.len());
e.extend_from_slice(&0u32.to_le_bytes()); e.extend_from_slice(&0u32.to_le_bytes()); if class == file_class::NAMES {
e.extend_from_slice(&(name.len() as u32).to_le_bytes());
e.extend_from_slice(&name);
return e;
}
meta.times(&mut e);
e.extend_from_slice(&meta.size.to_le_bytes()); e.extend_from_slice(&meta.allocation_size().to_le_bytes());
e.extend_from_slice(&meta.attributes().to_le_bytes());
e.extend_from_slice(&(name.len() as u32).to_le_bytes()); match class {
file_class::DIRECTORY => {}
file_class::FULL_DIRECTORY => e.extend_from_slice(&0u32.to_le_bytes()), file_class::BOTH_DIRECTORY => {
e.extend_from_slice(&0u32.to_le_bytes()); e.push(0); e.push(0); e.extend_from_slice(&[0u8; 24]); }
file_class::ID_BOTH_DIRECTORY => {
e.extend_from_slice(&0u32.to_le_bytes()); e.push(0); e.push(0); e.extend_from_slice(&[0u8; 24]); e.extend_from_slice(&0u16.to_le_bytes()); e.extend_from_slice(&meta.file_index.to_le_bytes()); }
file_class::ID_FULL_DIRECTORY => {
e.extend_from_slice(&0u32.to_le_bytes()); e.extend_from_slice(&0u32.to_le_bytes()); e.extend_from_slice(&meta.file_index.to_le_bytes()); }
_ => {}
}
e.extend_from_slice(&name);
e
}
pub fn join_directory_entries(entries: &[Vec<u8>]) -> Vec<u8> {
let mut out = Vec::new();
for (i, entry) in entries.iter().enumerate() {
let start = out.len();
out.extend_from_slice(entry);
if i + 1 < entries.len() {
while (out.len() - start) % 8 != 0 {
out.push(0);
}
let next = (out.len() - start) as u32;
out[start..start + 4].copy_from_slice(&next.to_le_bytes());
}
}
out
}
pub fn padded_len(entry: &[u8]) -> usize {
entry.len().div_ceil(8) * 8
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChainError {
NotSmb2,
BadNextCommand(RequestHeader),
}
#[derive(Debug, Clone, Copy)]
pub struct ChainedRequest<'a> {
pub header: RequestHeader,
pub message: &'a [u8],
pub next: Option<usize>,
}
pub fn next_in_chain(rest: &[u8]) -> Result<ChainedRequest<'_>, ChainError> {
let header = RequestHeader::parse(rest).ok_or(ChainError::NotSmb2)?;
let next = header.next_command as usize;
if next == 0 {
return Ok(ChainedRequest {
header,
message: rest,
next: None,
});
}
if next < HEADER_LEN || next > rest.len() || !next.is_multiple_of(8) {
return Err(ChainError::BadNextCommand(header));
}
Ok(ChainedRequest {
header,
message: &rest[..next],
next: Some(next),
})
}
pub fn body(message: &[u8]) -> &[u8] {
message.get(HEADER_LEN..).unwrap_or_default()
}
fn buffer(message: &[u8], offset: usize, len: usize) -> Option<&[u8]> {
message.get(offset..offset.checked_add(len)?)
}
fn file_id(body: &[u8], at: usize) -> Option<[u8; 16]> {
body.get(at..at + 16)?.try_into().ok()
}
pub fn parse_negotiate(body: &[u8]) -> Option<Vec<u16>> {
if body.len() < 36 {
return None;
}
let count = le16(body, 2) as usize;
let list = body.get(36..36 + 2 * count)?;
Some(
list.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect(),
)
}
pub fn parse_session_setup(message: &[u8]) -> Option<&[u8]> {
let body = body(message);
if body.len() < 24 {
return None;
}
let offset = le16(body, 12) as usize;
let len = le16(body, 14) as usize;
if len == 0 {
return Some(&[]);
}
buffer(message, offset, len)
}
pub fn parse_tree_connect(message: &[u8]) -> Option<String> {
let body = body(message);
if body.len() < 8 {
return None;
}
let offset = le16(body, 4) as usize;
let len = le16(body, 6) as usize;
from_utf16le(buffer(message, offset, len)?)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateRequest {
pub disposition: u32,
pub options: u32,
pub path: String,
}
pub fn parse_create(message: &[u8]) -> Option<CreateRequest> {
let body = body(message);
if body.len() < 56 {
return None;
}
let disposition = le32(body, 36);
let options = le32(body, 40);
let name_offset = le16(body, 44) as usize;
let name_len = le16(body, 46) as usize;
let name = if name_len == 0 {
String::new()
} else {
from_utf16le(buffer(message, name_offset, name_len)?)?
};
let name = name.replace('\\', "/");
Some(CreateRequest {
disposition,
options,
path: format!("/{}", name.trim_matches('/')),
})
}
pub fn parse_close(body: &[u8]) -> Option<(u16, [u8; 16])> {
if body.len() < 24 {
return None;
}
Some((le16(body, 2), file_id(body, 8)?))
}
pub fn parse_flush(body: &[u8]) -> Option<[u8; 16]> {
if body.len() < 24 {
return None;
}
file_id(body, 8)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReadRequest {
pub length: u32,
pub offset: u64,
pub file_id: [u8; 16],
}
pub fn parse_read(body: &[u8]) -> Option<ReadRequest> {
if body.len() < 48 {
return None;
}
Some(ReadRequest {
length: le32(body, 4),
offset: le64(body, 8),
file_id: file_id(body, 16)?,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WriteRequest<'a> {
pub length: u32,
pub offset: u64,
pub file_id: [u8; 16],
pub data: Option<&'a [u8]>,
}
pub fn parse_write(message: &[u8]) -> Option<WriteRequest<'_>> {
let body = body(message);
if body.len() < 48 {
return None;
}
let data_offset = le16(body, 2) as usize;
let length = le32(body, 4);
Some(WriteRequest {
length,
offset: le64(body, 8),
file_id: file_id(body, 16)?,
data: buffer(message, data_offset, length as usize),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QueryInfoRequest {
pub info_type: u8,
pub class: u8,
pub output_len: u32,
pub file_id: [u8; 16],
}
pub fn parse_query_info(body: &[u8]) -> Option<QueryInfoRequest> {
if body.len() < 40 {
return None;
}
Some(QueryInfoRequest {
info_type: body[2],
class: body[3],
output_len: le32(body, 4),
file_id: file_id(body, 24)?,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryDirectoryRequest {
pub class: u8,
pub flags: u8,
pub file_id: [u8; 16],
pub pattern: String,
pub output_len: u32,
}
pub fn parse_query_directory(message: &[u8]) -> Option<QueryDirectoryRequest> {
let body = body(message);
if body.len() < 32 {
return None;
}
let name_offset = le16(body, 24) as usize;
let name_len = le16(body, 26) as usize;
let pattern = if name_len == 0 {
"*".to_string()
} else {
from_utf16le(buffer(message, name_offset, name_len)?)?
};
Some(QueryDirectoryRequest {
class: body[2],
flags: body[3],
file_id: file_id(body, 8)?,
pattern,
output_len: le32(body, 28),
})
}
pub fn utf16le(s: &str) -> Vec<u8> {
s.encode_utf16().flat_map(|c| c.to_le_bytes()).collect()
}
pub fn from_utf16le(bytes: &[u8]) -> Option<String> {
if !bytes.len().is_multiple_of(2) {
return None;
}
let units: Vec<u16> = bytes
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.take_while(|&c| c != 0)
.collect();
String::from_utf16(&units).ok()
}
pub fn filetime_from_unix(secs: i64, nanos: u32) -> u64 {
const EPOCH_DIFF_SECS: i64 = 11_644_473_600;
let secs = secs.saturating_add(EPOCH_DIFF_SECS).max(0) as u64;
secs.saturating_mul(10_000_000) + (nanos / 100) as u64
}
pub fn le16(b: &[u8], off: usize) -> u16 {
u16::from_le_bytes([b[off], b[off + 1]])
}
pub fn le32(b: &[u8], off: usize) -> u32 {
u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
pub fn le64(b: &[u8], off: usize) -> u64 {
let mut a = [0u8; 8];
a.copy_from_slice(&b[off..off + 8]);
u64::from_le_bytes(a)
}