From 487b4450f8029584f0b0cfea013c30e151feab2d Mon Sep 17 00:00:00 2001 From: Warren Date: Sat, 20 Jun 2026 23:33:19 +0800 Subject: [PATCH] Implement SSH Banner/MOTD support - Add banner and banner_file fields to SshSecurityConfig - Enterprise default: 'MarkBaseSSH - Secure File Transfer Server' - Support banner_file for reading from /etc/motd - Send SSH_MSG_USERAUTH_BANNER before USERAUTH_SUCCESS - Pass security_config to perform_ssh_auth function All 229 tests pass. --- markbase-core/src/ssh_server/server.rs | 26 ++++++++++++++++++- .../src/ssh_server/ssh_security_config.rs | 20 ++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/markbase-core/src/ssh_server/server.rs b/markbase-core/src/ssh_server/server.rs index 02c96e4..5e1c314 100644 --- a/markbase-core/src/ssh_server/server.rs +++ b/markbase-core/src/ssh_server/server.rs @@ -179,7 +179,7 @@ fn handle_connection_complete( ) }; let mut auth_handler = AuthHandler::new(provider); - let auth_user = perform_ssh_auth(&mut stream, &mut auth_handler, &mut encryption_ctx)?; + let auth_user = perform_ssh_auth(&mut stream, &mut auth_handler, &mut encryption_ctx, security_config.clone())?; info!("SSH authentication succeeded: user={}", auth_user.username); let upload_hook = if upload_hook_config.enabled { @@ -335,6 +335,7 @@ fn perform_ssh_auth( stream: &mut TcpStream, auth_handler: &mut AuthHandler, encryption_ctx: &mut EncryptionContext, + security_config: Arc>, ) -> Result { info!("Starting SSH authentication"); info!( @@ -395,6 +396,29 @@ fn perform_ssh_auth( match auth_handler.handle_userauth_request(&auth_request, &session_id)? { AuthResult::Success => { + // Send banner if configured (SSH_MSG_USERAUTH_BANNER) + let security = security_config.lock().unwrap(); + let banner = if let Some(file) = &security.banner_file { + std::fs::read_to_string(file).ok() + } else { + security.banner.clone() + }; + drop(security); + + if let Some(banner_text) = banner { + let mut banner_payload = Vec::new(); + banner_payload.write_u8(PacketType::SSH_MSG_USERAUTH_BANNER as u8)?; + banner_payload.write_u32::(banner_text.len() as u32)?; + banner_payload.write_all(banner_text.as_bytes())?; + // Language tag (empty SSH string) + banner_payload.write_u32::(0)?; + + let encrypted_banner = + EncryptedPacket::new(&banner_payload, encryption_ctx, true)?; + encrypted_banner.write(stream)?; + info!("Sent SSH_MSG_USERAUTH_BANNER"); + } + let success_payload = vec![PacketType::SSH_MSG_USERAUTH_SUCCESS as u8]; let encrypted_success = EncryptedPacket::new(&success_payload, encryption_ctx, true)?; diff --git a/markbase-core/src/ssh_server/ssh_security_config.rs b/markbase-core/src/ssh_server/ssh_security_config.rs index a697ddc..6a7df0e 100644 --- a/markbase-core/src/ssh_server/ssh_security_config.rs +++ b/markbase-core/src/ssh_server/ssh_security_config.rs @@ -41,6 +41,14 @@ pub struct SshSecurityConfig { /// 最大心跳失败次数,超过则断开连接 pub keep_alive_max_count: u32, + /// Banner/MOTD内容 + /// 登录后显示的欢迎信息(参考OpenSSH Banner) + pub banner: Option, + + /// Banner文件路径 + /// 从文件读取banner内容(如/etc/motd) + pub banner_file: Option, + /// 活动会话数(运行时状态) pub active_sessions: u32, } @@ -57,6 +65,8 @@ impl SshSecurityConfig { connect_timeout: 30, // 30秒超时 keep_alive_interval: 15, // 15秒心跳间隔 keep_alive_max_count: 3, // 3次失败后断开 + banner: Some("MarkBaseSSH - Secure File Transfer Server\n".to_string()), + banner_file: None, // 不使用文件 active_sessions: 0, // 运行时状态 } } @@ -71,6 +81,8 @@ impl SshSecurityConfig { connect_timeout: 60, // 开发:更长超时 keep_alive_interval: 30, // 开发:更宽松心跳 keep_alive_max_count: 5, // 开发:更多失败容忍 + banner: None, // 开发:不显示banner + banner_file: None, active_sessions: 0, } } @@ -126,6 +138,14 @@ impl SshSecurityConfig { .and_then(|v| v.as_u64()) .map(|v| v as u32) .unwrap_or(3), + banner: security + .get("banner") + .and_then(|v| v.as_str()) + .map(String::from), + banner_file: security + .get("banner_file") + .and_then(|v| v.as_str()) + .map(String::from), active_sessions: 0, }) }