Complete Phase 15: Window Control + sshbuf zero-copy + SCP support
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

- Fix Window Control: decrease local_window on SSH_MSG_CHANNEL_DATA (critical fix)
- Implement SSH_MSG_CHANNEL_WINDOW_ADJUST (OpenSSH channels.c style)
- Add sshbuf.rs: zero-copy buffer management (339 lines, OpenSSH sshbuf.c reference)
- Add SCP command detection (scp -t/-f support for legacy protocol)
- Add handle_scp_exec() and handle_interactive_exec() for SCP/rsync
- Verify: rsync 100MB transfer successful, SCP legacy protocol working
- Security: All crypto using RustCrypto authoritative libraries (x25519-dalek, ed25519-dalek, aes, hmac)
This commit is contained in:
Warren
2026-06-17 13:59:28 +08:00
parent 99af9dc96e
commit 19a99cc676
6 changed files with 629 additions and 62 deletions

View File

@@ -202,14 +202,16 @@ fn perform_complete_kex_exchange(
kexdh_reply.write(stream)?;
info!("Sent SSH_MSG_KEX_ECDH_REPLY");
// Strict KEX: Wait for client NEWKEYS first (OpenSSH 10.2 requirement)
let client_newkeys = SshPacket::read(stream)?;
kex_state.handle_newkeys(&client_newkeys)?;
info!("Received SSH_MSG_NEWKEYS from client");
// Now send server NEWKEYS
let newkeys_packet = KexState::send_newkeys()?;
newkeys_packet.write(stream)?;
kex_state.newkeys_sent = true;
info!("Sent SSH_MSG_NEWKEYS");
let client_newkeys = SshPacket::read(stream)?;
kex_state.handle_newkeys(&client_newkeys)?;
info!("Received SSH_MSG_NEWKEYS");
info!("Sent SSH_MSG_NEWKEYS from server");
if kex_state.is_encryption_ready() {
info!("Encryption channel established successfully");
@@ -454,29 +456,39 @@ fn handle_ssh_service_loop(
let encrypted_response = EncryptedPacket::new(&response.payload, encryption_ctx, true)?;
encrypted_response.write(stream)?;
// Phase 6: 检查是否有命令输出需要发送
if let Some(channel_id) = channel_manager.get_channel_with_output() {
if let Some(output) = channel_manager.get_channel_output(channel_id) {
// 发送命令输出SSH_MSG_CHANNEL_DATA
let data_packet = channel_manager.build_channel_data(channel_id, &output)?;
let encrypted_data = EncryptedPacket::new(&data_packet.payload, encryption_ctx, true)?;
encrypted_data.write(stream)?;
info!("Sent command output ({} bytes)", output.len());
// 发送SSH_MSG_CHANNEL_EOF
let eof_packet = channel_manager.build_channel_eof(channel_id)?;
let encrypted_eof = EncryptedPacket::new(&eof_packet.payload, encryption_ctx, true)?;
encrypted_eof.write(stream)?;
info!("Sent SSH_MSG_CHANNEL_EOF");
// 发送SSH_MSG_CHANNEL_CLOSE
let close_packet = channel_manager.build_channel_close(channel_id)?;
let encrypted_close = EncryptedPacket::new(&close_packet.payload, encryption_ctx, true)?;
encrypted_close.write(stream)?;
info!("Sent SSH_MSG_CHANNEL_CLOSE");
// 移除channel
channel_manager.remove_channel(channel_id);
// ⭐⭐⭐⭐⭐ Phase 14.5修复:区分普通命令和交互式进程
// 检查是否有 exec_process交互式进程如 rsync
let has_exec_process = channel_manager.has_exec_process();
if has_exec_process {
info!("⭐⭐⭐⭐⭐ [INTERACTIVE_PROCESS] Detected exec_process (rsync/SCP), skipping immediate EOF");
// 对于交互式进程,只发送 SUCCESS等待 poll 循环处理数据流
// 不立即发送 EOF + CLOSE
} else {
// Phase 6: 普通命令执行,检查是否有命令输出需要发送
if let Some(channel_id) = channel_manager.get_channel_with_output() {
if let Some(output) = channel_manager.get_channel_output(channel_id) {
// 发送命令输出SSH_MSG_CHANNEL_DATA
let data_packet = channel_manager.build_channel_data(channel_id, &output)?;
let encrypted_data = EncryptedPacket::new(&data_packet.payload, encryption_ctx, true)?;
encrypted_data.write(stream)?;
info!("Sent command output ({} bytes)", output.len());
// 发送SSH_MSG_CHANNEL_EOF
let eof_packet = channel_manager.build_channel_eof(channel_id)?;
let encrypted_eof = EncryptedPacket::new(&eof_packet.payload, encryption_ctx, true)?;
encrypted_eof.write(stream)?;
info!("Sent SSH_MSG_CHANNEL_EOF");
// 发送SSH_MSG_CHANNEL_CLOSE
let close_packet = channel_manager.build_channel_close(channel_id)?;
let encrypted_close = EncryptedPacket::new(&close_packet.payload, encryption_ctx, true)?;
encrypted_close.write(stream)?;
info!("Sent SSH_MSG_CHANNEL_CLOSE");
// 移除channel
channel_manager.remove_channel(channel_id);
}
}
}
}