1use smb_server_proto::types::Status;
4use smb_server_proto_smb1::consts;
5use smb_server_proto_smb1::header::RespBody;
6use smb_server_proto_smb1::legacy as legacy_codec;
7use smb_server_vfs::SetOp;
8
9use crate::cmds::{IoCtx, share_vfs};
10use crate::dispatch::ReqView;
11
12fn read_path(req: &ReqView<'_>) -> String {
13 let mut rd = smb_server_proto::buf::Reader::new(req.data, 0);
14 if !req.data.is_empty() && req.data[0] == consts::BUFFER_FORMAT_DATA {
15 rd.skip(1);
16 }
17 rd.zstring(req.unicode(), req.bc_off_abs + consts::BYTE_COUNT_LEN)
18}
19
20fn read_two_paths(req: &ReqView<'_>) -> (String, String) {
21 let data = req.data;
22 let mut rd = smb_server_proto::buf::Reader::new(data, 0);
23 let base = req.bc_off_abs + consts::BYTE_COUNT_LEN;
24 if (!data.is_empty() && data[0] == consts::BUFFER_FORMAT_DATA)
25 || (unicode(req) && !base.is_multiple_of(consts::WORD_LEN) && !data.is_empty())
26 {
27 rd.skip(1);
28 }
29 let a = rd.zstring(unicode(req), base);
30 if !rd.at_end() {
31 if unicode(req) && (base + rd.pos()) & 1 != 0 {
32 rd.skip(1);
33 }
34 if rd.pos() < data.len() && data[rd.pos()] == consts::BUFFER_FORMAT_DATA {
35 rd.skip(1);
36 }
37 }
38 let b = rd.zstring(unicode(req), base + rd.pos());
39 (a, b)
40}
41
42fn unicode(req: &ReqView<'_>) -> bool {
43 req.unicode()
44}
45
46pub async fn mkdir(
48 io: &mut IoCtx<'_>,
49 req: &ReqView<'_>,
50 bodies: &mut Vec<RespBody>,
51) -> Result<Status, Status> {
52 let vfs = share_vfs(io, req.hdr.tid);
53 vfs.mkdir(&read_path(req)).await.map_err(vfs_err)?;
54 bodies.push(RespBody::new(
55 consts::COM_CREATE_DIRECTORY,
56 Vec::new(),
57 Vec::new(),
58 ));
59 Ok(Status::SUCCESS)
60}
61
62pub async fn rmdir(
64 io: &mut IoCtx<'_>,
65 req: &ReqView<'_>,
66 bodies: &mut Vec<RespBody>,
67) -> Result<Status, Status> {
68 let vfs = share_vfs(io, req.hdr.tid);
69 vfs.rmdir(&read_path(req)).await.map_err(vfs_err)?;
70 bodies.push(RespBody::new(
71 consts::COM_DELETE_DIRECTORY,
72 Vec::new(),
73 Vec::new(),
74 ));
75 Ok(Status::SUCCESS)
76}
77
78pub async fn check_dir(
80 io: &mut IoCtx<'_>,
81 req: &ReqView<'_>,
82 bodies: &mut Vec<RespBody>,
83) -> Result<Status, Status> {
84 let vfs = share_vfs(io, req.hdr.tid);
85 vfs.check_dir(&read_path(req)).await.map_err(vfs_err)?;
86 bodies.push(RespBody::new(
87 consts::COM_CHECK_DIRECTORY,
88 Vec::new(),
89 Vec::new(),
90 ));
91 Ok(Status::SUCCESS)
92}
93
94pub async fn delete(
96 io: &mut IoCtx<'_>,
97 req: &ReqView<'_>,
98 bodies: &mut Vec<RespBody>,
99) -> Result<Status, Status> {
100 let vfs = share_vfs(io, req.hdr.tid);
101 let pattern = read_path(req);
102 let (dir_rel, name_pat) = split_pattern_pub(&pattern);
103
104 if !name_pat.contains('*') && !name_pat.contains('?') {
105 let rel = join_rel(&dir_rel, &name_pat);
106 vfs.unlink(&rel).await.map_err(vfs_err)?;
107 } else {
108 let deleted = vfs
109 .delete_pattern(&dir_rel, &name_pat)
110 .await
111 .map_err(vfs_err)?;
112 if !deleted {
113 return Err(Status::NO_SUCH_FILE);
114 }
115 }
116 bodies.push(RespBody::new(consts::COM_DELETE, Vec::new(), Vec::new()));
117 Ok(Status::SUCCESS)
118}
119
120pub async fn rename(
122 io: &mut IoCtx<'_>,
123 req: &ReqView<'_>,
124 bodies: &mut Vec<RespBody>,
125) -> Result<Status, Status> {
126 let vfs = share_vfs(io, req.hdr.tid);
127 let (old, new) = read_two_paths(req);
128 if new.is_empty() {
129 return Err(Status::INVALID_PARAMETER);
130 }
131 vfs.rename(&old, &new).await.map_err(vfs_err)?;
132 bodies.push(RespBody::new(consts::COM_RENAME, Vec::new(), Vec::new()));
133 Ok(Status::SUCCESS)
134}
135
136#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
140pub async fn query_info_legacy(
141 io: &mut IoCtx<'_>,
142 req: &ReqView<'_>,
143 bodies: &mut Vec<RespBody>,
144) -> Result<Status, Status> {
145 let vfs = share_vfs(io, req.hdr.tid);
146 let path = read_path(req);
147 let m = vfs.stat(&path).await.map_err(vfs_err)?;
148 let (secs, _) = smb_server_proto::types::FileTime(m.times[2].0).to_unix();
149
150 let secs = secs.max(0);
152 let days = secs / 86400;
153 let tod = secs % 86400;
154 let (year, month, day) = civil_from_days(days);
155 let hours = tod / 3600;
156 let minutes = (tod % 3600) / 60;
157 let seconds = tod % 60 / 2 * 2;
158 let dos_time = ((hours as u32) << 11) | ((minutes as u32) << 5) | (seconds as u32);
159 let dos_date = (((year - 1980).clamp(0, 127) as u32) << 9) | (month << 5) | day;
160
161 let attrs = if m.is_dir {
162 0x10u16
163 } else if m.attrs.0 & smb_server_proto::types::AttrFlags::READONLY != 0 {
164 0x21u16
165 } else {
166 0x20u16
167 };
168 let resp = legacy_codec::QueryInfoResp {
169 attrs,
170 dos_time: dos_date | dos_time,
171 size: m.eof as u32,
172 };
173 bodies.push(RespBody::new(
174 consts::COM_QUERY_INFORMATION,
175 resp.encode(),
176 Vec::new(),
177 ));
178 Ok(Status::SUCCESS)
179}
180
181#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
183pub async fn set_info_legacy(
184 io: &mut IoCtx<'_>,
185 req: &ReqView<'_>,
186 bodies: &mut Vec<RespBody>,
187) -> Result<Status, Status> {
188 let vfs = share_vfs(io, req.hdr.tid);
189 let path = read_path(req);
190 let w = req.words;
191 if w.len() >= 6 {
192 let utime = u32::from_le_bytes([w[2], w[3], w[4], w[5]]);
193 if utime != 0 && utime != u32::MAX {
194 let ft = smb_server_proto::types::FileTime((utime as u64 + 11_644_473_600) * 10_000_000);
195 vfs.set_info_path(
196 &path,
197 &SetOp::Basic {
198 access: None,
199 write: Some(ft),
200 },
201 )
202 .await
203 .map_err(vfs_err)?;
204 }
205 }
206 bodies.push(RespBody::new(
207 consts::COM_SET_INFORMATION,
208 Vec::new(),
209 Vec::new(),
210 ));
211 Ok(Status::SUCCESS)
212}
213
214pub fn split_pattern_pub(pattern: &str) -> (String, String) {
218 let p = pattern.trim_start_matches(['\\', '/']);
219 match p.rfind(['\\', '/']) {
220 Some(i) => (p[..i].to_string(), p[i + 1..].to_string()),
221 None => (String::new(), p.to_string()),
222 }
223}
224
225pub(crate) fn join_rel(base: &str, name: &str) -> String {
226 let name = name.trim_start_matches(['\\', '/']);
227 if base.is_empty() {
228 name.to_string()
229 } else {
230 format!("{}\\{}", base.trim_end_matches(['\\', '/']), name)
231 }
232}
233
234pub(crate) fn vfs_err(e: smb_server_vfs::VfsError) -> Status {
235 use smb_server_vfs::VfsError as E;
236 match e {
237 E::NotFound => Status::OBJECT_PATH_NOT_FOUND,
238 E::AlreadyExists => Status::OBJECT_NAME_COLLISION,
239 E::AccessDenied => Status::ACCESS_DENIED,
240 E::DirectoryNotEmpty => Status::DIRECTORY_NOT_EMPTY,
241 E::InvalidArgument => Status::INVALID_PARAMETER,
242 E::NotSupported => Status::NOT_IMPLEMENTED,
243 E::StoppedOnSymlink { .. } => Status::STOPPED_ON_SYMLINK,
244 E::Io(_) => Status::UNSUCCESSFUL,
245 }
246}
247
248#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
251fn civil_from_days(z: i64) -> (i64, u32, u32) {
252 let z = z + 719_468;
253 let era = z.div_euclid(146_097);
254 let doe = (z - era * 146_097) as u64;
255 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
256 let y = yoe as i64 + era * 400;
257 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
258 let mp = (5 * doy + 2) / 153;
259 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
260 let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
261 (y + if m <= 2 { 1 } else { 0 }, m, d)
262}