Implement SFTP Phase 11: File hash extensions
- Add md5-hash@openssh.com extension (MD5 hash calculation) - Add sha256-hash@openssh.com extension (SHA256 hash calculation) - Server-side hash computation using md5 and sha2 crates - Support offset and length parameters for partial file hashing - SFTP now 100% complete with 6 extensions (2 new hash extensions) - Total SFTP operations: 20 core + 6 extensions = 26 operations
This commit is contained in:
BIN
data/auth.sqlite
BIN
data/auth.sqlite
Binary file not shown.
1
file1.txt
Symbolic link
1
file1.txt
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/Users/accusys/markbase/data/sftp_test/symlink_to_file1.txt
|
||||||
@@ -852,6 +852,12 @@ impl SftpHandler {
|
|||||||
"posix-rename@openssh.com" => {
|
"posix-rename@openssh.com" => {
|
||||||
self.handle_posix_rename(&mut cursor, id)
|
self.handle_posix_rename(&mut cursor, id)
|
||||||
}
|
}
|
||||||
|
"md5-hash@openssh.com" => {
|
||||||
|
self.handle_md5_hash(&mut cursor, id)
|
||||||
|
}
|
||||||
|
"sha256-hash@openssh.com" => {
|
||||||
|
self.handle_sha256_hash(&mut cursor, id)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Unsupported SFTP extension: {}", extension_name);
|
warn!("Unsupported SFTP extension: {}", extension_name);
|
||||||
self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Unsupported extension: {}", extension_name))
|
self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("Unsupported extension: {}", extension_name))
|
||||||
@@ -989,6 +995,93 @@ impl SftpHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 处理md5-hash@openssh.com扩展(Phase 11:MD5哈希计算)
|
||||||
|
fn handle_md5_hash(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result<Vec<u8>> {
|
||||||
|
let path = read_sftp_string(cursor)?;
|
||||||
|
let offset = cursor.read_u64::<BigEndian>()?;
|
||||||
|
let length = cursor.read_u64::<BigEndian>()?;
|
||||||
|
|
||||||
|
info!("md5-hash: path={}, offset={}, length={}", path, offset, length);
|
||||||
|
|
||||||
|
let full_path = self.resolve_path(&path)?;
|
||||||
|
|
||||||
|
match File::open(&full_path) {
|
||||||
|
Ok(mut file) => {
|
||||||
|
file.seek(SeekFrom::Start(offset))?;
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; length as usize];
|
||||||
|
file.read_exact(&mut buffer)?;
|
||||||
|
|
||||||
|
// 计算MD5哈希
|
||||||
|
let hash = md5::compute(&buffer);
|
||||||
|
let hash_hex = format!("{:x}", hash);
|
||||||
|
|
||||||
|
// 构建响应
|
||||||
|
let mut response = Vec::new();
|
||||||
|
response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?;
|
||||||
|
response.write_u32::<BigEndian>(id)?;
|
||||||
|
|
||||||
|
// hash-algorithm (SSH string)
|
||||||
|
response.write_u32::<BigEndian>(4)?;
|
||||||
|
response.write_all("md5".as_bytes())?;
|
||||||
|
|
||||||
|
// hash-value (SSH string)
|
||||||
|
response.write_u32::<BigEndian>(hash_hex.len() as u32)?;
|
||||||
|
response.write_all(hash_hex.as_bytes())?;
|
||||||
|
|
||||||
|
self.wrap_sftp_packet(&response)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("MD5 hash error: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理sha256-hash@openssh.com扩展(Phase 11:SHA256哈希计算)
|
||||||
|
fn handle_sha256_hash(&self, cursor: &mut std::io::Cursor<&[u8]>, id: u32) -> Result<Vec<u8>> {
|
||||||
|
let path = read_sftp_string(cursor)?;
|
||||||
|
let offset = cursor.read_u64::<BigEndian>()?;
|
||||||
|
let length = cursor.read_u64::<BigEndian>()?;
|
||||||
|
|
||||||
|
info!("sha256-hash: path={}, offset={}, length={}", path, offset, length);
|
||||||
|
|
||||||
|
let full_path = self.resolve_path(&path)?;
|
||||||
|
|
||||||
|
match File::open(&full_path) {
|
||||||
|
Ok(mut file) => {
|
||||||
|
file.seek(SeekFrom::Start(offset))?;
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; length as usize];
|
||||||
|
file.read_exact(&mut buffer)?;
|
||||||
|
|
||||||
|
// 计算SHA256哈希(使用sha2 crate)
|
||||||
|
use sha2::{Sha256, Digest};
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&buffer);
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
let hash_hex = format!("{:x}", hash);
|
||||||
|
|
||||||
|
// 构建响应
|
||||||
|
let mut response = Vec::new();
|
||||||
|
response.write_u8(SftpPacketType::SSH_FXP_EXTENDED_REPLY as u8)?;
|
||||||
|
response.write_u32::<BigEndian>(id)?;
|
||||||
|
|
||||||
|
// hash-algorithm (SSH string)
|
||||||
|
response.write_u32::<BigEndian>(6)?;
|
||||||
|
response.write_all("sha256".as_bytes())?;
|
||||||
|
|
||||||
|
// hash-value (SSH string)
|
||||||
|
response.write_u32::<BigEndian>(hash_hex.len() as u32)?;
|
||||||
|
response.write_all(hash_hex.as_bytes())?;
|
||||||
|
|
||||||
|
self.wrap_sftp_packet(&response)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, &format!("SHA256 hash error: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 解析路径(安全性检查,参考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> {
|
||||||
info!("resolve_path: input={}, root_dir={:?}", path, self.root_dir);
|
info!("resolve_path: input={}, root_dir={:?}", path, self.root_dir);
|
||||||
|
|||||||
Reference in New Issue
Block a user