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_hash(
133 &mut self,
134 host: &str,
135 domain: &str,
136 user: &str,
137 nt: &[u8; 16],
138 ) -> Result<()> {
139 self.login_cred(host, domain, user, Cred::NtHash(*nt)).await
140 }
141
142 pub async fn login_cred(
144 &mut self,
145 host: &str,
146 domain: &str,
147 user: &str,
148 cred: Cred<'_>,
149 ) -> Result<()> {
150 let mut guid = [0u8; 16];
152 rand::thread_rng().fill_bytes(&mut guid);
153 let resp = self.call(cmd::NEGOTIATE, &msg::negotiate(&guid)).await?;
154 Self::ok(&resp, cmd::NEGOTIATE)?;
155 self.dialect = u16::from_le_bytes([resp[68], resp[69]]);
157
158 let ntlm = Ntlm::new();
160 let init = crate::spnego::negotiate_init(ntlm.negotiate());
161 let resp = self
162 .call(cmd::SESSION_SETUP, &msg::session_setup(&init))
163 .await?;
164 let p = header::parse(&resp)?;
165 if p.status != crate::status::MORE_PROCESSING_REQUIRED {
166 return Err(SmbError::Status(p.status, cmd::SESSION_SETUP));
167 }
168 self.session_id = p.session_id;
169
170 let blob = msg::session_setup_token(&resp)?;
172 let challenge = crate::spnego::find_ntlm(&blob).ok_or(SmbError::BadToken)?;
173
174 let (type3, session_key) = match cred {
176 Cred::Password(pw) => ntlm.authenticate(challenge, domain, user, pw, host),
177 Cred::NtHash(nt) => ntlm.authenticate_hash(challenge, domain, user, &nt, host),
178 }
179 .map_err(|e| SmbError::Ntlm(e.to_string()))?;
180
181 let key = if self.dialect >= 0x0300 {
184 header::kdf_signing_key(&session_key)
185 } else {
186 session_key
187 };
188 if self.dialect >= 0x0300 {
191 self.sign_key = Some(key);
192 }
193 let token = crate::spnego::negotiate_resp(&type3);
194 let resp = self
195 .call(cmd::SESSION_SETUP, &msg::session_setup(&token))
196 .await?;
197 Self::ok(&resp, cmd::SESSION_SETUP)?;
198 self.sign_key = Some(key); Ok(())
200 }
201
202 pub async fn tree_connect(&mut self, unc: &str) -> Result<()> {
204 let resp = self
205 .call(cmd::TREE_CONNECT, &msg::tree_connect(unc))
206 .await?;
207 let p = Self::ok(&resp, cmd::TREE_CONNECT)?;
208 self.tree_id = p.tree_id;
209 Ok(())
210 }
211
212 pub async fn open_pipe(&mut self, name: &str) -> Result<[u8; 16]> {
214 let resp = self.call(cmd::CREATE, &msg::create_pipe(name)).await?;
215 Self::ok(&resp, cmd::CREATE)?;
216 msg::create_file_id(&resp)
217 }
218
219 pub async fn read_pipe(&mut self, file_id: &[u8; 16], max: u32) -> Result<Vec<u8>> {
222 let resp = self
223 .call(cmd::READ, &msg::read_req(file_id, 0, max))
224 .await?;
225 let p = header::parse(&resp)?;
226 if p.status != crate::status::SUCCESS {
227 return Ok(Vec::new()); }
229 msg::read_output(&resp)
230 }
231
232 pub async fn write_pipe(&mut self, file_id: &[u8; 16], data: &[u8]) -> Result<()> {
234 let resp = self
235 .call(cmd::WRITE, &msg::write_req(file_id, 0, data))
236 .await?;
237 Self::ok(&resp, cmd::WRITE)?;
238 Ok(())
239 }
240
241 pub async fn transact(&mut self, file_id: &[u8; 16], data: &[u8]) -> Result<Vec<u8>> {
243 let resp = self
244 .call(cmd::IOCTL, &msg::ioctl_transceive(file_id, data))
245 .await?;
246 Self::ok(&resp, cmd::IOCTL)?;
247 msg::ioctl_output(&resp)
248 }
249
250 pub async fn read_file_delete(&mut self, path: &str) -> Result<Vec<u8>> {
255 use crate::status;
256 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;
265 let mut last = status::OBJECT_NAME_NOT_FOUND;
266 for attempt in 0..40 {
267 let resp = self
268 .call(
269 cmd::CREATE,
270 &msg::create_file(path, ACCESS, SHARE, OPEN, OPTS),
271 )
272 .await?;
273 let p = header::parse(&resp)?;
274 if p.status == status::SUCCESS {
275 file_id = Some(msg::create_file_id(&resp)?);
276 break;
277 }
278 last = p.status;
279 if p.status != status::OBJECT_NAME_NOT_FOUND && p.status != status::SHARING_VIOLATION {
280 return Err(SmbError::Status(p.status, cmd::CREATE));
281 }
282 if attempt < 39 {
283 tokio::time::sleep(std::time::Duration::from_millis(250)).await;
284 }
285 }
286 let file_id = file_id.ok_or(SmbError::Status(last, cmd::CREATE))?;
287
288 let mut data = Vec::new();
290 loop {
291 let resp = self
292 .call(
293 cmd::READ,
294 &msg::read_req(&file_id, data.len() as u64, 0x0001_0000),
295 )
296 .await?;
297 let p = header::parse(&resp)?;
298 if p.status == status::END_OF_FILE {
299 break;
300 }
301 if p.status != status::SUCCESS {
302 break;
303 }
304 let chunk = msg::read_output(&resp)?;
305 if chunk.is_empty() {
306 break;
307 }
308 data.extend_from_slice(&chunk);
309 if chunk.len() < 0x0001_0000 {
310 break;
311 }
312 }
313 let _ = self.call(cmd::CLOSE, &msg::close_req(&file_id)).await;
315 Ok(data)
316 }
317}