VFS/DataProvider/Config refactoring + SSH public key authentication
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

Phase 1-6 of refactoring plan:
- VFS abstraction (VfsBackend trait + LocalFs + OpenFlags builder)
- DataProvider trait (SqliteProvider + PgProvider, SFTPGo-compatible)
- Config refactoring (AppConfig unified sections, env overrides)
- SSH handlers (sftp/scp/rsync) migrated to VFS + DataProvider
- SSH public key authentication (Ed25519 signature verification)
- SSH stderr → CHANNEL_EXTENDED_DATA support
- Web auth uses DataProvider instead of direct SQL
- User home directory from provider (per-user isolation)
- PostgreSQL auth provider for SFTPGo compatibility
This commit is contained in:
Warren
2026-06-18 23:35:18 +08:00
parent 83fb0de78a
commit f90e4f496c
25 changed files with 2039 additions and 612 deletions

View File

@@ -25,6 +25,8 @@ pub struct ChannelManager {
next_channel_id: u32,
/// ⭐⭐⭐⭐⭐ Phase 15.1: 待发送packet队列用于同时发送WINDOW_ADJUST和SFTP响应
pub pending_packets: VecDeque<SshPacket>,
/// 用户home目录SFTP/SCP/rsync根目录SFTPGo兼容
pub home_dir: PathBuf,
}
/// Phase 14: 交互式Exec进程管理参考OpenSSH session.c: do_exec_no_pty
@@ -40,11 +42,12 @@ pub struct ExecProcess {
}
impl ChannelManager {
pub fn new() -> Self {
pub fn new(home_dir: PathBuf) -> Self {
Self {
channels: HashMap::new(),
next_channel_id: 0,
pending_packets: VecDeque::new(),
home_dir,
}
}
@@ -371,9 +374,12 @@ impl ChannelManager {
info!("⭐⭐⭐⭐⭐ [{}_EXEC_START] Starting interactive process: {}", process_type, command);
// 启动子进程相当于OpenSSH fork
// ⭐⭐⭐⭐⭐ Phase 17: 设置工作目录为用户home_dirSFTPGo兼容
let home_dir = self.home_dir.clone();
let mut child = Command::new("sh")
.arg("-c")
.arg(command)
.current_dir(&home_dir)
.stdin(Stdio::piped()) // ← 创建stdin管道相当于pipe(pin)
.stdout(Stdio::piped()) // ← 创建stdout管道相当于pipe(pout)
.stderr(Stdio::piped()) // ← 创建stderr管道相当于pipe(perr)
@@ -446,8 +452,8 @@ impl ChannelManager {
if subsystem == "sftp" {
info!("SFTP subsystem requested");
// Phase 7: 初始化SFTP handler
let root_dir = PathBuf::from("/Users/accusys/markbase"); // 默认root目录
// Phase 7: 初始化SFTP handler使用用户home目录SFTPGo兼容
let root_dir = self.home_dir.clone();
// ⭐⭐⭐⭐⭐ Phase 4: 获取 client maxpack 限制(从 Channel 中获取)
let maxpacket = if let Some(ch) = self.channels.get(&channel) {
@@ -456,7 +462,8 @@ impl ChannelManager {
32768 // OpenSSH 默认值32KB
};
let sftp_handler = SftpHandler::new(root_dir, maxpacket); // ⭐⭐⭐⭐⭐ Phase 4: 传入 maxpack
let vfs = Box::new(crate::vfs::local_fs::LocalFs::new());
let sftp_handler = SftpHandler::new(root_dir, vfs, maxpacket); // ⭐⭐⭐⭐⭐ Phase 4: 传入 maxpack
// 存储到channel
if let Some(ch) = self.channels.get_mut(&channel) {
@@ -952,6 +959,22 @@ impl ChannelManager {
false
}
/// Phase 17: 关闭所有子进程stdin收到CHANNEL_EOF时调用
/// SCP upload需要scp -t 等待EOF on stdin才知道数据传输完毕
pub fn close_child_stdin(&mut self) {
let channel_ids: Vec<u32> = self.channels.keys().copied().collect();
for id in channel_ids {
if let Some(channel) = self.channels.get_mut(&id) {
if let Some(exec) = &mut channel.exec_process {
if let Some(stdin) = exec.stdin.take() {
drop(stdin);
info!("⭐⭐⭐⭐⭐ [CHANNEL_EOF] Closed child stdin (channel {})", id);
}
}
}
}
}
/// 获取channel输出Phase 6新增
pub fn get_channel_output(&mut self, channel_id: u32) -> Option<Vec<u8>> {
if let Some(channel) = self.channels.get_mut(&channel_id) {
@@ -1283,6 +1306,7 @@ impl ChannelManager {
// 4. 检查stdout/stderr fd是否有数据
let mut packets_data: Vec<(u32, Vec<u8>)> = Vec::new();
let mut stderr_packets: Vec<(u32, Vec<u8>)> = Vec::new(); // Phase 17: stderr → CHANNEL_EXTENDED_DATA
for (channel_id, (stdout_idx, stderr_idx)) in channel_fds_map {
if let Some(channel) = self.channels.get_mut(&channel_id) {
@@ -1325,7 +1349,8 @@ impl ChannelManager {
Ok(n) if n > 0 => {
info!("⭐⭐⭐⭐⭐ [AFTER stderr.read] Read {} bytes from stderr (channel {})", n, channel_id);
info!("⭐⭐⭐⭐⭐ stderr content: {:?}", &buffer[..std::cmp::min(50, n)]);
packets_data.push((channel_id, buffer[..n].to_vec()));
// ⭐⭐⭐⭐⭐ Phase 17: stderr → SSH_MSG_CHANNEL_EXTENDED_DATA (data_type=1)
stderr_packets.push((channel_id, buffer[..n].to_vec()));
}
Ok(0) => {
info!("stderr EOF (channel {}), closing stderr pipe", channel_id);
@@ -1351,12 +1376,17 @@ impl ChannelManager {
}
// 构建packets
if !packets_data.is_empty() {
if !packets_data.is_empty() || !stderr_packets.is_empty() {
let mut packets = Vec::new();
for (channel_id, data) in packets_data {
let packet = self.build_channel_data(channel_id, &data)?;
packets.push(packet);
}
// Phase 17: stderr → SSH_MSG_CHANNEL_EXTENDED_DATA (data_type=1)
for (channel_id, data) in stderr_packets {
let packet = self.build_channel_extended_data(channel_id, 1, &data)?;
packets.push(packet);
}
info!("⭐⭐⭐⭐⭐ Returning {} packets (stdout/stderr data)", packets.len());
return Ok((Some(packets), client_has_data, child_exited));
}
@@ -1689,13 +1719,13 @@ mod tests {
#[test]
fn test_channel_manager_creation() {
let manager = ChannelManager::new();
let manager = ChannelManager::new(PathBuf::from("/tmp"));
assert_eq!(manager.next_channel_id, 0);
}
#[test]
fn test_channel_open_confirmation() {
let manager = ChannelManager::new();
let manager = ChannelManager::new(PathBuf::from("/tmp"));
let packet = manager.build_channel_open_confirmation(0, 100, 2097152, 32768).unwrap();
assert_eq!(packet.payload[0], PacketType::SSH_MSG_CHANNEL_OPEN_CONFIRMATION as u8);
@@ -1703,7 +1733,7 @@ mod tests {
#[test]
fn test_channel_success() {
let manager = ChannelManager::new();
let manager = ChannelManager::new(PathBuf::from("/tmp"));
let packet = manager.build_channel_success(0).unwrap();
assert_eq!(packet.payload[0], PacketType::SSH_MSG_CHANNEL_SUCCESS as u8);