1use crate::header::{self, cmd};
6use crate::transport::SmbTransport;
7use crate::{msg, Result, SmbError};
8use ntlmssp::Ntlm;
9use rand::RngCore;
10
11pub enum Cred<'a> {
13 Password(&'a str),
14 NtHash([u8; 16]),
15}
16
17pub struct SmbClient {
18 transport: SmbTransport,
19 message_id: u64,
20 session_id: u64,
21 tree_id: u32,
22 sign_key: Option<[u8; 16]>,
23 dialect: u16,
24}
25
26impl SmbClient {
27 pub async fn connect(host: &str) -> Result<Self> {
28 Ok(SmbClient {
29 transport: SmbTransport::connect(host).await?,
30 message_id: 0,
31 session_id: 0,
32 tree_id: 0,
33 sign_key: None,
34 dialect: 0,
35 })
36 }
37
38 async fn call(&mut self, command: u16, body: &[u8]) -> Result<Vec<u8>> {
40 let mut m = header::build(
41 command,
42 self.message_id,
43 self.session_id,
44 self.tree_id,
45 self.sign_key.is_some(),
46 );
47 m.extend_from_slice(body);
48 if let Some(key) = &self.sign_key {
49 if self.dialect >= 0x0300 {
50 header::sign_v3(&mut m, key); } else {
52 header::sign(&mut m, key); }
54 }
55 self.message_id += 1;
56 self.transport.send(&m).await?;
57 let mut resp = self.transport.recv().await?;
58 while header::parse(&resp)
61 .map(|p| p.status)
62 .unwrap_or(crate::status::SUCCESS)
63 == crate::status::PENDING
64 {
65 resp = self.transport.recv().await?;
66 }
67 Ok(resp)
68 }
69
70 fn ok(resp: &[u8], expect: u16) -> Result<header::Parsed> {
71 let p = header::parse(resp)?;
72 if p.status != crate::status::SUCCESS {
73 return Err(SmbError::Status(p.status, expect));
74 }
75 Ok(p)
76 }
77
78 pub async fn probe_signing(&mut self) -> Result<(u16, bool)> {
81 let mut guid = [0u8; 16];
82 rand::thread_rng().fill_bytes(&mut guid);
83 let resp = self.call(cmd::NEGOTIATE, &msg::negotiate(&guid)).await?;
84 Self::ok(&resp, cmd::NEGOTIATE)?;
85 let security_mode = u16::from_le_bytes([resp[66], resp[67]]);
87 let dialect = u16::from_le_bytes([resp[68], resp[69]]);
88 Ok((dialect, security_mode & 0x0002 != 0)) }
90
91 pub async fn login(
93 &mut self,
94 host: &str,
95 domain: &str,
96 user: &str,
97 password: &str,
98 ) -> Result<()> {
99 self.login_cred(host, domain, user, Cred::Password(password))
100 .await
101 }
102
103 pub async fn login_kerberos(&mut self, gss_blob: &[u8], session_key: &[u8; 16]) -> Result<()> {
107 let mut guid = [0u8; 16];
108 rand::thread_rng().fill_bytes(&mut guid);
109 let resp = self.call(cmd::NEGOTIATE, &msg::negotiate(&guid)).await?;
110 Self::ok(&resp, cmd::NEGOTIATE)?;
111 self.dialect = u16::from_le_bytes([resp[68], resp[69]]);
112
113 let key = if self.dialect >= 0x0300 {
114 header::kdf_signing_key(session_key)
115 } else {
116 *session_key
117 };
118 let resp = self
120 .call(cmd::SESSION_SETUP, &msg::session_setup(gss_blob))
121 .await?;
122 let p = header::parse(&resp)?;
123 self.session_id = p.session_id;
124 if p.status != crate::status::SUCCESS {
125 return Err(SmbError::Status(p.status, cmd::SESSION_SETUP));
126 }
127 self.sign_key = Some(key); Ok(())
129 }
130
131 pub async fn login_null(&mut self, host: &str) -> Result<()> {
143 self.login_cred(host, "", "", Cred::Password("")).await
144 }
145
146 pub async fn login_hash(
148 &mut self,
149 host: &str,
150 domain: &str,
151 user: &str,
152 nt: &[u8; 16],
153 ) -> Result<()> {
154 self.login_cred(host, domain, user, Cred::NtHash(*nt)).await
155 }
156
157 pub async fn login_cred(
159 &mut self,
160 host: &str,
161 domain: &str,
162 user: &str,
163 cred: Cred<'_>,
164 ) -> Result<()> {
165 let mut guid = [0u8; 16];
167 rand::thread_rng().fill_bytes(&mut guid);
168 let resp = self.call(cmd::NEGOTIATE, &msg::negotiate(&guid)).await?;
169 Self::ok(&resp, cmd::NEGOTIATE)?;
170 self.dialect = u16::from_le_bytes([resp[68], resp[69]]);
172
173 let ntlm = Ntlm::new();
175 let init = crate::spnego::negotiate_init(ntlm.negotiate());
176 let resp = self
177 .call(cmd::SESSION_SETUP, &msg::session_setup(&init))
178 .await?;
179 let p = header::parse(&resp)?;
180 if p.status != crate::status::MORE_PROCESSING_REQUIRED {
181 return Err(SmbError::Status(p.status, cmd::SESSION_SETUP));
182 }
183 self.session_id = p.session_id;
184
185 let blob = msg::session_setup_token(&resp)?;
187 let challenge = crate::spnego::find_ntlm(&blob).ok_or(SmbError::BadToken)?;
188
189 let (type3, session_key) = match cred {
191 Cred::Password(pw) => ntlm.authenticate(challenge, domain, user, pw, host),
192 Cred::NtHash(nt) => ntlm.authenticate_hash(challenge, domain, user, &nt, host),
193 }
194 .map_err(|e| SmbError::Ntlm(e.to_string()))?;
195
196 let key = if self.dialect >= 0x0300 {
199 header::kdf_signing_key(&session_key)
200 } else {
201 session_key
202 };
203 if self.dialect >= 0x0300 {
206 self.sign_key = Some(key);
207 }
208 let token = crate::spnego::negotiate_resp(&type3);
209 let resp = self
210 .call(cmd::SESSION_SETUP, &msg::session_setup(&token))
211 .await?;
212 Self::ok(&resp, cmd::SESSION_SETUP)?;
213 self.sign_key = Some(key); Ok(())
215 }
216
217 pub async fn tree_connect(&mut self, unc: &str) -> Result<()> {
219 let resp = self
220 .call(cmd::TREE_CONNECT, &msg::tree_connect(unc))
221 .await?;
222 let p = Self::ok(&resp, cmd::TREE_CONNECT)?;
223 self.tree_id = p.tree_id;
224 Ok(())
225 }
226
227 pub async fn open_pipe(&mut self, name: &str) -> Result<[u8; 16]> {
229 let resp = self.call(cmd::CREATE, &msg::create_pipe(name)).await?;
230 Self::ok(&resp, cmd::CREATE)?;
231 msg::create_file_id(&resp)
232 }
233
234 pub async fn read_pipe(&mut self, file_id: &[u8; 16], max: u32) -> Result<Vec<u8>> {
237 let resp = self
238 .call(cmd::READ, &msg::read_req(file_id, 0, max))
239 .await?;
240 let p = header::parse(&resp)?;
241 if p.status != crate::status::SUCCESS {
242 return Ok(Vec::new()); }
244 msg::read_output(&resp)
245 }
246
247 pub async fn write_pipe(&mut self, file_id: &[u8; 16], data: &[u8]) -> Result<()> {
249 let resp = self
250 .call(cmd::WRITE, &msg::write_req(file_id, 0, data))
251 .await?;
252 Self::ok(&resp, cmd::WRITE)?;
253 Ok(())
254 }
255
256 pub async fn transact(&mut self, file_id: &[u8; 16], data: &[u8]) -> Result<Vec<u8>> {
258 let resp = self
259 .call(cmd::IOCTL, &msg::ioctl_transceive(file_id, data))
260 .await?;
261 Self::ok(&resp, cmd::IOCTL)?;
262 msg::ioctl_output(&resp)
263 }
264
265 pub async fn read_file_delete(&mut self, path: &str) -> Result<Vec<u8>> {
270 use crate::status;
271 const ACCESS: u32 = 0x0013_0081; const SHARE: u32 = 0x0000_0007; const OPEN: u32 = 0x0000_0001; const OPTS: u32 = 0x0000_1060; let mut file_id = None;
283 let mut last = status::OBJECT_NAME_NOT_FOUND;
284 for attempt in 0..12 {
285 let resp = self
286 .call(
287 cmd::CREATE,
288 &msg::create_file(path, ACCESS, SHARE, OPEN, OPTS),
289 )
290 .await?;
291 let p = header::parse(&resp)?;
292 if p.status == status::SUCCESS {
293 file_id = Some(msg::create_file_id(&resp)?);
294 break;
295 }
296 last = p.status;
297 if p.status != status::OBJECT_NAME_NOT_FOUND && p.status != status::SHARING_VIOLATION {
298 return Err(SmbError::Status(p.status, cmd::CREATE));
299 }
300 if attempt < 11 {
301 tokio::time::sleep(std::time::Duration::from_millis(250)).await;
302 }
303 }
304 let file_id = file_id.ok_or(SmbError::Status(last, cmd::CREATE))?;
305
306 let mut data = Vec::new();
308 loop {
309 let resp = self
310 .call(
311 cmd::READ,
312 &msg::read_req(&file_id, data.len() as u64, 0x0001_0000),
313 )
314 .await?;
315 let p = header::parse(&resp)?;
316 if p.status == status::END_OF_FILE {
317 break;
318 }
319 if p.status != status::SUCCESS {
320 break;
321 }
322 let chunk = msg::read_output(&resp)?;
323 if chunk.is_empty() {
324 break;
325 }
326 data.extend_from_slice(&chunk);
327 if chunk.len() < 0x0001_0000 {
328 break;
329 }
330 }
331 let _ = self.call(cmd::CLOSE, &msg::close_req(&file_id)).await;
333 Ok(data)
334 }
335
336 pub async fn list_directory(&mut self, path: &str) -> Result<Vec<msg::DirEntry>> {
342 use crate::status;
343 const ACCESS: u32 = 0x0010_0081;
345 const SHARE: u32 = 0x0000_0007; const OPEN: u32 = 0x0000_0001; const OPTS: u32 = 0x0000_0021; let resp = self
350 .call(
351 cmd::CREATE,
352 &msg::create_file(path, ACCESS, SHARE, OPEN, OPTS),
353 )
354 .await?;
355 Self::ok(&resp, cmd::CREATE)?;
356 let dir_id = msg::create_file_id(&resp)?;
357
358 let mut entries = Vec::new();
359 for _ in 0..4096 {
362 let resp = self
363 .call(
364 cmd::QUERY_DIRECTORY,
365 &msg::query_directory_req(&dir_id, "*", 0x0001_0000),
366 )
367 .await?;
368 let p = header::parse(&resp)?;
369 if p.status == status::NO_MORE_FILES {
370 break;
371 }
372 if p.status != status::SUCCESS {
373 let _ = self.call(cmd::CLOSE, &msg::close_req(&dir_id)).await;
374 return Err(SmbError::Status(p.status, cmd::QUERY_DIRECTORY));
375 }
376 let batch = msg::parse_directory_info(&resp)?;
377 if batch.is_empty() {
378 break;
379 }
380 entries.extend(batch);
381 }
382 let _ = self.call(cmd::CLOSE, &msg::close_req(&dir_id)).await;
383 Ok(entries)
384 }
385
386 pub async fn read_file(&mut self, path: &str) -> Result<Vec<u8>> {
391 use crate::status;
392 const ACCESS: u32 = 0x0010_0081;
394 const SHARE: u32 = 0x0000_0001; const OPEN: u32 = 0x0000_0001; const OPTS: u32 = 0x0000_0060; let resp = self
399 .call(
400 cmd::CREATE,
401 &msg::create_file(path, ACCESS, SHARE, OPEN, OPTS),
402 )
403 .await?;
404 Self::ok(&resp, cmd::CREATE)?;
405 let file_id = msg::create_file_id(&resp)?;
406
407 let mut data = Vec::new();
408 loop {
409 let resp = self
410 .call(
411 cmd::READ,
412 &msg::read_req(&file_id, data.len() as u64, 0x0001_0000),
413 )
414 .await?;
415 let p = header::parse(&resp)?;
416 if p.status == status::END_OF_FILE || p.status != status::SUCCESS {
417 break;
418 }
419 let chunk = msg::read_output(&resp)?;
420 if chunk.is_empty() {
421 break;
422 }
423 data.extend_from_slice(&chunk);
424 if chunk.len() < 0x0001_0000 {
425 break;
426 }
427 }
428 let _ = self.call(cmd::CLOSE, &msg::close_req(&file_id)).await;
429 Ok(data)
430 }
431}