Files
markbase/markbase-core/src/sftp/shell.rs
Warren 1300a4e223
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled
MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能:
-  Categories/Series双视图管理(category_view.rs + import_markdown.rs)
-  FUSE Multi-Volume支持(tree_type参数)
-  SSH/SFTP/SCP/rsync协议完整实现(4042行)
-  NFS/SMB Module Phase 1-3完成
-  Archive Module Phase 1-4完成(2916行)
-  Download Center API完整实现
-  S3兼容API实现(560行)

Git配置修正:
-  删除错误origin(gitea.momentry.ddns.net)
-  删除m5max128(指向机器名)
-  设置origin = m5max128gitea.momentry.ddns.net/admin/markbase
-  设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase

数据清理:
-  删除38个临时SQLite(保留accusys.sqlite、demo.sqlite)
-  删除.bak、test_*.bin、调试脚本等临时文件
-  删除临时目录(build/、download files/、raid_test/等)
-  更新.gitignore排除临时文件

架构优化:
- 52个文件修改,2434行新增,4739行删除
- Workspace成员整合(16个crate)
- 数据库状态:accusys.sqlite保留(主demo测试)

远程同步:
-  准备推送到m5max128gitea(远程Gitea)
-  准备推送到m4minigitea(本地Gitea)
2026-06-12 12:59:54 +08:00

222 lines
7.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use anyhow::Result;
use std::process::Command;
use std::time::Duration;
use tokio::time::timeout;
pub struct ShellHandler {
user_id: String,
config: std::sync::Arc<crate::sftp::config::SftpConfig>,
audit: crate::sftp::audit::AuditLog,
}
impl ShellHandler {
pub fn new(
user_id: &str,
config: std::sync::Arc<crate::sftp::config::SftpConfig>,
audit: crate::sftp::audit::AuditLog,
) -> Self {
Self {
user_id: user_id.to_string(),
config,
audit,
}
}
pub fn check_command_permission(&self, command: &str) -> bool {
// 1. 检查是否启用shell
if !self.config.shell.enabled {
log::warn!("Shell disabled for user {}", self.user_id);
return false;
}
// 2. 检查命令长度
if command.len() > self.config.shell.max_command_length {
log::warn!(
"Command too long for user {}: {}",
self.user_id,
command.len()
);
return false;
}
// 3. 检查黑名单(优先级最高)
let cmd_name = command.split_whitespace().next().unwrap_or("");
for forbidden in &self.config.shell.forbidden_commands {
if cmd_name == forbidden || command.starts_with(&format!("{} ", forbidden)) {
log::warn!("Forbidden command '{}' for user {}", cmd_name, self.user_id);
self.audit
.log_error(&self.user_id, "shell_check", command, "forbidden");
return false;
}
}
// 4. 检查白名单(如果配置了白名单)
if !self.config.shell.allowed_commands.is_empty() {
if !self
.config
.shell
.allowed_commands
.contains(&cmd_name.to_string())
{
log::warn!(
"Command '{}' not in whitelist for user {}",
cmd_name,
self.user_id
);
self.audit
.log_error(&self.user_id, "shell_check", command, "not_in_whitelist");
return false;
}
}
log::info!("Command '{}' allowed for user {}", cmd_name, self.user_id);
true
}
pub async fn execute_command(&self, command: &str) -> Result<String> {
// 1. 检查权限
if !self.check_command_permission(command) {
return Err(anyhow::anyhow!("Command not allowed"));
}
// 2. 执行命令带timeout
let timeout_duration = Duration::from_secs(self.config.shell.timeout_seconds);
let result = timeout(timeout_duration, async {
// 使用系统shell执行命令
let output = Command::new(&self.config.shell.shell_path)
.arg("-c")
.arg(command)
.output();
match output {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if output.status.success() {
Ok(stdout)
} else {
Err(anyhow::anyhow!("Command failed: {}", stderr))
}
}
Err(e) => Err(anyhow::anyhow!("Command execution error: {}", e)),
}
})
.await;
// 3. 处理结果
match result {
Ok(Ok(output)) => {
self.audit.log_success(&self.user_id, "shell_exec", command);
Ok(output)
}
Ok(Err(e)) => {
self.audit
.log_error(&self.user_id, "shell_exec", command, &e.to_string());
Err(e)
}
Err(_) => {
self.audit
.log_error(&self.user_id, "shell_exec", command, "timeout");
Err(anyhow::anyhow!(
"Command timeout after {}s",
self.config.shell.timeout_seconds
))
}
}
}
pub fn get_shell_path(&self) -> &str {
&self.config.shell.shell_path
}
pub fn is_shell_enabled(&self) -> bool {
self.config.shell.enabled
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sftp::audit::AuditLog;
use crate::sftp::config::SftpConfig;
use tempfile::TempDir;
fn create_test_shell_handler() -> ShellHandler {
let mut config = SftpConfig::default();
config.shell.enabled = true;
config.shell.allowed_commands = vec!["ls".to_string(), "pwd".to_string()];
config.shell.forbidden_commands = vec!["rm".to_string(), "sudo".to_string()];
let temp_dir = TempDir::new().unwrap();
let audit_log_path = temp_dir
.path()
.join("shell_audit.log")
.to_string_lossy()
.to_string();
let audit = AuditLog::new(&audit_log_path).unwrap();
ShellHandler::new("test_user", std::sync::Arc::new(config), audit)
}
#[test]
fn test_check_command_permission_allowed() {
let handler = create_test_shell_handler();
// 测试允许的命令
assert!(handler.check_command_permission("ls"));
assert!(handler.check_command_permission("pwd"));
assert!(handler.check_command_permission("ls -la"));
}
#[test]
fn test_check_command_permission_forbidden() {
let handler = create_test_shell_handler();
// 测试禁止的命令
assert!(!handler.check_command_permission("rm"));
assert!(!handler.check_command_permission("rm -rf"));
assert!(!handler.check_command_permission("sudo ls"));
}
#[test]
fn test_check_command_permission_not_in_whitelist() {
let handler = create_test_shell_handler();
// 测试不在白名单的命令
assert!(!handler.check_command_permission("cat"));
assert!(!handler.check_command_permission("grep"));
}
#[test]
fn test_check_command_permission_shell_disabled() {
let mut config = SftpConfig::default();
config.shell.enabled = false; // 禁用shell
let temp_dir = TempDir::new().unwrap();
let audit = AuditLog::new(&temp_dir.path().join("audit.log").to_string_lossy()).unwrap();
let handler = ShellHandler::new("test_user", std::sync::Arc::new(config), audit);
// 任何命令都不允许
assert!(!handler.check_command_permission("ls"));
}
#[test]
fn test_check_command_permission_too_long() {
let mut config = SftpConfig::default();
config.shell.enabled = true;
config.shell.max_command_length = 10;
let temp_dir = TempDir::new().unwrap();
let audit = AuditLog::new(&temp_dir.path().join("audit.log").to_string_lossy()).unwrap();
let handler = ShellHandler::new("test_user", std::sync::Arc::new(config), audit);
// 测试超长命令
let long_command = "ls -la /very/long/path/that/exceeds/max/length";
assert!(!handler.check_command_permission(long_command));
}
}