use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc;
use russh::server::{Config, Server};
use russh_keys::PrivateKey;
use super::handler::TuiHandler;
pub type AppFactory = Box<dyn Fn(Box<dyn crate::terminal::Backend>) + Send + Sync>;
pub struct SshServerConfig {
pub bind_addr: String,
pub keys: Vec<PrivateKey>,
pub max_connections: Option<usize>,
}
impl SshServerConfig {
pub fn new() -> Self {
Self {
bind_addr: "0.0.0.0:2222".to_string(),
keys: Vec::new(),
max_connections: None,
}
}
pub fn bind_addr(mut self, addr: impl Into<String>) -> Self {
self.bind_addr = addr.into();
self
}
pub fn add_key(mut self, key: PrivateKey) -> Self {
self.keys.push(key);
self
}
pub fn generate_key(mut self) -> Self {
use rand::rngs::OsRng;
if let Ok(key) = PrivateKey::random(&mut OsRng, ssh_key::Algorithm::Ed25519) {
self.keys.push(key);
}
self
}
pub fn load_or_generate_key(mut self, path: impl AsRef<Path>) -> Self {
let path = path.as_ref();
if path.exists() {
match load_key_from_file(path) {
Ok(key) => {
log::info!("Loaded host key from {}", path.display());
self.keys.push(key);
return self;
}
Err(e) => {
log::warn!("Failed to load host key from {}: {}", path.display(), e);
}
}
}
use rand::rngs::OsRng;
match PrivateKey::random(&mut OsRng, ssh_key::Algorithm::Ed25519) {
Ok(key) => {
if let Err(e) = save_key_to_file(&key, path) {
log::warn!("Failed to save host key to {}: {}", path.display(), e);
} else {
log::info!("Generated and saved new host key to {}", path.display());
}
self.keys.push(key);
}
Err(e) => {
log::error!("Failed to generate host key: {}", e);
}
}
self
}
pub fn max_connections(mut self, max: usize) -> Self {
self.max_connections = Some(max);
self
}
fn build_russh_config(&self) -> Config {
use rand::rngs::OsRng;
let mut config = Config::default();
if !self.keys.is_empty() {
config.keys = self.keys.clone();
} else {
if let Ok(key) = PrivateKey::random(&mut OsRng, ssh_key::Algorithm::Ed25519) {
config.keys = vec![key];
}
}
config
}
}
impl Default for SshServerConfig {
fn default() -> Self {
Self::new()
}
}
pub struct SshServer<F>
where
F: Fn() -> Box<dyn FnOnce(Box<dyn crate::terminal::Backend>) + Send> + Send + Sync + 'static,
{
config: SshServerConfig,
app_factory: Arc<F>,
}
impl<F> SshServer<F>
where
F: Fn() -> Box<dyn FnOnce(Box<dyn crate::terminal::Backend>) + Send> + Send + Sync + 'static,
{
pub fn new(config: SshServerConfig, factory: F) -> Self {
Self {
config,
app_factory: Arc::new(factory),
}
}
pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
let russh_config = Arc::new(self.config.build_russh_config());
let addr = &self.config.bind_addr;
log::info!("Starting SSH server on {}", addr);
let mut server = TuiServer {
app_factory: self.app_factory,
};
server.run_on_address(russh_config, addr).await?;
Ok(())
}
}
struct TuiServer<F>
where
F: Fn() -> Box<dyn FnOnce(Box<dyn crate::terminal::Backend>) + Send> + Send + Sync + 'static,
{
app_factory: Arc<F>,
}
impl<F> Server for TuiServer<F>
where
F: Fn() -> Box<dyn FnOnce(Box<dyn crate::terminal::Backend>) + Send> + Send + Sync + 'static,
{
type Handler = TuiHandler<Box<dyn FnOnce(Box<dyn crate::terminal::Backend>) + Send>>;
fn new_client(&mut self, peer_addr: Option<SocketAddr>) -> Self::Handler {
log::info!("New SSH connection from {:?}", peer_addr);
let factory = (self.app_factory)();
TuiHandler::new(factory, peer_addr)
}
}
pub async fn run_ssh_server<F>(
addr: &str,
app_factory: F,
) -> Result<(), Box<dyn std::error::Error>>
where
F: Fn() -> Box<dyn FnOnce(Box<dyn crate::terminal::Backend>) + Send> + Send + Sync + 'static,
{
let config = SshServerConfig::new()
.bind_addr(addr)
.generate_key();
let server = SshServer::new(config, app_factory);
server.run().await
}
fn load_key_from_file(path: &Path) -> Result<PrivateKey, Box<dyn std::error::Error>> {
let pem = std::fs::read_to_string(path)?;
let key = PrivateKey::from_openssh(&pem)?;
Ok(key)
}
fn save_key_to_file(key: &PrivateKey, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
use std::io::Write;
let pem = key.to_openssh(ssh_key::LineEnding::LF)?;
let mut file = std::fs::File::create(path)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let permissions = std::fs::Permissions::from_mode(0o600);
file.set_permissions(permissions)?;
}
file.write_all(pem.as_bytes())?;
Ok(())
}