diff --git a/data/auth.sqlite b/data/auth.sqlite index 09e7041..d92f7af 100644 Binary files a/data/auth.sqlite and b/data/auth.sqlite differ diff --git a/markbase-core/src/ssh_server/channel.rs b/markbase-core/src/ssh_server/channel.rs index f0522fb..095ae06 100644 --- a/markbase-core/src/ssh_server/channel.rs +++ b/markbase-core/src/ssh_server/channel.rs @@ -762,6 +762,66 @@ impl ChannelManager { data.len() ); + // ⭐⭐⭐⭐⭐ Phase 8: SCP handler (subsystem) + if let Some(scp_handler) = &mut channel.scp_handler { + info!( + "⭐⭐⭐⭐⭐ [SCP_DATA] Feeding {} bytes to ScpHandler", + data.len() + ); + + // Window Control - decrease local_window + channel.local_window -= data.len() as u32; + channel.local_consumed += data.len() as u32; + + // ⭐⭐⭐⭐⭐ Phase 14.4: SCP packet accumulation + channel.scp_input_buffer.extend_from_slice(&data); + info!( + "SCP buffer accumulated: {} bytes total", + channel.scp_input_buffer.len() + ); + + // Process SCP packets (line-based protocol) + // SCP uses newline-terminated commands: C0644, D0755, E, T + // Reference: OpenSSH scp.c + + // Find complete lines in buffer + let mut responses: Vec> = Vec::new(); + while let Some(newline_pos) = channel.scp_input_buffer.iter().position(|&b| b == b'\n') { + let line = channel.scp_input_buffer[..newline_pos].to_vec(); + channel.scp_input_buffer = channel.scp_input_buffer[newline_pos + 1..].to_vec(); + + info!("SCP command: {}", String::from_utf8_lossy(&line)); + + // Process SCP command + // TODO: Full implementation requires ScpHandler.handle_scp() with ReadWrite trait + // Current implementation: basic ACK (0 byte) + responses.push(vec![0]); // SCP ACK + } + + // Check for window adjust + if let Some(window_adjust_packet) = + channel_check_window(recipient_channel, &mut self.channels) + { + return Ok(Some(window_adjust_packet)); + } + + // Send SCP responses + if !responses.is_empty() { + // All responses except last go to pending_packets + for i in 0..responses.len().saturating_sub(1) { + let pending = self.build_channel_data(recipient_channel, &responses[i])?; + self.pending_packets.push_back(pending); + } + + // Last response is returned + if let Some(last_response) = responses.into_iter().last() { + return Ok(Some(self.build_channel_data(recipient_channel, &last_response)?)); + } + } + + return Ok(None); + } + // ⭐⭐⭐⭐⭐ Phase 16.5: rsync in-process handler (no child process) if let Some(rsync_handler) = &mut channel.rsync_handler { info!(