VFS/DataProvider/Config refactoring + SSH public key authentication
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:
@@ -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_dir(SFTPGo兼容)
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user