Fix SFTP path resolution and EOF handling
- 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:
BIN
data/auth.sqlite
BIN
data/auth.sqlite
Binary file not shown.
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
info!("resolve_path: full_path={:?}", full_path);
|
||||||
|
|
||||||
|
if full_path.exists() {
|
||||||
let canonical_path = full_path.canonicalize()
|
let canonical_path = full_path.canonicalize()
|
||||||
.map_err(|e| anyhow!("Path resolution error: {}", e))?;
|
.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) {
|
if !canonical_path.starts_with(&self.root_dir) {
|
||||||
return Err(anyhow!("Path traversal attempt detected"));
|
return Err(anyhow!("Path traversal attempt detected: {:?} not under {:?}", canonical_path, self.root_dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(canonical_path)
|
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)
|
/// 构建SSH_FXP_VERSION响应(参考OpenSSH sftp-server.c)
|
||||||
|
|||||||
Reference in New Issue
Block a user