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    NotMapped = 0xC0000073: "Not Mapped",
151    BadImpersonationLevel = 0xC00000A5: "Bad Impersonation Level",
152    IoTimeout = 0xC00000B5: "I/O Timeout",
153    FileIsADirectory = 0xC00000BA: "File is a Directory",
154    NotSupported = 0xC00000BB: "Not Supported",
155    NetworkNameDeleted = 0xC00000C9: "Network Name Deleted",
156    BadNetworkName = 0xC00000CC: "Bad Network Name",
157    RequestNotAccepted = 0xC00000D0: "Request Not Accepted",
158    DirectoryNotEmpty = 0xC0000101: "Directory Not Empty",
159    Cancelled = 0xC0000120: "Cancelled",
160    UserSessionDeleted = 0xC0000203: "User Session Deleted",
161    UserAccountLockedOut = 0xC0000234: "User Account Locked Out",
162    PathNotCovered = 0xC0000257: "Path Not Covered",
163    NetworkSessionExpired = 0xC000035C: "Network Session Expired",
164    SmbTooManyUids = 0xC000205A: "SMB Too Many UIDs",
165    DeviceFeatureNotSupported = 0xC0000463: "Device Feature Not Supported",
166}
167
168/// Sync and Async SMB2 Message header.
169#[binrw::binrw]
170#[derive(Debug, Clone, PartialEq, Eq)]
171#[brw(magic(b"\xfeSMB"), little)]
172pub struct Header {
173    #[bw(calc = Self::STRUCT_SIZE as u16)]
174    #[br(assert(_structure_size == Self::STRUCT_SIZE as u16))]
175    _structure_size: u16,
176    pub credit_charge: u16,
177    /// NT status. Use the [`Header::status()`] method to convert to a [`Status`].
178    pub status: u32,
179    pub command: Command,
180    pub credit_request: u16,
181    pub flags: HeaderFlags,
182    pub next_command: u32,
183    pub message_id: u64,
184
185    // Option 1 - Sync: Reserved + TreeId. flags.async_command MUST NOT be set.
186    #[brw(if(!flags.async_command()))]
187    #[bw(calc = 0)]
188    _reserved: u32,
189    #[br(if(!flags.async_command()))]
190    #[bw(assert(tree_id.is_some() != flags.async_command()))]
191    pub tree_id: Option<u32>,
192
193    // Option 2 - Async: AsyncId. flags.async_command MUST be set manually.
194    #[brw(if(flags.async_command()))]
195    #[bw(assert(tree_id.is_none() == flags.async_command()))]
196    pub async_id: Option<u64>,
197
198    pub session_id: u64,
199    pub signature: u128,
200}
201
202impl Header {
203    pub const STRUCT_SIZE: usize = 64;
204
205    /// Tries to convert the [`Header::status`] field to a [`Status`],
206    /// returning it, if successful.
207    pub fn status(&self) -> crate::Result<Status> {
208        self.status.try_into()
209    }
210
211    /// Turns the current header into an async header,
212    /// setting the [`async_id`][Self::async_id] and clearing the [`tree_id`][Self::tree_id].
213    /// Also sets the [`HeaderFlags::async_command`] in [`flags`][Self::flags] to true.
214    pub fn to_async(&mut self, async_id: u64) {
215        self.flags.set_async_command(true);
216        self.tree_id = None;
217        self.async_id = Some(async_id);
218    }
219}
220
221#[bitfield]
222#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
223#[bw(map = |&x| Self::into_bytes(x))]
224#[br(map = Self::from_bytes)]
225pub struct HeaderFlags {
226    pub server_to_redir: bool,
227    pub async_command: bool,
228    pub related_operations: bool,
229    pub signed: bool,
230    pub priority_mask: B3,
231    #[skip]
232    __: B21,
233    pub dfs_operation: bool,
234    pub replay_operation: bool,
235    #[skip]
236    __: B2,
237}
238
239#[cfg(test)]
240mod tests {
241    use smb_tests::*;
242
243    use super::*;
244
245    test_binrw! {
246        Header => async: Header {
247            credit_charge: 0,
248            status: Status::Pending as u32,
249            command: Command::ChangeNotify,
250            credit_request: 1,
251            flags: HeaderFlags::new()
252                .with_async_command(true)
253                .with_server_to_redir(true)
254                .with_priority_mask(1),
255            next_command: 0,
256            message_id: 8,
257            tree_id: None,
258            async_id: Some(8),
259            session_id: 0x00000000085327d7,
260            signature: u128::from_le_bytes(u128::to_be_bytes(
261                0x63f825deae02952fa3d8c8aaf46e7c99
262            )),
263        } => "fe534d4240000000030100000f000100130000000000000008000000000000000800000000000000d72753080000000063f825deae02952fa3d8c8aaf46e7c99"
264    }
265}