1use std::collections::{HashMap, VecDeque};
10use std::sync::Arc;
11use std::sync::atomic::{AtomicU64, Ordering};
12
13use metrics::{counter, gauge};
14use tokio::sync::{mpsc, oneshot};
15
16use smb_server_proto::types::Status;
17use smb_server_proto_smb2::commands as c;
18use smb_server_proto_smb2::consts::{aead, hdr, hdr_flags};
19use smb_server_proto_smb2::info;
20use smb_server_proto_smb2::session_setup as ss;
21use smb_server_transport::Transport;
22
23use crate::state::ServerShared;
24
25const MAX_TRANSACT_SIZE: u32 = 1024 * 1024;
28
29#[cfg(not(test))]
33const LEASE_ACK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
34#[cfg(test)]
35const LEASE_ACK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(50);
36
37pub struct Smb2Conn {
39 pub dialect: Option<u16>,
41 pub session_id: u64,
43 pub authenticated: bool,
45 pub challenge: [u8; 8],
47 pub user: String,
49 pub guest: bool,
51 pub session_key: Option<[u8; 16]>,
54 pub signing_key: Option<[u8; 16]>,
56 pub signing_algo: u16,
59 pub advertised_caps: u32,
62 pub client_credits: u32,
65 pub preauth_hash: [u8; 64],
67 pub cipher: Option<u16>,
69 pub enc_keys: Option<([u8; 32], [u8; 32])>,
73 pub encrypt_data: bool,
76 pub peer_encrypts: bool,
80 pub ntlm_blobs: Option<(Vec<u8>, Vec<u8>)>,
82 pub ntlm_targ: Option<Vec<u8>>,
84 pub raw_ntlm: bool,
89 pub lease_keys: HashMap<[u8; 16], [u8; 16]>,
93 pub pipes: HashMap<[u8; 16], crate::srvsvc::Pipe>,
95 pub searches: HashMap<[u8; 16], VecDeque<info::FindEntry>>,
97 pub integrity: HashMap<[u8; 16], (u16, u32)>,
100 pub outbound: mpsc::Sender<Vec<u8>>,
104 pub next_async_id: u64,
107 pub async_cancels: HashMap<u64, oneshot::Sender<Status>>,
111 pub async_msgids: HashMap<u64, u64>,
115 pub async_by_file: HashMap<[u8; 16], Vec<u64>>,
119 pub resume_keys: HashMap<[u8; 24], [u8; 16]>,
122 pub offload_tokens: HashMap<[u8; 16], Vec<u8>>,
126 pub durable: HashMap<[u8; 16], crate::state::DurableEntry>,
129 pub compress_algo: Option<u16>,
132 pub compress_chained: bool,
135 pub compress_response: bool,
139 pub client_guid: [u8; 16],
142 pub client_security_mode: u16,
144 pub client_capabilities: u32,
146 pub supports_notifications: bool,
150 pub client_dialects: Vec<u16>,
152 pub disconnect: bool,
155 pub chain_fid: Option<[u8; 16]>,
159 pub symlink_error: Option<Vec<u8>>,
162 pub resp_tree_id: Option<u32>,
166 pub seal_current: bool,
170 pub req_encrypted: bool,
174 pub binding: bool,
178 pub scope: Option<crate::session_scope::ScopeRef>,
182}
183
184impl Smb2Conn {
185 pub fn new(challenge: [u8; 8], outbound: mpsc::Sender<Vec<u8>>) -> Self {
188 Smb2Conn {
189 dialect: None,
190 session_id: 0,
191 authenticated: false,
192 challenge,
193 user: String::new(),
194 guest: false,
195 session_key: None,
196 signing_key: None,
197 signing_algo: smb_server_proto_smb2::negotiate::ctx_type::SIGNING_AES128_CMAC,
198 advertised_caps: 0,
199 client_credits: 0,
200 preauth_hash: [0u8; 64],
201 cipher: None,
202 enc_keys: None,
203 encrypt_data: false,
204 peer_encrypts: false,
205 ntlm_blobs: None,
206 ntlm_targ: None,
207 raw_ntlm: false,
208 lease_keys: HashMap::new(),
209 pipes: HashMap::new(),
210 searches: HashMap::new(),
211 integrity: HashMap::new(),
212 outbound,
213 next_async_id: 1,
214 async_cancels: HashMap::new(),
215 async_msgids: HashMap::new(),
216 async_by_file: HashMap::new(),
217 resume_keys: HashMap::new(),
218 offload_tokens: HashMap::new(),
219 durable: HashMap::new(),
220 compress_algo: None,
221 compress_chained: false,
222 compress_response: false,
223 client_guid: [0u8; 16],
224 client_security_mode: 0,
225 client_capabilities: 0,
226 supports_notifications: false,
227 client_dialects: Vec::new(),
228 disconnect: false,
229 chain_fid: None,
230 symlink_error: None,
231 resp_tree_id: None,
232 seal_current: false,
233 req_encrypted: false,
234 binding: false,
235 scope: Some(crate::session_scope::detached()),
236 }
237 }
238
239 pub fn outbound(&self) -> mpsc::Sender<Vec<u8>> {
241 self.outbound.clone()
242 }
243
244 pub(crate) fn tree_name(&self, tid: u32) -> Option<String> {
246 self.scope.as_ref()?.borrow().trees.get(&tid).cloned()
247 }
248
249 pub(crate) fn tree_exists(&self, tid: u32) -> bool {
251 self.scope
252 .as_ref()
253 .is_some_and(|s| s.borrow().trees.contains_key(&tid))
254 }
255
256 pub(crate) fn tree_insert(&self, tid: u32, name: String) {
258 if let Some(s) = self.scope.as_ref() {
259 s.borrow_mut().trees.insert(tid, name);
260 }
261 }
262
263 pub(crate) fn tree_remove(&self, tid: u32) {
265 if let Some(s) = self.scope.as_ref() {
266 s.borrow_mut().trees.remove(&tid);
267 }
268 }
269
270 pub(crate) fn handle_exists(&self, fid: &[u8; 16]) -> bool {
272 self.scope
273 .as_ref()
274 .is_some_and(|s| s.borrow().handles.contains_key(fid))
275 }
276
277 pub(crate) fn handle_insert(&self, fid: [u8; 16], open: Box<smb_server_vfs::OpenFile>) {
279 if let Some(s) = self.scope.as_ref() {
280 s.borrow_mut().handles.insert(fid, open);
281 }
282 }
283
284 pub(crate) fn handle_take(&self, fid: &[u8; 16]) -> Option<Box<smb_server_vfs::OpenFile>> {
289 self.scope.as_ref()?.borrow_mut().handles.remove(fid)
290 }
291
292 pub(crate) fn with_handle<R>(
294 &self,
295 fid: &[u8; 16],
296 f: impl FnOnce(&smb_server_vfs::OpenFile) -> R,
297 ) -> Option<R> {
298 Some(f(self.scope.as_ref()?.borrow().handles.get(fid)?))
299 }
300
301 pub fn alloc_async_id(&mut self) -> u64 {
303 let id = self.next_async_id;
304 self.next_async_id += 1;
305 id
306 }
307}
308
309fn next_session_id() -> u64 {
310 static S: AtomicU64 = AtomicU64::new(1);
311 S.fetch_add(1, Ordering::Relaxed)
312}
313
314#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn next_file_id() -> [u8; 16] {
317 static F: AtomicU64 = AtomicU64::new(0x2000);
318 let mut fid = [0u8; 16];
319 fid[..8].copy_from_slice(&F.fetch_add(1, Ordering::Relaxed).to_le_bytes());
320 fid
321}
322
323#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn next_tree_id() -> u32 {
326 static T: AtomicU64 = AtomicU64::new(1);
327 (T.fetch_add(1, Ordering::Relaxed) & 0x7fff_ffff) as u32
328}
329
330#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub async fn serve_client(
334 server: Arc<ServerShared>,
335 transport: Box<dyn Transport>,
336 mut pending: Option<Vec<u8>>,
337) {
338 let (mut reader, writer) = transport.split();
339 let (out_tx, out_rx) = mpsc::channel::<Vec<u8>>(64);
340 let writer_task = tokio_uring::spawn(crate::dispatch::writer_loop(writer, out_rx));
341
342 {
343 let mut conn = Smb2Conn::new(crate::dispatch::rand_challenge_pub(), out_tx.clone());
344 loop {
345 let frame = if let Some(f) = pending.take() {
346 f
347 } else {
348 match reader.recv().await {
349 Ok(Some(f)) => f.0,
350 _ => break,
351 }
352 };
353 if frame.len() < 64 || frame[0..4] != smb_server_proto_smb2::SMB2_MAGIC {
354 continue;
355 }
356 if let Some(resp) = process_frame(&server, &mut conn, &frame).await
357 && out_tx.send(resp).await.is_err() {
358 break;
359 }
360 if conn.disconnect {
361 break;
362 }
363 }
364 }
365 drop(out_tx);
366 let _ = writer_task.await;
367}
368
369#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub fn handle_multiprotocol_negotiate(buf: &[u8], guid: &[u8; 16]) -> Option<Vec<u8>> {
373 let (hdr, _wc_off) = smb_server_proto_smb1::header::parse_header(buf)?;
374 let _ = hdr;
375 let dialect = if buf.windows(5).any(|w| w == b"2.???") {
380 smb_server_proto_smb2::negotiate::DIALECT_WILDCARD
381 } else {
382 smb_server_proto_smb2::negotiate::DIALECT_202
383 };
384 let h2 = smb_server_proto_smb2::Header2 {
385 credit_charge: 0,
386 status: 0,
387 command: 0,
388 credits: 1,
389 flags: 1,
390 next_command: 0,
391 message_id: 0,
392 tree_id: 0,
393 session_id: 0,
394 signature: [0u8; 16],
395 };
396 Some(response(
397 &h2,
398 Status::SUCCESS,
399 smb_server_proto_smb2::negotiate::build_response(
400 dialect,
401 guid,
402 smb_server_proto::types::FileTime::now().0,
403 ),
404 0,
405 ))
406}
407
408#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) async fn process_frame(
413 server: &Arc<ServerShared>,
414 conn: &mut Smb2Conn,
415 buf: &[u8],
416) -> Option<Vec<u8>> {
417 let plaintext: Vec<u8>;
420 let mut buf = buf;
421 let mut request_encrypted = false;
422 if buf.len() >= 4 && buf[..4] == smb_server_proto_smb2::commands::TF_MAGIC {
423 let pt = decrypt_transform(conn, buf)?;
424 plaintext = if pt.len() >= 4 && pt[..4] == smb_server_proto_smb2::compress::PROTOCOL_ID {
427 smb_server_proto_smb2::compress::decompress_message(&pt)?
428 } else {
429 pt
430 };
431 buf = &plaintext;
432 request_encrypted = true;
433 conn.peer_encrypts = true;
434 }
435 conn.seal_current = false;
436 conn.req_encrypted = request_encrypted;
437
438 let mut parts: Vec<(Vec<u8>, bool)> = Vec::new(); let mut related_flags: Vec<bool> = Vec::new(); let mut work: Vec<u8> = buf.to_vec();
443 conn.chain_fid = None;
444 let mut off = 0usize;
445 loop {
446 if off + hdr::LEN > work.len() {
447 break;
448 }
449 let flags = g32(&work, off + hdr::FLAGS);
453 if flags & hdr_flags::RELATED_OPERATIONS != 0
454 && let Some(fid) = conn.chain_fid {
455 let cmd = g16(&work, off + hdr::COMMAND);
456 if let Some(foff) = file_id_body_offset(cmd) {
457 let abs = off + hdr::LEN + foff;
458 if abs + c::FileId::LEN <= work.len()
459 && work[abs..abs + c::FileId::LEN] == c::FileId::WILDCARD
460 {
461 work[abs..abs + c::FileId::LEN].copy_from_slice(&fid);
462 }
463 }
464 }
465 let rest = &work[off..];
466 #[cfg_attr(not(feature = "lib"), allow(unused_variables))]
467 let (single, may_wrap) = process_single(server, conn, rest).await?;
468 let granted = g16(&single, hdr::CREDIT) as u32;
472 conn.client_credits = conn.client_credits.saturating_add(granted);
473 #[cfg(not(feature = "lib"))]
474 let may_wrap = false; parts.push((single, may_wrap));
476 related_flags.push(flags & hdr_flags::RELATED_OPERATIONS != 0);
477
478 let next = g32(&work, off + hdr::NEXT_COMMAND) as usize;
479 if next == 0 || next >= work.len() - off {
480 break;
481 }
482 off += next;
483 }
484 if parts.is_empty() {
485 return None;
486 }
487
488 let mut out: Vec<u8> =
491 Vec::with_capacity(parts.iter().map(|p| p.0.len() + (hdr::ALIGN - 1)).sum());
492 let mut starts = Vec::with_capacity(parts.len());
493 for (p, _) in &parts {
494 while !out.len().is_multiple_of(hdr::ALIGN) {
495 out.push(0);
496 }
497 starts.push(out.len());
498 out.extend_from_slice(p);
499 }
500 for w in 0..starts.len().saturating_sub(1) {
501 let next = (starts[w + 1] - starts[w]) as u32;
502 let bytes = next.to_le_bytes();
503 let pos = starts[w] + hdr::NEXT_COMMAND;
504 out[pos..pos + bytes.len()].copy_from_slice(&bytes);
505 }
506 for w in 1..starts.len() {
510 if related_flags[w] {
511 out[starts[w] + hdr::FLAGS] |= hdr_flags::RELATED_OPERATIONS as u8;
512 }
513 }
514
515 let first_tid = if work.len() >= hdr::LEN {
520 g32(&work, hdr::TREE_ID)
521 } else {
522 0
523 };
524 let tree_requires_seal = conn.seal_current
525 || conn
526 .tree_name(first_tid)
527 .and_then(|n| server.shares.get(&n))
528 .is_some_and(|s| s.encrypt);
529 if (conn.encrypt_data || request_encrypted || tree_requires_seal)
530 && parts.iter().all(|(_, w)| *w)
531 {
532 for &s in &starts {
535 out[s + hdr::FLAGS] &= !(hdr_flags::SIGNED as u8);
536 out[s + hdr::SIGNATURE..s + hdr::LEN].fill(0);
537 }
538 let mut payload = out;
541 if let Some(algo) = conn.compress_algo
542 && conn.compress_response {
543 let packed = if conn.compress_chained {
544 smb_server_proto_smb2::compress::compress_message_chained(&payload, algo)
545 } else {
546 smb_server_proto_smb2::compress::compress_message(&payload, algo)
547 };
548 if let Some(packed) = packed {
549 payload = packed;
550 }
551 }
552 let sealed = encrypt_response(conn, &payload)?;
553 return Some(sealed);
554 }
555 Some(out)
556}
557
558#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn file_id_body_offset(command: u16) -> Option<usize> {
563 use smb_server_proto_smb2::session_setup::cmd as c;
564 match command {
565 c::CLOSE | c::FLUSH | c::LOCK | c::IOCTL | c::QUERY_DIRECTORY | c::CHANGE_NOTIFY => Some(8),
566 c::READ | c::WRITE | c::SET_INFO => Some(16),
567 c::QUERY_INFO => Some(24),
568 _ => None,
569 }
570}
571
572#[cfg(not(feature = "handrolled"))]
574fn cipher_is_gcm(cipher: u16) -> bool {
575 use smb_server_proto_smb2::negotiate::ctx_type::{AES128_GCM, AES256_GCM};
576 matches!(cipher, AES128_GCM | AES256_GCM)
577}
578
579fn cipher_is_256(cipher: u16) -> bool {
581 use smb_server_proto_smb2::negotiate::ctx_type::{AES256_CCM, AES256_GCM};
582 matches!(cipher, AES256_GCM | AES256_CCM)
583}
584
585fn cipher_key_len(cipher: u16) -> usize {
587 if cipher_is_256(cipher) { aead::AES256_KEY_LEN } else { aead::AES128_KEY_LEN }
588}
589
590#[cfg(not(feature = "lib"))]
593fn encrypt_response(conn: &Smb2Conn, msg: &[u8]) -> Option<Vec<u8>> {
594 let _ = (conn, msg);
595 None
596}
597#[cfg(not(feature = "lib"))]
598fn seal_pdu(
599 session_id: u64,
600 enc_keys: ([u8; 32], [u8; 32]),
601 cipher: u16,
602 msg: &[u8],
603) -> Option<Vec<u8>> {
604 let _ = (session_id, enc_keys, cipher, msg);
605 None
606}
607#[cfg(not(feature = "handrolled"))]
608fn encrypt_response(conn: &Smb2Conn, msg: &[u8]) -> Option<Vec<u8>> {
609 seal_pdu(conn.session_id, conn.enc_keys?, conn.cipher?, msg)
610}
611#[cfg(not(feature = "handrolled"))]
615fn seal_pdu(
616 session_id: u64,
617 enc_keys: ([u8; 32], [u8; 32]),
618 cipher: u16,
619 msg: &[u8],
620) -> Option<Vec<u8>> {
621 let (_c2s, s2c) = enc_keys;
622 let gcm = cipher_is_gcm(cipher);
623 let iv_size = if gcm {
624 aead::GCM_NONCE_LEN
625 } else {
626 aead::CCM_NONCE_LEN
627 };
628
629 let mut nonce_field = [0u8; 16];
630 nonce_field[..iv_size].copy_from_slice(&crate::dispatch::rand_bytes(iv_size));
631
632 let mut tf = smb_server_proto_smb2::commands::build_transform(session_id, &nonce_field, msg.len());
633 let aad = &tf[smb_server_proto_smb2::commands::tf_off::NONCE..];
635 let sealed = match (gcm, cipher_is_256(cipher)) {
636 (true, true) => smb_server_auth::crypto::aes256gcm_seal(
637 &s2c,
638 nonce_field[..aead::GCM_NONCE_LEN].try_into().ok()?,
639 aad,
640 msg,
641 ),
642 (true, false) => smb_server_auth::crypto::aes128gcm_seal(
643 s2c[..aead::AES128_KEY_LEN].try_into().ok()?,
644 nonce_field[..aead::GCM_NONCE_LEN].try_into().ok()?,
645 aad,
646 msg,
647 ),
648 (false, true) => smb_server_auth::crypto::aes256ccm_seal(
649 &s2c,
650 nonce_field[..aead::CCM_NONCE_LEN].try_into().ok()?,
651 aad,
652 msg,
653 ),
654 (false, false) => smb_server_auth::crypto::aes128ccm_seal(
655 s2c[..aead::AES128_KEY_LEN].try_into().ok()?,
656 nonce_field[..aead::CCM_NONCE_LEN].try_into().ok()?,
657 aad,
658 msg,
659 ),
660 };
661 let tag_at = smb_server_proto_smb2::commands::tf_off::SIGNATURE;
663 tf[tag_at..tag_at + aead::TAG_LEN].copy_from_slice(&sealed[sealed.len() - aead::TAG_LEN..]);
664 let mut frame = tf;
665 frame.extend_from_slice(&sealed[..sealed.len() - aead::TAG_LEN]);
666 counter!("smb_encrypted_responses_total").increment(1);
667 counter!("smb_encrypted_bytes_total").increment(msg.len() as u64);
668 Some(frame)
669}
670
671#[cfg(not(feature = "lib"))]
673fn decrypt_transform(_conn: &mut Smb2Conn, _frame: &[u8]) -> Option<Vec<u8>> {
674 None
675}
676#[cfg(not(feature = "handrolled"))]
677fn decrypt_transform(conn: &mut Smb2Conn, frame: &[u8]) -> Option<Vec<u8>> {
678 let tf = match smb_server_proto_smb2::commands::TransformHdr::parse(frame) {
679 Some(t) => t,
680 None => {
681 tracing::warn!("transform parse failed");
682 return None;
683 }
684 };
685 if tf.session_id != conn.session_id {
686 tracing::warn!(
687 sid = format!("{:#x}", tf.session_id),
688 "transform sid mismatch"
689 );
690 return None;
691 }
692 let Some((c2s, _s2c)) = conn.enc_keys else {
693 tracing::warn!("no cipher keys derived");
694 return None;
695 };
696 let Some(cipher) = conn.cipher else {
697 tracing::warn!("no cipher negotiated");
698 return None;
699 };
700 let gcm = cipher_is_gcm(cipher);
701 let is256 = cipher_is_256(cipher);
702 let aad = frame
703 .get(smb_server_proto_smb2::commands::tf_off::NONCE..smb_server_proto_smb2::commands::tf_off::HDR_SIZE)?;
704 let payload =
705 frame.get(smb_server_proto_smb2::commands::tf_off::HDR_SIZE..tf_off_end(tf.original_len))?;
706 let tag_at = smb_server_proto_smb2::commands::tf_off::SIGNATURE;
710 let mut sealed = payload.to_vec();
711 sealed.extend_from_slice(frame.get(tag_at..tag_at + aead::TAG_LEN)?);
712 let nonce = &frame[smb_server_proto_smb2::commands::tf_off::NONCE..];
713 let opened = match (gcm, is256) {
714 (true, true) => smb_server_auth::crypto::aes256gcm_open(
715 &c2s,
716 nonce.get(..aead::GCM_NONCE_LEN)?.try_into().ok()?,
717 aad,
718 &sealed,
719 ),
720 (true, false) => smb_server_auth::crypto::aes128gcm_open(
721 c2s[..aead::AES128_KEY_LEN].try_into().ok()?,
722 nonce.get(..aead::GCM_NONCE_LEN)?.try_into().ok()?,
723 aad,
724 &sealed,
725 ),
726 (false, true) => smb_server_auth::crypto::aes256ccm_open(
727 &c2s,
728 nonce.get(..aead::CCM_NONCE_LEN)?.try_into().ok()?,
729 aad,
730 &sealed,
731 ),
732 (false, false) => smb_server_auth::crypto::aes128ccm_open(
733 c2s[..aead::AES128_KEY_LEN].try_into().ok()?,
734 nonce.get(..aead::CCM_NONCE_LEN)?.try_into().ok()?,
735 aad,
736 &sealed,
737 ),
738 };
739 let pt = match opened {
740 Some(p) => p,
741 None => {
742 tracing::warn!(cipher = cipher, aad_len = aad.len(), "aead open failed");
743 return None;
744 }
745 };
746 counter!("smb_decrypted_msgs_total").increment(1);
747 counter!("smb_decrypted_bytes_total").increment(pt.len() as u64);
748 Some(pt)
749}
750
751#[cfg(not(feature = "handrolled"))]
753fn tf_off_end(original_len: usize) -> usize {
754 smb_server_proto_smb2::commands::tf_off::HDR_SIZE + original_len
755}
756
757enum Routed {
764 Framed(Status, Vec<u8>),
765 Raw(Vec<u8>),
766 Silent,
767}
768
769async fn via_typestate(
770 hdr: &smb_server_proto_smb2::Header2,
771 conn: &mut Smb2Conn,
772 server: &Arc<ServerShared>,
773 buf: &[u8],
774) -> Routed {
775 let reply = crate::io::ReplyHeader {
776 command: hdr.command,
777 message_id: hdr.message_id,
778 session_id: conn.session_id,
779 tree_id: hdr.tree_id,
780 credits: 0,
781 seal: crate::io::SealIntent::default(),
782 };
783 let req = match crate::io::SmbRequest::parse(hdr.command, buf) {
784 Ok(req) => req,
785 Err(status) => return Routed::Framed(status, Vec::new()),
786 };
787 let ctx = crate::io::IoContext::accept(reply, req);
788 let outcome = {
789 let mut res = crate::io::Resources {
790 conn,
791 server,
792 frame: buf,
793 };
794 crate::io::dispatch(ctx, &mut res).await
795 };
796 match outcome {
797 crate::io::Outcome::Final(r) => Routed::Framed(r.status, r.body),
798 crate::io::Outcome::Silent => Routed::Silent,
801 crate::io::Outcome::Interim { parked, interim } => {
805 let mut frame = build_async_frame(
806 interim.reply.session_id,
807 interim.reply.message_id,
808 parked.async_id(),
809 interim.reply.command,
810 interim.status,
811 &interim.body,
812 );
813 if hdr.is_signed()
814 && let Some(key) = conn.signing_key.or(conn.session_key) {
815 sign_pdu(&mut frame, &key, conn.dialect, conn.signing_algo);
816 }
817 Routed::Raw(frame)
818 }
819 }
820}
821
822#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] async fn process_single(
825 server: &Arc<ServerShared>,
826 conn: &mut Smb2Conn,
827 buf: &[u8],
828) -> Option<(Vec<u8>, bool)> {
829 let hdr = match smb_server_proto_smb2::Header2::parse(buf) {
830 Some(h) => h,
831 None => return None,
832 };
833 tracing::debug!(
834 cmd = hdr.command,
835 mid = hdr.message_id,
836 sid = format!("{:#x}", hdr.session_id),
837 tid = format!("{:#x}", hdr.tree_id),
838 len = buf.len(),
839 "smb2 request"
840 );
841 conn.compress_response = hdr.command == ss::cmd::READ
844 && buf
845 .get(hdr::LEN + 3)
846 .map(|f| f & 0x02 != 0)
847 .unwrap_or(false);
848 let is_preauth_msg = matches!(hdr.command, ss::cmd::NEGOTIATE | ss::cmd::SESSION_SETUP);
851 let preauth_before = conn.preauth_hash;
852 if is_preauth_msg {
853 let mut joined = Vec::with_capacity(hdr::LEN + buf.len());
854 joined.extend_from_slice(&conn.preauth_hash);
855 joined.extend_from_slice(buf);
856 conn.preauth_hash = smb_server_auth::crypto::sha512(&joined);
857 }
858 let sid = hdr.session_id;
859
860 let pre_session = matches!(
862 hdr.command,
863 ss::cmd::NEGOTIATE | ss::cmd::SESSION_SETUP | ss::cmd::ECHO
864 );
865 if !pre_session && (!conn.authenticated || sid != conn.session_id) {
866 return Some((response(&hdr, Status::ACCESS_DENIED, Vec::new(), 0), false));
867 }
868
869 let needs_tree = matches!(
873 hdr.command,
874 ss::cmd::CREATE
875 | ss::cmd::CLOSE
876 | ss::cmd::FLUSH
877 | ss::cmd::READ
878 | ss::cmd::WRITE
879 | ss::cmd::LOCK
880 | ss::cmd::IOCTL
881 | ss::cmd::QUERY_DIRECTORY
882 | ss::cmd::CHANGE_NOTIFY
883 | ss::cmd::QUERY_INFO
884 | ss::cmd::SET_INFO
885 );
886 if needs_tree
887 && hdr.flags & hdr_flags::RELATED_OPERATIONS == 0
888 && !conn.tree_exists(hdr.tree_id)
889 {
890 return Some((
891 response(
892 &hdr,
893 Status::NETWORK_NAME_DELETED,
894 Vec::new(),
895 conn.session_id,
896 ),
897 true,
898 ));
899 }
900
901 if matches!(hdr.command, ss::cmd::WRITE | ss::cmd::SET_INFO | ss::cmd::IOCTL)
905 && matches!(conn.dialect, Some(d) if d >= smb_server_proto_smb2::negotiate::DIALECT_300)
906 && let Some(foff) = file_id_body_offset(hdr.command) {
907 let abs = hdr::LEN + foff;
908 if let Some(fid) = buf
909 .get(abs..abs + 16)
910 .and_then(|s| <[u8; 16]>::try_from(s).ok())
911 {
912 let channel_seq = u16::from_le_bytes([buf[hdr::STATUS], buf[hdr::STATUS + 1]]);
913 let is_replay = hdr.flags & hdr_flags::REPLAY_OPERATION != 0;
914 if !verify_channel_sequence(conn, &fid, channel_seq, is_replay) {
915 return Some((
916 response(&hdr, Status::FILE_NOT_AVAILABLE, Vec::new(), conn.session_id),
917 true,
918 ));
919 }
920 }
921 }
922
923 if let Some(foff) = file_id_body_offset(hdr.command) {
926 let abs = hdr::LEN + foff;
927 if let Some(fid) = buf
928 .get(abs..abs + 16)
929 .and_then(|s| <[u8; 16]>::try_from(s).ok())
930 && server.app_instances.take_forced((conn.session_id, fid)) {
931 conn.handle_take(&fid);
932 return Some((
933 response(&hdr, Status::FILE_CLOSED, Vec::new(), conn.session_id),
934 true,
935 ));
936 }
937 }
938
939 if hdr.command == ss::cmd::NEGOTIATE {
947 conn.client_credits = conn.client_credits.max(1);
948 }
949 let charge = hdr.credit_charge as u32;
954 conn.client_credits = conn.client_credits.saturating_sub(charge);
955
956 if hdr.command != ss::cmd::NEGOTIATE
968 && hdr.command != ss::cmd::SESSION_SETUP
969 && !conn.guest
970 && !conn.encrypt_data
971 && !conn.req_encrypted
972 && let Some(key) = conn.signing_key
973 {
974 if hdr.is_signed() {
975 if !verify_pdu_signature(buf, &key, conn.dialect, conn.signing_algo) {
976 counter!("smb_reject_bad_signature_total").increment(1);
977 return Some((
978 response(&hdr, Status::ACCESS_DENIED, Vec::new(), conn.session_id),
979 true,
980 ));
981 }
982 } else if server.require_signing {
983 counter!("smb_reject_unsigned_total").increment(1);
984 return Some((
985 response(&hdr, Status::ACCESS_DENIED, Vec::new(), conn.session_id),
986 true,
987 ));
988 }
989 }
990
991 if !conn.req_encrypted
995 && hdr.command != ss::cmd::NEGOTIATE
996 && hdr.command != ss::cmd::SESSION_SETUP
997 && hdr.command != ss::cmd::TREE_CONNECT
998 && conn
999 .tree_name(hdr.tree_id)
1000 .and_then(|n| server.shares.get(&n))
1001 .map(|s| s.encrypt)
1002 .unwrap_or(false)
1003 {
1004 return Some((
1005 response(&hdr, Status::ACCESS_DENIED, Vec::new(), conn.session_id),
1006 true,
1007 ));
1008 }
1009
1010 macro_rules! route {
1016 () => {
1017 match via_typestate(&hdr, conn, server, buf).await {
1018 Routed::Framed(s, b) => (s, b),
1019 Routed::Raw(frame) => return Some((frame, true)),
1020 Routed::Silent => return None,
1021 }
1022 };
1023 }
1024
1025 let (status, body): (Status, Vec<u8>) = match hdr.command {
1026 ss::cmd::NEGOTIATE => route!(),
1027 ss::cmd::SESSION_SETUP => route!(),
1028 ss::cmd::TREE_CONNECT => route!(),
1029 ss::cmd::TREE_DISCONNECT => route!(),
1030 ss::cmd::LOGOFF => route!(),
1031 ss::cmd::CREATE => route!(),
1032 ss::cmd::READ => route!(),
1033 ss::cmd::WRITE => route!(),
1034 ss::cmd::CLOSE => route!(),
1035 ss::cmd::FLUSH => route!(),
1036 ss::cmd::IOCTL => route!(),
1037 ss::cmd::LOCK => route!(),
1038 ss::cmd::CHANGE_NOTIFY => route!(),
1039 ss::cmd::QUERY_DIRECTORY => route!(),
1040 ss::cmd::QUERY_INFO => route!(),
1041 ss::cmd::SET_INFO => route!(),
1042 ss::cmd::CANCEL => route!(),
1043 ss::cmd::ECHO => route!(),
1044 ss::cmd::OPLOCK_BREAK => route!(),
1045 _ => (Status::NOT_IMPLEMENTED, Vec::new()),
1046 };
1047
1048 if hdr.command == ss::cmd::SESSION_SETUP
1051 && conn.authenticated
1052 && let Some(key) = conn.session_key
1053 && conn.signing_key.is_none()
1054 {
1055 conn.signing_key = match conn.dialect {
1056 Some(
1057 smb_server_proto_smb2::negotiate::DIALECT_300 | smb_server_proto_smb2::negotiate::DIALECT_302,
1058 ) => {
1059 let k = smb_server_auth::crypto::kdf_counter_mode_hmac_sha256(
1060 &key,
1061 b"SMB2AESCMAC\0",
1062 b"SmbSign\0",
1063 16,
1064 );
1065 Some(k.try_into().unwrap())
1066 }
1067 Some(smb_server_proto_smb2::negotiate::DIALECT_311) => {
1068 let k = smb_server_auth::crypto::kdf_counter_mode_hmac_sha256(
1069 &key,
1070 b"SMBSigningKey\0",
1071 &conn.preauth_hash,
1072 16,
1073 );
1074 Some(k.try_into().unwrap())
1075 }
1076 _ => Some(key),
1077 };
1078 }
1079
1080 let mut seal_session = false;
1085 #[cfg(feature = "lib")]
1087 let sealing_available = true;
1088 #[cfg(not(feature = "lib"))]
1089 let sealing_available = false;
1090 if hdr.command == ss::cmd::SESSION_SETUP
1095 && status == Status::SUCCESS
1096 && sealing_available
1097 && conn.authenticated
1098 && !conn.guest
1099 && conn.cipher.is_some()
1100 && conn.enc_keys.is_none()
1101 && let Some(key) = conn.session_key {
1102 let (c2s_label, s2c_label) = match conn.dialect {
1103 Some(smb_server_proto_smb2::negotiate::DIALECT_311) => (
1104 b"SMBC2SCipherKey\0".as_slice(),
1105 b"SMBS2CCipherKey\0".as_slice(),
1106 ),
1107 _ => (b"SMB2AESCCM\0".as_slice(), b"SMB2AESCCM\0".as_slice()),
1108 };
1109 let s2c_ctx: &[u8] = match conn.dialect {
1110 Some(smb_server_proto_smb2::negotiate::DIALECT_311) => &conn.preauth_hash,
1111 _ => b"ServerOut\0",
1112 };
1113 let c2s_ctx: &[u8] = match conn.dialect {
1114 Some(smb_server_proto_smb2::negotiate::DIALECT_311) => &conn.preauth_hash,
1115 _ => b"ServerIn \0", };
1117 let klen = cipher_key_len(conn.cipher.unwrap());
1120 let kdf = |label: &[u8], context: &[u8]| -> [u8; 32] {
1121 let derived =
1122 smb_server_auth::crypto::kdf_counter_mode_hmac_sha256(&key, label, context, klen);
1123 let mut k = [0u8; 32];
1124 k[..klen].copy_from_slice(&derived);
1125 k
1126 };
1127 conn.enc_keys = Some((kdf(c2s_label, c2s_ctx), kdf(s2c_label, s2c_ctx)));
1128 if server.encrypt {
1131 conn.encrypt_data = true;
1132 seal_session = true;
1133 }
1134 tracing::info!(
1135 cipher = format!("{:#x}", conn.cipher.unwrap()),
1136 dialect = format!("{:#06x}", conn.dialect.unwrap_or(0)),
1137 required = server.encrypt,
1138 "cipher keys derived"
1139 );
1140 }
1141
1142 let mut resp = response(&hdr, status, body, conn.session_id);
1143 if let Some(new_tid) = conn.resp_tree_id.take() {
1144 resp[hdr::TREE_ID..hdr::SESSION_ID].copy_from_slice(&new_tid.to_le_bytes()); }
1146
1147 if hdr.command == ss::cmd::SESSION_SETUP
1152 && status == Status::SUCCESS
1153 && conn.authenticated
1154 && conn.session_id != 0
1155 && !conn.binding
1156 {
1157 server.sessions.insert(
1158 conn.session_id,
1159 crate::state::SessionEntry {
1160 session_key: conn.session_key,
1161 signing_key: conn.signing_key,
1162 dialect: conn.dialect,
1163 user: conn.user.clone(),
1164 guest: conn.guest,
1165 cipher: conn.cipher,
1166 enc_keys: conn.enc_keys,
1167 encrypt_data: conn.encrypt_data,
1168 },
1169 );
1170 }
1171
1172 let ss_failed = hdr.command == ss::cmd::SESSION_SETUP
1176 && !matches!(status, Status::SUCCESS | Status::MORE_PROCESSING_REQUIRED);
1177 if ss_failed {
1178 conn.preauth_hash = preauth_before;
1179 } else if is_preauth_msg && conn.dialect == Some(smb_server_proto_smb2::negotiate::DIALECT_311) {
1180 let mut joined = Vec::with_capacity(hdr::LEN + resp.len());
1181 joined.extend_from_slice(&conn.preauth_hash);
1182 joined.extend_from_slice(&resp);
1183 conn.preauth_hash = smb_server_auth::crypto::sha512(&joined);
1184 }
1185
1186 let allow_wrap = !seal_session;
1189
1190 let final_setup_leg =
1195 hdr.command == ss::cmd::SESSION_SETUP && status == Status::SUCCESS && conn.authenticated;
1196 let sign_setup = final_setup_leg;
1201 let sign_other = hdr.is_signed() && hdr.command != ss::cmd::SESSION_SETUP;
1202 if (sign_setup || sign_other)
1203 && let Some(key) = conn.signing_key.or(conn.session_key) {
1204 sign_pdu(&mut resp, &key, conn.dialect, conn.signing_algo);
1205 }
1206 Some((resp, allow_wrap))
1207}
1208
1209#[cfg(feature = "lib")]
1213fn gmac_nonce(msg: &[u8], server_sender: bool) -> [u8; 12] {
1214 const CANCEL_FLAG: u32 = 0x2;
1217 let msg_id_len = size_of::<u64>();
1218 let mut n = [0u8; 12];
1219 n[..msg_id_len].copy_from_slice(&msg[hdr::MESSAGE_ID..hdr::MESSAGE_ID + msg_id_len]);
1220 let mut flags = u32::from(server_sender);
1221 if u16::from_le_bytes([msg[hdr::COMMAND], msg[hdr::COMMAND + 1]]) == ss::cmd::CANCEL {
1222 flags |= CANCEL_FLAG;
1223 }
1224 n[msg_id_len..].copy_from_slice(&flags.to_le_bytes());
1225 n
1226}
1227
1228#[cfg(feature = "lib")]
1229fn sig_gmac(key: &[u8; 16], msg: &[u8], server_sender: bool) -> [u8; 16] {
1230 smb_server_auth::crypto::aes128_gmac(key, &gmac_nonce(msg, server_sender), msg)
1231}
1232#[cfg(not(feature = "lib"))]
1233fn sig_gmac(key: &[u8; 16], msg: &[u8], _server_sender: bool) -> [u8; 16] {
1234 let t = smb_server_auth::crypto::aes128_cmac(key, msg);
1235 let mut s = [0u8; 16];
1236 s.copy_from_slice(&t);
1237 s
1238}
1239
1240fn message_signature(
1244 msg: &[u8],
1245 key: &[u8; 16],
1246 dialect: Option<u16>,
1247 signing_algo: u16,
1248 server_sender: bool,
1249) -> [u8; 16] {
1250 use smb_server_proto_smb2::negotiate::ctx_type;
1251 let hmac16 = || {
1252 let t = smb_server_auth::crypto::hmac_sha256(key, msg);
1253 let mut s = [0u8; 16];
1254 s.copy_from_slice(&t[..hdr::SIGNATURE_LEN]);
1255 s
1256 };
1257 let is_3x = matches!(
1258 dialect,
1259 Some(
1260 smb_server_proto_smb2::negotiate::DIALECT_300
1261 | smb_server_proto_smb2::negotiate::DIALECT_302
1262 | smb_server_proto_smb2::negotiate::DIALECT_311
1263 )
1264 );
1265 if !is_3x {
1266 return hmac16();
1267 }
1268 match signing_algo {
1269 ctx_type::SIGNING_HMAC_SHA256 => hmac16(),
1270 ctx_type::SIGNING_AES128_GMAC => sig_gmac(key, msg, server_sender),
1271 _ => {
1272 let t = smb_server_auth::crypto::aes128_cmac(key, msg);
1273 let mut s = [0u8; 16];
1274 s.copy_from_slice(&t);
1275 s
1276 }
1277 }
1278}
1279
1280fn sign_pdu(resp: &mut [u8], key: &[u8; 16], dialect: Option<u16>, signing_algo: u16) {
1283 resp[hdr::FLAGS] |= hdr_flags::SIGNED as u8;
1284 let mut msg = Vec::with_capacity(resp.len());
1285 msg.extend_from_slice(&resp[..hdr::SIGNATURE]);
1286 msg.extend_from_slice(&[0u8; 16]); msg.extend_from_slice(&resp[hdr::LEN..]);
1288 let sig = message_signature(&msg, key, dialect, signing_algo, true);
1289 resp[hdr::SIGNATURE..hdr::LEN].copy_from_slice(&sig);
1290}
1291
1292fn verify_pdu_signature(buf: &[u8], key: &[u8; 16], dialect: Option<u16>, signing_algo: u16) -> bool {
1296 if buf.len() < hdr::LEN {
1297 return false;
1298 }
1299 let mut msg = Vec::with_capacity(buf.len());
1300 msg.extend_from_slice(&buf[..hdr::SIGNATURE]);
1301 msg.extend_from_slice(&[0u8; 16]); msg.extend_from_slice(&buf[hdr::LEN..]);
1303 let expected = message_signature(&msg, key, dialect, signing_algo, false);
1304 expected == buf[hdr::SIGNATURE..hdr::LEN]
1305}
1306
1307#[derive(Clone)]
1311struct AsyncCrypto {
1312 dialect: Option<u16>,
1313 signing_key: Option<[u8; 16]>,
1314 signing_algo: u16,
1315 enc_keys: Option<([u8; 32], [u8; 32])>,
1316 cipher: Option<u16>,
1317 session_id: u64,
1318 encrypt: bool,
1319 signed: bool,
1320}
1321
1322impl AsyncCrypto {
1323 fn snapshot(conn: &Smb2Conn, request_signed: bool) -> Self {
1324 Self {
1325 dialect: conn.dialect,
1326 signing_key: conn.signing_key.or(conn.session_key),
1327 signing_algo: conn.signing_algo,
1328 enc_keys: conn.enc_keys,
1329 cipher: conn.cipher,
1330 session_id: conn.session_id,
1331 encrypt: conn.encrypt_data || conn.peer_encrypts,
1332 signed: request_signed,
1333 }
1334 }
1335}
1336
1337struct AsyncReply {
1341 crypto: AsyncCrypto,
1342 message_id: u64,
1343 async_id: u64,
1344 outbound: mpsc::Sender<Vec<u8>>,
1345 cancel: oneshot::Receiver<Status>,
1346}
1347
1348fn build_async_frame(
1352 session_id: u64,
1353 message_id: u64,
1354 async_id: u64,
1355 command: u16,
1356 status: Status,
1357 body: &[u8],
1358) -> Vec<u8> {
1359 let mut f = Vec::with_capacity(hdr::LEN + body.len());
1360 f.extend_from_slice(&smb_server_proto_smb2::SMB2_MAGIC);
1361 f.extend_from_slice(&(hdr::LEN as u16).to_le_bytes()); f.extend_from_slice(&1u16.to_le_bytes()); f.extend_from_slice(&status.raw().to_le_bytes());
1364 f.extend_from_slice(&command.to_le_bytes());
1365 f.extend_from_slice(&1u16.to_le_bytes()); f.extend_from_slice(&(hdr_flags::SERVER_TO_REDIR | hdr_flags::ASYNC_COMMAND).to_le_bytes());
1367 f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&message_id.to_le_bytes());
1369 f.extend_from_slice(&async_id.to_le_bytes()); f.extend_from_slice(&session_id.to_le_bytes());
1371 f.extend_from_slice(&[0u8; 16]); f.extend_from_slice(body);
1373 f
1374}
1375
1376fn finalize_async(crypto: &AsyncCrypto, mut frame: Vec<u8>) -> Vec<u8> {
1379 if crypto.signed
1380 && let Some(key) = crypto.signing_key {
1381 sign_pdu(&mut frame, &key, crypto.dialect, crypto.signing_algo);
1382 }
1383 if crypto.encrypt
1384 && let (Some(keys), Some(cipher)) = (crypto.enc_keys, crypto.cipher)
1385 && let Some(sealed) = seal_pdu(crypto.session_id, keys, cipher, &frame) {
1386 return sealed;
1387 }
1388 frame
1389}
1390
1391pub(crate) enum AsyncStart {
1398 Reply(Status, Vec<u8>),
1399 Pending(u64),
1400}
1401
1402pub(crate) fn begin_lock(
1405 conn: &mut Smb2Conn,
1406 server: &Arc<ServerShared>,
1407 hdr: &smb_server_proto_smb2::Header2,
1408 buf: &[u8],
1409) -> AsyncStart {
1410 let Some(req) = c::LockReq::parse(buf) else {
1411 return AsyncStart::Reply(Status::INVALID_PARAMETER, Vec::new());
1412 };
1413 let Some(path) = conn.with_handle(&req.file_id.0, |h| h.path.clone()) else {
1414 return AsyncStart::Reply(Status::INVALID_HANDLE, Vec::new());
1415 };
1416 let owner = (conn.session_id, req.file_id.0);
1417
1418 for l in req.locks.iter().filter(|l| l.unlock) {
1420 server.locks.release(&path, &[(l.offset, l.length)], owner);
1421 }
1422 let acquires: Vec<(u64, u64, bool)> = req
1423 .locks
1424 .iter()
1425 .filter(|l| !l.unlock)
1426 .map(|l| (l.offset, l.length, l.exclusive))
1427 .collect();
1428
1429 if acquires.is_empty() || server.locks.try_acquire(&path, &acquires, owner) {
1430 return AsyncStart::Reply(Status::SUCCESS, c::build_lock_resp());
1431 }
1432 if req.locks.iter().any(|l| l.fail_immediately) {
1433 return AsyncStart::Reply(Status::LOCK_NOT_GRANTED, Vec::new());
1434 }
1435 let async_id = conn.alloc_async_id();
1438 let crypto = AsyncCrypto::snapshot(conn, hdr.is_signed());
1439 let (cancel_tx, cancel_rx) = oneshot::channel();
1440 conn.async_cancels.insert(async_id, cancel_tx);
1441 conn.async_msgids.insert(hdr.message_id, async_id);
1442 gauge!("smb_async_pending").increment(1.0);
1443 tokio_uring::spawn(run_lock_wait(
1444 server.locks.clone(),
1445 path,
1446 acquires,
1447 owner,
1448 AsyncReply {
1449 crypto,
1450 message_id: hdr.message_id,
1451 async_id,
1452 outbound: conn.outbound.clone(),
1453 cancel: cancel_rx,
1454 },
1455 ));
1456 AsyncStart::Pending(async_id)
1457}
1458
1459pub(crate) fn begin_change_notify(
1462 conn: &mut Smb2Conn,
1463 hdr: &smb_server_proto_smb2::Header2,
1464 buf: &[u8],
1465) -> AsyncStart {
1466 let Some(req) = c::ChangeNotifyReq::parse(buf) else {
1467 return AsyncStart::Reply(Status::INVALID_PARAMETER, Vec::new());
1468 };
1469 let Some((is_dir, can_read, dir_path)) =
1470 conn.with_handle(&req.file_id.0, |o| (o.is_dir, o.can_read, o.path.clone()))
1471 else {
1472 return AsyncStart::Reply(Status::INVALID_HANDLE, Vec::new());
1473 };
1474 if !is_dir {
1476 return AsyncStart::Reply(Status::INVALID_PARAMETER, Vec::new());
1477 }
1478 if !can_read {
1479 return AsyncStart::Reply(Status::ACCESS_DENIED, Vec::new());
1480 }
1481 if req.output_len > MAX_TRANSACT_SIZE {
1482 return AsyncStart::Reply(Status::INVALID_PARAMETER, Vec::new());
1483 }
1484
1485 let async_id = conn.alloc_async_id();
1486 let crypto = AsyncCrypto::snapshot(conn, hdr.is_signed());
1487 let (cancel_tx, cancel_rx) = oneshot::channel();
1488 conn.async_cancels.insert(async_id, cancel_tx);
1489 conn.async_msgids.insert(hdr.message_id, async_id);
1490 conn.async_by_file
1491 .entry(req.file_id.0)
1492 .or_default()
1493 .push(async_id);
1494 gauge!("smb_async_pending").increment(1.0);
1495 tokio_uring::spawn(run_change_notify(
1496 dir_path,
1497 req.watch_tree,
1498 req.filter,
1499 AsyncReply {
1500 crypto,
1501 message_id: hdr.message_id,
1502 async_id,
1503 outbound: conn.outbound.clone(),
1504 cancel: cancel_rx,
1505 },
1506 ));
1507 AsyncStart::Pending(async_id)
1508}
1509
1510pub(crate) fn cancel(conn: &mut Smb2Conn, hdr: &smb_server_proto_smb2::Header2, buf: &[u8]) {
1514 counter!("smb_cancels_total").increment(1);
1515 let async_id = if hdr.is_async() && buf.len() >= hdr::ASYNC_ID + size_of::<u64>() {
1516 Some(u64::from_le_bytes(
1517 buf[hdr::ASYNC_ID..hdr::SESSION_ID].try_into().unwrap(),
1518 ))
1519 } else {
1520 conn.async_msgids.get(&hdr.message_id).copied()
1521 };
1522 if let Some(aid) = async_id {
1523 conn.async_msgids.retain(|_, v| *v != aid);
1524 conn.async_by_file
1525 .values_mut()
1526 .for_each(|v| v.retain(|x| *x != aid));
1527 if let Some(tx) = conn.async_cancels.remove(&aid) {
1528 let _ = tx.send(Status::CANCELLED);
1529 }
1530 }
1531}
1532
1533async fn run_lock_wait(
1534 locks: Arc<crate::state::LockManager>,
1535 path: String,
1536 ranges: Vec<(u64, u64, bool)>,
1537 owner: crate::state::LockOwner,
1538 mut reply: AsyncReply,
1539) {
1540 let granted = loop {
1541 let notified = locks.released().notified();
1544 if locks.try_acquire(&path, &ranges, owner) {
1545 break true;
1546 }
1547 tokio::select! {
1548 _ = notified => {}
1549 _ = &mut reply.cancel => break false,
1550 }
1551 };
1552 let (status, body) = if granted {
1553 counter!("smb_locks_granted_total").increment(1);
1554 (Status::SUCCESS, c::build_lock_resp())
1555 } else {
1556 (Status::CANCELLED, Vec::new())
1557 };
1558 let frame = finalize_async(
1559 &reply.crypto,
1560 build_async_frame(
1561 reply.crypto.session_id,
1562 reply.message_id,
1563 reply.async_id,
1564 ss::cmd::LOCK,
1565 status,
1566 &body,
1567 ),
1568 );
1569 let _ = reply.outbound.send(frame).await;
1570 gauge!("smb_async_pending").decrement(1.0);
1571}
1572
1573async fn run_change_notify(dir_path: String, watch_tree: bool, filter: u32, mut reply: AsyncReply) {
1576 let frame = match watch_one_event(&dir_path, watch_tree, filter, &mut reply.cancel).await {
1577 Ok(entries) => {
1578 let pairs: Vec<(u32, &str)> = entries.iter().map(|(a, n)| (*a, n.as_str())).collect();
1579 let buf = c::build_file_notify_information(&pairs);
1580 let body = c::build_change_notify_resp(&buf);
1581 counter!("smb_notifies_sent").increment(1);
1582 build_async_frame(
1583 reply.crypto.session_id,
1584 reply.message_id,
1585 reply.async_id,
1586 ss::cmd::CHANGE_NOTIFY,
1587 Status::SUCCESS,
1588 &body,
1589 )
1590 }
1591 Err(status) => {
1592 let body = if status == Status::NOTIFY_CLEANUP {
1596 c::build_change_notify_resp(&[])
1597 } else {
1598 error_resp()
1599 };
1600 build_async_frame(
1601 reply.crypto.session_id,
1602 reply.message_id,
1603 reply.async_id,
1604 ss::cmd::CHANGE_NOTIFY,
1605 status,
1606 &body,
1607 )
1608 }
1609 };
1610 let _ = reply.outbound.send(finalize_async(&reply.crypto, frame)).await;
1611 gauge!("smb_async_pending").decrement(1.0);
1612}
1613
1614async fn watch_one_event(
1620 dir_path: &str,
1621 watch_tree: bool,
1622 filter: u32,
1623 cancel: &mut oneshot::Receiver<Status>,
1624) -> Result<Vec<(u32, String)>, Status> {
1625 use futures_util::StreamExt;
1626 use inotify::Inotify;
1627 use std::collections::HashMap;
1628
1629 let inotify = Inotify::init().map_err(|_| Status::CANCELLED)?;
1630 let mask = filter_to_mask(filter);
1631 let mut wd_rel: HashMap<inotify::WatchDescriptor, String> = HashMap::new();
1634 let root_wd = inotify
1635 .watches()
1636 .add(dir_path, mask)
1637 .map_err(|_| Status::CANCELLED)?;
1638 wd_rel.insert(root_wd, String::new());
1639 if watch_tree {
1640 for (abs, rel) in collect_subdirs(std::path::Path::new(dir_path)) {
1641 if let Ok(wd) = inotify.watches().add(&abs, mask) {
1642 wd_rel.insert(wd, rel);
1643 }
1644 }
1645 }
1646 let mut buf = [0u8; 4096];
1647 let mut stream = inotify
1648 .into_event_stream(&mut buf)
1649 .map_err(|_| Status::CANCELLED)?;
1650
1651 tokio::select! {
1652 ev = stream.next() => {
1653 let ev = ev.ok_or(Status::CANCELLED)?.map_err(|_| Status::CANCELLED)?;
1654 let name = ev
1655 .name
1656 .as_ref()
1657 .map(|n| n.to_string_lossy().into_owned())
1658 .unwrap_or_default();
1659 let prefix = wd_rel.get(&ev.wd).cloned().unwrap_or_default();
1660 let full = if prefix.is_empty() { name } else { format!("{prefix}\\{name}") };
1661 Ok(vec![(mask_to_action(ev.mask), full)])
1662 }
1663 status = cancel => Err(status.unwrap_or(Status::CANCELLED)),
1664 }
1665}
1666
1667fn collect_subdirs(root: &std::path::Path) -> Vec<(std::path::PathBuf, String)> {
1670 const CAP: usize = 4096;
1671 let mut out = Vec::new();
1672 let mut stack = vec![(root.to_path_buf(), String::new())];
1673 while let Some((dir, rel)) = stack.pop() {
1674 let Ok(rd) = std::fs::read_dir(&dir) else {
1675 continue;
1676 };
1677 for e in rd.flatten() {
1678 if e.file_type().map(|t| t.is_dir()).unwrap_or(false) {
1679 let name = e.file_name().to_string_lossy().into_owned();
1680 let child = if rel.is_empty() {
1681 name
1682 } else {
1683 format!("{rel}\\{name}")
1684 };
1685 out.push((e.path(), child.clone()));
1686 stack.push((e.path(), child));
1687 if out.len() >= CAP {
1688 return out;
1689 }
1690 }
1691 }
1692 }
1693 out
1694}
1695
1696fn filter_to_mask(filter: u32) -> inotify::WatchMask {
1699 use inotify::WatchMask as M;
1700 use smb_server_proto_smb2::commands::notify_filter as nf;
1701
1702 let mut m = M::empty();
1703 if filter & (nf::FILE_NAME | nf::DIR_NAME) != 0 {
1704 m |= M::CREATE | M::DELETE | M::MOVED_FROM | M::MOVED_TO;
1705 }
1706 if filter & (nf::ATTRIBUTES | nf::LAST_WRITE | nf::LAST_ACCESS | nf::CREATION | nf::EA) != 0 {
1709 m |= M::ATTRIB;
1710 }
1711 if filter & (nf::SIZE | nf::LAST_WRITE | nf::STREAM_SIZE | nf::STREAM_WRITE | nf::STREAM_NAME)
1712 != 0
1713 {
1714 m |= M::MODIFY | M::CLOSE_WRITE;
1715 }
1716 if m.is_empty() {
1717 m = M::CREATE | M::DELETE | M::MODIFY | M::MOVED_FROM | M::MOVED_TO;
1718 }
1719 m
1720}
1721
1722fn mask_to_action(mask: inotify::EventMask) -> u32 {
1725 use inotify::EventMask as E;
1726 use smb_server_proto_smb2::commands::notify_action as na;
1727
1728 if mask.contains(E::CREATE) {
1729 na::ADDED
1730 } else if mask.contains(E::DELETE) {
1731 na::REMOVED
1732 } else if mask.contains(E::MOVED_FROM) {
1733 na::RENAMED_OLD_NAME
1734 } else if mask.contains(E::MOVED_TO) {
1735 na::RENAMED_NEW_NAME
1736 } else {
1737 na::MODIFIED
1738 }
1739}
1740
1741#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn probe_negotiate_resp() -> Vec<u8> {
1746 let mut b = Vec::with_capacity(68);
1747 b.extend_from_slice(&65u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0x02FFu16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&[0u8; 16]); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b
1762}
1763
1764pub(crate) enum NegotiateReply {
1772 Reply(Status, Vec<u8>),
1773 Silent,
1774}
1775
1776#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn negotiate(
1782 conn: &mut Smb2Conn,
1783 server: &Arc<ServerShared>,
1784 hdr: &smb_server_proto_smb2::Header2,
1785 buf: &[u8],
1786) -> NegotiateReply {
1787 let body_start = hdr::LEN;
1788 if conn.dialect.is_some() {
1791 conn.disconnect = true;
1792 return NegotiateReply::Silent;
1793 }
1794 let parsed = smb_server_proto_smb2::negotiate::Request::parse(buf.get(body_start..).unwrap_or(&[]));
1798 tracing::debug!(
1799 status = hdr.status,
1800 parsed_ok = parsed.is_some(),
1801 len = buf.len(),
1802 "negotiate probe check"
1803 );
1804 if hdr.status == Status::INVALID_PARAMETER.raw() || parsed.is_none() {
1805 if parsed.is_none() {
1806 tracing::debug!(len = buf.len(), body = %hex_str(buf.get(body_start..).unwrap_or(&[])), "negotiate parse failed");
1807 }
1808 return NegotiateReply::Reply(Status::INVALID_PARAMETER, probe_negotiate_resp());
1809 }
1810 let req = parsed.expect("validated above");
1811 let Some(dialect) = smb_server_proto_smb2::negotiate::pick(&req.dialects) else {
1812 return NegotiateReply::Reply(Status::INVALID_PARAMETER, Vec::new());
1813 };
1814 conn.dialect = Some(dialect);
1815 conn.client_guid = req.client_guid;
1818 conn.client_dialects = req.dialects.clone();
1819 conn.client_security_mode = g16(buf, body_start + 4);
1820 conn.client_capabilities = buf
1821 .get(body_start + 8..body_start + 12)
1822 .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
1823 .unwrap_or(0);
1824 if dialect == smb_server_proto_smb2::negotiate::DIALECT_311 {
1828 let preauth: Vec<_> = req
1829 .contexts
1830 .iter()
1831 .filter(|c| c.kind == smb_server_proto_smb2::negotiate::ctx_type::PREAUTH_INTEGRITY)
1832 .collect();
1833 if preauth.len() != 1 {
1834 return NegotiateReply::Reply(Status::INVALID_PARAMETER, Vec::new());
1835 }
1836 let pd = &preauth[0].data;
1838 let count = pd
1839 .get(0..2)
1840 .map(|s| u16::from_le_bytes([s[0], s[1]]) as usize)
1841 .unwrap_or(0);
1842 let algos: Vec<u16> = pd
1843 .get(4..)
1844 .map(|d| {
1845 d.chunks_exact(2)
1846 .take(count)
1847 .map(|w| u16::from_le_bytes([w[0], w[1]]))
1848 .collect()
1849 })
1850 .unwrap_or_default();
1851 if !algos.contains(&smb_server_proto_smb2::negotiate::ctx_type::SHA512) {
1852 return NegotiateReply::Reply(
1853 Status::SMB_NO_PREAUTH_INTEGRITY_HASH_OVERLAP,
1854 Vec::new(),
1855 );
1856 }
1857 }
1858 if dialect == smb_server_proto_smb2::negotiate::DIALECT_311
1863 && let Some(c) = req
1864 .contexts
1865 .iter()
1866 .find(|c| c.kind == smb_server_proto_smb2::negotiate::ctx_type::COMPRESSION)
1867 {
1868 let count = c
1869 .data
1870 .get(0..2)
1871 .map(|s| u16::from_le_bytes([s[0], s[1]]))
1872 .unwrap_or(0);
1873 if c.data.len() < 8 || count == 0 {
1874 return NegotiateReply::Reply(Status::INVALID_PARAMETER, Vec::new());
1875 }
1876 }
1877 let notifications = dialect == smb_server_proto_smb2::negotiate::DIALECT_311
1884 && conn.client_capabilities & smb_server_proto_smb2::negotiate::caps::NOTIFICATIONS != 0;
1885 conn.supports_notifications = notifications;
1886 conn.advertised_caps = if dialect >= smb_server_proto_smb2::negotiate::DIALECT_300 {
1887 smb_server_proto_smb2::negotiate::caps::LARGE_MTU
1888 | smb_server_proto_smb2::negotiate::caps::MULTI_CHANNEL
1889 | smb_server_proto_smb2::negotiate::caps::LEASING
1890 | smb_server_proto_smb2::negotiate::caps::DIRECTORY_LEASING
1891 | smb_server_proto_smb2::negotiate::caps::PERSISTENT_HANDLES
1892 } else if dialect >= smb_server_proto_smb2::negotiate::DIALECT_210 {
1893 smb_server_proto_smb2::negotiate::caps::LARGE_MTU | smb_server_proto_smb2::negotiate::caps::LEASING
1894 } else {
1895 0
1896 };
1897 conn.advertised_caps |= if notifications {
1898 smb_server_proto_smb2::negotiate::caps::NOTIFICATIONS
1899 } else {
1900 0
1901 };
1902 tracing::debug!(dialect = format!("{:#06x}", dialect), "negotiated");
1903 let mut salt = [0u8; 32];
1904 salt.copy_from_slice(&crate::dispatch::rand_bytes(32));
1905 let client_ciphers: Vec<u16> = req
1907 .contexts
1908 .iter()
1909 .find(|c| c.kind == smb_server_proto_smb2::negotiate::ctx_type::ENCRYPTION)
1910 .and_then(|c| {
1911 if c.data.len() >= 2 {
1912 let n = u16::from_le_bytes([c.data[0], c.data[1]]) as usize;
1913 Some(
1914 c.data[2..]
1915 .chunks_exact(2)
1916 .take(n)
1917 .map(|w| u16::from_le_bytes([w[0], w[1]]))
1918 .collect::<Vec<_>>(),
1919 )
1920 } else {
1921 None
1922 }
1923 })
1924 .unwrap_or_default();
1925 let chosen = {
1926 let pref = std::env::var("RUSTSMB_CIPHER")
1929 .unwrap_or_default()
1930 .to_lowercase();
1931 use smb_server_proto_smb2::negotiate::ctx_type::{AES128_CCM, AES128_GCM, AES256_CCM, AES256_GCM};
1932 let order = match pref.as_str() {
1933 "ccm" | "128ccm" => [AES128_CCM, AES256_CCM, AES128_GCM, AES256_GCM],
1934 "256" | "256gcm" => [AES256_GCM, AES128_GCM, AES256_CCM, AES128_CCM],
1935 "256ccm" => [AES256_CCM, AES128_CCM, AES256_GCM, AES128_GCM],
1936 _ => [AES128_GCM, AES256_GCM, AES128_CCM, AES256_CCM],
1937 };
1938 order.into_iter().find(|ours| client_ciphers.contains(ours))
1939 };
1940 if dialect == smb_server_proto_smb2::negotiate::DIALECT_311 && !client_ciphers.is_empty() {
1944 conn.cipher = chosen;
1945 }
1946 let comp_ctx = req
1949 .contexts
1950 .iter()
1951 .find(|c| c.kind == smb_server_proto_smb2::negotiate::ctx_type::COMPRESSION);
1952 let comp_algos = comp_ctx
1953 .map(|c| {
1954 smb_server_proto_smb2::compress::negotiate_algos(
1955 &smb_server_proto_smb2::compress::parse_compression_caps(&c.data),
1956 )
1957 })
1958 .unwrap_or_default();
1959 if dialect == smb_server_proto_smb2::negotiate::DIALECT_311 {
1960 use smb_server_proto_smb2::compress::algo;
1964 conn.compress_algo = comp_algos
1965 .iter()
1966 .copied()
1967 .find(|a| matches!(*a, algo::LZNT1 | algo::LZ77));
1968 conn.compress_chained = conn.compress_algo.is_some()
1971 && comp_ctx.is_some_and(|c| {
1972 smb_server_proto_smb2::compress::parse_compression_flags(&c.data)
1973 & smb_server_proto_smb2::compress::CAP_FLAG_CHAINED
1974 != 0
1975 });
1976 }
1977 let compression_chained = conn.compress_chained;
1980 let signing_algo = req
1983 .contexts
1984 .iter()
1985 .find(|c| c.kind == smb_server_proto_smb2::negotiate::ctx_type::SIGNING)
1986 .and_then(|c| {
1987 smb_server_proto_smb2::negotiate::select_signing_algo(
1988 &smb_server_proto_smb2::negotiate::parse_signing_algos(&c.data),
1989 )
1990 });
1991 if dialect == smb_server_proto_smb2::negotiate::DIALECT_311
1992 && let Some(a) = signing_algo {
1993 conn.signing_algo = a;
1994 }
1995 NegotiateReply::Reply(
1996 Status::SUCCESS,
1997 smb_server_proto_smb2::negotiate::build_response_full(
1998 dialect,
1999 &server.guid,
2000 smb_server_proto::types::FileTime::now().0,
2001 &salt,
2002 (!client_ciphers.is_empty()).then(|| chosen.unwrap_or(0)),
2006 signing_algo,
2007 &comp_algos,
2008 compression_chained,
2009 server.require_signing,
2010 notifications,
2011 ),
2012 )
2013}
2014
2015#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn session_setup(
2017 server: &Arc<ServerShared>,
2018 conn: &mut Smb2Conn,
2019 buf: &[u8],
2020) -> Result<(Status, Vec<u8>), Status> {
2021 let req = ss::Request::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
2022 let hdr_session = buf
2026 .get(hdr::SESSION_ID..hdr::SIGNATURE)
2027 .and_then(|s| s.try_into().ok())
2028 .map(u64::from_le_bytes)
2029 .unwrap_or(0);
2030 let binding = req.flags & ss::FLAG_BINDING != 0
2031 && hdr_session != 0
2032 && server.sessions.get(hdr_session).is_some();
2033 if req.flags & ss::FLAG_BINDING != 0
2036 && hdr_session != 0
2037 && !matches!(conn.dialect, Some(d) if d >= smb_server_proto_smb2::negotiate::DIALECT_300)
2038 {
2039 return Err(Status::REQUEST_NOT_ACCEPTED);
2040 }
2041 conn.binding = binding;
2042 let reauth = !binding && hdr_session != 0 && server.sessions.get(hdr_session).is_some();
2046 let inner = smb_server_auth::ntlm::unwrap_blob(&req.blob).unwrap_or(&[]);
2047 if binding && !matches!(req.blob.first(), Some(0x60) | Some(0xA1)) {
2052 return Err(Status::INVALID_PARAMETER);
2053 }
2054 tracing::trace!(
2055 blob_len = req.blob.len(),
2056 msg_type = ?smb_server_auth::ntlm::msg_type(inner),
2057 "session setup token"
2058 );
2059 match smb_server_auth::ntlm::msg_type(inner) {
2060 Some(smb_server_auth::ntlm::MSG_TYPE1) | None => {
2061 conn.raw_ntlm = req.blob.first() != Some(&0x60) && req.blob.first() != Some(&0xA1);
2067 conn.session_id = if binding || reauth {
2071 hdr_session
2072 } else {
2073 next_session_id()
2074 };
2075 let mut t2 =
2076 smb_server_auth::ntlm::build_type2(&conn.challenge, &server.domain, &server.server_name);
2077 let extra = smb_server_auth::ntlm::NEGOTIATE_SIGN
2080 | smb_server_auth::ntlm::NEGOTIATE_SEAL
2081 | smb_server_auth::ntlm::NEGOTIATE_KEY_EXCH;
2082 let fl_off = 60 - 8; let fpos = 12 + 4 + 4; let _ = (fl_off, fpos);
2087 {
2088 const FLAGS_OFF: usize = 20;
2091 let cur = u32::from_le_bytes(t2[FLAGS_OFF..FLAGS_OFF + 4].try_into().unwrap());
2092 t2[FLAGS_OFF..FLAGS_OFF + 4].copy_from_slice(&(cur | extra).to_le_bytes());
2093 }
2094 let wrapped = if conn.raw_ntlm {
2095 t2
2096 } else {
2097 smb_server_auth::ntlm::wrap_negtoken_targ(&t2)
2098 };
2099 conn.ntlm_targ = Some(wrapped.clone());
2100 conn.ntlm_blobs = Some((req.blob.clone(), Vec::new()));
2101 Ok((
2102 Status::MORE_PROCESSING_REQUIRED,
2103 ss::build_response(0, &wrapped),
2104 ))
2105 }
2106 Some(smb_server_auth::ntlm::MSG_TYPE3) => {
2107 if let Some(blobs) = &mut conn.ntlm_blobs {
2108 blobs.1 = req.blob.clone();
2109 }
2110 if smb_server_auth::ntlm::parse_type3(inner).is_none() {
2111 tracing::warn!("session setup: malformed NTLMSSP type3");
2112 return Err(Status::INVALID_PARAMETER);
2113 }
2114 let t3 = smb_server_auth::ntlm::parse_type3(inner).unwrap();
2115 let out = crate::auth::authenticate_ntlmssp(
2116 &server.users,
2117 server.allow_guest,
2118 &conn.challenge,
2119 &t3,
2120 );
2121 if !out.ok {
2122 counter!("smb_server_auth_total", "outcome" => "fail").increment(1);
2123 return Err(Status::LOGON_FAILURE);
2124 }
2125 counter!("smb_server_auth_total", "outcome" => "ok").increment(1);
2126 gauge!("smb_sessions_active").increment(1.0);
2127 tracing::info!(user = %out.user, guest = out.guest,
2128 signed = out.session_key.is_some(), "session established");
2129 conn.user = out.user.clone();
2130 conn.guest = out.guest;
2131 conn.session_key = out.session_key;
2137 conn.authenticated = true;
2139 if reauth {
2143 conn.signing_key = None;
2144 conn.enc_keys = None;
2145 }
2146 if let Some(dialect) = conn
2152 .dialect
2153 .filter(|&d| d != smb_server_proto_smb2::negotiate::DIALECT_202)
2154 {
2155 use std::sync::LazyLock;
2156 static CLIENT_DIALECTS: LazyLock<
2157 std::sync::Mutex<std::collections::HashMap<[u8; 16], u16>>,
2158 > = LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
2159 let mut table = CLIENT_DIALECTS.lock().unwrap();
2160 match table.get(&conn.client_guid) {
2161 Some(&seen) if seen != dialect => return Err(Status::USER_SESSION_DELETED),
2162 Some(_) => {}
2163 None => {
2164 table.insert(conn.client_guid, dialect);
2165 }
2166 }
2167 }
2168 if binding
2175 && let Some(entry) = server.sessions.get(conn.session_id) {
2176 conn.cipher = entry.cipher;
2177 conn.enc_keys = entry.enc_keys;
2178 conn.encrypt_data = entry.encrypt_data;
2179 conn.dialect = entry.dialect.or(conn.dialect);
2180 }
2181 conn.scope = Some(crate::session_scope::get_or_create(conn.session_id));
2185 let flags: u16 = if out.guest { 0x0001 } else { 0x0000 };
2186 const MIC_OFFSET: usize = 60;
2196 const MIC_SIZE: usize = 16;
2197 let mic_field_present = req.blob.len() > MIC_OFFSET + MIC_SIZE;
2198 let client_mic_nonzero = mic_field_present
2199 && req.blob[MIC_OFFSET..MIC_OFFSET + MIC_SIZE]
2200 .iter()
2201 .any(|&b| b != 0);
2202 let wrapped = if conn.raw_ntlm {
2203 Vec::new()
2204 } else if client_mic_nonzero && conn.session_key.is_some() {
2205 let key = conn.session_key.unwrap();
2210 let init = conn
2211 .ntlm_blobs
2212 .as_ref()
2213 .map(|b| b.0.clone())
2214 .unwrap_or_default();
2215 #[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn der_len(b: &[u8], i: usize) -> Option<(usize, usize)> {
2223 let n = *b.get(i + 1)?;
2224 Some(match n {
2225 v @ 0..=0x7F => (v as usize, 2),
2226 0x81 => (*b.get(i + 2)? as usize, 3),
2227 _ => return None,
2228 })
2229 }
2230 let mech_types: Vec<u8> = (|| {
2231 for a in 0..init.len().saturating_sub(4) {
2232 if init[a] != 0xA0 {
2233 continue;
2234 }
2235 let (_alen, ahdr) = match der_len(&init, a) {
2237 Some(v) => v,
2238 None => continue,
2239 };
2240 let s30 = a + ahdr;
2241 if init.get(s30) != Some(&0x30) {
2242 continue;
2243 }
2244 let (slen, _) = match der_len(&init, s30) {
2245 Some(v) => v,
2246 None => continue,
2247 };
2248 let send = s30 + slen;
2249 for m in s30 + 2..send.saturating_sub(4) {
2251 if init[m] != 0xA0 {
2252 break; }
2254 let (_, mhdr) = match der_len(&init, m) {
2255 Some(v) => v,
2256 None => continue,
2257 };
2258 let t30 = m + mhdr;
2259 if init.get(t30) == Some(&0x30) {
2260 let (ilen, ihdr) = match der_len(&init, t30) {
2262 Some(v) => v,
2263 None => continue,
2264 };
2265 let end = (t30 + ihdr + ilen).min(init.len());
2266 if end > t30 {
2267 return init[t30..end].to_vec();
2268 }
2269 }
2270 }
2271 }
2272 Vec::new()
2273 })();
2274 let key_exch = t3.encrypted_session_key.len() == 16;
2275 #[cfg(feature = "lib")]
2276 let mic =
2281 smb_server_auth::crypto::ntlm_mech_list_mic(&key, true, key_exch, 0, &mech_types);
2282 #[cfg(not(feature = "lib"))]
2283 let mic = [0u8; 16];
2284 tracing::debug!(
2285 sk = %hex_str(&key),
2286 init = %hex_str(&init),
2287 mech = %hex_str(&mech_types),
2288 key_exch,
2289 mic = %hex_str(&mic),
2290 "mechListMIC inputs"
2291 );
2292 smb_server_auth::ntlm::wrap_accept_complete_with_mic(&mic)
2293 } else {
2294 smb_server_auth::ntlm::wrap_accept_complete()
2295 };
2296 Ok((Status::SUCCESS, ss::build_response(flags, &wrapped)))
2297 }
2298 _ => Err(Status::LOGON_FAILURE),
2299 }
2300}
2301
2302#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn tree_connect(
2306 server: &Arc<ServerShared>,
2307 _conn: &mut Smb2Conn,
2308 buf: &[u8],
2309) -> Result<(String, u8, bool, bool, bool), Status> {
2310 let path_len = g16(buf, c::tcon_off::PATH_LENGTH) as usize;
2311 let path_off = g16(buf, c::tcon_off::PATH_OFFSET) as usize;
2312 let raw = buf.get(path_off..path_off + path_len).unwrap_or(&[]);
2313 let path = String::from_utf16_lossy(
2314 &raw.chunks_exact(2)
2315 .map(|p| u16::from_le_bytes([p[0], p[1]]))
2316 .collect::<Vec<_>>(),
2317 )
2318 .trim_end_matches('\0')
2319 .to_string();
2320 let name = path
2321 .rsplit(['/', '\\'])
2322 .find(|s| !s.is_empty())
2323 .unwrap_or("")
2324 .to_lowercase();
2325 let share = server.shares.get(&name).ok_or(Status::BAD_NETWORK_NAME)?;
2326 let share_type = if share.is_ipc {
2327 c::share_type::PIPE
2328 } else {
2329 c::share_type::DISK
2330 };
2331 Ok((name, share_type, share.encrypt, share.compress, share.ca))
2332}
2333
2334#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) async fn create(
2338 conn: &mut Smb2Conn,
2339 vfs: Arc<dyn smb_server_vfs::Vfs>,
2340 server: &Arc<ServerShared>,
2341 req_signed: bool,
2342 is_ca: bool,
2343 buf: &[u8],
2344) -> Result<Vec<u8>, Status> {
2345 let req = c::CreateReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
2346
2347 const OPT_DIRECTORY_FILE: u32 = 0x1;
2348 const OPT_NON_DIRECTORY_FILE: u32 = 0x40;
2349 const OPT_DELETE_ON_CLOSE: u32 = 0x1000;
2350
2351 if let Some((id, guid)) = durable_reconnect_ids(&req.durable) {
2354 return durable_reconnect(conn, vfs, server, &req, id, guid).await;
2355 }
2356
2357 {
2360 let mut seen_real = false;
2361 for c in req.name.split(['\\', '/']).filter(|c| !c.is_empty()) {
2362 if c == ".." && seen_real {
2363 return Err(Status::INVALID_PARAMETER);
2364 }
2365 if c != "." && c != ".." {
2366 seen_real = true;
2367 }
2368 }
2369 }
2370 if req.options & OPT_DELETE_ON_CLOSE != 0
2373 && req.desired_access & (0x0001_0000 | 0x1000_0000) == 0
2374 {
2375 return Err(Status::ACCESS_DENIED);
2376 }
2377
2378 let rel = req.name.trim_start_matches(['\\', '/']).to_string();
2379 let want_dir = req.options & OPT_DIRECTORY_FILE != 0;
2380 if let Ok(list) = server.durables.list().await
2384 && list
2385 .iter()
2386 .any(|r| r.path == rel && r.flags & c::durable::FLAG_PERSISTENT != 0)
2387 {
2388 return Err(Status::FILE_NOT_AVAILABLE);
2389 }
2390
2391 let (mut open, meta, action) = match vfs
2392 .create(
2393 &rel,
2394 want_dir,
2395 req.desired_access,
2396 req.disposition,
2397 req.options,
2398 req.attrs,
2399 )
2400 .await
2401 {
2402 Ok(v) => v,
2403 Err(smb_server_vfs::VfsError::StoppedOnSymlink {
2407 target,
2408 unparsed_len,
2409 relative,
2410 }) => {
2411 conn.symlink_error = Some(c::build_symlink_error_response(
2412 &target,
2413 &target,
2414 unparsed_len,
2415 relative,
2416 ));
2417 return Err(Status::STOPPED_ON_SYMLINK);
2418 }
2419 Err(e) => return Err(vfs_err(e)),
2420 };
2421
2422 if req.options & OPT_NON_DIRECTORY_FILE != 0 && open.is_dir {
2423 return Err(Status::FILE_IS_A_DIRECTORY);
2424 }
2425 open.delete_on_close |= req.options & OPT_DELETE_ON_CLOSE != 0;
2426
2427 let fid_bytes = next_file_id();
2428 let path = open.path.clone();
2429 let is_dir = open.is_dir;
2430 if let Some(app_id) = req.app_instance_id {
2435 let is_311 = conn.dialect == Some(smb_server_proto_smb2::negotiate::DIALECT_311);
2436 match server.app_instances.resolve(
2437 app_id,
2438 &path,
2439 conn.client_guid,
2440 is_311,
2441 req.app_instance_version,
2442 ) {
2443 crate::state::AppInstanceMatch::Reject => {
2444 let _ = vfs.close(open).await;
2445 return Err(Status::FILE_FORCED_CLOSED);
2446 }
2447 crate::state::AppInstanceMatch::ForceClose { path: vpath, owner } => {
2448 server.share_modes.close(&vpath, owner);
2449 server.oplocks.take(&vpath);
2450 server.locks.release_owner(owner);
2451 server.leases.release(&vpath, owner);
2452 }
2453 crate::state::AppInstanceMatch::None => {}
2454 }
2455 }
2456 if !server.share_modes.try_open(
2462 &path,
2463 req.desired_access,
2464 req.share_access,
2465 (conn.session_id, fid_bytes),
2466 ) {
2467 if is_dir {
2468 break_dir_lease_wait(server, conn, &path, fid_bytes, c::lease::HANDLE_CACHING).await;
2469 }
2470 let _ = vfs.close(open).await;
2471 counter!("smb_sharing_violations_total").increment(1);
2472 return Err(Status::SHARING_VIOLATION);
2473 }
2474
2475 let fid = c::FileId(fid_bytes);
2476 conn.searches.remove(&fid_bytes);
2477 conn.handle_insert(fid_bytes, open);
2478 if let Some(scope) = conn.scope.as_ref() {
2480 let channel_sequence = u16::from_le_bytes([buf[hdr::STATUS], buf[hdr::STATUS + 1]]);
2481 scope.borrow_mut().channel_seq.insert(
2482 fid_bytes,
2483 crate::session_scope::ChannelSeq {
2484 channel_sequence,
2485 outstanding_request_count: 1,
2486 outstanding_pre_request_count: 0,
2487 },
2488 );
2489 }
2490 conn.chain_fid = Some(fid_bytes);
2493 if let Some(app_id) = req.app_instance_id {
2494 server.app_instances.register(
2495 app_id,
2496 path.clone(),
2497 conn.client_guid,
2498 (conn.session_id, fid_bytes),
2499 req.app_instance_version,
2500 );
2501 }
2502
2503 if req.lease.is_none() && !is_dir {
2507 for (k, old, new, ep, out, crypto) in server.leases.break_conflict(
2508 &path,
2509 (conn.session_id, fid_bytes),
2510 None,
2511 c::lease::WRITE_CACHING,
2512 ) {
2513 send_lease_break(&out, &crypto, k, old, new, ep);
2514 }
2515 }
2516
2517 const FILE_ACTION_CREATED: u32 = 2;
2521 if action == FILE_ACTION_CREATED {
2522 break_dir_lease(server, conn, parent_dir(&path), fid_bytes, c::lease::RH);
2523 }
2524
2525 let lease_context = req.oplock_level == c::oplock::LEASE && req.lease.is_some();
2531 let wants_oplock = matches!(
2532 req.oplock_level,
2533 c::oplock::LEVEL_II | c::oplock::EXCLUSIVE | c::oplock::BATCH
2534 ) || (req.oplock_level == c::oplock::LEASE && req.lease.is_none());
2535 let mut lease_grant: Option<c::LeaseResp> = None;
2538 let dir_leasing = is_dir
2541 && conn
2542 .dialect
2543 .map(|d| d >= smb_server_proto_smb2::negotiate::DIALECT_300)
2544 .unwrap_or(false);
2545 let granted = if lease_context && (!is_dir || dir_leasing) {
2546 if let Some(lr) = req.lease {
2547 let (resp, ack_waits) = arbitrate_lease(
2548 server, conn, &path, fid_bytes, &lr, req_signed, is_dir,
2549 );
2550 lease_grant = Some(resp);
2551 conn.lease_keys.insert(fid_bytes, lr.key);
2552 for rx in ack_waits {
2555 let _ = tokio::time::timeout(LEASE_ACK_TIMEOUT, rx).await;
2556 }
2557 c::oplock::LEASE
2558 } else {
2559 c::oplock::NONE
2560 }
2561 } else if is_dir {
2562 c::oplock::NONE
2563 } else if server.share_modes.open_count(&path) > 1 {
2564 if let Some(holder) = server.oplocks.take(&path) {
2565 send_oplock_break(&holder, c::oplock::NONE);
2566 }
2567 c::oplock::NONE
2568 } else if wants_oplock {
2569 let holder = crate::state::OplockHolder {
2570 session_id: conn.session_id,
2571 file_id: fid_bytes,
2572 outbound: conn.outbound.clone(),
2573 crypto: break_crypto(conn, req_signed),
2574 };
2575 if server.oplocks.grant(&path, holder) {
2576 counter!("smb_oplocks_granted_total").increment(1);
2577 c::oplock::EXCLUSIVE
2578 } else {
2579 c::oplock::NONE
2580 }
2581 } else {
2582 c::oplock::NONE
2583 };
2584
2585 tracing::debug!(
2586 name = %rel, eof = meta.eof, dir = meta.is_dir, action,
2587 oplock = granted, lease = ?lease_grant.map(|l| l.state), "create"
2588 );
2589 counter!("smb_creates_total").increment(1);
2590
2591 let durable_grant = grant_durable(
2595 conn,
2596 &req,
2597 fid_bytes,
2598 &rel,
2599 is_dir,
2600 lease_grant.as_ref().map(|l| l.state),
2601 is_ca,
2602 );
2603
2604 let mut ctx_entries: Vec<(&[u8], Vec<u8>)> = Vec::new();
2606 if let Some(l) = &lease_grant {
2607 ctx_entries.push((c::lease::CONTEXT_NAME, c::lease_context_data(l)));
2608 }
2609 if let Some(d) = &durable_grant {
2610 ctx_entries.push((d.0, d.1.clone()));
2611 }
2612 let contexts = c::encode_create_contexts(&ctx_entries);
2613 Ok(c::build_create_resp(
2614 fid,
2615 action,
2616 [
2617 meta.times[0].0,
2618 meta.times[1].0,
2619 meta.times[2].0,
2620 meta.times[3].0,
2621 ],
2622 meta.attrs.0,
2623 meta.alloc,
2624 meta.eof,
2625 meta.is_dir,
2626 granted,
2627 &contexts,
2628 ))
2629}
2630
2631fn durable_reconnect_ids(d: &Option<c::DurableReq>) -> Option<([u8; 16], Option<[u8; 16]>)> {
2634 match d {
2635 Some(c::DurableReq::ReconnectV2 {
2636 file_id,
2637 create_guid,
2638 ..
2639 }) => Some((*file_id, Some(*create_guid))),
2640 Some(c::DurableReq::ReconnectV1 { file_id }) => Some((*file_id, None)),
2641 _ => None,
2642 }
2643}
2644
2645#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] async fn durable_reconnect(
2649 conn: &mut Smb2Conn,
2650 vfs: Arc<dyn smb_server_vfs::Vfs>,
2651 server: &Arc<ServerShared>,
2652 req: &c::CreateReq,
2653 id: [u8; 16],
2654 guid: Option<[u8; 16]>,
2655) -> Result<Vec<u8>, Status> {
2656 let tags = req.durable_ctx_tags;
2661 if guid.is_none() {
2662 if tags & (c::durable::tag::REQ_V2 | c::durable::tag::RECONNECT_V2) != 0 {
2663 return Err(Status::INVALID_PARAMETER);
2664 }
2665 } else if tags
2666 & (c::durable::tag::REQ_V1 | c::durable::tag::RECONNECT_V1 | c::durable::tag::REQ_V2)
2667 != 0
2668 {
2669 return Err(Status::OBJECT_NAME_NOT_FOUND);
2670 }
2671
2672 let reconnect_persistent = matches!(
2676 req.durable,
2677 Some(c::DurableReq::ReconnectV2 { flags, .. }) if flags & c::durable::FLAG_PERSISTENT != 0
2678 );
2679 let mut key = id;
2680 let mut via_create_guid = false;
2681 if server.durables.get(&key).await.ok().flatten().is_none()
2682 && reconnect_persistent
2683 && let Some(g) = guid
2684 && let Ok(list) = server.durables.list().await
2685 && let Some(rec) = list.into_iter().find(|r| r.match_guid == Some(g)) {
2686 key = rec.create_guid;
2687 via_create_guid = true;
2688 }
2689
2690 let peeked = server
2693 .durables
2694 .get(&key)
2695 .await
2696 .ok()
2697 .flatten()
2698 .ok_or(Status::OBJECT_NAME_NOT_FOUND)?;
2699 if guid.is_some() && peeked.match_guid != guid {
2700 return Err(Status::OBJECT_NAME_NOT_FOUND);
2701 }
2702 if !via_create_guid {
2708 if peeked.flags & c::durable::FLAG_PERSISTENT != 0 && !reconnect_persistent {
2709 return Err(Status::OBJECT_NAME_NOT_FOUND);
2710 }
2711 if let Some(_orig_key) = peeked.lease_key {
2712 if req.name.trim_start_matches(['\\', '/']) != peeked.path {
2713 return Err(if guid.is_some() {
2714 Status::OBJECT_NAME_NOT_FOUND
2715 } else {
2716 Status::INVALID_PARAMETER
2717 });
2718 }
2719 if peeked.client_guid != conn.client_guid {
2720 return Err(Status::OBJECT_NAME_NOT_FOUND);
2721 }
2722 if req.lease.is_none() {
2723 return Err(Status::OBJECT_NAME_NOT_FOUND);
2724 }
2725 }
2726 }
2727 if let (Some(l), Some(orig_key)) = (&req.lease, peeked.lease_key)
2730 && l.key != orig_key {
2731 return Err(Status::OBJECT_NAME_NOT_FOUND);
2732 }
2733
2734 if !peeked.owner_user.eq_ignore_ascii_case(&conn.user) {
2738 return Err(Status::ACCESS_DENIED);
2739 }
2740
2741 let record = server
2742 .durables
2743 .take(&key, guid, crate::state::now_ms())
2744 .await
2745 .ok()
2746 .flatten()
2747 .ok_or(Status::OBJECT_NAME_NOT_FOUND)?;
2748 let persistent = record.flags & c::durable::FLAG_PERSISTENT != 0;
2749 let timeout = record.timeout_ms as u32;
2750 let (mut open, meta, _action) = vfs
2752 .create(
2753 &record.path,
2754 record.is_dir,
2755 record.access,
2756 1,
2757 record.create_options,
2758 0,
2759 )
2760 .await
2761 .map_err(vfs_err)?;
2762 open.delete_on_close = false;
2763 conn.handle_insert(id, open);
2764 conn.durable.insert(
2765 id,
2766 crate::state::DurableEntry {
2767 persistent_id: id,
2768 create_guid: record.match_guid.unwrap_or([0u8; 16]),
2769 rel: record.path.clone(),
2770 is_dir: record.is_dir,
2771 access: record.access,
2772 options: record.create_options,
2773 session_id: conn.session_id,
2774 owner_user: record.owner_user.clone(),
2775 client_guid: record.client_guid,
2776 lease_key: record.lease_key,
2777 lease_state: record.lease_state,
2778 persistent,
2779 timeout,
2780 deadline: std::time::Instant::now(),
2781 },
2782 );
2783 counter!("smb_durable_reconnects_total").increment(1);
2784
2785 let (name, data): (&[u8], Vec<u8>) = if guid.is_some() {
2786 let flags = if persistent {
2787 c::durable::FLAG_PERSISTENT
2788 } else {
2789 0
2790 };
2791 (c::durable::REQ_V2, c::durable_v2_resp_data(timeout, flags))
2792 } else {
2793 (c::durable::REQ_V1, c::durable_v1_resp_data())
2794 };
2795 let mut ctx_list: Vec<(&[u8], Vec<u8>)> = vec![(name, data)];
2796 if let Some(lease_key) = record.lease_key {
2799 let grant = c::LeaseResp {
2800 key: lease_key,
2801 state: record.lease_state,
2802 flags: 0,
2803 epoch: 0,
2804 v2: false,
2805 };
2806 ctx_list.push((c::lease::CONTEXT_NAME, c::lease_context_data(&grant)));
2807 }
2808 let contexts = c::encode_create_contexts(&ctx_list);
2809 Ok(c::build_create_resp(
2810 c::FileId(id),
2811 1, [
2813 meta.times[0].0,
2814 meta.times[1].0,
2815 meta.times[2].0,
2816 meta.times[3].0,
2817 ],
2818 meta.attrs.0,
2819 meta.alloc,
2820 meta.eof,
2821 meta.is_dir,
2822 c::oplock::NONE,
2823 &contexts,
2824 ))
2825}
2826
2827fn grant_durable(
2833 conn: &mut Smb2Conn,
2834 req: &c::CreateReq,
2835 fid: [u8; 16],
2836 rel: &str,
2837 is_dir: bool,
2838 lease_state: Option<u32>,
2839 is_ca: bool,
2840) -> Option<(&'static [u8], Vec<u8>)> {
2841 let persistent_eligible = is_ca
2845 && matches!(req.durable, Some(c::DurableReq::RequestV2 { flags, .. }) if flags & c::durable::FLAG_PERSISTENT != 0);
2846 if !persistent_eligible {
2847 match lease_state {
2848 Some(state) if state & c::lease::HANDLE_CACHING != 0 => {}
2849 None if req.oplock_level == c::oplock::BATCH => {}
2850 _ => return None,
2851 }
2852 }
2853 const DEFAULT_TIMEOUT_MS: u32 = 60_000;
2855 let make = |create_guid: [u8; 16], persistent: bool, timeout: u32| crate::state::DurableEntry {
2856 persistent_id: fid,
2857 create_guid,
2858 rel: rel.to_string(),
2859 is_dir,
2860 access: req.desired_access,
2861 options: req.options,
2862 session_id: conn.session_id,
2863 owner_user: conn.user.clone(),
2864 client_guid: conn.client_guid,
2865 lease_key: req.lease.as_ref().map(|l| l.key),
2866 lease_state: lease_state.unwrap_or(0),
2867 persistent,
2868 timeout,
2869 deadline: std::time::Instant::now(),
2870 };
2871 match req.durable {
2872 Some(c::DurableReq::RequestV2 {
2873 timeout,
2874 flags,
2875 create_guid,
2876 }) => {
2877 let persistent = is_ca && flags & c::durable::FLAG_PERSISTENT != 0;
2880 let to = if timeout == 0 {
2881 DEFAULT_TIMEOUT_MS
2882 } else {
2883 timeout
2884 };
2885 conn.durable.insert(fid, make(create_guid, persistent, to));
2886 counter!("smb_durables_granted_total").increment(1);
2887 Some((
2888 c::durable::REQ_V2,
2889 c::durable_v2_resp_data(
2890 to,
2891 if persistent {
2892 c::durable::FLAG_PERSISTENT
2893 } else {
2894 0
2895 },
2896 ),
2897 ))
2898 }
2899 Some(c::DurableReq::RequestV1) => {
2900 conn.durable
2901 .insert(fid, make([0u8; 16], false, DEFAULT_TIMEOUT_MS));
2902 counter!("smb_durables_granted_total").increment(1);
2903 Some((c::durable::REQ_V1, c::durable_v1_resp_data()))
2904 }
2905 _ => None,
2906 }
2907}
2908
2909fn arbitrate_lease(
2916 server: &Arc<ServerShared>,
2917 conn: &Smb2Conn,
2918 path: &str,
2919 fid: [u8; 16],
2920 lr: &c::LeaseReq,
2921 signed: bool,
2922 is_dir: bool,
2923) -> (c::LeaseResp, Vec<tokio::sync::oneshot::Receiver<()>>) {
2924 let allowed = if is_dir { c::lease::RH } else { c::lease::RWH };
2927 let requested = lr.state & allowed;
2928 if let Some((state, epoch, v2)) = server.leases.peek_key(path, lr.key) {
2931 return (
2932 c::LeaseResp {
2933 key: lr.key,
2934 state,
2935 flags: 0,
2936 epoch,
2937 v2,
2938 },
2939 Vec::new(),
2940 );
2941 }
2942 let contended = server.leases.has_holders(path);
2947 let mut ack_waits = Vec::new();
2948 if !is_dir {
2949 for (hkey, old, new, ep, out, crypto) in server.leases.break_conflict(
2950 path,
2951 (conn.session_id, fid),
2952 Some(lr.key),
2953 c::lease::WRITE_CACHING,
2954 ) {
2955 let rx = server.leases.register_ack_wait(hkey);
2956 send_lease_break(&out, &crypto, hkey, old, new, ep);
2957 ack_waits.push(rx);
2958 }
2959 }
2960 let grant_state = if contended && !is_dir {
2964 requested & c::lease::RH
2965 } else {
2966 requested
2967 };
2968 let granted_epoch = if !lr.v2 {
2973 0
2974 } else if contended && !is_dir {
2975 lr.epoch
2976 } else {
2977 lr.epoch.wrapping_add(1)
2978 };
2979 let holder = crate::state::LeaseHolder {
2980 key: lr.key,
2981 state: grant_state,
2982 epoch: granted_epoch,
2983 v2: lr.v2,
2984 session_id: conn.session_id,
2985 file_id: fid,
2986 outbound: conn.outbound.clone(),
2987 crypto: break_crypto(conn, signed),
2988 client_guid: conn.client_guid,
2989 breaking: false,
2990 break_to: grant_state,
2991 };
2992 server.leases.grant(path, holder);
2993 counter!("smb_leases_granted_total").increment(1);
2994 (
2995 c::LeaseResp {
2996 key: lr.key,
2997 state: grant_state,
2998 flags: 0,
2999 epoch: granted_epoch,
3000 v2: lr.v2,
3001 },
3002 ack_waits,
3003 )
3004}
3005
3006fn network_interface_info() -> Vec<u8> {
3009 let mut entries: Vec<(u32, std::net::IpAddr)> = Vec::new();
3010 if let Ok(ifaces) = if_addrs::get_if_addrs() {
3011 for (i, iface) in ifaces.iter().enumerate() {
3012 entries.push((i as u32 + 1, iface.ip()));
3013 }
3014 }
3015 build_interface_list(&entries)
3016}
3017
3018#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn build_interface_list(entries: &[(u32, std::net::IpAddr)]) -> Vec<u8> {
3022 const LINK_SPEED: u64 = 10_000_000_000;
3023 let mut out = Vec::new();
3024 for (idx, (ifindex, ip)) in entries.iter().enumerate() {
3025 let start = out.len();
3026 out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&ifindex.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&LINK_SPEED.to_le_bytes()); let mut sa = [0u8; 128]; match ip {
3033 std::net::IpAddr::V4(v4) => {
3034 sa[0..2].copy_from_slice(&2u16.to_le_bytes()); sa[4..8].copy_from_slice(&v4.octets()); }
3037 std::net::IpAddr::V6(v6) => {
3038 sa[0..2].copy_from_slice(&23u16.to_le_bytes()); sa[8..24].copy_from_slice(&v6.octets()); }
3041 }
3042 out.extend_from_slice(&sa);
3043 if idx + 1 < entries.len() {
3044 let next = (out.len() - start) as u32;
3045 out[start..start + 4].copy_from_slice(&next.to_le_bytes());
3046 }
3047 }
3048 out
3049}
3050
3051fn send_lease_break(
3054 outbound: &mpsc::Sender<Vec<u8>>,
3055 crypto: &crate::state::BreakCrypto,
3056 key: [u8; 16],
3057 current: u32,
3058 new: u32,
3059 epoch: u16,
3060) {
3061 let body = c::build_lease_break(key, current, new, epoch, c::lease::BREAK_FLAG_ACK_REQUIRED);
3062 let frame = finalize_break(crypto, build_break_frame(&body));
3063 if outbound.try_send(frame).is_ok() {
3064 counter!("smb_lease_breaks_total").increment(1);
3065 }
3066}
3067
3068pub(crate) fn break_dir_lease(
3073 server: &Arc<ServerShared>,
3074 conn: &Smb2Conn,
3075 dir: &str,
3076 owner: [u8; 16],
3077 clear: u32,
3078) {
3079 let req_key = conn.lease_keys.get(&owner).copied();
3080 for (key, old, new, epoch, outbound, crypto) in
3081 server
3082 .leases
3083 .break_conflict(dir, (conn.session_id, owner), req_key, clear)
3084 {
3085 send_lease_break(&outbound, &crypto, key, old, new, epoch);
3086 }
3087}
3088
3089pub(crate) async fn break_dir_lease_wait(
3093 server: &Arc<ServerShared>,
3094 conn: &Smb2Conn,
3095 dir: &str,
3096 owner: [u8; 16],
3097 clear: u32,
3098) {
3099 let req_key = conn.lease_keys.get(&owner).copied();
3100 let mut waits = Vec::new();
3101 for (key, old, new, epoch, outbound, crypto) in
3102 server
3103 .leases
3104 .break_conflict(dir, (conn.session_id, owner), req_key, clear)
3105 {
3106 let rx = server.leases.register_ack_wait(key);
3107 send_lease_break(&outbound, &crypto, key, old, new, epoch);
3108 waits.push(rx);
3109 }
3110 for rx in waits {
3111 let _ = tokio::time::timeout(LEASE_ACK_TIMEOUT, rx).await;
3112 }
3113}
3114
3115pub(crate) async fn break_subtree_lease_wait(
3121 server: &Arc<ServerShared>,
3122 conn: &Smb2Conn,
3123 dir: &str,
3124 owner: [u8; 16],
3125 clear: u32,
3126) {
3127 let mut waits = Vec::new();
3128 for (key, old, new, epoch, outbound, crypto) in
3129 server
3130 .leases
3131 .break_subtree(dir, (conn.session_id, owner), clear)
3132 {
3133 let rx = server.leases.register_ack_wait(key);
3134 send_lease_break(&outbound, &crypto, key, old, new, epoch);
3135 waits.push(rx);
3136 }
3137 for rx in waits {
3138 let _ = tokio::time::timeout(LEASE_ACK_TIMEOUT, rx).await;
3139 }
3140}
3141
3142pub(crate) fn parent_dir(path: &str) -> &str {
3145 path.rsplit_once(['/', '\\']).map(|(p, _)| p).unwrap_or("")
3146}
3147
3148fn break_crypto(conn: &Smb2Conn, signed: bool) -> crate::state::BreakCrypto {
3151 crate::state::BreakCrypto {
3152 dialect: conn.dialect,
3153 signing_key: conn.signing_key.or(conn.session_key),
3154 enc_keys: conn.enc_keys,
3155 cipher: conn.cipher,
3156 session_id: conn.session_id,
3157 encrypt: conn.encrypt_data || conn.peer_encrypts,
3158 signed,
3159 }
3160}
3161
3162fn send_oplock_break(holder: &crate::state::OplockHolder, level: u8) {
3165 let body = c::build_oplock_break(c::FileId(holder.file_id), level);
3166 let frame = finalize_break(&holder.crypto, build_break_frame(&body));
3167 if holder.outbound.try_send(frame).is_ok() {
3168 counter!("smb_oplock_breaks_total").increment(1);
3169 }
3170}
3171
3172fn build_break_frame(body: &[u8]) -> Vec<u8> {
3175 let mut f = Vec::with_capacity(hdr::LEN + body.len());
3176 f.extend_from_slice(&smb_server_proto_smb2::SMB2_MAGIC);
3177 f.extend_from_slice(&(hdr::LEN as u16).to_le_bytes()); f.extend_from_slice(&0u16.to_le_bytes()); f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&ss::cmd::OPLOCK_BREAK.to_le_bytes());
3181 f.extend_from_slice(&0u16.to_le_bytes()); f.extend_from_slice(&hdr_flags::SERVER_TO_REDIR.to_le_bytes()); f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&u64::MAX.to_le_bytes()); f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&0u64.to_le_bytes());
3189 f.extend_from_slice(&[0u8; 16]); f.extend_from_slice(body);
3191 f
3192}
3193
3194fn finalize_break(crypto: &crate::state::BreakCrypto, frame: Vec<u8>) -> Vec<u8> {
3198 if crypto.encrypt
3199 && let (Some(keys), Some(cipher)) = (crypto.enc_keys, crypto.cipher)
3200 && let Some(sealed) = seal_pdu(crypto.session_id, keys, cipher, &frame) {
3201 return sealed;
3202 }
3203 frame
3204}
3205
3206#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) async fn read(
3210 conn: &mut Smb2Conn,
3211 vfs: Arc<dyn smb_server_vfs::Vfs>,
3212 server: &Arc<ServerShared>,
3213 buf: &[u8],
3214) -> Result<Vec<u8>, Status> {
3215 let req = c::ReadReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
3216 let len = (req.length as usize).min(1 << 20); let (path, is_dir, can_read) = conn
3218 .with_handle(&req.file_id.0, |h| (h.path.clone(), h.is_dir, h.can_read))
3219 .ok_or(Status::FILE_CLOSED)?;
3220 if is_dir || !can_read {
3221 return Err(Status::ACCESS_DENIED);
3222 }
3223 if server.locks.read_conflict(
3225 &path,
3226 req.offset,
3227 len as u64,
3228 (conn.session_id, req.file_id.0),
3229 ) {
3230 return Err(Status::FILE_LOCK_CONFLICT);
3231 }
3232 let mut open = conn
3235 .handle_take(&req.file_id.0)
3236 .ok_or(Status::FILE_CLOSED)?;
3237 let result = vfs.read(&mut open, req.offset, len).await;
3238 conn.handle_insert(req.file_id.0, open);
3239 let data = result.map_err(vfs_err)?;
3240 counter!("smb_bytes_read_total").increment(data.len() as u64);
3241 tracing::trace!(offset = req.offset, got = data.len(), "read");
3242 Ok(c::build_read_resp(&data))
3243}
3244
3245pub(crate) async fn write(
3246 conn: &mut Smb2Conn,
3247 vfs: Arc<dyn smb_server_vfs::Vfs>,
3248 server: &Arc<ServerShared>,
3249 buf: &[u8],
3250) -> Result<Vec<u8>, Status> {
3251 let req = c::WriteReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
3252 let (path, is_dir, can_write) = conn
3253 .with_handle(&req.file_id.0, |h| (h.path.clone(), h.is_dir, h.can_write))
3254 .ok_or(Status::FILE_CLOSED)?;
3255 if is_dir || !can_write {
3256 return Err(Status::ACCESS_DENIED);
3257 }
3258 if server.locks.write_conflict(
3260 &path,
3261 req.offset,
3262 req.payload.len() as u64,
3263 (conn.session_id, req.file_id.0),
3264 ) {
3265 return Err(Status::FILE_LOCK_CONFLICT);
3266 }
3267 let mut open = conn
3268 .handle_take(&req.file_id.0)
3269 .ok_or(Status::FILE_CLOSED)?;
3270 let result = vfs.write(&mut open, req.offset, &req.payload, false).await;
3271 conn.handle_insert(req.file_id.0, open);
3272 let written = result.map_err(vfs_err)?;
3273 counter!("smb_bytes_written_total").increment(written);
3274 tracing::trace!(offset = req.offset, wrote = written, "write");
3275 let req_key = conn.lease_keys.get(&req.file_id.0).copied();
3278 for (k, old, new, ep, out, crypto) in server.leases.break_conflict(
3279 &path,
3280 (conn.session_id, req.file_id.0),
3281 req_key,
3282 c::lease::RWH,
3283 ) {
3284 send_lease_break(&out, &crypto, k, old, new, ep);
3285 }
3286 break_dir_lease(server, conn, parent_dir(&path), req.file_id.0, c::lease::RH);
3289 Ok(c::build_write_resp(written as u32))
3290}
3291
3292pub(crate) enum IoctlReply {
3295 Reply(Status, Vec<u8>),
3296 Silent,
3297}
3298
3299#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) async fn ioctl(
3303 conn: &mut Smb2Conn,
3304 server: &Arc<ServerShared>,
3305 tid: u32,
3306 buf: &[u8],
3307) -> IoctlReply {
3308 let Some(req) = c::IoctlReq::parse(buf) else {
3309 tracing::debug!("ioctl parse failed");
3310 return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new());
3311 };
3312 tracing::debug!(ctl = format!("{:#010x}", req.ctl_code), "ioctl");
3313 let (status, body) = match req.ctl_code {
3314 c::fsctl::VALIDATE_NEGOTIATE_INFO => {
3321 let input = &req.input;
3322 let terminate = 'v: {
3323 if conn.dialect == Some(smb_server_proto_smb2::negotiate::DIALECT_311) {
3324 break 'v true;
3325 }
3326 if input.len() < 24 || (req.max_output as usize) < 24 {
3327 break 'v true;
3328 }
3329 let req_caps = u32::from_le_bytes(input[0..4].try_into().unwrap());
3330 if req_caps != conn.client_capabilities {
3331 break 'v true;
3332 }
3333 if input[4..20] != conn.client_guid {
3334 break 'v true;
3335 }
3336 let req_secmode = u16::from_le_bytes(input[20..22].try_into().unwrap());
3337 if req_secmode != conn.client_security_mode {
3338 break 'v true;
3339 }
3340 let count = u16::from_le_bytes(input[22..24].try_into().unwrap()) as usize;
3341 let mut dialects = Vec::with_capacity(count);
3342 for i in 0..count {
3343 match input.get(24 + i * 2..26 + i * 2) {
3344 Some(s) => dialects.push(u16::from_le_bytes(s.try_into().unwrap())),
3345 None => break 'v true,
3346 }
3347 }
3348 const SUPPORTED: [u16; 5] = [0x0202, 0x0210, 0x0300, 0x0302, 0x0311];
3349 let gcd = dialects
3350 .iter()
3351 .copied()
3352 .filter(|d| SUPPORTED.contains(d))
3353 .max();
3354 gcd != conn.dialect
3355 };
3356 if terminate {
3357 conn.disconnect = true;
3358 return IoctlReply::Silent;
3359 }
3360 let mut out = Vec::with_capacity(24);
3361 out.extend_from_slice(&conn.advertised_caps.to_le_bytes());
3365 out.extend_from_slice(&server.guid);
3366 let sec_mode = smb_server_proto_smb2::negotiate::SIGNING_ENABLED
3367 | if server.require_signing {
3368 smb_server_proto_smb2::negotiate::SIGNING_REQUIRED
3369 } else {
3370 0
3371 };
3372 out.extend_from_slice(&sec_mode.to_le_bytes()); out.extend_from_slice(
3374 &conn
3375 .dialect
3376 .unwrap_or(smb_server_proto_smb2::negotiate::DIALECT_210)
3377 .to_le_bytes(),
3378 );
3379 (
3380 Status::SUCCESS,
3381 c::build_ioctl_resp(req.file_id, req.ctl_code, &out),
3382 )
3383 }
3384 c::fsctl::LMR_REQUEST_RESILIENCY => {
3388 if let Some((can_write, is_dir, rel)) =
3389 conn.with_handle(&req.file_id.0, |o| (o.can_write, o.is_dir, o.rel.clone()))
3390 {
3391 let timeout = req
3392 .input
3393 .get(0..4)
3394 .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
3395 .filter(|t| *t != 0)
3396 .unwrap_or(60_000);
3397 let access = if can_write { 0x001F_01FF } else { 0x0012_0089 };
3398 let options = if is_dir { 0x1 } else { 0x40 };
3399 let entry = crate::state::DurableEntry {
3400 persistent_id: req.file_id.0,
3401 create_guid: [0u8; 16],
3402 rel,
3403 is_dir,
3404 access,
3405 options,
3406 session_id: conn.session_id,
3407 owner_user: conn.user.clone(),
3408 client_guid: conn.client_guid,
3409 lease_key: None,
3410 lease_state: 0,
3411 persistent: false,
3412 timeout,
3413 deadline: std::time::Instant::now(),
3414 };
3415 conn.durable.insert(req.file_id.0, entry);
3416 }
3417 (
3418 Status::SUCCESS,
3419 c::build_ioctl_resp(req.file_id, req.ctl_code, &[]),
3420 )
3421 }
3422 c::fsctl::PIPE_TRANSACT => {
3427 let Some(pipe) = conn.pipes.get_mut(&req.file_id.0) else {
3428 tracing::debug!(pipes = conn.pipes.len(), "transact on non-pipe fid");
3429 return IoctlReply::Reply(Status::NOT_IMPLEMENTED, Vec::new());
3430 };
3431 let shares = server.share_infos();
3432 let out = {
3433 pipe.on_write(&req.input, &shares);
3434 counter!("smb_pipe_writes_total").increment(1);
3435 pipe.take(req.max_output as usize)
3436 };
3437 counter!("smb_pipe_reads_total").increment(1);
3438 let out_hex = hex_str(&out);
3439 tracing::debug!(out_len = out.len(), %out_hex, "pipe transact");
3440 (
3441 Status::SUCCESS,
3442 c::build_ioctl_resp(req.file_id, req.ctl_code, &out),
3443 )
3444 }
3445 c::fsctl::SRV_REQUEST_RESUME_KEY => {
3448 if !conn.handle_exists(&req.file_id.0) {
3449 return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new());
3450 }
3451 let mut key = [0u8; 24];
3452 key[..16].copy_from_slice(&req.file_id.0);
3453 key[16..24].copy_from_slice(&next_resume_nonce().to_le_bytes());
3454 conn.resume_keys.insert(key, req.file_id.0);
3455 (
3456 Status::SUCCESS,
3457 c::build_ioctl_resp(req.file_id, req.ctl_code, &c::build_resume_key_resp(&key)),
3458 )
3459 }
3460 c::fsctl::SRV_COPYCHUNK | c::fsctl::SRV_COPYCHUNK_WRITE => {
3463 let vfs = match share_vfs(server, conn, tid) {
3464 Some(v) => v,
3465 None => return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new()),
3466 };
3467 match do_copychunk(conn, vfs, &req).await {
3468 Ok(out) => {
3469 counter!("smb_copychunk_bytes_total").increment(copychunk_total(&out) as u64);
3470 (
3471 Status::SUCCESS,
3472 c::build_ioctl_resp(req.file_id, req.ctl_code, &out),
3473 )
3474 }
3475 Err((status, out)) => {
3476 let body = c::build_ioctl_resp(req.file_id, req.ctl_code, &out);
3477 return IoctlReply::Reply(status, body);
3478 }
3479 }
3480 }
3481 c::fsctl::OFFLOAD_READ => {
3485 let vfs = match share_vfs(server, conn, tid) {
3486 Some(v) => v,
3487 None => return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new()),
3488 };
3489 let Some((file_offset, copy_length)) = c::parse_offload_read(&req.input) else {
3490 return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new());
3491 };
3492 let Some(mut open) = conn.handle_take(&req.file_id.0) else {
3493 return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new());
3494 };
3495 let read = vfs.read(&mut open, file_offset, copy_length as usize).await;
3496 conn.handle_insert(req.file_id.0, open);
3497 let data = match read {
3498 Ok(d) => d,
3499 Err(e) => return IoctlReply::Reply(vfs_err(e), Vec::new()),
3500 };
3501 let transfer_length = data.len() as u64;
3502 let id = next_offload_id();
3503 conn.offload_tokens.insert(id, data);
3504 (
3505 Status::SUCCESS,
3506 c::build_ioctl_resp(
3507 req.file_id,
3508 req.ctl_code,
3509 &c::build_offload_read_resp(transfer_length, &id),
3510 ),
3511 )
3512 }
3513 c::fsctl::OFFLOAD_WRITE => {
3516 let vfs = match share_vfs(server, conn, tid) {
3517 Some(v) => v,
3518 None => return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new()),
3519 };
3520 let Some((file_offset, copy_length, transfer_offset, token_id)) =
3521 c::parse_offload_write(&req.input)
3522 else {
3523 return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new());
3524 };
3525 let chunk = match conn.offload_tokens.get(&token_id) {
3526 Some(data) => {
3527 let start = transfer_offset as usize;
3528 let end = match start.checked_add(copy_length as usize) {
3529 Some(e) if e <= data.len() => e,
3530 _ => return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new()),
3531 };
3532 data[start..end].to_vec()
3533 }
3534 None => return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new()),
3535 };
3536 let Some(mut open) = conn.handle_take(&req.file_id.0) else {
3537 return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new());
3538 };
3539 let written = vfs.write(&mut open, file_offset, &chunk, false).await;
3540 conn.handle_insert(req.file_id.0, open);
3541 match written {
3542 Ok(w) => {
3543 counter!("smb_offload_bytes_total").increment(w);
3544 (
3545 Status::SUCCESS,
3546 c::build_ioctl_resp(
3547 req.file_id,
3548 req.ctl_code,
3549 &c::build_offload_write_resp(w),
3550 ),
3551 )
3552 }
3553 Err(e) => return IoctlReply::Reply(vfs_err(e), Vec::new()),
3554 }
3555 }
3556 c::fsctl::SET_SPARSE => (
3558 Status::SUCCESS,
3559 c::build_ioctl_resp(req.file_id, req.ctl_code, &[]),
3560 ),
3561 c::fsctl::FILE_LEVEL_TRIM => match c::parse_file_level_trim(&req.input) {
3565 Some((0, num_ranges)) => (
3566 Status::SUCCESS,
3567 c::build_ioctl_resp(
3568 req.file_id,
3569 req.ctl_code,
3570 &c::build_file_level_trim_resp(num_ranges),
3571 ),
3572 ),
3573 _ => return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new()),
3574 },
3575 c::fsctl::GET_INTEGRITY_INFORMATION => {
3579 if !conn.handle_exists(&req.file_id.0) {
3580 return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new());
3581 }
3582 let (algo, flags) = conn.integrity.get(&req.file_id.0).copied().unwrap_or((0, 0));
3583 (
3584 Status::SUCCESS,
3585 c::build_ioctl_resp(
3586 req.file_id,
3587 req.ctl_code,
3588 &c::build_get_integrity_resp(algo, flags),
3589 ),
3590 )
3591 }
3592 c::fsctl::SET_INTEGRITY_INFORMATION => {
3593 if !conn.handle_exists(&req.file_id.0) {
3594 return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new());
3595 }
3596 let Some(algo) = c::parse_set_integrity(&req.input) else {
3597 return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new());
3598 };
3599 let flags = req
3600 .input
3601 .get(4..8)
3602 .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
3603 .unwrap_or(0);
3604 conn.integrity.insert(req.file_id.0, (algo, flags));
3605 (
3606 Status::SUCCESS,
3607 c::build_ioctl_resp(req.file_id, req.ctl_code, &[]),
3608 )
3609 }
3610 c::fsctl::PIPE_WAIT => (
3613 Status::SUCCESS,
3614 c::build_ioctl_resp(req.file_id, req.ctl_code, &[]),
3615 ),
3616 c::fsctl::DFS_GET_REFERRALS => {
3620 return IoctlReply::Reply(Status::NOT_FOUND, Vec::new());
3621 }
3622 c::fsctl::QUERY_NETWORK_INTERFACE_INFO => {
3626 let out = network_interface_info();
3627 (
3628 Status::SUCCESS,
3629 c::build_ioctl_resp(req.file_id, req.ctl_code, &out),
3630 )
3631 }
3632 c::fsctl::SET_ZERO_DATA => {
3634 let vfs = match share_vfs(server, conn, tid) {
3635 Some(v) => v,
3636 None => return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new()),
3637 };
3638 let Some((start, end)) = c::parse_zero_data(&req.input).filter(|(s, e)| e >= s) else {
3639 return IoctlReply::Reply(Status::INVALID_PARAMETER, Vec::new());
3640 };
3641 let Some(mut open) = conn.handle_take(&req.file_id.0) else {
3642 return IoctlReply::Reply(Status::INVALID_HANDLE, Vec::new());
3643 };
3644 let result = vfs.zero_range(&mut open, start, end - start).await;
3645 conn.handle_insert(req.file_id.0, open);
3646 match result {
3647 Ok(()) => {
3648 counter!("smb_zeroed_bytes_total").increment(end - start);
3649 (
3650 Status::SUCCESS,
3651 c::build_ioctl_resp(req.file_id, req.ctl_code, &[]),
3652 )
3653 }
3654 Err(e) => (vfs_err(e), Vec::new()),
3655 }
3656 }
3657 _ => (Status::NOT_IMPLEMENTED, Vec::new()),
3658 };
3659 IoctlReply::Reply(status, body)
3660}
3661
3662#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) async fn close(
3664 conn: &mut Smb2Conn,
3665 vfs: Arc<dyn smb_server_vfs::Vfs>,
3666 buf: &[u8],
3667) -> Result<Vec<u8>, Status> {
3668 let req = c::CloseReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
3669 let Some(h) = conn.handle_take(&req.file_id.0) else {
3670 return Err(Status::INVALID_HANDLE);
3671 };
3672 conn.searches.remove(&req.file_id.0);
3673 conn.lease_keys.remove(&req.file_id.0);
3674 conn.integrity.remove(&req.file_id.0);
3675 if let Some(s) = conn.scope.as_ref() {
3676 s.borrow_mut().channel_seq.remove(&req.file_id.0);
3677 }
3678 let meta = vfs.stat(&h.path).await.ok().unwrap_or_default();
3679 match vfs.close(h).await {
3680 Ok(()) => {}
3681 Err(smb_server_vfs::VfsError::DirectoryNotEmpty) => {}
3685 Err(e) => return Err(vfs_err(e)),
3686 }
3687 counter!("smb_closes_total").increment(1);
3688 Ok(c::build_close_resp(
3689 [
3690 meta.times[0].0,
3691 meta.times[1].0,
3692 meta.times[2].0,
3693 meta.times[3].0,
3694 ],
3695 meta.alloc,
3696 meta.eof,
3697 meta.attrs.0,
3698 ))
3699}
3700
3701pub(crate) fn close_all_handles(conn: &mut Smb2Conn) {
3702 if let Some(s) = conn.scope.as_ref() {
3703 let mut s = s.borrow_mut();
3704 s.handles.clear();
3705 s.channel_seq.clear();
3706 }
3707 conn.pipes.clear();
3708}
3709
3710fn verify_channel_sequence(conn: &Smb2Conn, fid: &[u8; 16], channel_seq: u16, is_replay: bool) -> bool {
3715 let Some(scope) = conn.scope.as_ref() else {
3716 return true;
3717 };
3718 let mut scope = scope.borrow_mut();
3719 let Some(cs) = scope.channel_seq.get_mut(fid) else {
3720 return true;
3721 };
3722 let diff = channel_seq.wrapping_sub(cs.channel_sequence);
3724 if !is_replay {
3725 if channel_seq == cs.channel_sequence {
3726 cs.outstanding_request_count += 1;
3727 true
3728 } else if diff <= smb_server_proto_smb2::consts::CHANNEL_SEQUENCE_WINDOW {
3729 cs.outstanding_pre_request_count += cs.outstanding_request_count;
3730 cs.outstanding_request_count = 1;
3731 cs.channel_sequence = channel_seq;
3732 true
3733 } else {
3734 false
3735 }
3736 } else if channel_seq == cs.channel_sequence {
3737 if cs.outstanding_pre_request_count == 0 {
3738 cs.outstanding_request_count += 1;
3739 true
3740 } else {
3741 false
3742 }
3743 } else if diff <= smb_server_proto_smb2::consts::CHANNEL_SEQUENCE_WINDOW {
3744 cs.outstanding_pre_request_count += cs.outstanding_request_count;
3745 cs.channel_sequence = channel_seq;
3746 if cs.outstanding_pre_request_count == 0 {
3747 cs.outstanding_request_count = 1;
3748 true
3749 } else {
3750 cs.outstanding_request_count = 0;
3751 false
3752 }
3753 } else {
3754 false
3755 }
3756}
3757
3758pub(crate) fn share_is_ipc(server: &Arc<ServerShared>, conn: &Smb2Conn, tid: u32) -> bool {
3762 conn.tree_name(tid)
3763 .and_then(|n| server.shares.get(&n))
3764 .map(|s| s.is_ipc)
3765 .unwrap_or(false)
3766}
3767
3768pub(crate) fn share_is_ca(server: &Arc<ServerShared>, conn: &Smb2Conn, tid: u32) -> bool {
3771 conn.tree_name(tid)
3772 .and_then(|n| server.shares.get(&n))
3773 .map(|s| s.ca)
3774 .unwrap_or(false)
3775}
3776
3777const KNOWN_PIPES: &[&str] = &["srvsvc", "wkssvc", "lanman", "netlogon"];
3779
3780#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn pipe_create(conn: &mut Smb2Conn, buf: &[u8]) -> Result<Vec<u8>, Status> {
3783 let req = c::CreateReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
3784 let name = req.name.trim_start_matches(['\\', '/']).to_lowercase();
3785 if !KNOWN_PIPES.contains(&name.as_str()) {
3786 return Err(Status::OBJECT_PATH_NOT_FOUND);
3787 }
3788 let fid_bytes = next_file_id();
3789 let fid = c::FileId(fid_bytes);
3790 conn.pipes
3791 .insert(fid_bytes, crate::srvsvc::Pipe::new(&name));
3792 tracing::debug!(pipe = %name, "pipe opened");
3793 Ok(c::build_create_resp(
3795 fid,
3796 1, [0u64; 4],
3798 0, 4096,
3800 0,
3801 false,
3802 c::oplock::NONE,
3803 &[],
3804 ))
3805}
3806
3807pub(crate) fn pipe_read(conn: &mut Smb2Conn, buf: &[u8]) -> Option<Vec<u8>> {
3810 let Some(req) = c::ReadReq::parse(buf) else {
3811 tracing::debug!("pipe_read: parse failed");
3812 return None;
3813 };
3814 let Some(pipe) = conn.pipes.get_mut(&req.file_id.0) else {
3815 tracing::debug!(fid = ?req.file_id, "read on non-pipe");
3816 return None;
3817 };
3818 tracing::debug!(pending = pipe.pending(), max = req.length, "pipe read");
3819 let data = pipe.take(req.length as usize);
3820 counter!("smb_pipe_reads_total").increment(1);
3821 Some(c::build_read_resp(&data))
3822}
3823
3824pub(crate) fn pipe_write(
3827 server: &Arc<ServerShared>,
3828 conn: &mut Smb2Conn,
3829 buf: &[u8],
3830) -> Option<Vec<u8>> {
3831 let req = c::WriteReq::parse(buf);
3832 let Some(req) = req else {
3833 tracing::debug!("pipe_write: parse failed");
3834 return None;
3835 };
3836 if !conn.pipes.contains_key(&req.file_id.0) {
3837 return None;
3838 }
3839 let shares = server.share_infos();
3840 let written = req.payload.len() as u32;
3841 let pipe = conn.pipes.get_mut(&req.file_id.0)?;
3842 tracing::debug!(pipe = %pipe.name, len = req.payload.len(), payload = %hex_str(&req.payload), "pipe write");
3843 pipe.on_write(&req.payload, &shares);
3844 tracing::debug!(pending = pipe.pending(), "pipe read queued");
3845 counter!("smb_pipe_writes_total").increment(1);
3846 Some(c::build_write_resp(written))
3847}
3848
3849pub(crate) fn pipe_close(conn: &mut Smb2Conn, buf: &[u8]) -> bool {
3851 c::CloseReq::parse(buf)
3852 .map(|r| conn.pipes.remove(&r.file_id.0).is_some())
3853 .unwrap_or(false)
3854}
3855
3856#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) async fn query_directory(
3860 conn: &mut Smb2Conn,
3861 vfs: Arc<dyn smb_server_vfs::Vfs>,
3862 buf: &[u8],
3863) -> Result<Option<Vec<u8>>, Status> {
3864 let req = c::QueryDirReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
3865
3866 let has_state = conn.searches.contains_key(&req.file_id.0);
3870 let restart = !has_state
3871 || req.flags
3872 & (c::find_flags::RESTART_SCANS
3873 | c::find_flags::REOPEN
3874 | c::find_flags::SCAN
3875 | c::find_flags::INDEX_SPECIFIED)
3876 != 0;
3877
3878 if restart {
3879 let dir_rel = {
3880 let (is_dir, rel) = conn
3881 .with_handle(&req.file_id.0, |h| (h.is_dir, h.rel.clone()))
3882 .ok_or(Status::INVALID_HANDLE)?;
3883 if !is_dir {
3884 return Err(Status::INVALID_PARAMETER);
3886 }
3887 let pattern = req.pattern.trim_start_matches(['\\', '/']).to_string();
3890 let (dir_part, _) = crate::cmds::dir_cmds_split(&pattern);
3891 if dir_part.is_empty() {
3892 rel
3893 } else if rel.is_empty() {
3894 dir_part
3895 } else {
3896 format!("{}\\{}", rel.trim_end_matches(['\\', '/']), dir_part)
3897 }
3898 };
3899
3900 let (_, name_pat) =
3901 crate::cmds::dir_cmds_split(req.pattern.trim_start_matches(['\\', '/']));
3902 let entries = vfs.list(&dir_rel).await.map_err(vfs_err)?;
3903 let dot_meta = vfs.stat(&dir_rel).await.map_err(vfs_err)?;
3908 let dots = [".", ".."].into_iter().map(|n| info::FindEntry {
3909 name: n.to_string(),
3910 meta: info::QueryMeta::from_vfs(&dot_meta),
3911 });
3912 let mut matched: Vec<info::FindEntry> = dots
3913 .chain(entries.into_iter().map(|e| info::FindEntry {
3914 name: e.name,
3915 meta: info::QueryMeta::from_vfs(&e.meta),
3916 }))
3917 .filter(|e| {
3918 name_pat.is_empty()
3919 || crate::cmds::wildcard(&e.name.to_lowercase(), &name_pat.to_lowercase())
3920 })
3921 .collect();
3922 if req.flags & c::find_flags::INDEX_SPECIFIED != 0 && req.file_index > 0 {
3924 let skip = (req.file_index as usize).min(matched.len());
3925 matched.drain(..skip);
3926 }
3927 conn.searches.insert(req.file_id.0, matched.into());
3928 } else if !conn.searches.contains_key(&req.file_id.0) {
3929 return Err(Status::INVALID_PARAMETER);
3930 }
3931
3932 let take = if req.flags & c::find_flags::RETURN_SINGLE_ENTRY != 0 {
3934 1
3935 } else {
3936 512
3937 };
3938 let Some(queue) = conn.searches.get_mut(&req.file_id.0) else {
3939 return Err(Status::INVALID_PARAMETER);
3940 };
3941 let out: Vec<info::FindEntry> = (0..take).filter_map(|_| queue.pop_front()).collect();
3942
3943 if out.is_empty() {
3944 return Ok(None); }
3946 Ok(Some(info::encode_find_entries(&out, req.class)))
3947}
3948
3949pub(crate) async fn query_info(
3952 conn: &mut Smb2Conn,
3953 vfs: Arc<dyn smb_server_vfs::Vfs>,
3954 buf: &[u8],
3955) -> Result<Option<Vec<u8>>, Status> {
3956 let req = c::QueryInfoReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
3957 match req.info_type {
3958 c::info_type::FILE => {
3959 let (path, rel) = conn
3960 .with_handle(&req.file_id.0, |h| (h.path.clone(), h.rel.clone()))
3961 .ok_or(Status::INVALID_HANDLE)?;
3962 let m = vfs.stat(&path).await.map_err(vfs_err)?;
3963 if req.class == info::file_class::STREAM {
3964 let mut streams = Vec::new();
3966 if !m.is_dir {
3967 streams.push(("::$DATA".to_string(), m.eof));
3968 }
3969 streams.extend(vfs.list_streams(&path).await.map_err(vfs_err)?);
3970 return Ok(Some(info::encode_stream_info(&streams)));
3971 }
3972 let qm = info::QueryMeta::from_vfs(&m);
3973 let name = if req.class == info::file_class::NORMALIZED_NAME {
3974 rel.trim_start_matches(['\\', '/']).replace('/', "\\")
3977 } else {
3978 rel.rsplit(['\\', '/']).next().unwrap_or("").to_string()
3979 };
3980 info::encode_file_info(req.class, &qm, &name)
3981 .map(Some)
3982 .ok_or(Status::NOT_IMPLEMENTED)
3983 }
3984 c::info_type::FS => info::encode_fs_info(req.class)
3985 .map(Some)
3986 .ok_or(Status::NOT_IMPLEMENTED),
3987 c::info_type::SECURITY => {
3988 let path = conn
3989 .with_handle(&req.file_id.0, |h| h.path.clone())
3990 .ok_or(Status::INVALID_HANDLE)?;
3991 let stored = vfs.get_security(&path).await.map_err(vfs_err)?;
3992 let additional = if req.additional == 0 {
3993 crate::security::sec_info::DEFAULT
3994 } else {
3995 req.additional
3996 };
3997 let sd = crate::security::query_security(stored.as_deref(), additional)
3998 .ok_or(Status::INVALID_PARAMETER)?;
3999 if (req.output_len as usize) < sd.len() {
4000 return Err(Status::BUFFER_TOO_SMALL);
4001 }
4002 Ok(Some(sd))
4003 }
4004 c::info_type::QUOTA => Ok(Some(Vec::new())),
4008 _ => Ok(None),
4009 }
4010}
4011
4012pub(crate) async fn set_info(
4015 conn: &mut Smb2Conn,
4016 vfs: Arc<dyn smb_server_vfs::Vfs>,
4017 buf: &[u8],
4018) -> Result<(), Status> {
4019 let req = c::SetInfoReq::parse(buf).ok_or(Status::INVALID_PARAMETER)?;
4020 match req.info_type {
4021 c::info_type::FILE => {
4022 let op = match info::decode_set_file_op(req.class, &req.buffer) {
4023 Ok(op) => op,
4024 Err(_) => return Err(Status::INVALID_PARAMETER),
4025 };
4026 let Some(op) = op else { return Ok(()) }; let mut open = conn
4028 .handle_take(&req.file_id.0)
4029 .ok_or(Status::INVALID_HANDLE)?;
4030 let result = vfs.set_info_open(&mut open, &op).await;
4031 conn.handle_insert(req.file_id.0, open);
4032 result.map_err(vfs_err)
4033 }
4034 c::info_type::SECURITY => {
4035 let path = conn
4036 .with_handle(&req.file_id.0, |h| h.path.clone())
4037 .ok_or(Status::INVALID_HANDLE)?;
4038 vfs.set_security(&path, &req.buffer).await.map_err(vfs_err)
4039 }
4040 c::info_type::QUOTA => Err(Status::INVALID_DEVICE_REQUEST),
4042 _ => Err(Status::NOT_IMPLEMENTED),
4043 }
4044}
4045
4046fn hex_str(b: &[u8]) -> String {
4049 b.iter().map(|x| format!("{:02x}", x)).collect()
4050}
4051
4052fn g16(b: &[u8], o: usize) -> u16 {
4053 b.get(o..o + size_of::<u16>())
4054 .map(|s| u16::from_le_bytes([s[0], s[1]]))
4055 .unwrap_or(0)
4056}
4057
4058fn g32(b: &[u8], o: usize) -> u32 {
4059 b.get(o..o + size_of::<u32>())
4060 .and_then(|s| s.try_into().ok())
4061 .map(u32::from_le_bytes)
4062 .unwrap_or(0)
4063}
4064
4065pub(crate) fn share_vfs(
4068 server: &Arc<ServerShared>,
4069 conn: &Smb2Conn,
4070 tid: u32,
4071) -> Option<Arc<dyn smb_server_vfs::Vfs>> {
4072 conn.tree_name(tid)
4073 .and_then(|n| server.shares.get(&n))
4074 .map(|s| s.vfs.clone())
4075}
4076
4077fn next_resume_nonce() -> u64 {
4080 static N: AtomicU64 = AtomicU64::new(0x5253_554d_4b45_5900);
4081 N.fetch_add(1, Ordering::Relaxed)
4082}
4083
4084#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn next_offload_id() -> [u8; 16] {
4088 static N: AtomicU64 = AtomicU64::new(0x4F44_5849_4400_0000);
4089 let n = N.fetch_add(1, Ordering::Relaxed);
4090 let mut id = [0u8; 16];
4091 id[..8].copy_from_slice(&n.to_le_bytes());
4092 id[8..].copy_from_slice(&next_resume_nonce().to_le_bytes());
4093 id
4094}
4095
4096#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn copychunk_total(resp: &[u8]) -> u32 {
4099 resp.get(8..12)
4100 .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
4101 .unwrap_or(0)
4102}
4103
4104async fn do_copychunk(
4109 conn: &mut Smb2Conn,
4110 vfs: Arc<dyn smb_server_vfs::Vfs>,
4111 req: &c::IoctlReq,
4112) -> Result<Vec<u8>, (Status, Vec<u8>)> {
4113 use smb_server_proto_smb2::commands::copychunk_limits as lim;
4114 let cc = c::CopyChunkCopy::parse(&req.input).ok_or((Status::INVALID_PARAMETER, Vec::new()))?;
4115
4116 let total: u64 = cc.chunks.iter().map(|k| k.length as u64).sum();
4117 if cc.chunks.len() as u32 > lim::MAX_CHUNKS
4118 || cc.chunks.iter().any(|k| k.length > lim::MAX_CHUNK_SIZE)
4119 || total > lim::MAX_TOTAL_SIZE as u64
4120 {
4121 let limits =
4122 c::build_copychunk_resp(lim::MAX_CHUNKS, lim::MAX_CHUNK_SIZE, lim::MAX_TOTAL_SIZE);
4123 return Err((Status::INVALID_PARAMETER, limits));
4124 }
4125
4126 let src_fid = *conn
4127 .resume_keys
4128 .get(&cc.source_key)
4129 .ok_or((Status::OBJECT_NAME_NOT_FOUND, Vec::new()))?;
4130 let tgt_fid = req.file_id.0;
4131
4132 let mut chunks_written = 0u32;
4133 let mut total_written = 0u32;
4134 for k in &cc.chunks {
4135 let mut src = conn
4136 .handle_take(&src_fid)
4137 .ok_or((Status::INVALID_HANDLE, Vec::new()))?;
4138 let read = vfs.read(&mut src, k.source_offset, k.length as usize).await;
4139 conn.handle_insert(src_fid, src);
4140 let data = read.map_err(|e| (vfs_err(e), Vec::new()))?;
4141 let mut tgt = conn
4142 .handle_take(&tgt_fid)
4143 .ok_or((Status::INVALID_HANDLE, Vec::new()))?;
4144 let written = vfs.write(&mut tgt, k.target_offset, &data, false).await;
4145 conn.handle_insert(tgt_fid, tgt);
4146 let w = written.map_err(|e| (vfs_err(e), Vec::new()))?;
4147 total_written += w as u32;
4148 chunks_written += 1;
4149 }
4150 Ok(c::build_copychunk_resp(chunks_written, 0, total_written))
4152}
4153
4154pub(crate) fn vfs_err(e: smb_server_vfs::VfsError) -> Status {
4155 use smb_server_vfs::VfsError as E;
4156 match e {
4157 E::NotFound => Status::OBJECT_PATH_NOT_FOUND,
4158 E::AlreadyExists => Status::OBJECT_NAME_COLLISION,
4159 E::AccessDenied => Status::ACCESS_DENIED,
4160 E::DirectoryNotEmpty => Status::DIRECTORY_NOT_EMPTY,
4161 E::InvalidArgument => Status::INVALID_PARAMETER,
4162 E::NotSupported => Status::NOT_IMPLEMENTED,
4163 E::StoppedOnSymlink { .. } => Status::STOPPED_ON_SYMLINK,
4164 E::Io(_) => Status::UNSUCCESSFUL,
4165 }
4166}
4167
4168#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn error_resp() -> Vec<u8> {
4174 let mut b = Vec::with_capacity(8);
4175 b.extend_from_slice(&9u16.to_le_bytes()); b.push(0); b.push(0); b.resize(8, 0); b
4180}
4181
4182#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub(crate) fn response(
4186 req: &smb_server_proto_smb2::Header2,
4187 status: Status,
4188 body: Vec<u8>,
4189 session_id: u64,
4190) -> Vec<u8> {
4191 let body = if status != Status::SUCCESS && body.is_empty() {
4194 error_resp()
4195 } else {
4196 body
4197 };
4198 let mut f = Vec::with_capacity(64 + body.len());
4199 f.extend_from_slice(&smb_server_proto_smb2::SMB2_MAGIC);
4200 f.extend_from_slice(&64u16.to_le_bytes()); f.extend_from_slice(&req.credit_charge.to_le_bytes()); f.extend_from_slice(&status.raw().to_le_bytes());
4203 f.extend_from_slice(&req.command.to_le_bytes());
4204 let grant = req.credits.max(req.credit_charge).clamp(1, 512);
4208 f.extend_from_slice(&grant.to_le_bytes());
4209 counter!("smb_credits_granted_total").increment(grant as u64);
4210 f.extend_from_slice(&1u32.to_le_bytes()); f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&req.message_id.to_le_bytes());
4213 f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&req.tree_id.to_le_bytes());
4215 f.extend_from_slice(&session_id.to_le_bytes());
4216 f.extend_from_slice(&[0u8; 16]); debug_assert_eq!(f.len(), 64);
4218 f.extend_from_slice(&body);
4219 f
4220}
4221
4222#[cfg(test)]
4223mod async_notify_tests {
4224 use super::*;
4225
4226 #[test]
4227 fn async_frame_sets_async_flag_and_ids() {
4228 let f = build_async_frame(
4229 0xABCD,
4230 42,
4231 7,
4232 ss::cmd::CHANGE_NOTIFY,
4233 Status::PENDING,
4234 &[0u8; 8],
4235 );
4236 assert_eq!(&f[0..4], &smb_server_proto_smb2::SMB2_MAGIC);
4237 let flags = u32::from_le_bytes(f[16..20].try_into().unwrap());
4238 assert_eq!(flags & 0x2, 0x2, "ASYNC_COMMAND set");
4239 assert_eq!(flags & 0x1, 0x1, "SERVER_TO_REDIR set");
4240 assert_eq!(
4241 u64::from_le_bytes(f[24..32].try_into().unwrap()),
4242 42,
4243 "MessageId"
4244 );
4245 assert_eq!(
4246 u64::from_le_bytes(f[32..40].try_into().unwrap()),
4247 7,
4248 "AsyncId"
4249 );
4250 assert_eq!(
4251 u64::from_le_bytes(f[40..48].try_into().unwrap()),
4252 0xABCD,
4253 "SessionId"
4254 );
4255 assert_eq!(
4256 u32::from_le_bytes(f[8..12].try_into().unwrap()),
4257 Status::PENDING.raw()
4258 );
4259 }
4260
4261 #[test]
4264 fn watch_reports_created_file() {
4265 tokio_uring::start(async {
4266 let dir = std::env::temp_dir().join(format!("rustsmb_notify_{}", std::process::id()));
4267 std::fs::create_dir_all(&dir).unwrap();
4268 let path = dir.to_string_lossy().into_owned();
4269
4270 let (_tx, mut rx) = oneshot::channel();
4271 let watch = tokio_uring::spawn(async move {
4272 watch_one_event(
4273 &path,
4274 false,
4275 smb_server_proto_smb2::commands::notify_filter::FILE_NAME,
4276 &mut rx,
4277 )
4278 .await
4279 });
4280 tokio::time::sleep(std::time::Duration::from_millis(150)).await;
4281 std::fs::write(dir.join("created.txt"), b"hi").unwrap();
4282
4283 let events = watch.await.unwrap().expect("event fired");
4284 assert_eq!(events.len(), 1);
4285 assert_eq!(events[0].0, smb_server_proto_smb2::commands::notify_action::ADDED);
4286 assert_eq!(events[0].1, "created.txt");
4287 std::fs::remove_dir_all(&dir).ok();
4288 });
4289 }
4290
4291 #[test]
4294 fn recursive_watch_reports_subdir_file() {
4295 tokio_uring::start(async {
4296 let dir =
4297 std::env::temp_dir().join(format!("rustsmb_notify_rec_{}", std::process::id()));
4298 let sub = dir.join("nested");
4299 std::fs::create_dir_all(&sub).unwrap();
4300 let path = dir.to_string_lossy().into_owned();
4301
4302 let (_tx, mut rx) = oneshot::channel();
4303 let watch = tokio_uring::spawn(async move {
4304 watch_one_event(
4305 &path,
4306 true,
4307 smb_server_proto_smb2::commands::notify_filter::FILE_NAME,
4308 &mut rx,
4309 )
4310 .await
4311 });
4312 tokio::time::sleep(std::time::Duration::from_millis(150)).await;
4313 std::fs::write(sub.join("deep.txt"), b"hi").unwrap();
4314
4315 let events = watch.await.unwrap().expect("subdir event fired");
4316 assert_eq!(events[0].0, smb_server_proto_smb2::commands::notify_action::ADDED);
4317 assert_eq!(
4318 events[0].1, "nested\\deep.txt",
4319 "named relative to watch root"
4320 );
4321 std::fs::remove_dir_all(&dir).ok();
4322 });
4323 }
4324
4325 #[test]
4327 fn cancel_stops_watch() {
4328 tokio_uring::start(async {
4329 let dir =
4330 std::env::temp_dir().join(format!("rustsmb_notify_cancel_{}", std::process::id()));
4331 std::fs::create_dir_all(&dir).unwrap();
4332 let path = dir.to_string_lossy().into_owned();
4333
4334 let (tx, mut rx) = oneshot::channel();
4335 let watch = tokio_uring::spawn(async move {
4336 watch_one_event(
4337 &path,
4338 false,
4339 smb_server_proto_smb2::commands::notify_filter::FILE_NAME,
4340 &mut rx,
4341 )
4342 .await
4343 });
4344 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
4345 tx.send(Status::CANCELLED).unwrap();
4346 assert!(
4347 watch.await.unwrap().is_err(),
4348 "cancelled watch yields no events"
4349 );
4350 std::fs::remove_dir_all(&dir).ok();
4351 });
4352 }
4353}
4354
4355#[cfg(test)]
4356mod fsctl_tests {
4357 use super::*;
4358
4359 fn conn_with_vfs(dir: &std::path::Path) -> (Smb2Conn, Arc<dyn smb_server_vfs::Vfs>) {
4360 let vfs: Arc<dyn smb_server_vfs::Vfs> = Arc::new(smb_server_backend_posix::PosixVfs::new(dir));
4361 let (tx, _rx) = mpsc::channel(8);
4362 (Smb2Conn::new([0u8; 8], tx), vfs)
4363 }
4364
4365 #[test]
4368 fn copychunk_copies_between_handles() {
4369 tokio_uring::start(async {
4370 let dir = std::env::temp_dir().join(format!("rustsmb_cc_{}", std::process::id()));
4371 std::fs::create_dir_all(&dir).unwrap();
4372 std::fs::write(dir.join("src.txt"), b"hello copychunk world").unwrap();
4373 std::fs::write(dir.join("dst.txt"), b"").unwrap();
4374
4375 let (mut conn, vfs) = conn_with_vfs(&dir);
4376 let (src, _m, _a) = vfs
4377 .create("src.txt", false, 0x8000_0000, 1, 0, 0)
4378 .await
4379 .unwrap();
4380 let (dst, _m, _a) = vfs
4381 .create("dst.txt", false, 0x4000_0000, 1, 0, 0)
4382 .await
4383 .unwrap();
4384 let (src_fid, dst_fid) = ([1u8; 16], [2u8; 16]);
4385 conn.handle_insert(src_fid, src);
4386 conn.handle_insert(dst_fid, dst);
4387
4388 let mut key = [0u8; 24];
4389 key[..16].copy_from_slice(&src_fid);
4390 conn.resume_keys.insert(key, src_fid);
4391
4392 let mut input = Vec::new();
4393 input.extend_from_slice(&key);
4394 input.extend_from_slice(&1u32.to_le_bytes()); input.extend_from_slice(&0u32.to_le_bytes()); input.extend_from_slice(&0u64.to_le_bytes()); input.extend_from_slice(&0u64.to_le_bytes()); input.extend_from_slice(&21u32.to_le_bytes()); input.extend_from_slice(&0u32.to_le_bytes()); let req = c::IoctlReq {
4402 ctl_code: c::fsctl::SRV_COPYCHUNK,
4403 file_id: c::FileId(dst_fid),
4404 input,
4405 max_output: 4096,
4406 is_fsctl: true,
4407 };
4408 let out = do_copychunk(&mut conn, vfs.clone(), &req)
4409 .await
4410 .expect("copychunk");
4411 assert_eq!(&out[0..4], &1u32.to_le_bytes(), "ChunksWritten");
4412 assert_eq!(&out[8..12], &21u32.to_le_bytes(), "TotalBytesWritten");
4413
4414 let dst = conn.handle_take(&dst_fid).unwrap();
4415 vfs.close(dst).await.unwrap();
4416 assert_eq!(
4417 std::fs::read(dir.join("dst.txt")).unwrap(),
4418 b"hello copychunk world"
4419 );
4420 std::fs::remove_dir_all(&dir).ok();
4421 });
4422 }
4423
4424 #[test]
4426 fn copychunk_rejects_oversized_request() {
4427 tokio_uring::start(async {
4428 use smb_server_proto_smb2::commands::copychunk_limits as lim;
4429 let dir = std::env::temp_dir().join(format!("rustsmb_cc_lim_{}", std::process::id()));
4430 std::fs::create_dir_all(&dir).unwrap();
4431 std::fs::write(dir.join("f"), b"x").unwrap();
4432 let (mut conn, vfs) = conn_with_vfs(&dir);
4433 let (f, _m, _a) = vfs.create("f", false, 0x8000_0000, 1, 0, 0).await.unwrap();
4434 let fid = [3u8; 16];
4435 conn.handle_insert(fid, f);
4436 let mut key = [0u8; 24];
4437 key[..16].copy_from_slice(&fid);
4438 conn.resume_keys.insert(key, fid);
4439
4440 let mut input = Vec::new();
4441 input.extend_from_slice(&key);
4442 input.extend_from_slice(&1u32.to_le_bytes());
4443 input.extend_from_slice(&0u32.to_le_bytes());
4444 input.extend_from_slice(&0u64.to_le_bytes());
4445 input.extend_from_slice(&0u64.to_le_bytes());
4446 input.extend_from_slice(&(lim::MAX_CHUNK_SIZE + 1).to_le_bytes()); input.extend_from_slice(&0u32.to_le_bytes());
4448
4449 let req = c::IoctlReq {
4450 ctl_code: c::fsctl::SRV_COPYCHUNK,
4451 file_id: c::FileId(fid),
4452 input,
4453 max_output: 4096,
4454 is_fsctl: true,
4455 };
4456 let err = do_copychunk(&mut conn, vfs, &req).await.unwrap_err();
4457 assert_eq!(err.0, Status::INVALID_PARAMETER);
4458 assert_eq!(
4459 &err.1[4..8],
4460 &lim::MAX_CHUNK_SIZE.to_le_bytes(),
4461 "limits echoed"
4462 );
4463 std::fs::remove_dir_all(&dir).ok();
4464 });
4465 }
4466
4467 #[test]
4469 fn zero_range_zeros_bytes() {
4470 tokio_uring::start(async {
4471 let dir = std::env::temp_dir().join(format!("rustsmb_zd_{}", std::process::id()));
4472 std::fs::create_dir_all(&dir).unwrap();
4473 std::fs::write(dir.join("z.bin"), vec![0xFFu8; 16]).unwrap();
4474 let (conn, vfs) = conn_with_vfs(&dir);
4475 let (f, _m, _a) = vfs
4476 .create("z.bin", false, 0x4000_0000, 1, 0, 0)
4477 .await
4478 .unwrap();
4479 let fid = [4u8; 16];
4480 conn.handle_insert(fid, f);
4481
4482 let mut open = conn.handle_take(&fid).unwrap();
4483 vfs.zero_range(&mut open, 4, 8).await.expect("zero range");
4484 conn.handle_insert(fid, open);
4485 let f = conn.handle_take(&fid).unwrap();
4486 vfs.close(f).await.unwrap();
4487
4488 let mut expected = vec![0xFFu8; 16];
4489 for b in &mut expected[4..12] {
4490 *b = 0;
4491 }
4492 assert_eq!(std::fs::read(dir.join("z.bin")).unwrap(), expected);
4493 std::fs::remove_dir_all(&dir).ok();
4494 });
4495 }
4496}
4497
4498#[cfg(test)]
4499mod oplock_tests {
4500 use super::*;
4501
4502 fn server_with_share(dir: &std::path::Path) -> Arc<ServerShared> {
4503 use crate::state::*;
4504 let vfs: Arc<dyn smb_server_vfs::Vfs> = Arc::new(smb_server_backend_posix::PosixVfs::new(dir));
4505 let mut shares = HashMap::new();
4506 shares.insert(
4507 "public".to_string(),
4508 Share {
4509 name: "public".into(),
4510 root: dir.to_path_buf(),
4511 vfs,
4512 is_ipc: false,
4513 encrypt: false,
4514 compress: false,
4515 ca: false,
4516 },
4517 );
4518 Arc::new(ServerShared {
4519 shares,
4520 guid: [0; 16],
4521 domain: "W".into(),
4522 server_name: "R".into(),
4523 users: HashMap::new(),
4524 allow_guest: true,
4525 require_signing: false,
4526 encrypt: false,
4527 locks: Arc::new(LockManager::new()),
4528 share_modes: Arc::new(ShareModeTable::new()),
4529 oplocks: Arc::new(OplockTable::new()),
4530 leases: Arc::new(LeaseTable::new()),
4531 durables: Arc::new(smb_server_handle_store::MemStore::new()),
4532 sessions: Arc::new(SessionTable::new()),
4533 app_instances: Arc::new(AppInstanceTable::new()),
4534 })
4535 }
4536
4537 fn create_request(name: &str, oplock: u8, access: u32, share: u32) -> Vec<u8> {
4538 let name16: Vec<u8> = name.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
4539 let name_off = 64 + 56; let mut body = vec![0u8; 56];
4541 body[0..2].copy_from_slice(&57u16.to_le_bytes()); body[3] = oplock; body[24..28].copy_from_slice(&access.to_le_bytes()); body[32..36].copy_from_slice(&share.to_le_bytes()); body[36..40].copy_from_slice(&1u32.to_le_bytes()); body[44..46].copy_from_slice(&(name_off as u16).to_le_bytes()); body[46..48].copy_from_slice(&(name16.len() as u16).to_le_bytes()); let mut f = vec![0u8; 64];
4549 f.extend_from_slice(&body);
4550 f.extend_from_slice(&name16);
4551 f
4552 }
4553
4554 #[test]
4558 fn oplock_granted_then_broken_on_second_open() {
4559 tokio_uring::start(async {
4560 let dir = std::env::temp_dir().join(format!("rustsmb_op_{}", std::process::id()));
4561 std::fs::create_dir_all(&dir).unwrap();
4562 std::fs::write(dir.join("shared.bin"), b"data").unwrap();
4563 let server = server_with_share(&dir);
4564 let vfs = server.shares["public"].vfs.clone();
4565
4566 let access = 0x8000_0000 | 0x4000_0000; let share = 0x7; let (tx_a, mut rx_a) = mpsc::channel(8);
4570 let mut conn_a = Smb2Conn::new([0u8; 8], tx_a);
4571 conn_a.session_id = 1;
4572 let resp_a = create(
4573 &mut conn_a,
4574 vfs.clone(),
4575 &server,
4576 false,
4577 false,
4578 &create_request("shared.bin", c::oplock::BATCH, access, share),
4579 )
4580 .await
4581 .unwrap();
4582 assert_eq!(
4583 resp_a[2],
4584 c::oplock::EXCLUSIVE,
4585 "sole opener granted exclusive"
4586 );
4587
4588 let (tx_b, _rx_b) = mpsc::channel(8);
4589 let mut conn_b = Smb2Conn::new([0u8; 8], tx_b);
4590 conn_b.session_id = 2;
4591 let resp_b = create(
4592 &mut conn_b,
4593 vfs.clone(),
4594 &server,
4595 false,
4596 false,
4597 &create_request("shared.bin", c::oplock::NONE, access, share),
4598 )
4599 .await
4600 .unwrap();
4601 assert_eq!(resp_b[2], c::oplock::NONE, "contending open gets no oplock");
4602
4603 let brk = rx_a.try_recv().expect("break notification delivered to A");
4604 assert_eq!(
4605 u16::from_le_bytes([brk[12], brk[13]]),
4606 ss::cmd::OPLOCK_BREAK,
4607 "frame is an OPLOCK_BREAK",
4608 );
4609 assert_eq!(brk[64 + 2], c::oplock::NONE, "broken down to NONE");
4610
4611 std::fs::remove_dir_all(&dir).ok();
4612 });
4613 }
4614}
4615
4616#[cfg(test)]
4617mod lease_tests {
4618 use super::*;
4619 use crate::state::*;
4620 use std::collections::HashMap;
4621
4622 fn server_with_share(dir: &std::path::Path) -> Arc<ServerShared> {
4623 let vfs: Arc<dyn smb_server_vfs::Vfs> = Arc::new(smb_server_backend_posix::PosixVfs::new(dir));
4624 let mut shares = HashMap::new();
4625 shares.insert(
4626 "public".to_string(),
4627 Share {
4628 name: "public".into(),
4629 root: dir.to_path_buf(),
4630 vfs,
4631 is_ipc: false,
4632 encrypt: false,
4633 compress: false,
4634 ca: false,
4635 },
4636 );
4637 Arc::new(ServerShared {
4638 shares,
4639 guid: [0; 16],
4640 domain: "W".into(),
4641 server_name: "R".into(),
4642 users: HashMap::new(),
4643 allow_guest: true,
4644 require_signing: false,
4645 encrypt: false,
4646 locks: Arc::new(LockManager::new()),
4647 share_modes: Arc::new(ShareModeTable::new()),
4648 oplocks: Arc::new(OplockTable::new()),
4649 leases: Arc::new(LeaseTable::new()),
4650 durables: Arc::new(smb_server_handle_store::MemStore::new()),
4651 sessions: Arc::new(SessionTable::new()),
4652 app_instances: Arc::new(AppInstanceTable::new()),
4653 })
4654 }
4655
4656 fn create_request_lease(
4658 name: &str,
4659 key: [u8; 16],
4660 state: u32,
4661 access: u32,
4662 share: u32,
4663 ) -> Vec<u8> {
4664 let name16: Vec<u8> = name.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
4665 let name_off = 64 + 56; let name_end = name_off + name16.len();
4667 let pad = (8 - (name_end % 8)) % 8; let ctx_off = name_end + pad;
4669
4670 let mut ctx = Vec::new();
4671 ctx.extend_from_slice(&0u32.to_le_bytes()); ctx.extend_from_slice(&16u16.to_le_bytes()); ctx.extend_from_slice(&4u16.to_le_bytes()); ctx.extend_from_slice(&0u16.to_le_bytes()); ctx.extend_from_slice(&24u16.to_le_bytes()); ctx.extend_from_slice(&32u32.to_le_bytes()); ctx.extend_from_slice(c::lease::CONTEXT_NAME);
4678 ctx.extend_from_slice(&[0u8; 4]); ctx.extend_from_slice(&key);
4680 ctx.extend_from_slice(&state.to_le_bytes());
4681 ctx.extend_from_slice(&0u32.to_le_bytes()); ctx.extend_from_slice(&0u64.to_le_bytes()); let mut body = vec![0u8; 56];
4685 body[0..2].copy_from_slice(&57u16.to_le_bytes()); body[3] = c::oplock::LEASE; body[24..28].copy_from_slice(&access.to_le_bytes());
4688 body[32..36].copy_from_slice(&share.to_le_bytes());
4689 body[36..40].copy_from_slice(&1u32.to_le_bytes()); body[44..46].copy_from_slice(&(name_off as u16).to_le_bytes());
4691 body[46..48].copy_from_slice(&(name16.len() as u16).to_le_bytes());
4692 body[48..52].copy_from_slice(&(ctx_off as u32).to_le_bytes());
4693 body[52..56].copy_from_slice(&(ctx.len() as u32).to_le_bytes());
4694
4695 let mut f = vec![0u8; 64];
4696 f.extend_from_slice(&body);
4697 f.extend_from_slice(&name16);
4698 f.extend(std::iter::repeat_n(0u8, pad));
4699 f.extend_from_slice(&ctx);
4700 f
4701 }
4702
4703 fn granted_state(resp: &[u8]) -> u32 {
4706 u32::from_le_bytes(resp[128..132].try_into().unwrap())
4707 }
4708
4709 #[test]
4714 fn lease_granted_then_broken_on_second_key() {
4715 tokio_uring::start(async {
4716 let dir = std::env::temp_dir().join(format!("rustsmb_ls_{}", std::process::id()));
4717 std::fs::create_dir_all(&dir).unwrap();
4718 std::fs::write(dir.join("leased.bin"), b"data").unwrap();
4719 let server = server_with_share(&dir);
4720 let vfs = server.shares["public"].vfs.clone();
4721
4722 let access = 0x8000_0000 | 0x4000_0000; let share = 0x7; let k1 = [0x11u8; 16];
4725 let k2 = [0x22u8; 16];
4726
4727 let (tx_a, mut rx_a) = mpsc::channel(8);
4728 let mut conn_a = Smb2Conn::new([0u8; 8], tx_a);
4729 conn_a.session_id = 1;
4730 let resp_a = create(
4731 &mut conn_a,
4732 vfs.clone(),
4733 &server,
4734 false,
4735 false,
4736 &create_request_lease("leased.bin", k1, c::lease::RWH, access, share),
4737 )
4738 .await
4739 .unwrap();
4740 assert_eq!(resp_a[2], c::oplock::LEASE, "response is a lease grant");
4741 assert_eq!(
4742 granted_state(&resp_a),
4743 c::lease::RWH,
4744 "sole opener gets RWH"
4745 );
4746
4747 let (tx_b, _rx_b) = mpsc::channel(8);
4748 let mut conn_b = Smb2Conn::new([0u8; 8], tx_b);
4749 conn_b.session_id = 2;
4750 let resp_b = create(
4751 &mut conn_b,
4752 vfs.clone(),
4753 &server,
4754 false,
4755 false,
4756 &create_request_lease("leased.bin", k2, c::lease::RWH, access, share),
4757 )
4758 .await
4759 .unwrap();
4760 assert_eq!(
4761 granted_state(&resp_b),
4762 c::lease::RH,
4763 "contender gets read+handle"
4764 );
4765
4766 let brk = rx_a.try_recv().expect("lease break delivered to A");
4767 assert_eq!(
4768 u16::from_le_bytes([brk[12], brk[13]]),
4769 ss::cmd::OPLOCK_BREAK,
4770 "break rides the OPLOCK_BREAK command",
4771 );
4772 assert_eq!(
4773 u16::from_le_bytes([brk[64], brk[65]]),
4774 44,
4775 "lease-break StructureSize"
4776 );
4777 assert_eq!(&brk[72..88], &k1, "names holder A's lease key");
4778 assert_eq!(
4779 u32::from_le_bytes(brk[88..92].try_into().unwrap()),
4780 c::lease::RWH,
4781 "current"
4782 );
4783 assert_eq!(
4784 u32::from_le_bytes(brk[92..96].try_into().unwrap()),
4785 c::lease::RH,
4786 "new"
4787 );
4788
4789 std::fs::remove_dir_all(&dir).ok();
4790 });
4791 }
4792
4793 #[test]
4796 fn same_lease_key_reopen_does_not_break() {
4797 tokio_uring::start(async {
4798 let dir = std::env::temp_dir().join(format!("rustsmb_ls2_{}", std::process::id()));
4799 std::fs::create_dir_all(&dir).unwrap();
4800 std::fs::write(dir.join("f.bin"), b"data").unwrap();
4801 let server = server_with_share(&dir);
4802 let vfs = server.shares["public"].vfs.clone();
4803 let access = 0x8000_0000 | 0x4000_0000;
4804 let share = 0x7;
4805 let key = [0x55u8; 16];
4806
4807 let (tx_a, mut rx_a) = mpsc::channel(8);
4808 let mut conn_a = Smb2Conn::new([0u8; 8], tx_a);
4809 conn_a.session_id = 1;
4810 let _ = create(
4811 &mut conn_a,
4812 vfs.clone(),
4813 &server,
4814 false,
4815 false,
4816 &create_request_lease("f.bin", key, c::lease::RWH, access, share),
4817 )
4818 .await
4819 .unwrap();
4820
4821 let (tx_b, _rx_b) = mpsc::channel(8);
4822 let mut conn_b = Smb2Conn::new([0u8; 8], tx_b);
4823 conn_b.session_id = 2;
4824 let resp_b = create(
4825 &mut conn_b,
4826 vfs.clone(),
4827 &server,
4828 false,
4829 false,
4830 &create_request_lease("f.bin", key, c::lease::RWH, access, share),
4831 )
4832 .await
4833 .unwrap();
4834
4835 assert_eq!(granted_state(&resp_b), c::lease::RWH, "same key keeps RWH");
4836 assert!(rx_a.try_recv().is_err(), "no break for same lease key");
4837
4838 std::fs::remove_dir_all(&dir).ok();
4839 });
4840 }
4841}
4842
4843#[cfg(test)]
4844mod security_tests {
4845 use super::*;
4846 use win_sd::{AccessMask, SecurityDescriptor, SecurityDescriptorBuilder, Sid};
4847
4848 fn query_info_frame(
4849 info_type: u8,
4850 class: u8,
4851 output_len: u32,
4852 additional: u32,
4853 fid: [u8; 16],
4854 ) -> Vec<u8> {
4855 let mut b = vec![0u8; 40];
4856 b[0..2].copy_from_slice(&41u16.to_le_bytes()); b[2] = info_type;
4858 b[3] = class;
4859 b[4..8].copy_from_slice(&output_len.to_le_bytes());
4860 b[16..20].copy_from_slice(&additional.to_le_bytes()); b[24..40].copy_from_slice(&fid); let mut f = vec![0u8; 64];
4863 f.extend_from_slice(&b);
4864 f
4865 }
4866
4867 fn set_info_frame(
4868 info_type: u8,
4869 class: u8,
4870 additional: u32,
4871 fid: [u8; 16],
4872 buffer: &[u8],
4873 ) -> Vec<u8> {
4874 let mut b = vec![0u8; 32];
4875 b[0..2].copy_from_slice(&33u16.to_le_bytes()); b[2] = info_type;
4877 b[3] = class;
4878 b[4..8].copy_from_slice(&(buffer.len() as u32).to_le_bytes()); b[8..10].copy_from_slice(&((64 + 32) as u16).to_le_bytes()); b[12..16].copy_from_slice(&additional.to_le_bytes()); b[16..32].copy_from_slice(&fid); let mut f = vec![0u8; 64];
4883 f.extend_from_slice(&b);
4884 f.extend_from_slice(buffer);
4885 f
4886 }
4887
4888 #[test]
4891 fn security_query_set_round_trip() {
4892 tokio_uring::start(async {
4893 let dir = std::env::temp_dir().join(format!("rustsmb_sec_{}", std::process::id()));
4894 std::fs::create_dir_all(&dir).unwrap();
4895 std::fs::write(dir.join("sec.bin"), b"data").unwrap();
4896 let vfs: Arc<dyn smb_server_vfs::Vfs> = Arc::new(smb_server_backend_posix::PosixVfs::new(&dir));
4897 let (tx, _rx) = mpsc::channel(8);
4898 let mut conn = Smb2Conn::new([0u8; 8], tx);
4899 let (open, _m, _a) = vfs
4900 .create("sec.bin", false, 0x8000_0000 | 0x4000_0000, 1, 0, 0)
4901 .await
4902 .unwrap();
4903 let fid = [1u8; 16];
4904 conn.handle_insert(fid, open);
4905
4906 let q = query_info_frame(
4908 c::info_type::SECURITY,
4909 0,
4910 4096,
4911 crate::security::sec_info::DEFAULT,
4912 fid,
4913 );
4914 let out = query_info(&mut conn, vfs.clone(), &q)
4915 .await
4916 .unwrap()
4917 .unwrap();
4918 let sd = SecurityDescriptor::from_bytes(&out).expect("default parse");
4919 assert!(sd.dacl().is_some(), "default DACL present");
4920
4921 let custom = SecurityDescriptorBuilder::new()
4923 .owner(Sid::local_system())
4924 .allow(Sid::everyone(), AccessMask::FILE_GENERIC_READ)
4925 .build()
4926 .to_bytes()
4927 .unwrap();
4928 let s = set_info_frame(
4929 c::info_type::SECURITY,
4930 0,
4931 crate::security::sec_info::OWNER | crate::security::sec_info::DACL,
4932 fid,
4933 &custom,
4934 );
4935 set_info(&mut conn, vfs.clone(), &s)
4936 .await
4937 .expect("set security");
4938
4939 let q2 = query_info_frame(
4940 c::info_type::SECURITY,
4941 0,
4942 4096,
4943 crate::security::sec_info::OWNER,
4944 fid,
4945 );
4946 let out2 = query_info(&mut conn, vfs.clone(), &q2)
4947 .await
4948 .unwrap()
4949 .unwrap();
4950 let sd2 = SecurityDescriptor::from_bytes(&out2).expect("stored parse");
4951 assert_eq!(
4952 sd2.owner(),
4953 Some(&Sid::local_system()),
4954 "stored owner round-trips"
4955 );
4956
4957 std::fs::remove_dir_all(&dir).ok();
4958 });
4959 }
4960
4961 #[test]
4963 fn security_query_rejects_small_buffer() {
4964 tokio_uring::start(async {
4965 let dir = std::env::temp_dir().join(format!("rustsmb_sec2_{}", std::process::id()));
4966 std::fs::create_dir_all(&dir).unwrap();
4967 std::fs::write(dir.join("s.bin"), b"x").unwrap();
4968 let vfs: Arc<dyn smb_server_vfs::Vfs> = Arc::new(smb_server_backend_posix::PosixVfs::new(&dir));
4969 let (tx, _rx) = mpsc::channel(8);
4970 let mut conn = Smb2Conn::new([0u8; 8], tx);
4971 let (open, _m, _a) = vfs
4972 .create("s.bin", false, 0x8000_0000, 1, 0, 0)
4973 .await
4974 .unwrap();
4975 let fid = [2u8; 16];
4976 conn.handle_insert(fid, open);
4977
4978 let q = query_info_frame(
4979 c::info_type::SECURITY,
4980 0,
4981 8,
4982 crate::security::sec_info::DEFAULT,
4983 fid,
4984 );
4985 let err = query_info(&mut conn, vfs.clone(), &q).await.unwrap_err();
4986 assert_eq!(err, Status::BUFFER_TOO_SMALL);
4987
4988 std::fs::remove_dir_all(&dir).ok();
4989 });
4990 }
4991}
4992
4993#[cfg(test)]
4994mod durable_tests {
4995 use super::*;
4996 use crate::state::*;
4997 use std::collections::HashMap;
4998
4999 fn server_with_share(dir: &std::path::Path) -> Arc<ServerShared> {
5000 let vfs: Arc<dyn smb_server_vfs::Vfs> = Arc::new(smb_server_backend_posix::PosixVfs::new(dir));
5001 let mut shares = HashMap::new();
5002 shares.insert(
5003 "public".to_string(),
5004 Share {
5005 name: "public".into(),
5006 root: dir.to_path_buf(),
5007 vfs,
5008 is_ipc: false,
5009 encrypt: false,
5010 compress: false,
5011 ca: false,
5012 },
5013 );
5014 Arc::new(ServerShared {
5015 shares,
5016 guid: [0; 16],
5017 domain: "W".into(),
5018 server_name: "R".into(),
5019 users: HashMap::new(),
5020 allow_guest: true,
5021 require_signing: false,
5022 encrypt: false,
5023 locks: Arc::new(LockManager::new()),
5024 share_modes: Arc::new(ShareModeTable::new()),
5025 oplocks: Arc::new(OplockTable::new()),
5026 leases: Arc::new(LeaseTable::new()),
5027 durables: Arc::new(smb_server_handle_store::MemStore::new()),
5028 sessions: Arc::new(SessionTable::new()),
5029 app_instances: Arc::new(AppInstanceTable::new()),
5030 })
5031 }
5032
5033 fn create_req_ctx(
5035 name: &str,
5036 access: u32,
5037 disp: u32,
5038 ctx_name: &[u8],
5039 ctx_data: &[u8],
5040 ) -> Vec<u8> {
5041 let name16: Vec<u8> = name.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
5042 let name_off = 64 + 56;
5043 let name_end = name_off + name16.len();
5044 let pad = (8 - name_end % 8) % 8;
5045 let ctx_off = name_end + pad;
5046
5047 let data_off = (16 + ctx_name.len()).next_multiple_of(8);
5048 let mut ctx = Vec::new();
5049 ctx.extend_from_slice(&0u32.to_le_bytes());
5050 ctx.extend_from_slice(&16u16.to_le_bytes());
5051 ctx.extend_from_slice(&(ctx_name.len() as u16).to_le_bytes());
5052 ctx.extend_from_slice(&0u16.to_le_bytes());
5053 ctx.extend_from_slice(&(data_off as u16).to_le_bytes());
5054 ctx.extend_from_slice(&(ctx_data.len() as u32).to_le_bytes());
5055 ctx.extend_from_slice(ctx_name);
5056 while ctx.len() < data_off {
5057 ctx.push(0);
5058 }
5059 ctx.extend_from_slice(ctx_data);
5060
5061 let mut body = vec![0u8; 56];
5062 body[0..2].copy_from_slice(&57u16.to_le_bytes());
5063 body[24..28].copy_from_slice(&access.to_le_bytes());
5064 body[32..36].copy_from_slice(&0x7u32.to_le_bytes());
5065 body[36..40].copy_from_slice(&disp.to_le_bytes());
5066 body[44..46].copy_from_slice(&(name_off as u16).to_le_bytes());
5067 body[46..48].copy_from_slice(&(name16.len() as u16).to_le_bytes());
5068 body[48..52].copy_from_slice(&(ctx_off as u32).to_le_bytes());
5069 body[52..56].copy_from_slice(&(ctx.len() as u32).to_le_bytes());
5070 let mut f = vec![0u8; 64];
5071 f.extend_from_slice(&body);
5072 f.extend_from_slice(&name16);
5073 f.extend(std::iter::repeat_n(0u8, pad));
5074 f.extend_from_slice(&ctx);
5075 f
5076 }
5077
5078 #[test]
5082 fn durable_grant_then_reconnect() {
5083 tokio_uring::start(async {
5084 let dir = std::env::temp_dir().join(format!("rustsmb_dur_{}", std::process::id()));
5085 std::fs::create_dir_all(&dir).unwrap();
5086 std::fs::write(dir.join("dur.bin"), b"durable-data").unwrap();
5087 let server = server_with_share(&dir);
5088 let vfs = server.shares["public"].vfs.clone();
5089 let guid = [0x5Au8; 16];
5090 let access = 0x8000_0000 | 0x4000_0000;
5091
5092 let mut dq = Vec::new();
5093 dq.extend_from_slice(&10_000u32.to_le_bytes()); dq.extend_from_slice(&0u32.to_le_bytes()); dq.extend_from_slice(&[0u8; 8]); dq.extend_from_slice(&guid); let frame = create_req_ctx("dur.bin", access, 1, c::durable::REQ_V2, &dq);
5098
5099 let (tx, _rx) = mpsc::channel(8);
5100 let mut conn = Smb2Conn::new([0u8; 8], tx);
5101 conn.session_id = 1;
5102 let mut frame = frame;
5103 frame[67] = c::oplock::BATCH; let resp = create(&mut conn, vfs.clone(), &server, false, false, &frame)
5105 .await
5106 .unwrap();
5107 assert_ne!(
5108 u32::from_le_bytes(resp[80..84].try_into().unwrap()),
5109 0,
5110 "durable resp ctx"
5111 );
5112 assert_eq!(conn.durable.len(), 1, "durable handle recorded");
5113 let pid: [u8; 16] = resp[64..80].try_into().unwrap();
5114
5115 for (_f, e) in conn.durable.drain() {
5117 let _ = server
5118 .durables
5119 .put(e.into_record(crate::state::now_ms()))
5120 .await;
5121 }
5122
5123 let mut dc = Vec::new();
5125 dc.extend_from_slice(&pid);
5126 dc.extend_from_slice(&guid);
5127 dc.extend_from_slice(&0u32.to_le_bytes()); let rframe = create_req_ctx("dur.bin", 0x8000_0000, 1, c::durable::RECONNECT_V2, &dc);
5129 let (tx2, _rx2) = mpsc::channel(8);
5130 let mut conn2 = Smb2Conn::new([0u8; 8], tx2);
5131 conn2.session_id = 2;
5132 let resp2 = create(&mut conn2, vfs.clone(), &server, false, false, &rframe)
5133 .await
5134 .expect("reconnect");
5135 let rid: [u8; 16] = resp2[64..80].try_into().unwrap();
5136 assert_eq!(rid, pid, "reconnect returns the persistent id");
5137 assert!(
5138 conn2.handle_exists(&pid),
5139 "handle reinstated on new connection"
5140 );
5141
5142 std::fs::remove_dir_all(&dir).ok();
5143 });
5144 }
5145
5146 #[test]
5148 fn durable_reconnect_unknown_id_fails() {
5149 tokio_uring::start(async {
5150 let dir = std::env::temp_dir().join(format!("rustsmb_dur2_{}", std::process::id()));
5151 std::fs::create_dir_all(&dir).unwrap();
5152 std::fs::write(dir.join("x.bin"), b"data").unwrap();
5153 let server = server_with_share(&dir);
5154 let vfs = server.shares["public"].vfs.clone();
5155 let mut dc = Vec::new();
5156 dc.extend_from_slice(&[0xEE; 16]);
5157 dc.extend_from_slice(&[0xEE; 16]);
5158 dc.extend_from_slice(&0u32.to_le_bytes());
5159 let rframe = create_req_ctx("x.bin", 0x8000_0000, 1, c::durable::RECONNECT_V2, &dc);
5160 let (tx, _rx) = mpsc::channel(8);
5161 let mut conn = Smb2Conn::new([0u8; 8], tx);
5162 let err = create(&mut conn, vfs.clone(), &server, false, false, &rframe)
5163 .await
5164 .unwrap_err();
5165 assert_eq!(err, Status::OBJECT_NAME_NOT_FOUND);
5166 std::fs::remove_dir_all(&dir).ok();
5167 });
5168 }
5169}
5170
5171#[cfg(test)]
5172mod interface_tests {
5173 use super::*;
5174 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
5175
5176 #[test]
5177 fn interface_list_chains_and_encodes_sockaddr() {
5178 let entries = [
5179 (1u32, IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5))),
5180 (2u32, IpAddr::V6(Ipv6Addr::LOCALHOST)),
5181 ];
5182 let buf = build_interface_list(&entries);
5183 assert_eq!(buf.len(), 152 * 2, "two fixed-size entries");
5184 assert_eq!(u32::from_le_bytes(buf[0..4].try_into().unwrap()), 152);
5186 assert_eq!(u32::from_le_bytes(buf[4..8].try_into().unwrap()), 1);
5187 assert_eq!(
5188 u16::from_le_bytes(buf[24..26].try_into().unwrap()),
5189 2,
5190 "AF_INET"
5191 );
5192 assert_eq!(&buf[28..32], &[10, 0, 0, 5], "sin_addr");
5193 assert_eq!(u32::from_le_bytes(buf[152..156].try_into().unwrap()), 0);
5195 assert_eq!(
5196 u16::from_le_bytes(buf[152 + 24..152 + 26].try_into().unwrap()),
5197 23,
5198 "AF_INET6"
5199 );
5200 }
5201}
5202
5203#[cfg(test)]
5204mod signing_tests {
5205 use super::*;
5206
5207 #[test]
5208 fn sign_then_verify_round_trips_and_detects_tamper() {
5209 let key = [0x42u8; 16];
5210 let dialect = Some(smb_server_proto_smb2::negotiate::DIALECT_311);
5211 let algo = smb_server_proto_smb2::negotiate::ctx_type::SIGNING_AES128_CMAC;
5212 let mut pdu = vec![0u8; 96];
5213 pdu[0..4].copy_from_slice(&smb_server_proto_smb2::SMB2_MAGIC);
5214 for (i, b) in pdu[64..].iter_mut().enumerate() {
5215 *b = i as u8; }
5217 sign_pdu(&mut pdu, &key, dialect, algo);
5218 assert!(
5219 verify_pdu_signature(&pdu, &key, dialect, algo),
5220 "valid signature verifies"
5221 );
5222 pdu[70] ^= 0xFF;
5224 assert!(
5225 !verify_pdu_signature(&pdu, &key, dialect, algo),
5226 "tampered body rejected"
5227 );
5228 pdu[70] ^= 0xFF;
5230 assert!(
5231 !verify_pdu_signature(&pdu, &[0u8; 16], dialect, algo),
5232 "wrong key rejected"
5233 );
5234 }
5235}