1use smb_server_proto::types::Status;
4use smb_server_proto_smb1::consts;
5use smb_server_proto_smb1::header::RespBody;
6use smb_server_proto_smb1::query;
7use smb_server_proto_smb1::trans2 as t2;
8
9use crate::cmds::{IoCtx, share_vfs};
10use crate::dispatch::ReqView;
11use crate::state::SearchCtx;
12
13pub async fn dispatch_trans2(
15 io: &mut IoCtx<'_>,
16 req: &ReqView<'_>,
17 bodies: &mut Vec<RespBody>,
18) -> Result<Status, Status> {
19 let Some(t) = t2::Trans2Req::parse(
20 req.wct as u8,
21 req.words,
22 req.frame,
23 req.bc_off_abs,
24 req.unicode(),
25 ) else {
26 return Err(Status::INVALID_PARAMETER);
27 };
28
29 tracing::trace!(
30 subcmd = format!("{:#06x}", t.subcmd),
31 unicode = t.unicode,
32 "trans2 dispatch"
33 );
34 let (params, data) = match t.subcmd {
35 t2::subcmd::FIND_FIRST2 => find_first2(io, req.hdr.tid, &t).await?,
36 t2::subcmd::FIND_NEXT2 => find_next2(io, &t).await?,
37 t2::subcmd::QUERY_FS_INFO => query_fs(io, req.hdr.tid, &t)?,
38 t2::subcmd::QUERY_PATH_INFO => query_path(io, req.hdr.tid, &t).await?,
39 t2::subcmd::QUERY_FILE_INFO => query_file(io, req.hdr.tid, &t).await?,
40 t2::subcmd::SET_FILE_INFO => set_file(io, &t).await?,
41 t2::subcmd::SET_PATH_INFO => set_path(io, req.hdr.tid, &t).await?,
42 other => {
43 tracing::debug!(subcmd = format!("{:#06x}", other), "trans2: unsupported");
44 return Err(Status::INVALID_PARAMETER);
45 }
46 };
47
48 let (rp, rd) = t2::trans2_resp(params, data);
49 bodies.push(RespBody::new(consts::COM_TRANSACTION2, rp, rd));
50 Ok(Status::SUCCESS)
51}
52
53#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] async fn find_first2(
57 io: &mut IoCtx<'_>,
58 tid: u16,
59 t: &t2::Trans2Req,
60) -> Result<(Vec<u8>, Vec<u8>), Status> {
61 if t.params.len() < 12 {
62 return Err(Status::INVALID_PARAMETER);
63 }
64 let g16 = |o: usize| -> u16 {
65 match t.params.get(o..o + 2) {
66 Some(s) => u16::from_le_bytes([s[0], s[1]]),
67 None => 0,
68 }
69 };
70 let _count = g16(2) as usize;
71 let level = g16(6);
72
73 let mut rd = smb_server_proto::buf::Reader::new(&t.params, 12);
76 let pattern = rd.zstring(t.unicode, t.param_base);
77 tracing::trace!(level = format!("{:#06x}", level), pattern = %pattern, "find_first2");
78
79 let _share = share_name(io, tid)?;
80 let vfs = share_vfs(io, tid);
81 let (dir_rel, fname_pat) = crate::cmds::dir_cmds_split(&pattern);
82
83 if !pattern.contains('*') && !pattern.contains('?')
85 && let Ok(m) = vfs.stat(pattern.trim_start_matches(['\\', '/'])).await {
86 let name = pattern.rsplit(['\\', '/']).next().unwrap_or("").to_string();
87 let entry = smb_server_proto_smb1::find::FindEntry {
88 name,
89 meta: smb_server_proto_smb1::query::QueryMeta {
90 times: [m.times[0].0, m.times[1].0, m.times[2].0, m.times[3].0],
91 attrs: m.attrs,
92 eof: m.eof,
93 alloc: m.alloc,
94 is_dir: m.is_dir,
95 },
96 };
97 let (data_buf, last) = smb_server_proto_smb1::find::encode_entries(
98 std::slice::from_ref(&entry),
99 level,
100 t.unicode,
101 );
102 return Ok(find_params(sid(), 1, true, last, data_buf));
103 }
104
105 let entries = vfs.list(&dir_rel).await.map_err(vfs_err)?;
106 let matched: Vec<smb_server_vfs::Entry> = entries
107 .into_iter()
108 .filter(|e| crate::cmds::wildcard(&e.name.to_lowercase(), &fname_pat.to_lowercase()))
109 .collect();
110 let end_of_search = true; let sid_v = sid();
113 let returned: Vec<String> = matched.iter().map(|e| e.name.clone()).collect();
114 io.conn.searches.insert(
115 sid_v,
116 SearchCtx {
117 queue: Vec::new().into_iter(),
118 level,
119 base_dir: std::path::PathBuf::new(),
120 },
121 );
122
123 let find_entries: Vec<smb_server_proto_smb1::find::FindEntry> = matched
124 .into_iter()
125 .map(|e| smb_server_proto_smb1::find::FindEntry {
126 name: e.name,
127 meta: smb_server_proto_smb1::query::QueryMeta {
128 times: [
129 e.meta.times[0].0,
130 e.meta.times[1].0,
131 e.meta.times[2].0,
132 e.meta.times[3].0,
133 ],
134 attrs: e.meta.attrs,
135 eof: e.meta.eof,
136 alloc: e.meta.alloc,
137 is_dir: e.meta.is_dir,
138 },
139 })
140 .collect();
141 let (data_buf, last_off) =
142 smb_server_proto_smb1::find::encode_entries(&find_entries, level, t.unicode);
143 Ok(find_params(
144 sid_v,
145 returned.len() as u16,
146 end_of_search,
147 last_off,
148 data_buf,
149 ))
150}
151
152#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn find_params(
154 sid: u16,
155 count: u16,
156 eos: bool,
157 last_off: Option<usize>,
158 data: Vec<u8>,
159) -> (Vec<u8>, Vec<u8>) {
160 let mut p = Vec::with_capacity(10);
161 p.extend_from_slice(&sid.to_le_bytes());
162 p.extend_from_slice(&count.to_le_bytes());
163 p.extend_from_slice(&(eos as u16).to_le_bytes());
164 p.extend_from_slice(&0u16.to_le_bytes()); p.extend_from_slice(&(last_off.unwrap_or(0) as u16).to_le_bytes());
166 (p, data)
167}
168
169fn sid() -> u16 {
170 crate::state::next_sid()
171}
172
173#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] async fn find_next2(io: &mut IoCtx<'_>, t: &t2::Trans2Req) -> Result<(Vec<u8>, Vec<u8>), Status> {
176 if t.params.len() < 4 {
177 return Err(Status::INVALID_PARAMETER);
178 }
179 let sid_v = u16::from_le_bytes([t.params[0], t.params[1]]);
180 let Some(mut ctx) = io.conn.searches.remove(&sid_v) else {
182 return Err(Status::INVALID_HANDLE);
183 };
184 let vfs = share_vfs(io, tid_placeholder());
185 let mut out: Vec<smb_server_proto_smb1::find::FindEntry> = Vec::new();
186 while out.len() < 64 {
187 let Some(name) = ctx.queue.next() else { break };
188 let m = match vfs
189 .stat(&format!("{}\\{}", ctx.base_dir.display(), name))
190 .await
191 {
192 Ok(m) => m,
193 Err(_) => continue,
194 };
195 out.push(smb_server_proto_smb1::find::FindEntry {
196 name,
197 meta: smb_server_proto_smb1::query::QueryMeta {
198 times: [m.times[0].0, m.times[1].0, m.times[2].0, m.times[3].0],
199 attrs: m.attrs,
200 eof: m.eof,
201 alloc: m.alloc,
202 is_dir: m.is_dir,
203 },
204 });
205 }
206 let drained = ctx.queue.len() == 0 && out.is_empty();
207 let lvl = if t.params.len() >= 6 {
208 u16::from_le_bytes([t.params[4], t.params[5]])
209 } else {
210 ctx.level
211 };
212 let _eos = ctx.queue.as_slice().is_empty();
213 let _ = drained;
214 io.conn.searches.insert(sid_v, ctx);
215 let eos_final = !io.conn.searches.contains_key(&sid_v) || {
216 false
218 };
219 let _ = eos_final;
220 let (data_buf, last_off) = smb_server_proto_smb1::find::encode_entries(&out, lvl, t.unicode);
221 let has_more = !io.conn.searches.contains_key(&sid_v);
222 let _ = has_more;
223 Ok(find_params(
224 sid_v,
225 out.len() as u16,
226 true,
227 last_off,
228 data_buf,
229 ))
230}
231
232#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn query_fs(io: &IoCtx<'_>, tid: u16, t: &t2::Trans2Req) -> Result<(Vec<u8>, Vec<u8>), Status> {
236 if t.params.len() < 2 {
237 return Err(Status::INVALID_PARAMETER);
238 }
239 let level = u16::from_le_bytes([t.params[0], t.params[1]]);
240 share_vfs(io, tid); let mut d = smb_server_proto::buf::Writer::new(0);
242 match level {
243 0x102 => {
244 d.push_u64(smb_server_proto::types::FileTime::now().0);
245 d.push_u32(0x1234_5678);
246 const LABEL: &str = "RUSTSMB";
247 d.push_u32((LABEL.len() * 2) as u32);
248 for u in LABEL.encode_utf16() {
249 d.push_u16(u);
250 }
251 }
252 0x103 => {
253 d.push_u64(1_048_576);
254 d.push_u64(524_288);
255 d.push_u32(64);
256 d.push_u32(512);
257 }
258 0x201 => {
259 d.push_u32(0);
260 d.push_u32(0x20);
261 }
262 0x202 => {
263 d.push_u32(0x0004_2700 | 0x8 | 0x200 | 0x4);
264 d.push_u32(255);
265 const NAME: &str = "NTFS";
266 d.push_u32((NAME.len() * 2) as u32);
267 for u in NAME.encode_utf16() {
268 d.push_u16(u);
269 }
270 }
271 _ => return Err(Status::INVALID_PARAMETER),
272 }
273 Ok((Vec::new(), d.into_inner()))
274}
275
276#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] async fn query_path(
279 io: &IoCtx<'_>,
280 tid: u16,
281 t: &t2::Trans2Req,
282) -> Result<(Vec<u8>, Vec<u8>), Status> {
283 if t.params.len() < 2 {
284 return Err(Status::INVALID_PARAMETER);
285 }
286 let level = u16::from_le_bytes([t.params[0], t.params[1]]);
287 let vfs = share_vfs(io, tid);
288
289 let mut rd = smb_server_proto::buf::Reader::new(&t.params, 2);
290 let name = rd.zstring(t.unicode, t.param_base);
291 if name.is_empty() {
292 return Err(Status::INVALID_PARAMETER);
293 }
294 let m = vfs.stat(&name).await.map_err(vfs_err)?;
295 let short = name.rsplit(['\\', '/']).next().unwrap_or("").to_string();
296 let qm = qmeta_from(&m);
297 query::encode_payload(level, &qm, &short)
298 .map(|d| (Vec::new(), d))
299 .ok_or(Status::INVALID_PARAMETER)
300}
301
302#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) async fn query_file(
305 io: &IoCtx<'_>,
306 tid: u16,
307 t: &t2::Trans2Req,
308) -> Result<(Vec<u8>, Vec<u8>), Status> {
309 if t.params.len() < 4 {
310 return Err(Status::INVALID_PARAMETER);
311 }
312 let fid = u16::from_le_bytes([t.params[0], t.params[1]]);
313 let level = u16::from_le_bytes([t.params[2], t.params[3]]);
314 let Some(h) = io.conn.handles.get(&fid) else {
315 return Err(Status::INVALID_HANDLE);
316 };
317 let name = h.path.rsplit(['\\', '/']).next().unwrap_or("").to_string();
318 let vfs = share_vfs(io, tid);
319 let m = vfs.stat(&h.path).await.unwrap_or_default();
320 let qm = smb_server_proto_smb1::query::QueryMeta {
321 times: [m.times[0].0, m.times[1].0, m.times[2].0, m.times[3].0],
322 attrs: m.attrs,
323 eof: m.eof,
324 alloc: m.alloc,
325 is_dir: m.is_dir,
326 };
327 query::encode_payload(level, &qm, &name)
328 .map(|d| (Vec::new(), d))
329 .ok_or(Status::INVALID_PARAMETER)
330}
331
332#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] async fn set_file(io: &mut IoCtx<'_>, t: &t2::Trans2Req) -> Result<(Vec<u8>, Vec<u8>), Status> {
337 if t.params.len() < 4 || t.data.is_empty() {
338 return Err(Status::INVALID_PARAMETER);
339 }
340 let fid = u16::from_le_bytes([t.params[0], t.params[1]]);
341 let level = u16::from_le_bytes([t.params[2], t.params[3]]);
342 let op = decode_set_op(level, &t.data)?;
343 let vfs = share_vfs(io, tid_placeholder());
344 let Some(h) = io.conn.handles.get_mut(&fid) else {
345 return Err(Status::INVALID_HANDLE);
346 };
347 vfs.set_info_open(h, &op).await.map_err(vfs_err)?;
348 Ok((Vec::new(), Vec::new()))
349}
350
351#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] async fn set_path(
354 io: &mut IoCtx<'_>,
355 tid: u16,
356 t: &t2::Trans2Req,
357) -> Result<(Vec<u8>, Vec<u8>), Status> {
358 if t.params.len() < 2 {
359 return Err(Status::INVALID_PARAMETER);
360 }
361 let level = u16::from_le_bytes([t.params[0], t.params[1]]);
362 let mut rd = smb_server_proto::buf::Reader::new(&t.params, 2);
363 let name = rd.zstring(t.unicode, t.param_base);
364 if name.is_empty() {
365 return Err(Status::INVALID_PARAMETER);
366 }
367 let op = decode_set_op(level, &t.data)?;
368 let vfs = share_vfs(io, tid);
369 vfs.set_info_path(&name, &op).await.map_err(vfs_err)?;
370 Ok((Vec::new(), Vec::new()))
371}
372
373#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn decode_set_op(level: u16, data: &[u8]) -> Result<smb_server_vfs::SetOp, Status> {
376 let r64 = |i: usize| -> u64 {
377 data.get(i..i + 8)
378 .map(|s| u64::from_le_bytes(s.try_into().unwrap()))
379 .unwrap_or(0)
380 };
381 match level {
382 0x101 | 0x1004 => {
383 let access = r64(8);
384 let ft = r64(16);
385 let sel = |v: u64| (v != 0 && v != u64::MAX).then_some(smb_server_proto::types::FileTime(v));
386 Ok(smb_server_vfs::SetOp::Basic {
387 access: sel(access),
388 write: sel(ft),
389 })
390 }
391 0x103 | 0x100B => Ok(smb_server_vfs::SetOp::Allocation(r64(0))),
392 0x104 | 0x100C => Ok(smb_server_vfs::SetOp::EndOfFile(r64(0))),
393 0x102 | 0x100D => Ok(smb_server_vfs::SetOp::Disposition {
394 delete: data.first().copied().unwrap_or(0) != 0,
395 }),
396 _ => Err(Status::INVALID_PARAMETER),
397 }
398}
399
400fn share_name<'a>(io: &'a IoCtx<'_>, tid: u16) -> Result<&'a str, Status> {
403 io.conn
404 .trees
405 .get(&tid)
406 .map(String::as_str)
407 .ok_or(Status::INVALID_HANDLE)
408}
409
410fn tid_placeholder() -> u16 {
411 0
412}
413fn vfs_err(e: smb_server_vfs::VfsError) -> Status {
414 use smb_server_vfs::VfsError as E;
415 match e {
416 E::NotFound => Status::OBJECT_PATH_NOT_FOUND,
417 E::AlreadyExists => Status::OBJECT_NAME_COLLISION,
418 E::AccessDenied => Status::ACCESS_DENIED,
419 E::DirectoryNotEmpty => Status::DIRECTORY_NOT_EMPTY,
420 E::InvalidArgument => Status::INVALID_PARAMETER,
421 E::NotSupported => Status::NOT_IMPLEMENTED,
422 E::StoppedOnSymlink { .. } => Status::STOPPED_ON_SYMLINK,
423 E::Io(_) => Status::UNSUCCESSFUL,
424 }
425}
426
427#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn qmeta_from(m: &smb_server_vfs::FileMeta) -> smb_server_proto_smb1::query::QueryMeta {
430 smb_server_proto_smb1::query::QueryMeta {
431 times: [m.times[0].0, m.times[1].0, m.times[2].0, m.times[3].0],
432 attrs: m.attrs,
433 eof: m.eof,
434 alloc: m.alloc,
435 is_dir: m.is_dir,
436 }
437}