1use std::sync::Arc;
8
9use smb_server_proto::types::Status;
10use smb_server_proto_smb1::consts;
11use smb_server_proto_smb1::header::{Header, RespBody, build_response, parse_header};
12
13use smb_server_proto_smb1::consts::flags2;
14use smb_server_proto_smb2::{PROTO_ID_COMPRESSED, PROTO_ID_ENCRYPTED, PROTO_ID_SMB2};
15use smb_server_transport::{FrameSink, Transport};
16
17use metrics::{counter, histogram};
18use tokio::sync::mpsc;
19
20use crate::state::{ConnState, ServerShared};
21
22const OUTBOUND_QUEUE_DEPTH: usize = 64;
24
25pub type IoCtx<'a> = IoContext<'a>;
27
28pub struct IoContext<'a> {
30 pub server: Arc<ServerShared>,
32 pub conn: &'a mut ConnState,
34}
35
36#[derive(Debug)]
38pub struct ReqView<'a> {
39 pub hdr: Header,
41 pub wct: usize,
43 pub words: &'a [u8],
45 pub data: &'a [u8],
47 pub bc_off_abs: usize,
49 pub frame: &'a [u8],
52}
53
54impl<'a> ReqView<'a> {
55 pub fn unicode(&self) -> bool {
57 self.hdr.flags2 & flags2::UNICODE != 0
58 }
59
60 pub fn andx_command(&self) -> Option<u8> {
62 if self.wct < consts::andx::MIN_WCT {
63 return None;
64 }
65 self.words
66 .first()
67 .copied()
68 .filter(|c| *c != consts::ANDX_NONE)
69 }
70
71 pub fn andx_offset(&self) -> Option<usize> {
73 if self.wct < consts::andx::MIN_WCT_OFFSET || self.andx_command().is_none() {
74 return None;
75 }
76 Some(u16::from_le_bytes([
77 *self.words.get(consts::andx::OFFSET_POS)?,
78 *self.words.get(consts::andx::OFFSET_POS + 1)?,
79 ]) as usize)
80 }
81}
82
83pub(crate) async fn writer_loop(
88 mut writer: Box<dyn FrameSink>,
89 mut out_rx: mpsc::Receiver<Vec<u8>>,
90) {
91 while let Some(frame) = out_rx.recv().await {
92 if writer.send(&frame).await.is_err() {
93 break;
94 }
95 }
96}
97
98pub async fn serve_client(server: Arc<crate::state::ServerShared>, transport: Box<dyn Transport>) {
102 let (mut reader, writer) = transport.split();
103 let (out_tx, out_rx) = mpsc::channel::<Vec<u8>>(OUTBOUND_QUEUE_DEPTH);
104 let writer_task = tokio_uring::spawn(writer_loop(writer, out_rx));
105
106 {
107 let challenge = rand_challenge();
108 let mut conn = ConnState::new(challenge);
109 let mut smb2_conn: Option<crate::smb2::Smb2Conn> = None;
110
111 loop {
112 let Some(frame) = (match reader.recv().await {
113 Ok(Some(f)) => Some(f),
114 Ok(None) | Err(_) => break,
115 }) else {
116 break;
117 };
118 if frame.0.len() < smb_server_proto_smb2::SMB2_MAGIC.len() {
119 continue;
120 }
121
122 counter!("smb_nbss_frames_total").increment(1);
123
124 if frame.0[0] == PROTO_ID_SMB2
128 || frame.0[0] == PROTO_ID_ENCRYPTED
129 || frame.0[0] == PROTO_ID_COMPRESSED
130 || conn.upgraded_smb2
131 {
132 if conn.upgraded_smb2
136 && frame.0[0] != PROTO_ID_SMB2
137 && frame.0[0] != PROTO_ID_ENCRYPTED
138 && frame.0[0] != PROTO_ID_COMPRESSED
139 {
140 break;
141 }
142 if smb2_conn.is_none() {
143 smb2_conn = Some(crate::smb2::Smb2Conn::new(rand_challenge(), out_tx.clone()));
144 }
145 let c2 = smb2_conn.as_mut().unwrap();
146 let decompressed;
152 let msg: &[u8] = if frame.0[0] == PROTO_ID_COMPRESSED {
153 match smb_server_proto_smb2::compress::decompress_message(&frame.0) {
154 Some(d) if d.first() == Some(&PROTO_ID_SMB2) => {
155 decompressed = d;
156 &decompressed
157 }
158 _ => break,
159 }
160 } else {
161 &frame.0
162 };
163 let start = std::time::Instant::now();
164 if let Some(mut resp) = crate::smb2::process_frame(&server, c2, msg).await {
165 if let Some(algo) = c2.compress_algo
171 && c2.compress_response
172 && resp.first() != Some(&PROTO_ID_ENCRYPTED)
173 {
174 let packed = if c2.compress_chained {
175 smb_server_proto_smb2::compress::compress_message_chained(&resp, algo)
176 } else {
177 smb_server_proto_smb2::compress::compress_message(&resp, algo)
178 };
179 if let Some(packed) = packed {
180 resp = packed;
181 }
182 }
183 histogram!("smb_frame_duration_us").record(start.elapsed().as_micros() as f64);
184 counter!("smb_responses_total").increment(1);
185 if out_tx.send(resp).await.is_err() {
186 break;
187 }
188 }
189 if c2.disconnect {
193 break;
194 }
195 continue;
196 }
197
198 if let Some(resp) = process_frame(&server, &mut conn, &frame.0).await
200 && out_tx.send(resp).await.is_err() {
201 break;
202 }
203 }
204
205 if let Some(c2) = &mut smb2_conn {
207 if c2.session_id != 0 {
208 server.locks.release_session(c2.session_id);
209 server.share_modes.close_session(c2.session_id);
210 server.oplocks.release_session(c2.session_id);
211 server.app_instances.close_session(c2.session_id);
212 server.leases.release_session(c2.session_id);
213 }
214 let now_ms = crate::state::now_ms();
217 for (_fid, entry) in c2.durable.drain() {
218 let _ = server.durables.put(entry.into_record(now_ms)).await;
219 }
220 }
221 }
222
223 drop(out_tx);
226 let _ = writer_task.await;
227}
228
229pub fn rand_challenge_pub() -> [u8; 8] {
231 rand_challenge()
232}
233
234pub fn rand_bytes(n: usize) -> Vec<u8> {
236 use std::io::Read;
237 let mut buf = vec![0u8; n];
238 if let Ok(mut f) = std::fs::File::open("/dev/urandom")
239 && f.read_exact(&mut buf).is_ok() {
240 return buf;
241 }
242 let nanos = std::time::SystemTime::now()
243 .duration_since(std::time::UNIX_EPOCH)
244 .unwrap_or_default()
245 .as_nanos()
246 .to_le_bytes();
247 for (i, b) in buf.iter_mut().enumerate() {
248 *b = nanos[i % nanos.len()];
249 }
250 buf
251}
252
253fn rand_challenge() -> [u8; 8] {
255 use std::io::Read;
256 let mut b = [0u8; 8];
257 if let Ok(mut f) = std::fs::File::open("/dev/urandom")
258 && f.read_exact(&mut b).is_ok() {
259 return b;
260 }
261 let nanos = std::time::SystemTime::now()
262 .duration_since(std::time::UNIX_EPOCH)
263 .unwrap_or_default()
264 .as_nanos()
265 .to_le_bytes();
266 let n = b.len();
267 b.copy_from_slice(&nanos[..n]);
268 b
269}
270
271pub(crate) async fn process_frame(
273 server: &Arc<crate::state::ServerShared>,
274 conn: &mut ConnState,
275 buf: &[u8],
276) -> Option<Vec<u8>> {
277 let (hdr, wc_off) = parse_header(buf)?;
278
279 if hdr.command == consts::COM_NT_CANCEL {
281 return Some(build_response(&hdr, Status::CANCELLED, Vec::new()));
282 }
283
284 if hdr.command == consts::COM_NEGOTIATE
289 && !conn.upgraded_smb2
290 && (buf
291 .windows(smb_server_proto_smb2::SMB2_MAGIC.len())
292 .any(|w| w == smb_server_proto_smb2::SMB2_MAGIC)
293 || buf.windows(b"SMB 2.".len()).any(|w| w == b"SMB 2."))
294 && let Some(resp) = crate::smb2::handle_multiprotocol_negotiate(buf, &server.guid) {
295 conn.upgraded_smb2 = true;
296 tracing::info!("client upgraded to SMB2 via multi-protocol negotiate");
297 return Some(resp);
298 }
299
300 let wct = buf[wc_off] as usize;
301 let _bc_off_abs = wc_off + 1 + wct * consts::WORD_LEN;
302
303 let mut views: Vec<ReqView> = Vec::new();
305 let mut off = wc_off;
306 while off < buf.len() {
307 let cwct = buf[off] as usize;
308 let cbc = off + 1 + cwct * consts::WORD_LEN;
309 if cbc + consts::BYTE_COUNT_LEN > buf.len() {
310 break;
311 }
312 let words = &buf[off + 1..off + 1 + cwct * consts::WORD_LEN];
313 views.push(ReqView {
314 hdr: hdr.clone(),
315 wct: cwct,
316 words,
317 data: &buf[cbc + consts::BYTE_COUNT_LEN..],
318 bc_off_abs: cbc,
319 frame: buf,
320 });
321 let next_cmd = words.first().copied().unwrap_or(consts::ANDX_NONE);
322 let next_off = u16::from_le_bytes([
323 *words.get(consts::andx::OFFSET_POS).unwrap_or(&0),
324 *words.get(consts::andx::OFFSET_POS + 1).unwrap_or(&0),
325 ]) as usize;
326 off = if next_cmd != consts::ANDX_NONE && next_off > off && next_off < buf.len() {
327 next_off
328 } else {
329 break;
330 };
331 }
332
333 let mut io = IoContext {
334 server: server.clone(),
335 conn,
336 };
337 let mut bodies: Vec<RespBody> = Vec::new();
338 let mut last_status = Status::SUCCESS;
339 let mut final_hdr = hdr.clone();
340
341 for req in &views {
342 match crate::cmds::dispatch_one(&mut io, req, &mut bodies).await {
343 Ok(st) => last_status = st,
344 Err(status) => {
345 tracing::warn!(
346 cmd = format!("{:#06x}", req.hdr.command),
347 tid = format!("{:#06x}", req.hdr.tid),
348 uid = format!("{:#06x}", req.hdr.uid),
349 status = format!("{:08x}", status.raw()),
350 "handler error"
351 );
352 counter!("smb_handler_errors_total").increment(1);
353 final_hdr = req.hdr.clone();
354 last_status = status;
355 bodies.clear();
356 break;
357 }
358 }
359 }
360
361 Some(build_response(&final_hdr, last_status, bodies))
362}