smb_msg/
header.rs

1use std::io::Cursor;
2
3use binrw::prelude::*;
4use modular_bitfield::prelude::*;
5
6#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
7#[brw(repr(u16))]
8pub enum Command {
9    Negotiate = 0,
10    SessionSetup = 1,
11    Logoff = 2,
12    TreeConnect = 3,
13    TreeDisconnect = 4,
14    Create = 5,
15    Close = 6,
16    Flush = 7,
17    Read = 8,
18    Write = 9,
19    Lock = 0xA,
20    Ioctl = 0xB,
21    Cancel = 0xC,
22    Echo = 0xD,
23    QueryDirectory = 0xE,
24    ChangeNotify = 0xF,
25    QueryInfo = 0x10,
26    SetInfo = 0x11,
27    OplockBreak = 0x12,
28    ServerToClientNotification = 0x13,
29}
30
31impl std::fmt::Display for Command {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        let message_as_string = match self {
34            Command::Negotiate => "Negotiate",
35            Command::SessionSetup => "Session Setup",
36            Command::Logoff => "Logoff",
37            Command::TreeConnect => "Tree Connect",
38            Command::TreeDisconnect => "Tree Disconnect",
39            Command::Create => "Create",
40            Command::Close => "Close",
41            Command::Flush => "Flush",
42            Command::Read => "Read",
43            Command::Write => "Write",
44            Command::Lock => "Lock",
45            Command::Ioctl => "Ioctl",
46            Command::Cancel => "Cancel",
47            Command::Echo => "Echo",
48            Command::QueryDirectory => "Query Directory",
49            Command::ChangeNotify => "Change Notify",
50            Command::QueryInfo => "Query Info",
51            Command::SetInfo => "Set Info",
52            Command::OplockBreak => "Oplock Break",
53            Command::ServerToClientNotification => "Server to Client Notification",
54        };
55        write!(f, "{} ({:#x})", message_as_string, *self as u16)
56    }
57}
58
59macro_rules! make_status {
60    (
61        $($name:ident = $value:literal: $description:literal, )+
62    ) => {
63
64/// NT Status codes.
65#[binrw::binrw]
66#[derive(Debug, PartialEq, Eq, Clone, Copy)]
67#[repr(u32)]
68#[brw(repr(u32))]
69pub enum Status {
70    $(
71        $name = $value,
72    )+
73}
74
75impl std::fmt::Display for Status {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        let message_as_string = match self {
78            $(
79                Status::$name => $description,
80            )+
81        };
82        write!(f, "{} ({:#x})", message_as_string, *self as u32)
83    }
84}
85
86impl Status {
87    // Consts for easier status code as u32 access.
88    paste::paste! {
89        $(
90            #[doc = concat!("`", stringify!($name), "` as u32")]
91            pub const [<U32_ $name:snake:upper>]: u32 = $value;
92        )+
93    }
94
95    /// A helper function that tries converting u32 to a [`Status`],
96    /// and returns a string representation of the status. Otherwise,
97    /// it returns the hex representation of the u32 value.
98    /// This is useful for displaying NT status codes that are not necessarily
99    /// defined in the [`Status`] enum.
100    pub fn try_display_as_status(value: u32) -> String {
101        match Self::try_from(value) {
102            Ok(status) => format!("{}", status),
103            Err(_) => format!("{:#06x}", value),
104        }
105    }
106}
107
108impl TryFrom<u32> for Status {
109    type Error = crate::SmbMsgError;
110
111    fn try_from(value: u32) -> Result<Self, Self::Error> {
112        Status::read_le(&mut Cursor::new(value.to_le_bytes())).map_err(|_| {
113            Self::Error::MissingErrorCodeDefinition(value)
114        })
115    }
116}
117    };
118}
119
120make_status! {
121    Success = 0x00000000: "Success",
122    Pending = 0x00000103: "Pending",
123    NotifyCleanup = 0x0000010B: "Notify Cleanup",
124    InvalidSmb = 0x00010002: "Invalid SMB",
125    SmbBadTid = 0x00050002: "SMB Bad TID",
126    SmbBadCommand = 0x00160002: "SMB Bad Command",
127    SmbBadUid = 0x005B0002: "SMB Bad UID",
128    SmbUseStandard = 0x00FB0002: "SMB Use Standard",
129    BufferOverflow = 0x80000005: "Buffer Overflow",
130    NoMoreFiles = 0x80000006: "No More Files",
131    StoppedOnSymlink = 0x8000002D: "Stopped on Symlink",
132    NotImplemented = 0xC0000002: "Not Implemented",
133    InfoLengthMismatch = 0xC0000004: "Info Length Mismatch",
134    InvalidParameter = 0xC000000D: "Invalid Parameter",
135    NoSuchDevice = 0xC000000E: "No Such Device",
136    InvalidDeviceRequest0 = 0xC0000010: "Invalid Device Request",
137    EndOfFile = 0xC0000011: "End of File",
138    MoreProcessingRequired = 0xC0000016: "More Processing Required",
139    AccessDenied = 0xC0000022: "Access Denied",
140    BufferTooSmall = 0xC0000023: "Buffer Too Small",
141    ObjectNameInvalid = 0xC0000033: "Object Name Invalid",
142    ObjectNameNotFound = 0xC0000034: "Object Name Not Found",
143    ObjectNameCollision = 0xC0000035: "Object Name Collision",
144    SharingViloation = 0xC0000043: "Sharing Violation",
145    ObjectPathNotFound = 0xC000003A: "Object Path Not Found",
146    LogonFailure = 0xC000006D: "Logon Failure",
147    BadImpersonationLevel = 0xC00000A5: "Bad Impersonation Level",
148    IoTimeout = 0xC00000B5: "I/O Timeout",
149    FileIsADirectory = 0xC00000BA: "File is a Directory",
150    NotSupported = 0xC00000BB: "Not Supported",
151    NetworkNameDeleted = 0xC00000C9: "Network Name Deleted",
152    BadNetworkName = 0xC00000CC: "Bad Network Name",
153    RequestNotAccepted = 0xC00000D0: "Request Not Accepted",
154    DirectoryNotEmpty = 0xC0000101: "Directory Not Empty",
155    Cancelled = 0xC0000120: "Cancelled",
156    UserSessionDeleted = 0xC0000203: "User Session Deleted",
157    UserAccountLockedOut = 0xC0000234: "User Account Locked Out",
158    PathNotCovered = 0xC0000257: "Path Not Covered",
159    NetworkSessionExpired = 0xC000035C: "Network Session Expired",
160    SmbTooManyUids = 0xC000205A: "SMB Too Many UIDs",
161    DeviceFeatureNotSupported = 0xC0000463: "Device Feature Not Supported",
162}
163
164/// Sync and Async SMB2 Message header.
165///
166#[binrw::binrw]
167#[derive(Debug, Clone, PartialEq, Eq)]
168#[brw(magic(b"\xfeSMB"), little)]
169pub struct Header {
170    #[bw(calc = Self::STRUCT_SIZE as u16)]
171    #[br(assert(_structure_size == Self::STRUCT_SIZE as u16))]
172    _structure_size: u16,
173    pub credit_charge: u16,
174    /// NT status. Use the [`Header::status()`] method to convert to a [`Status`].
175    pub status: u32,
176    pub command: Command,
177    pub credit_request: u16,
178    pub flags: HeaderFlags,
179    pub next_command: u32,
180    pub message_id: u64,
181
182    // Option 1 - Sync: Reserved + TreeId. flags.async_command MUST NOT be set.
183    #[brw(if(!flags.async_command()))]
184    #[bw(calc = 0)]
185    _reserved: u32,
186    #[br(if(!flags.async_command()))]
187    #[bw(assert(tree_id.is_some() != flags.async_command()))]
188    pub tree_id: Option<u32>,
189
190    // Option 2 - Async: AsyncId. flags.async_command MUST be set manually.
191    #[brw(if(flags.async_command()))]
192    #[bw(assert(tree_id.is_none() == flags.async_command()))]
193    pub async_id: Option<u64>,
194
195    pub session_id: u64,
196    pub signature: u128,
197}
198
199impl Header {
200    pub const STRUCT_SIZE: usize = 64;
201
202    /// Tries to convert the [`Header::status`] field to a [`Status`],
203    /// returning it, if successful.
204    pub fn status(&self) -> crate::Result<Status> {
205        self.status.try_into()
206    }
207}
208
209#[bitfield]
210#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
211#[bw(map = |&x| Self::into_bytes(x))]
212#[br(map = Self::from_bytes)]
213pub struct HeaderFlags {
214    pub server_to_redir: bool,
215    pub async_command: bool,
216    pub related_operations: bool,
217    pub signed: bool,
218    pub priority_mask: B3,
219    #[skip]
220    __: B21,
221    pub dfs_operation: bool,
222    pub replay_operation: bool,
223    #[skip]
224    __: B2,
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use std::io::Cursor;
231
232    #[test]
233    pub fn test_async_header_parse() {
234        let arr = &[
235            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x0, 0x0, 0x3, 0x1, 0x0, 0x0, 0xf, 0x0, 0x1, 0x0,
236            0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
237            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0x27, 0x53, 0x8, 0x0, 0x0, 0x0, 0x0, 0x63,
238            0xf8, 0x25, 0xde, 0xae, 0x2, 0x95, 0x2f, 0xa3, 0xd8, 0xc8, 0xaa, 0xf4, 0x6e, 0x7c,
239            0x99,
240        ];
241        let mut cursor = Cursor::new(arr);
242        let header = Header::read_le(&mut cursor).unwrap();
243        assert_eq!(
244            header,
245            Header {
246                credit_charge: 0,
247                status: Status::Pending as u32,
248                command: Command::ChangeNotify,
249                credit_request: 1,
250                flags: HeaderFlags::new()
251                    .with_async_command(true)
252                    .with_server_to_redir(true)
253                    .with_priority_mask(1),
254                next_command: 0,
255                message_id: 8,
256                tree_id: None,
257                async_id: Some(8),
258                session_id: 0x00000000085327d7,
259                signature: u128::from_le_bytes(u128::to_be_bytes(
260                    0x63f825deae02952fa3d8c8aaf46e7c99
261                )),
262            }
263        )
264    }
265}