Fix SFTP path resolution and EOF handling
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

- Fix resolve_path() to handle non-existent files (for upload)
- Add SSH_MSG_CHANNEL_EOF handling in service loop
- Canonicalize root_dir in SftpHandler constructor
- SFTP now fully working: pwd, ls, cd, get, put operations verified
This commit is contained in:
Warren
2026-06-15 13:14:16 +08:00
parent 4122ceac94
commit 91d29e40ea
3 changed files with 30 additions and 9 deletions

Binary file not shown.

View File

@@ -345,6 +345,10 @@ fn handle_ssh_service_loop(
} }
break; break;
} }
Some(&pt) if pt == PacketType::SSH_MSG_CHANNEL_EOF as u8 => {
info!("Received SSH_MSG_CHANNEL_EOF");
// EOF means client won't send more data, just acknowledge and continue
}
Some(&pt) if pt == PacketType::SSH_MSG_DISCONNECT as u8 => { Some(&pt) if pt == PacketType::SSH_MSG_DISCONNECT as u8 => {
info!("Received SSH_MSG_DISCONNECT"); info!("Received SSH_MSG_DISCONNECT");
break; break;

View File

@@ -236,8 +236,9 @@ pub struct SftpHandler {
impl SftpHandler { impl SftpHandler {
pub fn new(root_dir: PathBuf) -> Self { pub fn new(root_dir: PathBuf) -> Self {
let canonical_root = root_dir.canonicalize().unwrap_or(root_dir);
Self { Self {
root_dir, root_dir: canonical_root,
next_handle_id: 0, next_handle_id: 0,
handles: std::collections::HashMap::new(), handles: std::collections::HashMap::new(),
} }
@@ -728,20 +729,36 @@ impl SftpHandler {
/// 解析路径安全性检查参考OpenSSH sftp-server.c: path_resolve()) /// 解析路径安全性检查参考OpenSSH sftp-server.c: path_resolve())
fn resolve_path(&self, path: &str) -> Result<PathBuf> { fn resolve_path(&self, path: &str) -> Result<PathBuf> {
let full_path = if path.starts_with('/') { info!("resolve_path: input={}, root_dir={:?}", path, self.root_dir);
self.root_dir.join(path.trim_start_matches('/'))
let full_path = if path.is_empty() || path == "." {
self.root_dir.clone()
} else if path.starts_with('/') {
PathBuf::from(path)
} else { } else {
self.root_dir.join(path) self.root_dir.join(path)
}; };
let canonical_path = full_path.canonicalize() info!("resolve_path: full_path={:?}", full_path);
.map_err(|e| anyhow!("Path resolution error: {}", e))?;
if !canonical_path.starts_with(&self.root_dir) { if full_path.exists() {
return Err(anyhow!("Path traversal attempt detected")); let canonical_path = full_path.canonicalize()
.map_err(|e| anyhow!("Path resolution error for {:?}: {}", full_path, e))?;
info!("resolve_path: canonical_path={:?}", canonical_path);
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)
} }
Ok(canonical_path)
} }
/// 构建SSH_FXP_VERSION响应参考OpenSSH sftp-server.c /// 构建SSH_FXP_VERSION响应参考OpenSSH sftp-server.c