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    pastey::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    NotifyEnumDir = 0x0000010C: "Notify Enum Dir",
125    InvalidSmb = 0x00010002: "Invalid SMB",
126    SmbBadTid = 0x00050002: "SMB Bad TID",
127    SmbBadCommand = 0x00160002: "SMB Bad Command",
128    SmbBadUid = 0x005B0002: "SMB Bad UID",
129    SmbUseStandard = 0x00FB0002: "SMB Use Standard",
130    BufferOverflow = 0x80000005: "Buffer Overflow",
131    NoMoreFiles = 0x80000006: "No More Files",
132    StoppedOnSymlink = 0x8000002D: "Stopped on Symlink",
133    NotImplemented = 0xC0000002: "Not Implemented",
134    InvalidInfoClass = 0xC0000003: "Invalid Info Class",
135    InfoLengthMismatch = 0xC0000004: "Info Length Mismatch",
136    InvalidParameter = 0xC000000D: "Invalid Parameter",
137    NoSuchDevice = 0xC000000E: "No Such Device",
138    InvalidDeviceRequest0 = 0xC0000010: "Invalid Device Request",
139    EndOfFile = 0xC0000011: "End of File",
140    MoreProcessingRequired = 0xC0000016: "More Processing Required",
141    AccessDenied = 0xC0000022: "Access Denied",
142    BufferTooSmall = 0xC0000023: "Buffer Too Small",
143    ObjectNameInvalid = 0xC0000033: "Object Name Invalid",
144    ObjectNameNotFound = 0xC0000034: "Object Name Not Found",
145    ObjectNameCollision = 0xC0000035: "Object Name Collision",
146    SharingViolation = 0xC0000043: "Sharing Violation",
147    ObjectPathNotFound = 0xC000003A: "Object Path Not Found",
148    NoEasOnFile = 0xC0000044: "No EAs on File",
149    LogonFailure = 0xC000006D: "Logon Failure",
150    BadImpersonationLevel = 0xC00000A5: "Bad Impersonation Level",
151    IoTimeout = 0xC00000B5: "I/O Timeout",
152    FileIsADirectory = 0xC00000BA: "File is a Directory",
153    NotSupported = 0xC00000BB: "Not Supported",
154    NetworkNameDeleted = 0xC00000C9: "Network Name Deleted",
155    BadNetworkName = 0xC00000CC: "Bad Network Name",
156    RequestNotAccepted = 0xC00000D0: "Request Not Accepted",
157    DirectoryNotEmpty = 0xC0000101: "Directory Not Empty",
158    Cancelled = 0xC0000120: "Cancelled",
159    UserSessionDeleted = 0xC0000203: "User Session Deleted",
160    UserAccountLockedOut = 0xC0000234: "User Account Locked Out",
161    PathNotCovered = 0xC0000257: "Path Not Covered",
162    NetworkSessionExpired = 0xC000035C: "Network Session Expired",
163    SmbTooManyUids = 0xC000205A: "SMB Too Many UIDs",
164    DeviceFeatureNotSupported = 0xC0000463: "Device Feature Not Supported",
165}
166
167/// Sync and Async SMB2 Message header.
168#[binrw::binrw]
169#[derive(Debug, Clone, PartialEq, Eq)]
170#[brw(magic(b"\xfeSMB"), little)]
171pub struct Header {
172    #[bw(calc = Self::STRUCT_SIZE as u16)]
173    #[br(assert(_structure_size == Self::STRUCT_SIZE as u16))]
174    _structure_size: u16,
175    pub credit_charge: u16,
176    /// NT status. Use the [`Header::status()`] method to convert to a [`Status`].
177    pub status: u32,
178    pub command: Command,
179    pub credit_request: u16,
180    pub flags: HeaderFlags,
181    pub next_command: u32,
182    pub message_id: u64,
183
184    // Option 1 - Sync: Reserved + TreeId. flags.async_command MUST NOT be set.
185    #[brw(if(!flags.async_command()))]
186    #[bw(calc = 0)]
187    _reserved: u32,
188    #[br(if(!flags.async_command()))]
189    #[bw(assert(tree_id.is_some() != flags.async_command()))]
190    pub tree_id: Option<u32>,
191
192    // Option 2 - Async: AsyncId. flags.async_command MUST be set manually.
193    #[brw(if(flags.async_command()))]
194    #[bw(assert(tree_id.is_none() == flags.async_command()))]
195    pub async_id: Option<u64>,
196
197    pub session_id: u64,
198    pub signature: u128,
199}
200
201impl Header {
202    pub const STRUCT_SIZE: usize = 64;
203
204    /// Tries to convert the [`Header::status`] field to a [`Status`],
205    /// returning it, if successful.
206    pub fn status(&self) -> crate::Result<Status> {
207        self.status.try_into()
208    }
209
210    /// Turns the current header into an async header,
211    /// setting the [`async_id`][Self::async_id] and clearing the [`tree_id`][Self::tree_id].
212    /// Also sets the [`HeaderFlags::async_command`] in [`flags`][Self::flags] to true.
213    pub fn to_async(&mut self, async_id: u64) {
214        self.flags.set_async_command(true);
215        self.tree_id = None;
216        self.async_id = Some(async_id);
217    }
218}
219
220#[bitfield]
221#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
222#[bw(map = |&x| Self::into_bytes(x))]
223#[br(map = Self::from_bytes)]
224pub struct HeaderFlags {
225    pub server_to_redir: bool,
226    pub async_command: bool,
227    pub related_operations: bool,
228    pub signed: bool,
229    pub priority_mask: B3,
230    #[skip]
231    __: B21,
232    pub dfs_operation: bool,
233    pub replay_operation: bool,
234    #[skip]
235    __: B2,
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use std::io::Cursor;
242
243    #[test]
244    pub fn test_async_header_parse() {
245        let arr = &[
246            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x0, 0x0, 0x3, 0x1, 0x0, 0x0, 0xf, 0x0, 0x1, 0x0,
247            0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
248            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0x27, 0x53, 0x8, 0x0, 0x0, 0x0, 0x0, 0x63,
249            0xf8, 0x25, 0xde, 0xae, 0x2, 0x95, 0x2f, 0xa3, 0xd8, 0xc8, 0xaa, 0xf4, 0x6e, 0x7c,
250            0x99,
251        ];
252        let mut cursor = Cursor::new(arr);
253        let header = Header::read_le(&mut cursor).unwrap();
254        assert_eq!(
255            header,
256            Header {
257                credit_charge: 0,
258                status: Status::Pending as u32,
259                command: Command::ChangeNotify,
260                credit_request: 1,
261                flags: HeaderFlags::new()
262                    .with_async_command(true)
263                    .with_server_to_redir(true)
264                    .with_priority_mask(1),
265                next_command: 0,
266                message_id: 8,
267                tree_id: None,
268                async_id: Some(8),
269                session_id: 0x00000000085327d7,
270                signature: u128::from_le_bytes(u128::to_be_bytes(
271                    0x63f825deae02952fa3d8c8aaf46e7c99
272                )),
273            }
274        )
275    }
276}