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.
This commit is contained in:
@@ -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)?;
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user