Skip to main content

rustsmb/cmds/
mod.rs

1//! SMB1 command routing and handlers.
2
3pub mod dir_cmds;
4pub mod file_cmds;
5pub mod session;
6pub mod trans2_cmds;
7
8use smb_server_proto::types::Status;
9use smb_server_proto_smb1::consts;
10use smb_server_proto_smb1::header::RespBody;
11
12pub use crate::dispatch::IoCtx;
13use crate::dispatch::{IoContext, ReqView};
14use crate::state::{next_uid, Session};
15
16/// Route one request: fills `bodies` and returns the response status.
17pub async fn dispatch_one<'a>(
18    io: &mut IoContext<'a>,
19    req: &ReqView<'a>,
20    bodies: &mut Vec<RespBody>,
21) -> Result<Status, Status> {
22
23    // Pre-session commands: negotiate/setup/echo/exit/cancel.
24    let needs_session = !matches!(
25        req.hdr.command,
26        consts::COM_NEGOTIATE
27            | consts::COM_SESSION_SETUP_ANDX
28            | consts::COM_ECHO
29            | consts::COM_PROCESS_EXIT
30            | consts::COM_NT_CANCEL
31    );
32
33    // Map unauthenticated clients to guest when no user DB is configured
34    // (mirrors Samba's "map to guest = bad user").
35    if io.conn.session.is_none() && !io.conn.auth_pending && io.server.allow_guest && needs_session {
36        let uid = if io.conn.uid != 0 { io.conn.uid } else { next_uid() };
37        io.conn.uid = uid;
38        io.conn.session = Some(Session { user: "nobody".into(), guest: true, trees: Vec::new() });
39    }
40    if needs_session && io.conn.session.is_none() && !io.conn.auth_pending {
41        return Err(Status::ACCESS_DENIED);
42    }
43
44    let needs_tree = needs_session
45        && !matches!(req.hdr.command, consts::COM_TREE_CONNECT_ANDX | consts::COM_LOGOFF_ANDX);
46    if needs_tree && !io.conn.trees.contains_key(&req.hdr.tid) {
47        return Err(Status::INVALID_HANDLE);
48    }
49
50    match req.hdr.command {
51        consts::COM_NEGOTIATE => session::negotiate(io, req, bodies),
52        consts::COM_SESSION_SETUP_ANDX => session::setup(io, req, bodies),
53        consts::COM_TREE_CONNECT_ANDX => session::tree_connect(io, req, bodies),
54        consts::COM_TREE_DISCONNECT => {
55            let tid = req.hdr.tid;
56            io.conn.trees.remove(&tid);
57            if let Some(s) = io.conn.session.as_mut() {
58                s.trees.retain(|t| *t != tid);
59            }
60            *bodies = vec![RespBody::new(consts::COM_TREE_DISCONNECT, Vec::new(), Vec::new())];
61            Ok(Status::SUCCESS)
62        }
63        consts::COM_LOGOFF_ANDX => {
64            for fid in io.conn.handles.keys().copied().collect::<Vec<_>>() {
65                if let Some(h) = io.conn.handles.remove(&fid) {
66                    let vfs = share_vfs(io, req.hdr.tid);
67                    let _ = vfs.close(h).await;
68                }
69            }
70            io.conn.session = None;
71            io.conn.auth_pending = false;
72            *bodies =
73                vec![RespBody::new(consts::COM_LOGOFF_ANDX, vec![consts::ANDX_NONE, 0, 0, 0], Vec::new())];
74            Ok(Status::SUCCESS)
75        }
76        consts::COM_ECHO => session::echo(req, bodies),
77        consts::COM_QUERY_INFORMATION_DISK => file_cmds::query_disk(io, req, bodies).await,
78        consts::COM_NT_CREATE_ANDX => file_cmds::nt_create(io, req, bodies).await,
79        consts::COM_READ_ANDX => file_cmds::read_andx(io, req, bodies).await,
80        consts::COM_WRITE_ANDX => file_cmds::write_andx(io, req, bodies).await,
81        consts::COM_CLOSE => file_cmds::close(io, req, bodies).await,
82        consts::COM_FLUSH => file_cmds::flush(io, req, bodies).await,
83        consts::COM_SEEK => file_cmds::seek(io, req, bodies).await,
84        consts::COM_LOCKING_ANDX
85        | consts::COM_LOCK_BYTE_RANGE
86        | consts::COM_UNLOCK_BYTE_RANGE => file_cmds::locking(io, req, bodies).await,
87        consts::COM_CREATE_DIRECTORY => dir_cmds::mkdir(io, req, bodies).await,
88        consts::COM_DELETE_DIRECTORY => dir_cmds::rmdir(io, req, bodies).await,
89        consts::COM_CHECK_DIRECTORY => dir_cmds::check_dir(io, req, bodies).await,
90        consts::COM_DELETE => dir_cmds::delete(io, req, bodies).await,
91        consts::COM_RENAME => dir_cmds::rename(io, req, bodies).await,
92        consts::COM_NT_RENAME => dir_cmds::rename(io, req, bodies).await,
93        consts::COM_QUERY_INFORMATION => dir_cmds::query_info_legacy(io, req, bodies).await,
94        consts::COM_SET_INFORMATION => dir_cmds::set_info_legacy(io, req, bodies).await,
95        consts::COM_PROCESS_EXIT => {
96            for (_, h) in std::mem::take(&mut io.conn.handles) {
97                let vfs = share_vfs_any(io);
98                let _ = vfs.close(h).await;
99            }
100            *bodies = vec![RespBody::new(consts::COM_PROCESS_EXIT, Vec::new(), Vec::new())];
101            Ok(Status::SUCCESS)
102        }
103        consts::COM_TRANSACTION2 => trans2_cmds::dispatch_trans2(io, req, bodies).await,
104        _ => Err(Status::INVALID_DEVICE_REQUEST),
105    }
106}
107
108/// Split a find pattern into directory + filename parts.
109pub fn dir_cmds_split(pattern: &str) -> (String, String) {
110    crate::cmds::dir_cmds::split_pattern_pub(pattern)
111}
112
113pub use crate::cmds::dir_cmds::split_pattern_pub as split_pattern;
114
115/// DOS wildcard match.
116pub fn wildcard(name: &str, pat: &str) -> bool {
117    smb_server_backend_posix::wildcard_match(name, pat)
118}
119
120/// Resolve the VFS for the request's tree; IPC$ yields a stub that rejects.
121/// Clone the VFS handle for the request's tree (Arc so handlers can hold it
122    /// across mutable connection access).
123    pub fn share_vfs(io: &IoContext<'_>, tid: u16) -> std::sync::Arc<dyn smb_server_vfs::Vfs> {
124        static IPC: std::sync::OnceLock<smb_server_vfs_stub::IpcVfs> = std::sync::OnceLock::new();
125        match io.conn.trees.get(&tid).and_then(|n| io.server.shares.get(n)) {
126            Some(share) => share.vfs.clone(),
127            None => std::sync::Arc::new(IPC.get_or_init(smb_server_vfs_stub::IpcVfs::default).clone())
128                as std::sync::Arc<dyn smb_server_vfs::Vfs>,
129        }
130    }
131
132fn share_vfs_any(_io: &IoContext) -> &'static dyn smb_server_vfs::Vfs {
133    static IPC: std::sync::OnceLock<smb_server_vfs_stub::IpcVfs> = std::sync::OnceLock::new();
134    IPC.get_or_init(smb_server_vfs_stub::IpcVfs::default) as &dyn smb_server_vfs::Vfs
135}
136
137/// Stub namespace so `share_vfs` can always return something.
138mod smb_server_vfs_stub {
139    /// Rejecting backend used for unknown trees.
140    #[derive(Debug, Default, Clone)]
141    pub struct IpcVfs;
142
143    #[async_trait::async_trait(?Send)]
144    impl smb_server_vfs::Vfs for IpcVfs {
145        async fn create(
146            &self,
147            _: &str,
148            _: bool,
149            _: u32,
150            _: u32,
151            _: u32,
152            _: u32,
153        ) -> smb_server_vfs::VfsResult<(Box<smb_server_vfs::OpenFile>, smb_server_vfs::FileMeta, u32)> {
154            Err(smb_server_vfs::VfsError::AccessDenied)
155        }
156        async fn read(
157            &self,
158            _: &mut smb_server_vfs::OpenFile,
159            _: u64,
160            _: usize,
161        ) -> smb_server_vfs::VfsResult<Vec<u8>> {
162            Err(smb_server_vfs::VfsError::AccessDenied)
163        }
164        async fn write(
165            &self,
166            _: &mut smb_server_vfs::OpenFile,
167            _: u64,
168            _: &[u8],
169            _: bool,
170        ) -> smb_server_vfs::VfsResult<u64> {
171            Err(smb_server_vfs::VfsError::AccessDenied)
172        }
173        async fn seek(
174            &self,
175            _: &mut smb_server_vfs::OpenFile,
176            _: u16,
177            _: i64,
178        ) -> smb_server_vfs::VfsResult<u64> {
179            Err(smb_server_vfs::VfsError::NotSupported)
180        }
181        async fn flush(&self, _: &mut smb_server_vfs::OpenFile) -> smb_server_vfs::VfsResult<()> {
182            Ok(())
183        }
184        async fn flush_all(&self) -> smb_server_vfs::VfsResult<()> {
185            Ok(())
186        }
187        async fn close(&self, _: Box<smb_server_vfs::OpenFile>) -> smb_server_vfs::VfsResult<()> {
188            Ok(())
189        }
190        async fn mkdir(&self, _: &str) -> smb_server_vfs::VfsResult<()> {
191            Err(smb_server_vfs::VfsError::AccessDenied)
192        }
193        async fn rmdir(&self, _: &str) -> smb_server_vfs::VfsResult<()> {
194            Err(smb_server_vfs::VfsError::AccessDenied)
195        }
196        async fn check_dir(&self, _: &str) -> smb_server_vfs::VfsResult<()> {
197            Err(smb_server_vfs::VfsError::NotFound)
198        }
199        async fn unlink(&self, _: &str) -> smb_server_vfs::VfsResult<()> {
200            Err(smb_server_vfs::VfsError::AccessDenied)
201        }
202        async fn delete_pattern(&self, _: &str, _: &str) -> smb_server_vfs::VfsResult<bool> {
203            Err(smb_server_vfs::VfsError::AccessDenied)
204        }
205        async fn rename(&self, _: &str, _: &str) -> smb_server_vfs::VfsResult<()> {
206            Err(smb_server_vfs::VfsError::AccessDenied)
207        }
208        async fn list(&self, _: &str) -> smb_server_vfs::VfsResult<Vec<smb_server_vfs::Entry>> {
209            Err(smb_server_vfs::VfsError::AccessDenied)
210        }
211        async fn stat(&self, _: &str) -> smb_server_vfs::VfsResult<smb_server_vfs::FileMeta> {
212            Err(smb_server_vfs::VfsError::NotFound)
213        }
214        async fn set_info_open(
215            &self,
216            _: &mut smb_server_vfs::OpenFile,
217            _: &smb_server_vfs::SetOp,
218        ) -> smb_server_vfs::VfsResult<()> {
219            Err(smb_server_vfs::VfsError::AccessDenied)
220        }
221        async fn set_info_path(
222            &self,
223            _: &str,
224            _: &smb_server_vfs::SetOp,
225        ) -> smb_server_vfs::VfsResult<()> {
226            Err(smb_server_vfs::VfsError::AccessDenied)
227        }
228        async fn query_disk(&self) -> smb_server_vfs::VfsResult<(u32, u32, u16, u16)> {
229            Err(smb_server_vfs::VfsError::NotSupported)
230        }
231    }
232}