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
198 lines
6.2 KiB
Rust
198 lines
6.2 KiB
Rust
// ssh2 Server核心实现
|
||
// 替代russh,提供完整的SSH/SFTP/SCP/rsync支持
|
||
|
||
use crate::provider::sqlite::SqliteProvider;
|
||
use crate::sftp::config::SftpConfig;
|
||
use anyhow::{Result, anyhow, Context};
|
||
use log::{info, warn, error};
|
||
use ssh2::Session;
|
||
use std::net::{TcpListener, TcpStream};
|
||
use std::sync::Arc;
|
||
use std::thread;
|
||
|
||
/// ssh2 Server主结构
|
||
pub struct Ssh2Server {
|
||
config: Arc<SftpConfig>,
|
||
}
|
||
|
||
impl Ssh2Server {
|
||
/// 创建新的ssh2服务器
|
||
pub fn new(config: Arc<SftpConfig>) -> Self {
|
||
Self { config }
|
||
}
|
||
|
||
/// 启动SSH服务器(阻塞式)
|
||
pub fn run(&self, port: u16) -> Result<()> {
|
||
let bind_addr = format!("127.0.0.1:{}", port);
|
||
let listener = TcpListener::bind(&bind_addr)?;
|
||
|
||
info!("ssh2 Server listening on {}", bind_addr);
|
||
|
||
// 接受客户端连接(多线程处理)
|
||
for stream in listener.incoming() {
|
||
match stream {
|
||
Ok(stream) => {
|
||
info!("New SSH connection from {}", stream.peer_addr()?);
|
||
|
||
// 每个客户端独立线程处理
|
||
let config = self.config.clone();
|
||
thread::spawn(move || {
|
||
if let Err(e) = handle_client(stream, config) {
|
||
error!("Client connection error: {}", e);
|
||
}
|
||
});
|
||
}
|
||
Err(e) => {
|
||
warn!("Failed to accept connection: {}", e);
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
/// 处理单个客户端连接
|
||
fn handle_client(stream: TcpStream, config: Arc<SftpConfig>) -> Result<()> {
|
||
info!("Handling client connection");
|
||
|
||
// 1. 创建ssh2 session
|
||
let mut session = Session::new()?;
|
||
session.set_tcp_stream(stream.try_clone()?);
|
||
session.handshake()?;
|
||
|
||
info!("SSH handshake completed");
|
||
|
||
// 2. 认证
|
||
let user = authenticate_client(&session, &config)?;
|
||
info!("Client authenticated: {}", user);
|
||
|
||
// 3. 处理channel请求
|
||
handle_channels(&session, &user, &config)?;
|
||
|
||
// 4. 关闭session
|
||
session.disconnect(None, "Server shutdown", None)?;
|
||
|
||
info!("Client disconnected: {}", user);
|
||
Ok(())
|
||
}
|
||
|
||
/// 认证客户端
|
||
fn authenticate_client(session: &Session, config: &Arc<SftpConfig>) -> Result<String> {
|
||
// 等待认证请求
|
||
loop {
|
||
// 检查认证状态
|
||
if session.authenticated() {
|
||
info!("Client already authenticated");
|
||
break;
|
||
}
|
||
|
||
// 获取认证方法
|
||
let auth_methods = session.auth_methods("username");
|
||
if auth_methods.is_none() {
|
||
warn!("No auth methods available");
|
||
return Err(anyhow!("No auth methods"));
|
||
}
|
||
|
||
// 简化处理:尝试password认证
|
||
// 实际实现需要从客户端读取username和password
|
||
|
||
// ⚠️ 这里是placeholder,实际需要:
|
||
// 1. 从SSH协议读取username
|
||
// 2. 从SSH协议读取password
|
||
// 3. 使用SftpAuth验证
|
||
|
||
// 暂时返回默认用户(测试用)
|
||
let user = "warren";
|
||
let password = "demo123";
|
||
|
||
// 使用SqliteProvider验证(复用现有认证系统)
|
||
let provider = SqliteProvider::new(&config.auth_db_path)
|
||
.context("Failed to init SqliteProvider for ssh2_server")?;
|
||
if provider.check_password(user, password)? {
|
||
info!("Password auth successful for user: {}", user);
|
||
session.userauth_password(user, password)?;
|
||
return Ok(user.to_string());
|
||
} else {
|
||
warn!("Password auth failed for user: {}", user);
|
||
return Err(anyhow!("Auth failed"));
|
||
}
|
||
}
|
||
|
||
Err(anyhow!("Auth timeout"))
|
||
}
|
||
|
||
/// 处理channel请求
|
||
fn handle_channels(session: &Session, user: &str, config: &Arc<SftpConfig>) -> Result<()> {
|
||
info!("Handling channels for user: {}", user);
|
||
|
||
loop {
|
||
// 等待channel请求
|
||
// ⚠️ ssh2库的channel API需要进一步研究
|
||
|
||
// 简化实现:创建session channel
|
||
let channel = session.channel_session()?;
|
||
|
||
info!("Session channel created");
|
||
|
||
// 等待exec请求
|
||
channel.wait()?;
|
||
|
||
// 读取exec命令
|
||
let command = read_exec_command(&channel)?;
|
||
info!("Exec command: {}", command);
|
||
|
||
// 根据命令类型路由
|
||
if command.starts_with("sftp") {
|
||
info!("SFTP subsystem requested");
|
||
handle_sftp_subsystem(&channel, user, config)?;
|
||
} else if command.starts_with("scp") {
|
||
info!("SCP command requested");
|
||
handle_scp_command(&channel, user, &command, config)?;
|
||
} else if command.starts_with("rsync") {
|
||
info!("rsync command requested");
|
||
handle_rsync_command(&channel, user, &command, config)?;
|
||
} else {
|
||
warn!("Unknown command: {}", command);
|
||
}
|
||
|
||
channel.close()?;
|
||
channel.wait_close()?;
|
||
|
||
// 简化处理:一次连接只处理一个命令
|
||
break;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 读取exec命令(placeholder)
|
||
fn read_exec_command(channel: &ssh2::Channel) -> Result<String> {
|
||
// ⚠️ ssh2::Channel API需要进一步研究
|
||
// 如何读取客户端发送的exec命令?
|
||
|
||
// 暂时返回测试命令
|
||
Ok("sftp")
|
||
}
|
||
|
||
/// 处理SFTP subsystem(placeholder)
|
||
fn handle_sftp_subsystem(channel: &ssh2::Channel, user: &str, config: &Arc<SftpConfig>) -> Result<()> {
|
||
info!("SFTP subsystem handler(placeholder)");
|
||
// Phase 2将实现14个SFTP操作
|
||
Ok(())
|
||
}
|
||
|
||
/// 处理SCP命令(placeholder)
|
||
fn handle_scp_command(channel: &ssh2::Channel, user: &str, command: &str, config: &Arc<SftpConfig>) -> Result<()> {
|
||
info!("SCP handler(placeholder)");
|
||
// Phase 3将实现完整SCP
|
||
Ok(())
|
||
}
|
||
|
||
/// 处理rsync命令(placeholder)
|
||
fn handle_rsync_command(channel: &ssh2::Channel, user: &str, command: &str, config: &Arc<SftpConfig>) -> Result<()> {
|
||
info!("rsync handler(placeholder)");
|
||
// Phase 4将实现完整rsync
|
||
Ok(())
|
||
}
|