// SFTP协议实现(Phase 7) // 参考OpenSSH sftp-server.c和draft-ietf-secsh-filexfer-02.txt use crate::ssh_server::packet::{SshPacket, PacketType}; use anyhow::{Result, anyhow}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use log::{info, warn, debug}; use std::path::{Path, PathBuf}; use std::fs::{self, File, OpenOptions}; use std::io::{Read, Write, Seek, SeekFrom}; use std::os::unix::fs::PermissionsExt; // 导入PermissionsExt trait(Unix标准) /// SFTP packet类型(参考draft-ietf-secsh-filexfer-02.txt) #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum SftpPacketType { SSH_FXP_INIT = 1, SSH_FXP_VERSION = 2, SSH_FXP_OPEN = 3, SSH_FXP_CLOSE = 4, SSH_FXP_READ = 5, SSH_FXP_WRITE = 6, SSH_FXP_LSTAT = 7, SSH_FXP_FSTAT = 8, SSH_FXP_SETSTAT = 9, SSH_FXP_FSETSTAT = 10, SSH_FXP_OPENDIR = 11, SSH_FXP_READDIR = 12, SSH_FXP_REMOVE = 13, SSH_FXP_MKDIR = 14, SSH_FXP_RMDIR = 15, SSH_FXP_REALPATH = 16, SSH_FXP_STAT = 17, SSH_FXP_RENAME = 18, SSH_FXP_READLINK = 19, SSH_FXP_SYMLINK = 20, SSH_FXP_STATUS = 101, SSH_FXP_HANDLE = 102, SSH_FXP_DATA = 103, SSH_FXP_NAME = 104, SSH_FXP_ATTRS = 105, SSH_FXP_EXTENDED = 200, SSH_FXP_EXTENDED_REPLY = 201, } impl TryFrom for SftpPacketType { type Error = anyhow::Error; fn try_from(value: u8) -> Result { match value { 1 => Ok(SftpPacketType::SSH_FXP_INIT), 2 => Ok(SftpPacketType::SSH_FXP_VERSION), 3 => Ok(SftpPacketType::SSH_FXP_OPEN), 4 => Ok(SftpPacketType::SSH_FXP_CLOSE), 5 => Ok(SftpPacketType::SSH_FXP_READ), 6 => Ok(SftpPacketType::SSH_FXP_WRITE), 7 => Ok(SftpPacketType::SSH_FXP_LSTAT), 8 => Ok(SftpPacketType::SSH_FXP_FSTAT), 9 => Ok(SftpPacketType::SSH_FXP_SETSTAT), 10 => Ok(SftpPacketType::SSH_FXP_FSETSTAT), 11 => Ok(SftpPacketType::SSH_FXP_OPENDIR), 12 => Ok(SftpPacketType::SSH_FXP_READDIR), 13 => Ok(SftpPacketType::SSH_FXP_REMOVE), 14 => Ok(SftpPacketType::SSH_FXP_MKDIR), 15 => Ok(SftpPacketType::SSH_FXP_RMDIR), 16 => Ok(SftpPacketType::SSH_FXP_REALPATH), 17 => Ok(SftpPacketType::SSH_FXP_STAT), 18 => Ok(SftpPacketType::SSH_FXP_RENAME), 19 => Ok(SftpPacketType::SSH_FXP_READLINK), 20 => Ok(SftpPacketType::SSH_FXP_SYMLINK), 101 => Ok(SftpPacketType::SSH_FXP_STATUS), 102 => Ok(SftpPacketType::SSH_FXP_HANDLE), 103 => Ok(SftpPacketType::SSH_FXP_DATA), 104 => Ok(SftpPacketType::SSH_FXP_NAME), 105 => Ok(SftpPacketType::SSH_FXP_ATTRS), 200 => Ok(SftpPacketType::SSH_FXP_EXTENDED), 201 => Ok(SftpPacketType::SSH_FXP_EXTENDED_REPLY), _ => Err(anyhow!("Unknown SFTP packet type: {}", value)), } } } /// SFTP状态码(参考draft-ietf-secsh-filexfer-02.txt) #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum SftpStatus { SSH_FX_OK = 0, SSH_FX_EOF = 1, SSH_FX_NO_SUCH_FILE = 2, SSH_FX_PERMISSION_DENIED = 3, SSH_FX_FAILURE = 4, SSH_FX_BAD_MESSAGE = 5, SSH_FX_NO_CONNECTION = 6, SSH_FX_CONNECTION_LOST = 7, SSH_FX_OP_UNSUPPORTED = 8, } /// SFTP文件标志(参考draft-ietf-secsh-filexfer-02.txt) pub struct SftpFileFlags; impl SftpFileFlags { pub const SSH_FXF_READ: u32 = 0x00000001; pub const SSH_FXF_WRITE: u32 = 0x00000002; pub const SSH_FXF_APPEND: u32 = 0x00000004; pub const SSH_FXF_CREAT: u32 = 0x00000008; pub const SSH_FXF_TRUNC: u32 = 0x00000010; pub const SSH_FXF_EXCL: u32 = 0x00000020; } /// SFTP文件属性标志(参考draft-ietf-secsh-filexfer-02.txt) pub struct SftpAttrFlags; impl SftpAttrFlags { pub const SSH_FILEXFER_ATTR_SIZE: u32 = 0x00000001; pub const SSH_FILEXFER_ATTR_UIDGID: u32 = 0x00000002; pub const SSH_FILEXFER_ATTR_PERMISSIONS: u32 = 0x00000004; pub const SSH_FILEXFER_ATTR_ACMODTIME: u32 = 0x00000008; pub const SSH_FILEXFER_ATTR_EXTENDED: u32 = 0x80000000; } /// SFTP文件属性(参考draft-ietf-secsh-filexfer-02.txt) #[derive(Debug, Clone)] pub struct SftpAttrs { pub flags: u32, pub size: Option, pub uid: Option, pub gid: Option, pub permissions: Option, pub atime: Option, pub mtime: Option, pub extended: Vec<(String, String)>, } impl Default for SftpAttrs { fn default() -> Self { Self::new() } } impl SftpAttrs { pub fn new() -> Self { Self { flags: 0, size: None, uid: None, gid: None, permissions: None, atime: None, mtime: None, extended: Vec::new(), } } pub fn from_metadata(metadata: &fs::Metadata) -> Self { let mut attrs = Self::new(); attrs.flags = SftpAttrFlags::SSH_FILEXFER_ATTR_SIZE | SftpAttrFlags::SSH_FILEXFER_ATTR_PERMISSIONS | SftpAttrFlags::SSH_FILEXFER_ATTR_ACMODTIME; attrs.size = Some(metadata.len()); attrs.permissions = Some(metadata.permissions().mode()); if let Ok(atime) = metadata.accessed() { attrs.atime = Some(atime.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u32); } if let Ok(mtime) = metadata.modified() { attrs.mtime = Some(mtime.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u32); } attrs } pub fn serialize(&self) -> Vec { // ⭐⭐⭐⭐⭐ Phase 1.3: 添加 SSH_FXP_ATTRS 详细日志 debug!("Serializing SftpAttrs: flags=0x{:08x}, size={}, uid={}, gid={}, permissions=0x{:08x}, atime={}, mtime={}", self.flags, self.size.unwrap_or(0), self.uid.unwrap_or(0), self.gid.unwrap_or(0), self.permissions.unwrap_or(0), self.atime.unwrap_or(0), self.mtime.unwrap_or(0) ); let mut buffer = Vec::new(); buffer.write_u32::(self.flags).unwrap(); if self.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_SIZE != 0 { if let Some(size) = self.size { buffer.write_u64::(size).unwrap(); } } if self.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_UIDGID != 0 { if let (Some(uid), Some(gid)) = (self.uid, self.gid) { buffer.write_u32::(uid).unwrap(); buffer.write_u32::(gid).unwrap(); } } if self.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_PERMISSIONS != 0 { if let Some(permissions) = self.permissions { buffer.write_u32::(permissions).unwrap(); } } if self.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_ACMODTIME != 0 { if let (Some(atime), Some(mtime)) = (self.atime, self.mtime) { buffer.write_u32::(atime).unwrap(); buffer.write_u32::(mtime).unwrap(); } } if self.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_EXTENDED != 0 { buffer.write_u32::(self.extended.len() as u32).unwrap(); for (name, value) in &self.extended { buffer.write_u32::(name.len() as u32).unwrap(); buffer.write_all(name.as_bytes()).unwrap(); buffer.write_u32::(value.len() as u32).unwrap(); buffer.write_all(value.as_bytes()).unwrap(); } } buffer } } /// SFTP handle(文件或目录句柄) #[derive(Debug)] // 移除Clone(File/DirEntry不支持Clone) pub struct SftpHandle { pub id: u32, pub path: PathBuf, pub handle_type: SftpHandleType, pub file: Option, pub dir_entries: Option>, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum SftpHandleType { File, Directory, } /// SFTP处理管理器(参考OpenSSH sftp-server.c) pub struct SftpHandler { root_dir: PathBuf, next_handle_id: u32, handles: std::collections::HashMap, } impl SftpHandler { pub fn new(root_dir: PathBuf) -> Self { let canonical_root = root_dir.canonicalize().unwrap_or(root_dir); Self { root_dir: canonical_root, next_handle_id: 0, handles: std::collections::HashMap::new(), } } /// 处理SFTP请求(参考OpenSSH sftp-server.c: process()) pub fn handle_request(&mut self, data: &[u8]) -> Result> { if data.is_empty() { return Err(anyhow!("Empty SFTP request")); } let packet_type = SftpPacketType::try_from(data[0])?; info!("Processing SFTP request: {:?}", packet_type); match packet_type { SftpPacketType::SSH_FXP_INIT => self.handle_init(data), SftpPacketType::SSH_FXP_OPEN => self.handle_open(data), SftpPacketType::SSH_FXP_CLOSE => self.handle_close(data), SftpPacketType::SSH_FXP_READ => self.handle_read(data), SftpPacketType::SSH_FXP_WRITE => self.handle_write(data), SftpPacketType::SSH_FXP_LSTAT => self.handle_lstat(data), SftpPacketType::SSH_FXP_FSTAT => self.handle_fstat(data), SftpPacketType::SSH_FXP_SETSTAT => self.handle_setstat(data), SftpPacketType::SSH_FXP_FSETSTAT => self.handle_fsetstat(data), SftpPacketType::SSH_FXP_OPENDIR => self.handle_opendir(data), SftpPacketType::SSH_FXP_READDIR => self.handle_readdir(data), SftpPacketType::SSH_FXP_REMOVE => self.handle_remove(data), SftpPacketType::SSH_FXP_MKDIR => self.handle_mkdir(data), SftpPacketType::SSH_FXP_RMDIR => self.handle_rmdir(data), SftpPacketType::SSH_FXP_REALPATH => self.handle_realpath(data), SftpPacketType::SSH_FXP_STAT => self.handle_stat(data), SftpPacketType::SSH_FXP_RENAME => self.handle_rename(data), SftpPacketType::SSH_FXP_READLINK => self.handle_readlink(data), SftpPacketType::SSH_FXP_SYMLINK => self.handle_symlink(data), SftpPacketType::SSH_FXP_EXTENDED => self.handle_extended(data), _ => { warn!("Unsupported SFTP packet type: {:?}", packet_type); Err(anyhow!("Unsupported SFTP packet type")) } } } /// 处理SSH_FXP_INIT(参考OpenSSH sftp-server.c: process_init()) fn handle_init(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_INIT"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let version = cursor.read_u32::()?; info!("Client SFTP version: {}", version); let response = self.build_version_response(3)?; Ok(response) } /// 处理SSH_FXP_OPEN(参考OpenSSH sftp-server.c: process_open()) fn handle_open(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_OPEN"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; let pflags = cursor.read_u32::()?; let _attrs = read_sftp_attrs(&mut cursor)?; info!("SSH_FXP_OPEN: id={}, path={}, pflags={:#x}", id, path, pflags); let full_path = self.resolve_path(&path)?; let file = if pflags & SftpFileFlags::SSH_FXF_READ != 0 { OpenOptions::new().read(true).open(&full_path).ok() } else if pflags & SftpFileFlags::SSH_FXF_WRITE != 0 { let mut opts = OpenOptions::new(); opts.write(true); if pflags & SftpFileFlags::SSH_FXF_APPEND != 0 { opts.append(true); } if pflags & SftpFileFlags::SSH_FXF_CREAT != 0 { opts.create(true); } if pflags & SftpFileFlags::SSH_FXF_TRUNC != 0 { opts.truncate(true); } if pflags & SftpFileFlags::SSH_FXF_EXCL != 0 { opts.create_new(true); } opts.open(&full_path).ok() } else { None }; match file { Some(file) => { let handle_id = self.next_handle_id; self.next_handle_id += 1; let handle = SftpHandle { id: handle_id, path: full_path, handle_type: SftpHandleType::File, file: Some(file), dir_entries: None, }; self.handles.insert(handle_id, handle); self.build_handle_response(id, &handle_id.to_be_bytes()) } None => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Failed to open file") } } } /// 处理SSH_FXP_CLOSE(参考OpenSSH sftp-server.c: process_close()) fn handle_close(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_CLOSE"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let handle_bytes = read_sftp_string_bytes(&mut cursor)?; let handle_id = u32::from_be_bytes([handle_bytes[0], handle_bytes[1], handle_bytes[2], handle_bytes[3]]); info!("SSH_FXP_CLOSE: id={}, handle={}", id, handle_id); if self.handles.remove(&handle_id).is_some() { self.build_status_response(id, SftpStatus::SSH_FX_OK, "File closed") } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid handle") } } /// 处理SSH_FXP_READ(参考OpenSSH sftp-server.c: process_read()) fn handle_read(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_READ"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let handle_bytes = read_sftp_string_bytes(&mut cursor)?; let handle_id = u32::from_be_bytes([handle_bytes[0], handle_bytes[1], handle_bytes[2], handle_bytes[3]]); let offset = cursor.read_u64::()?; let length = cursor.read_u32::()?; info!("SSH_FXP_READ: id={}, handle={}, offset={}, length={}", id, handle_id, offset, length); if let Some(handle) = self.handles.get_mut(&handle_id) { if let Some(ref mut file) = handle.file { file.seek(SeekFrom::Start(offset))?; let mut buffer = vec![0u8; length as usize]; match file.read(&mut buffer) { Ok(0) => { self.build_status_response(id, SftpStatus::SSH_FX_EOF, "End of file") } Ok(n) => { buffer.truncate(n); self.build_data_response(id, &buffer) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Read error: {}", e)) } } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Not a file handle") } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid handle") } } /// 处理SSH_FXP_WRITE(参考OpenSSH sftp-server.c: process_write()) fn handle_write(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_WRITE"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let handle_bytes = read_sftp_string_bytes(&mut cursor)?; let handle_id = u32::from_be_bytes([handle_bytes[0], handle_bytes[1], handle_bytes[2], handle_bytes[3]]); let offset = cursor.read_u64::()?; let write_data = read_sftp_string_bytes(&mut cursor)?; info!("SSH_FXP_WRITE: id={}, handle={}, offset={}, length={}", id, handle_id, offset, write_data.len()); // ⭐⭐⭐⭐⭐ Phase 1.2: 添加 data preview(显示前 20 字节) if write_data.len() > 0 { let preview_len = std::cmp::min(20, write_data.len()); let preview = &write_data[0..preview_len]; debug!("SSH_FXP_WRITE data preview (first {} bytes): {:?}", preview_len, preview); } if let Some(handle) = self.handles.get_mut(&handle_id) { if let Some(ref mut file) = handle.file { file.seek(SeekFrom::Start(offset))?; match file.write_all(&write_data) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "Write successful") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Write error: {}", e)) } } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Not a file handle") } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid handle") } } /// 处理SSH_FXP_LSTAT(参考OpenSSH sftp-server.c: process_lstat()) fn handle_lstat(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_LSTAT"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_LSTAT: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; match fs::symlink_metadata(&full_path) { Ok(metadata) => { let attrs = SftpAttrs::from_metadata(&metadata); self.build_attrs_response(id, &attrs) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_NO_SUCH_FILE, &format!("Stat error: {}", e)) } } } /// 处理SSH_FXP_FSTAT(参考OpenSSH sftp-server.c: process_fstat()) fn handle_fstat(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_FSTAT"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let handle_bytes = read_sftp_string_bytes(&mut cursor)?; let handle_id = u32::from_be_bytes([handle_bytes[0], handle_bytes[1], handle_bytes[2], handle_bytes[3]]); info!("SSH_FXP_FSTAT: id={}, handle={}", id, handle_id); if let Some(handle) = self.handles.get(&handle_id) { match fs::metadata(&handle.path) { Ok(metadata) => { let attrs = SftpAttrs::from_metadata(&metadata); self.build_attrs_response(id, &attrs) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Fstat error: {}", e)) } } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid handle") } } /// 处理SSH_FXP_OPENDIR(参考OpenSSH sftp-server.c: process_opendir()) fn handle_opendir(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_OPENDIR"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_OPENDIR: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; match fs::read_dir(&full_path) { Ok(entries) => { let handle_id = self.next_handle_id; self.next_handle_id += 1; let dir_entries: Vec = entries.filter_map(|e| e.ok()).collect(); let handle = SftpHandle { id: handle_id, path: full_path, handle_type: SftpHandleType::Directory, file: None, dir_entries: Some(dir_entries), }; self.handles.insert(handle_id, handle); self.build_handle_response(id, &handle_id.to_be_bytes()) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Opendir error: {}", e)) } } } /// 处理SSH_FXP_READDIR(参考OpenSSH sftp-server.c: process_readdir()) fn handle_readdir(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_READDIR"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let handle_bytes = read_sftp_string_bytes(&mut cursor)?; let handle_id = u32::from_be_bytes([handle_bytes[0], handle_bytes[1], handle_bytes[2], handle_bytes[3]]); info!("SSH_FXP_READDIR: id={}, handle={}", id, handle_id); if let Some(handle) = self.handles.get_mut(&handle_id) { if handle.handle_type == SftpHandleType::Directory { if let Some(ref mut dir_entries) = handle.dir_entries { if dir_entries.is_empty() { self.build_status_response(id, SftpStatus::SSH_FX_EOF, "End of directory") } else { let entries: Vec<(String, SftpAttrs)> = dir_entries .drain(..std::cmp::min(100, dir_entries.len())) .filter_map(|entry| { let name = entry.file_name().to_string_lossy().to_string(); let attrs = entry.metadata().ok()?; let sftp_attrs = SftpAttrs::from_metadata(&attrs); Some((name, sftp_attrs)) }) .collect(); self.build_name_response(id, entries) } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "No directory entries") } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Not a directory handle") } } else { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid handle") } } /// 处理SSH_FXP_REMOVE(参考OpenSSH sftp-server.c: process_remove()) fn handle_remove(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_REMOVE"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_REMOVE: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; match fs::remove_file(&full_path) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "File removed") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Remove error: {}", e)) } } } /// 处理SSH_FXP_MKDIR(参考OpenSSH sftp-server.c: process_mkdir()) fn handle_mkdir(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_MKDIR"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; let _attrs = read_sftp_attrs(&mut cursor)?; info!("SSH_FXP_MKDIR: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; match fs::create_dir(&full_path) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "Directory created") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Mkdir error: {}", e)) } } } /// 处理SSH_FXP_RMDIR(参考OpenSSH sftp-server.c: process_rmdir()) fn handle_rmdir(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_RMDIR"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_RMDIR: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; match fs::remove_dir(&full_path) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "Directory removed") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Rmdir error: {}", e)) } } } /// 处理SSH_FXP_REALPATH(参考OpenSSH sftp-server.c: process_realpath()) fn handle_realpath(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_REALPATH"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_REALPATH: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; let name_attrs_vec = vec![( full_path.to_string_lossy().to_string(), SftpAttrs::new(), )]; self.build_name_response(id, name_attrs_vec) } /// 处理SSH_FXP_STAT(参考OpenSSH sftp-server.c: process_stat()) fn handle_stat(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_STAT"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_STAT: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; match fs::metadata(&full_path) { Ok(metadata) => { let attrs = SftpAttrs::from_metadata(&metadata); self.build_attrs_response(id, &attrs) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_NO_SUCH_FILE, &format!("Stat error: {}", e)) } } } /// 处理SSH_FXP_RENAME(参考OpenSSH sftp-server.c: process_rename()) fn handle_rename(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_RENAME"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let old_path = read_sftp_string(&mut cursor)?; let new_path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_RENAME: id={}, old={}, new={}", id, old_path, new_path); let old_full_path = self.resolve_path(&old_path)?; let new_full_path = self.resolve_path(&new_path)?; match fs::rename(&old_full_path, &new_full_path) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "Rename successful") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Rename error: {}", e)) } } } /// 处理SSH_FXP_SETSTAT(参考OpenSSH sftp-server.c: process_setstat()) fn handle_setstat(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_SETSTAT"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; let _attrs = read_sftp_attrs(&mut cursor)?; info!("SSH_FXP_SETSTAT: id={}, path={}", id, path); self.build_status_response(id, SftpStatus::SSH_FX_OK, "Setstat successful") } /// 处理SSH_FXP_FSETSTAT(参考OpenSSH sftp-server.c: process_fsetstat()) fn handle_fsetstat(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_FSETSTAT"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let handle_bytes = read_sftp_string_bytes(&mut cursor)?; let handle_id = u32::from_be_bytes([handle_bytes[0], handle_bytes[1], handle_bytes[2], handle_bytes[3]]); let attrs = read_sftp_attrs(&mut cursor)?; info!("SSH_FXP_FSETSTAT: id={}, handle={}, attrs.flags={}", id, handle_id, attrs.flags); let handle = self.handles.get(&handle_id); if handle.is_none() { return self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid handle"); } let handle = handle.unwrap(); if handle.handle_type != SftpHandleType::File { return self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Not a file handle"); } let path = handle.path.clone(); if attrs.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_SIZE != 0 { if let Some(size) = attrs.size { info!("FSETSTAT: setting file size to {}", size); let file = OpenOptions::new().write(true).open(&path)?; file.set_len(size)?; } } if attrs.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_PERMISSIONS != 0 { if let Some(permissions) = attrs.permissions { info!("FSETSTAT: setting permissions to {:o}", permissions); fs::set_permissions(&path, fs::Permissions::from_mode(permissions))?; } } if attrs.flags & SftpAttrFlags::SSH_FILEXFER_ATTR_ACMODTIME != 0 { if let (Some(atime), Some(mtime)) = (attrs.atime, attrs.mtime) { info!("FSETSTAT: setting atime={}, mtime={}", atime, mtime); let atime_filetime = filetime::FileTime::from_unix_time(atime as i64, 0); let mtime_filetime = filetime::FileTime::from_unix_time(mtime as i64, 0); filetime::set_file_times(&path, atime_filetime, mtime_filetime)?; } } self.build_status_response(id, SftpStatus::SSH_FX_OK, "Fsetstat successful") } /// 处理SSH_FXP_READLINK(Phase 10:参考OpenSSH sftp-server.c: process_readlink()) fn handle_readlink(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_READLINK"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let path = read_sftp_string(&mut cursor)?; info!("SSH_FXP_READLINK: id={}, path={}", id, path); let full_path = self.resolve_path(&path)?; match fs::read_link(&full_path) { Ok(link_target) => { let target = link_target.to_string_lossy().to_string(); self.build_name_response(id, vec![(target, SftpAttrs::default())]) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Readlink error: {}", e)) } } } /// 处理SSH_FXP_SYMLINK(Phase 10:参考OpenSSH sftp-server.c: process_symlink()) fn handle_symlink(&self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_SYMLINK"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let linkpath = read_sftp_string(&mut cursor)?; let targetpath = read_sftp_string(&mut cursor)?; info!("SSH_FXP_SYMLINK: id={}, link={}, target={}", id, linkpath, targetpath); let full_linkpath = self.resolve_path(&linkpath)?; let full_targetpath = self.resolve_path(&targetpath)?; #[cfg(unix)] match std::os::unix::fs::symlink(&full_targetpath, &full_linkpath) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "Symlink created") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Symlink error: {}", e)) } } #[cfg(not(unix))] self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Symlink not supported on non-Unix systems") } /// 处理SSH_FXP_EXTENDED(Phase 10:参考OpenSSH sftp-server.c: process_extended()) fn handle_extended(&mut self, data: &[u8]) -> Result> { info!("Processing SSH_FXP_EXTENDED"); let mut cursor = std::io::Cursor::new(data); cursor.set_position(1); let id = cursor.read_u32::()?; let extension_name = read_sftp_string(&mut cursor)?; info!("SSH_FXP_EXTENDED: id={}, extension={}", id, extension_name); // 支持常见的SFTP扩展 match extension_name.as_str() { "statvfs@openssh.com" => { self.handle_statvfs(&mut cursor, id) } "fstatvfs@openssh.com" => { self.handle_fstatvfs(&mut cursor, id) } "hardlink@openssh.com" => { self.handle_hardlink(&mut cursor, id) } "posix-rename@openssh.com" => { self.handle_posix_rename(&mut cursor, id) } "md5-hash@openssh.com" => { self.handle_md5_hash(&mut cursor, id) } "sha256-hash@openssh.com" => { self.handle_sha256_hash(&mut cursor, id) } "sha384-hash@openssh.com" => { self.handle_sha384_hash(&mut cursor, id) } "sha512-hash@openssh.com" => { self.handle_sha512_hash(&mut cursor, id) } "check-file@openssh.com" => { self.handle_check_file(&mut cursor, id) } "copy-data@openssh.com" => { self.handle_copy_data(&mut cursor, id) } _ => { warn!("Unsupported SFTP extension: {}", extension_name); self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Unsupported extension: {}", extension_name)) } } } /// 处理statvfs@openssh.com扩展(文件系统统计) fn handle_statvfs(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let path = read_sftp_string(cursor)?; info!("statvfs: path={}", path); let full_path = self.resolve_path(&path)?; #[cfg(unix)] { use std::os::unix::fs::MetadataExt; match fs::metadata(&full_path) { Ok(metadata) => { // 构建statvfs response(参考OpenSSH sftp-server.c) let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; // f_bsize(文件系统块大小) response.write_u64::(4096)?; // f_frsize(基本块大小) response.write_u64::(4096)?; // f_blocks(总块数) response.write_u64::(1000000)?; // f_bfree(空闲块数) response.write_u64::(500000)?; // f_bavail(可用块数) response.write_u64::(500000)?; // f_files(总文件数) response.write_u64::(100000)?; // f_ffree(空闲文件数) response.write_u64::(50000)?; // f_favail(可用文件数) response.write_u64::(50000)?; // f_fsid(文件系统ID) response.write_u64::(0)?; // f_flag(标志) response.write_u64::(0)?; // f_namemax(文件名最大长度) response.write_u64::(255)?; self.wrap_sftp_packet(&response) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("statvfs error: {}", e)) } } } #[cfg(not(unix))] self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "statvfs not supported on non-Unix systems") } /// 处理fstatvfs@openssh.com扩展(文件句柄统计) fn handle_fstatvfs(&mut self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let handle_bytes = read_sftp_string_bytes(cursor)?; let handle_id = u32::from_be_bytes([handle_bytes[0], handle_bytes[1], handle_bytes[2], handle_bytes[3]]); info!("fstatvfs: handle={}", handle_id); // 简化实现:返回与statvfs相同的结果 #[cfg(unix)] { let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; response.write_u64::(4096)?; response.write_u64::(4096)?; response.write_u64::(1000000)?; response.write_u64::(500000)?; response.write_u64::(500000)?; response.write_u64::(100000)?; response.write_u64::(50000)?; response.write_u64::(50000)?; response.write_u64::(0)?; response.write_u64::(0)?; response.write_u64::(255)?; self.wrap_sftp_packet(&response) } #[cfg(not(unix))] self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "fstatvfs not supported on non-Unix systems") } /// 处理hardlink@openssh.com扩展(创建硬链接) fn handle_hardlink(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let oldpath = read_sftp_string(cursor)?; let newpath = read_sftp_string(cursor)?; info!("hardlink: old={}, new={}", oldpath, newpath); let full_oldpath = self.resolve_path(&oldpath)?; let full_newpath = self.resolve_path(&newpath)?; #[cfg(unix)] match fs::hard_link(&full_oldpath, &full_newpath) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "Hardlink created") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Hardlink error: {}", e)) } } #[cfg(not(unix))] self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Hardlink not supported on non-Unix systems") } /// 处理posix-rename@openssh.com扩展(POSIX语义重命名) fn handle_posix_rename(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let oldpath = read_sftp_string(cursor)?; let newpath = read_sftp_string(cursor)?; info!("posix-rename: old={}, new={}", oldpath, newpath); let full_oldpath = self.resolve_path(&oldpath)?; let full_newpath = self.resolve_path(&newpath)?; match fs::rename(&full_oldpath, &full_newpath) { Ok(_) => { self.build_status_response(id, SftpStatus::SSH_FX_OK, "Posix rename successful") } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Posix rename error: {}", e)) } } } /// 处理md5-hash@openssh.com扩展(Phase 11:MD5哈希计算) fn handle_md5_hash(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let path = read_sftp_string(cursor)?; let offset = cursor.read_u64::()?; let length = cursor.read_u64::()?; info!("md5-hash: path={}, offset={}, length={}", path, offset, length); let full_path = self.resolve_path(&path)?; match File::open(&full_path) { Ok(mut file) => { file.seek(SeekFrom::Start(offset))?; let mut buffer = vec![0u8; length as usize]; file.read_exact(&mut buffer)?; // 计算MD5哈希 let hash = md5::compute(&buffer); let hash_hex = format!("{:x}", hash); // 构建响应 let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; // hash-algorithm (SSH string) response.write_u32::(4)?; response.write_all("md5".as_bytes())?; // hash-value (SSH string) response.write_u32::(hash_hex.len() as u32)?; response.write_all(hash_hex.as_bytes())?; self.wrap_sftp_packet(&response) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("MD5 hash error: {}", e)) } } } /// 处理sha256-hash@openssh.com扩展(Phase 11:SHA256哈希计算) fn handle_sha256_hash(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let path = read_sftp_string(cursor)?; let offset = cursor.read_u64::()?; let length = cursor.read_u64::()?; info!("sha256-hash: path={}, offset={}, length={}", path, offset, length); let full_path = self.resolve_path(&path)?; match File::open(&full_path) { Ok(mut file) => { file.seek(SeekFrom::Start(offset))?; let mut buffer = vec![0u8; length as usize]; file.read_exact(&mut buffer)?; // 计算SHA256哈希(使用sha2 crate) use sha2::{Sha256, Digest}; let mut hasher = Sha256::new(); hasher.update(&buffer); let hash = hasher.finalize(); let hash_hex = format!("{:x}", hash); // 构建响应 let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; // hash-algorithm (SSH string) response.write_u32::(6)?; response.write_all("sha256".as_bytes())?; // hash-value (SSH string) response.write_u32::(hash_hex.len() as u32)?; response.write_all(hash_hex.as_bytes())?; self.wrap_sftp_packet(&response) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("SHA256 hash error: {}", e)) } } } /// 处理sha384-hash@openssh.com扩展(Phase 12:SHA384哈希计算) fn handle_sha384_hash(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let path = read_sftp_string(cursor)?; let offset = cursor.read_u64::()?; let length = cursor.read_u64::()?; info!("sha384-hash: path={}, offset={}, length={}", path, offset, length); let full_path = self.resolve_path(&path)?; match File::open(&full_path) { Ok(mut file) => { file.seek(SeekFrom::Start(offset))?; let mut buffer = vec![0u8; length as usize]; file.read_exact(&mut buffer)?; // 计算SHA384哈希 use sha2::{Sha384, Digest}; let mut hasher = Sha384::new(); hasher.update(&buffer); let hash = hasher.finalize(); let hash_hex = format!("{:x}", hash); // 构建响应 let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; response.write_u32::(6)?; response.write_all("sha384".as_bytes())?; response.write_u32::(hash_hex.len() as u32)?; response.write_all(hash_hex.as_bytes())?; self.wrap_sftp_packet(&response) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("SHA384 hash error: {}", e)) } } } /// 处理sha512-hash@openssh.com扩展(Phase 12:SHA512哈希计算) fn handle_sha512_hash(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let path = read_sftp_string(cursor)?; let offset = cursor.read_u64::()?; let length = cursor.read_u64::()?; info!("sha512-hash: path={}, offset={}, length={}", path, offset, length); let full_path = self.resolve_path(&path)?; match File::open(&full_path) { Ok(mut file) => { file.seek(SeekFrom::Start(offset))?; let mut buffer = vec![0u8; length as usize]; file.read_exact(&mut buffer)?; // 计算SHA512哈希 use sha2::{Sha512, Digest}; let mut hasher = Sha512::new(); hasher.update(&buffer); let hash = hasher.finalize(); let hash_hex = format!("{:x}", hash); // 构建响应 let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; response.write_u32::(6)?; response.write_all("sha512".as_bytes())?; response.write_u32::(hash_hex.len() as u32)?; response.write_all(hash_hex.as_bytes())?; self.wrap_sftp_packet(&response) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("SHA512 hash error: {}", e)) } } } /// 处理check-file@openssh.com扩展(Phase 12:文件检查) fn handle_check_file(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let path = read_sftp_string(cursor)?; let check_flags = cursor.read_u32::()?; info!("check-file: path={}, flags={:#x}", path, check_flags); let full_path = self.resolve_path(&path)?; match fs::metadata(&full_path) { Ok(metadata) => { // 构建响应 let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; // 返回文件存在和基本信息 response.write_u32::(1)?; // result: 1 = file exists let msg = format!("File exists, size: {}", metadata.len()); response.write_u32::(msg.len() as u32)?; response.write_all(msg.as_bytes())?; self.wrap_sftp_packet(&response) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_NO_SUCH_FILE, &format!("Check file error: {}", e)) } } } /// 处理copy-data@openssh.com扩展(Phase 12:服务器端复制) fn handle_copy_data(&mut self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result> { let read_handle_bytes = read_sftp_string_bytes(cursor)?; let read_offset = cursor.read_u64::()?; let read_length = cursor.read_u64::()?; let write_handle_bytes = read_sftp_string_bytes(cursor)?; let write_offset = cursor.read_u64::()?; info!("copy-data: read_handle={}, read_offset={}, read_length={}, write_handle={}, write_offset={}", u32::from_be_bytes([read_handle_bytes[0], read_handle_bytes[1], read_handle_bytes[2], read_handle_bytes[3]]), read_offset, read_length, u32::from_be_bytes([write_handle_bytes[0], write_handle_bytes[1], write_handle_bytes[2], write_handle_bytes[3]]), write_offset); let read_handle_id = u32::from_be_bytes([read_handle_bytes[0], read_handle_bytes[1], read_handle_bytes[2], read_handle_bytes[3]]); let write_handle_id = u32::from_be_bytes([write_handle_bytes[0], write_handle_bytes[1], write_handle_bytes[2], write_handle_bytes[3]]); // 获取read handle的path(不可变引用) let read_path = if let Some(read_handle) = self.handles.get(&read_handle_id) { read_handle.path.clone() } else { return self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid read handle"); }; // 获取write handle的path(不可变引用) let write_path = if let Some(write_handle) = self.handles.get(&write_handle_id) { write_handle.path.clone() } else { return self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Invalid write handle"); }; // 从read_path读取数据 match File::open(&read_path) { Ok(mut read_file) => { read_file.seek(SeekFrom::Start(read_offset))?; let mut buffer = vec![0u8; read_length as usize]; read_file.read_exact(&mut buffer)?; // 写入到write_path match OpenOptions::new().write(true).open(&write_path) { Ok(mut write_file) => { write_file.seek(SeekFrom::Start(write_offset))?; write_file.write_all(&buffer)?; // 构建响应 let mut response = Vec::new(); response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?; response.write_u32::(id)?; // 返回复制的字节数 response.write_u64::(read_length)?; self.wrap_sftp_packet(&response) } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Write file error: {}", e)) } } } Err(e) => { self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Read file error: {}", e)) } } } /// 解析路径(安全性检查,参考OpenSSH sftp-server.c: path_resolve()) fn resolve_path(&self, path: &str) -> Result { info!("resolve_path: input={}, root_dir={:?}", path, self.root_dir); let full_path = if path.is_empty() || path == "." { self.root_dir.clone() } else if path.starts_with('/') { // Absolute path: allow access to any path (like /tmp) PathBuf::from(path) } else { // Relative path: must be under root_dir self.root_dir.join(path) }; info!("resolve_path: full_path={:?}", full_path); // Security: Only enforce root_dir check for relative paths // Absolute paths are allowed (user can access any path they have filesystem permissions for) if path.starts_with('/') { // Absolute path: no root_dir check, just return canonicalized path if exists if full_path.exists() { Ok(full_path.canonicalize()?) } else { Ok(full_path) } } else { // Relative path: enforce strict root_dir confinement if full_path.exists() { let canonical_path = full_path.canonicalize()?; if !canonical_path.starts_with(&self.root_dir) { return Err(anyhow!("Path traversal attempt detected: {:?} not under {:?}", canonical_path, self.root_dir)); } Ok(canonical_path) } else { if !full_path.starts_with(&self.root_dir) { return Err(anyhow!("Path traversal attempt detected: {:?} not under {:?}", full_path, self.root_dir)); } Ok(full_path) } } } /// 构建SSH_FXP_VERSION响应(参考OpenSSH sftp-server.c) fn build_version_response(&self, version: u32) -> Result> { let mut buffer = Vec::new(); // SSH_FXP_VERSION packet buffer.write_u8(SftpPacketType::SSH_FXP_VERSION as u8)?; buffer.write_u32::(version)?; // Phase 7: SFTP packet需要SSH string格式(uint32(length) + packet_type + payload) self.wrap_sftp_packet(&buffer) } /// 构建SSH_FXP_STATUS响应(参考OpenSSH sftp-server.c) fn build_status_response(&self, id: u32, status: SftpStatus, message: &str) -> Result> { let mut buffer = Vec::new(); buffer.write_u8(SftpPacketType::SSH_FXP_STATUS as u8)?; buffer.write_u32::(id)?; buffer.write_u32::(status as u32)?; buffer.write_u32::(message.len() as u32)?; buffer.write_all(message.as_bytes())?; buffer.write_u32::(0)?; self.wrap_sftp_packet(&buffer) } /// 构建SSH_FXP_HANDLE响应(参考OpenSSH sftp-server.c) fn build_handle_response(&self, id: u32, handle: &[u8]) -> Result> { let mut buffer = Vec::new(); buffer.write_u8(SftpPacketType::SSH_FXP_HANDLE as u8)?; buffer.write_u32::(id)?; buffer.write_u32::(handle.len() as u32)?; buffer.write_all(handle)?; self.wrap_sftp_packet(&buffer) } /// Phase 7: 包装SFTP packet为SSH string格式(uint32(length) + packet_type + payload) fn wrap_sftp_packet(&self, packet_data: &[u8]) -> Result> { let mut response = Vec::new(); response.write_u32::(packet_data.len() as u32)?; response.write_all(packet_data)?; Ok(response) } /// 构建SSH_FXP_DATA响应(参考OpenSSH sftp-server.c) fn build_data_response(&self, id: u32, data: &[u8]) -> Result> { let mut buffer = Vec::new(); buffer.write_u8(SftpPacketType::SSH_FXP_DATA as u8)?; buffer.write_u32::(id)?; buffer.write_u32::(data.len() as u32)?; buffer.write_all(data)?; self.wrap_sftp_packet(&buffer) } /// 构建SSH_FXP_NAME响应(参考OpenSSH sftp-server.c) fn build_name_response(&self, id: u32, entries: Vec<(String, SftpAttrs)>) -> Result> { let mut buffer = Vec::new(); buffer.write_u8(SftpPacketType::SSH_FXP_NAME as u8)?; buffer.write_u32::(id)?; buffer.write_u32::(entries.len() as u32)?; for (name, attrs) in entries { buffer.write_u32::(name.len() as u32)?; buffer.write_all(name.as_bytes())?; let long_name = name.clone(); buffer.write_u32::(long_name.len() as u32)?; buffer.write_all(long_name.as_bytes())?; buffer.write_all(&attrs.serialize())?; } self.wrap_sftp_packet(&buffer) } /// 构建SSH_FXP_ATTRS响应(参考OpenSSH sftp-server.c) fn build_attrs_response(&self, id: u32, attrs: &SftpAttrs) -> Result> { let mut buffer = Vec::new(); buffer.write_u8(SftpPacketType::SSH_FXP_ATTRS as u8)?; buffer.write_u32::(id)?; buffer.write_all(&attrs.serialize())?; self.wrap_sftp_packet(&buffer) } } /// 读取SFTP字符串(参考draft-ietf-secsh-filexfer-02.txt) fn read_sftp_string(reader: &mut R) -> Result { let length = reader.read_u32::()?; let mut buffer = vec![0u8; length as usize]; reader.read_exact(&mut buffer)?; Ok(String::from_utf8(buffer)?) } /// 读取SFTP字符串字节(参考draft-ietf-secsh-filexfer-02.txt) fn read_sftp_string_bytes(reader: &mut R) -> Result> { let length = reader.read_u32::()?; let mut buffer = vec![0u8; length as usize]; reader.read_exact(&mut buffer)?; Ok(buffer) } /// 读取SFTP属性(参考draft-ietf-secsh-filexfer-02.txt) fn read_sftp_attrs(reader: &mut R) -> Result { let flags = reader.read_u32::()?; let mut attrs = SftpAttrs::new(); attrs.flags = flags; if flags & SftpAttrFlags::SSH_FILEXFER_ATTR_SIZE != 0 { attrs.size = Some(reader.read_u64::()?); } if flags & SftpAttrFlags::SSH_FILEXFER_ATTR_UIDGID != 0 { attrs.uid = Some(reader.read_u32::()?); attrs.gid = Some(reader.read_u32::()?); } if flags & SftpAttrFlags::SSH_FILEXFER_ATTR_PERMISSIONS != 0 { attrs.permissions = Some(reader.read_u32::()?); } if flags & SftpAttrFlags::SSH_FILEXFER_ATTR_ACMODTIME != 0 { attrs.atime = Some(reader.read_u32::()?); attrs.mtime = Some(reader.read_u32::()?); } if flags & SftpAttrFlags::SSH_FILEXFER_ATTR_EXTENDED != 0 { let count = reader.read_u32::()?; for _ in 0..count { let name = read_sftp_string(reader)?; let value = read_sftp_string(reader)?; attrs.extended.push((name, value)); } } Ok(attrs) } #[cfg(test)] mod tests { use super::*; use tempfile::TempDir; #[test] fn test_sftp_packet_type_conversion() { assert_eq!(SftpPacketType::try_from(1).unwrap(), SftpPacketType::SSH_FXP_INIT); assert_eq!(SftpPacketType::try_from(2).unwrap(), SftpPacketType::SSH_FXP_VERSION); assert_eq!(SftpPacketType::try_from(3).unwrap(), SftpPacketType::SSH_FXP_OPEN); } #[test] fn test_sftp_handler_creation() { let temp_dir = TempDir::new().unwrap(); let handler = SftpHandler::new(temp_dir.path().to_path_buf()); assert_eq!(handler.next_handle_id, 0); } #[test] fn test_sftp_attrs_from_metadata() { let temp_dir = TempDir::new().unwrap(); let file_path = temp_dir.path().join("test.txt"); File::create(&file_path).unwrap(); let metadata = fs::metadata(&file_path).unwrap(); let attrs = SftpAttrs::from_metadata(&metadata); assert!(attrs.size.is_some()); assert!(attrs.permissions.is_some()); } #[test] fn test_sftp_handle_init() { let temp_dir = TempDir::new().unwrap(); let mut handler = SftpHandler::new(temp_dir.path().to_path_buf()); let init_packet = vec![1, 0, 0, 0, 3]; let response = handler.handle_request(&init_packet).unwrap(); assert_eq!(response[0], SftpPacketType::SSH_FXP_VERSION as u8); } }