Implement SSH Banner/MOTD support
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

- 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.
This commit is contained in:
Warren
2026-06-20 23:33:19 +08:00
parent 783356852e
commit 487b4450f8
2 changed files with 45 additions and 1 deletions

View File

@@ -179,7 +179,7 @@ fn handle_connection_complete(
) )
}; };
let mut auth_handler = AuthHandler::new(provider); 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); info!("SSH authentication succeeded: user={}", auth_user.username);
let upload_hook = if upload_hook_config.enabled { let upload_hook = if upload_hook_config.enabled {
@@ -335,6 +335,7 @@ fn perform_ssh_auth(
stream: &mut TcpStream, stream: &mut TcpStream,
auth_handler: &mut AuthHandler, auth_handler: &mut AuthHandler,
encryption_ctx: &mut EncryptionContext, encryption_ctx: &mut EncryptionContext,
security_config: Arc<Mutex<SshSecurityConfig>>,
) -> Result<AuthUser> { ) -> Result<AuthUser> {
info!("Starting SSH authentication"); info!("Starting SSH authentication");
info!( info!(
@@ -395,6 +396,29 @@ fn perform_ssh_auth(
match auth_handler.handle_userauth_request(&auth_request, &session_id)? { match auth_handler.handle_userauth_request(&auth_request, &session_id)? {
AuthResult::Success => { 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::<BigEndian>(banner_text.len() as u32)?;
banner_payload.write_all(banner_text.as_bytes())?;
// Language tag (empty SSH string)
banner_payload.write_u32::<BigEndian>(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 success_payload = vec![PacketType::SSH_MSG_USERAUTH_SUCCESS as u8];
let encrypted_success = let encrypted_success =
EncryptedPacket::new(&success_payload, encryption_ctx, true)?; EncryptedPacket::new(&success_payload, encryption_ctx, true)?;

View File

@@ -41,6 +41,14 @@ pub struct SshSecurityConfig {
/// 最大心跳失败次数,超过则断开连接 /// 最大心跳失败次数,超过则断开连接
pub keep_alive_max_count: u32, pub keep_alive_max_count: u32,
/// Banner/MOTD内容
/// 登录后显示的欢迎信息参考OpenSSH Banner
pub banner: Option<String>,
/// Banner文件路径
/// 从文件读取banner内容如/etc/motd
pub banner_file: Option<String>,
/// 活动会话数(运行时状态) /// 活动会话数(运行时状态)
pub active_sessions: u32, pub active_sessions: u32,
} }
@@ -57,6 +65,8 @@ impl SshSecurityConfig {
connect_timeout: 30, // 30秒超时 connect_timeout: 30, // 30秒超时
keep_alive_interval: 15, // 15秒心跳间隔 keep_alive_interval: 15, // 15秒心跳间隔
keep_alive_max_count: 3, // 3次失败后断开 keep_alive_max_count: 3, // 3次失败后断开
banner: Some("MarkBaseSSH - Secure File Transfer Server\n".to_string()),
banner_file: None, // 不使用文件
active_sessions: 0, // 运行时状态 active_sessions: 0, // 运行时状态
} }
} }
@@ -71,6 +81,8 @@ impl SshSecurityConfig {
connect_timeout: 60, // 开发:更长超时 connect_timeout: 60, // 开发:更长超时
keep_alive_interval: 30, // 开发:更宽松心跳 keep_alive_interval: 30, // 开发:更宽松心跳
keep_alive_max_count: 5, // 开发:更多失败容忍 keep_alive_max_count: 5, // 开发:更多失败容忍
banner: None, // 开发不显示banner
banner_file: None,
active_sessions: 0, active_sessions: 0,
} }
} }
@@ -126,6 +138,14 @@ impl SshSecurityConfig {
.and_then(|v| v.as_u64()) .and_then(|v| v.as_u64())
.map(|v| v as u32) .map(|v| v as u32)
.unwrap_or(3), .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, active_sessions: 0,
}) })
} }