smb_msg/
header.rs

1//! Plain Message Header and related types.
2
3use std::io::Cursor;
4
5use binrw::prelude::*;
6use modular_bitfield::prelude::*;
7use smb_msg_derive::{smb_message_binrw, smb_request_response};
8
9/// SMB2/SMB3 protocol command codes.
10///
11/// Reference: MS-SMB2 2.2.1.2
12#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
13#[brw(repr(u16))]
14pub enum Command {
15    Negotiate = 0,
16    SessionSetup = 1,
17    Logoff = 2,
18    TreeConnect = 3,
19    TreeDisconnect = 4,
20    Create = 5,
21    Close = 6,
22    Flush = 7,
23    Read = 8,
24    Write = 9,
25    Lock = 0xA,
26    Ioctl = 0xB,
27    Cancel = 0xC,
28    Echo = 0xD,
29    QueryDirectory = 0xE,
30    ChangeNotify = 0xF,
31    QueryInfo = 0x10,
32    SetInfo = 0x11,
33    OplockBreak = 0x12,
34    ServerToClientNotification = 0x13,
35}
36
37impl std::fmt::Display for Command {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        let message_as_string = match self {
40            Command::Negotiate => "Negotiate",
41            Command::SessionSetup => "Session Setup",
42            Command::Logoff => "Logoff",
43            Command::TreeConnect => "Tree Connect",
44            Command::TreeDisconnect => "Tree Disconnect",
45            Command::Create => "Create",
46            Command::Close => "Close",
47            Command::Flush => "Flush",
48            Command::Read => "Read",
49            Command::Write => "Write",
50            Command::Lock => "Lock",
51            Command::Ioctl => "Ioctl",
52            Command::Cancel => "Cancel",
53            Command::Echo => "Echo",
54            Command::QueryDirectory => "Query Directory",
55            Command::ChangeNotify => "Change Notify",
56            Command::QueryInfo => "Query Info",
57            Command::SetInfo => "Set Info",
58            Command::OplockBreak => "Oplock Break",
59            Command::ServerToClientNotification => "Server to Client Notification",
60        };
61        write!(f, "{} ({:#x})", message_as_string, *self as u16)
62    }
63}
64
65macro_rules! make_status {
66    (
67        $($name:ident = $value:literal: $description:literal, )+
68    ) => {
69
70/// NT Status codes for SMB.
71///
72/// For each status code, a U32 constant is also provided for easier access.
73/// for example, [`Status::U32_END_OF_FILE`] is `0xC0000011`, matching [`Status::EndOfFile`].
74#[smb_message_binrw]
75#[derive(Clone, Copy)]
76#[repr(u32)]
77#[brw(repr(u32))]
78pub enum Status {
79    $(
80        #[doc = concat!($description, " (", stringify!($value), ")")]
81        $name = $value,
82    )+
83}
84
85impl std::fmt::Display for Status {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        let message_as_string = match self {
88            $(
89                Status::$name => $description,
90            )+
91        };
92        write!(f, "{} ({:#x})", message_as_string, *self as u32)
93    }
94}
95
96impl Status {
97    // Consts for easier status code as u32 access.
98    pastey::paste! {
99        $(
100            #[doc = concat!("[`", stringify!($name), "`][Self::", stringify!($name), "] as u32")]
101            pub const [<U32_ $name:snake:upper>]: u32 = $value;
102        )+
103    }
104
105    /// A helper function that tries converting u32 to a [`Status`],
106    /// and returns a string representation of the status. Otherwise,
107    /// it returns the hex representation of the u32 value.
108    /// This is useful for displaying NT status codes that are not necessarily
109    /// defined in the [`Status`] enum.
110    pub fn try_display_as_status(value: u32) -> String {
111        match Self::try_from(value) {
112            Ok(status) => format!("{}", status),
113            Err(_) => format!("{:#06x}", value),
114        }
115    }
116}
117
118impl TryFrom<u32> for Status {
119    type Error = crate::SmbMsgError;
120
121    fn try_from(value: u32) -> Result<Self, Self::Error> {
122        Status::read_le(&mut Cursor::new(value.to_le_bytes())).map_err(|_| {
123            Self::Error::MissingErrorCodeDefinition(value)
124        })
125    }
126}
127    };
128}
129
130make_status! {
131    Success = 0x00000000: "Success",
132    Pending = 0x00000103: "Pending",
133    NotifyCleanup = 0x0000010B: "Notify Cleanup",
134    NotifyEnumDir = 0x0000010C: "Notify Enum Dir",
135    InvalidSmb = 0x00010002: "Invalid SMB",
136    SmbBadTid = 0x00050002: "SMB Bad TID",
137    SmbBadCommand = 0x00160002: "SMB Bad Command",
138    SmbBadUid = 0x005B0002: "SMB Bad UID",
139    SmbUseStandard = 0x00FB0002: "SMB Use Standard",
140    BufferOverflow = 0x80000005: "Buffer Overflow",
141    NoMoreFiles = 0x80000006: "No More Files",
142    StoppedOnSymlink = 0x8000002D: "Stopped on Symlink",
143    NotImplemented = 0xC0000002: "Not Implemented",
144    InvalidInfoClass = 0xC0000003: "Invalid Info Class",
145    InfoLengthMismatch = 0xC0000004: "Info Length Mismatch",
146    InvalidParameter = 0xC000000D: "Invalid Parameter",
147    NoSuchDevice = 0xC000000E: "No Such Device",
148    InvalidDeviceRequest0 = 0xC0000010: "Invalid Device Request",
149    EndOfFile = 0xC0000011: "End of File",
150    MoreProcessingRequired = 0xC0000016: "More Processing Required",
151    AccessDenied = 0xC0000022: "Access Denied",
152    BufferTooSmall = 0xC0000023: "Buffer Too Small",
153    ObjectNameInvalid = 0xC0000033: "Object Name Invalid",
154    ObjectNameNotFound = 0xC0000034: "Object Name Not Found",
155    ObjectNameCollision = 0xC0000035: "Object Name Collision",
156    SharingViolation = 0xC0000043: "Sharing Violation",
157    ObjectPathNotFound = 0xC000003A: "Object Path Not Found",
158    NoEasOnFile = 0xC0000044: "No EAs on File",
159    LogonFailure = 0xC000006D: "Logon Failure",
160    NotMapped = 0xC0000073: "Not Mapped",
161    BadImpersonationLevel = 0xC00000A5: "Bad Impersonation Level",
162    IoTimeout = 0xC00000B5: "I/O Timeout",
163    FileIsADirectory = 0xC00000BA: "File is a Directory",
164    NotSupported = 0xC00000BB: "Not Supported",
165    NetworkNameDeleted = 0xC00000C9: "Network Name Deleted",
166    BadNetworkName = 0xC00000CC: "Bad Network Name",
167    RequestNotAccepted = 0xC00000D0: "Request Not Accepted",
168    DirectoryNotEmpty = 0xC0000101: "Directory Not Empty",
169    Cancelled = 0xC0000120: "Cancelled",
170    UserSessionDeleted = 0xC0000203: "User Session Deleted",
171    UserAccountLockedOut = 0xC0000234: "User Account Locked Out",
172    PathNotCovered = 0xC0000257: "Path Not Covered",
173    NetworkSessionExpired = 0xC000035C: "Network Session Expired",
174    SmbTooManyUids = 0xC000205A: "SMB Too Many UIDs",
175    DeviceFeatureNotSupported = 0xC0000463: "Device Feature Not Supported",
176}
177
178/// SMB2 Packet Header.
179///
180/// Common header structure for all SMB2/SMB3 messages, supporting both
181/// synchronous and asynchronous operations.
182///
183/// Reference: MS-SMB2 2.2.1.1, 2.2.1.2
184#[smb_request_response(size = 64)]
185#[derive(Clone)]
186#[brw(magic(b"\xfeSMB"), little)]
187pub struct Header {
188    /// Number of credits charged for this request.
189    pub credit_charge: u16,
190    /// NT status code. Use [`Header::status()`] to convert to [`Status`].
191    pub status: u32,
192    /// Command code identifying the request/response type.
193    pub command: Command,
194    /// Number of credits requested or granted.
195    pub credit_request: u16,
196    /// Header flags indicating message properties.
197    pub flags: HeaderFlags,
198    /// Offset to next message in a compounded request chain (0 if not compounded).
199    pub next_command: u32,
200    /// Unique message identifier.
201    pub message_id: u64,
202
203    // Option 1 - Sync: Reserved + TreeId. flags.async_command MUST NOT be set.
204    #[brw(if(!flags.async_command()))]
205    #[bw(calc = 0)]
206    _reserved: u32,
207    /// Tree identifier (synchronous operations only).
208    #[br(if(!flags.async_command()))]
209    #[bw(assert(tree_id.is_some() != flags.async_command()))]
210    pub tree_id: Option<u32>,
211
212    // Option 2 - Async: AsyncId. flags.async_command MUST be set manually.
213    #[brw(if(flags.async_command()))]
214    #[bw(assert(tree_id.is_none() == flags.async_command()))]
215    pub async_id: Option<u64>,
216
217    /// Unique session identifier.
218    pub session_id: u64,
219    /// Message signature for signed messages.
220    pub signature: u128,
221}
222
223impl Header {
224    pub const STRUCT_SIZE: usize = 64;
225
226    /// Tries to convert the [`Header::status`] field to a [`Status`],
227    /// returning it, if successful.
228    pub fn status(&self) -> crate::Result<Status> {
229        self.status.try_into()
230    }
231
232    /// Turns the current header into an async header,
233    /// setting the [`async_id`][Self::async_id] and clearing the [`tree_id`][Self::tree_id].
234    /// Also sets the [`HeaderFlags::async_command`] in [`flags`][Self::flags] to true.
235    pub fn to_async(&mut self, async_id: u64) {
236        self.flags.set_async_command(true);
237        self.tree_id = None;
238        self.async_id = Some(async_id);
239    }
240}
241
242/// SMB2 header flags.
243///
244/// Indicates how to process the operation.
245///
246/// Reference: MS-SMB2 2.2.1.2
247#[smb_dtyp::mbitfield]
248pub struct HeaderFlags {
249    /// Message is a server response (set in responses).
250    pub server_to_redir: bool,
251    /// Message is part of an asynchronous operation.
252    pub async_command: bool,
253    /// Request is a related operation in a compounded chain.
254    pub related_operations: bool,
255    /// Message is signed.
256    pub signed: bool,
257    /// Priority mask for quality of service.
258    pub priority_mask: B3,
259    #[skip]
260    __: B21,
261    /// Request is a DFS operation.
262    pub dfs_operation: bool,
263    /// Request is a replay operation for resilient handles.
264    pub replay_operation: bool,
265    #[skip]
266    __: B2,
267}
268
269#[cfg(test)]
270mod tests {
271    use smb_tests::*;
272
273    use super::*;
274
275    test_binrw! {
276        Header => async: Header {
277            credit_charge: 0,
278            status: Status::Pending as u32,
279            command: Command::ChangeNotify,
280            credit_request: 1,
281            flags: HeaderFlags::new()
282                .with_async_command(true)
283                .with_server_to_redir(true)
284                .with_priority_mask(1),
285            next_command: 0,
286            message_id: 8,
287            tree_id: None,
288            async_id: Some(8),
289            session_id: 0x00000000085327d7,
290            signature: u128::from_le_bytes(u128::to_be_bytes(
291                0x63f825deae02952fa3d8c8aaf46e7c99
292            )),
293        } => "fe534d4240000000030100000f000100130000000000000008000000000000000800000000000000d72753080000000063f825deae02952fa3d8c8aaf46e7c99"
294    }
295}