use log::{info, warn}; use std::fs; use std::io::Write; use std::os::unix::fs::PermissionsExt; use std::process::Stdio; use tokio::net; use tokio::process::Command; use super::{retry_or_ignore, Config}; pub async fn start(cfg: &Config) { retry_or_ignore(async || { info!("ssh: writing authorized keys"); let ssh_dir = "/root/.ssh"; let authorized_keys = format!("{ssh_dir}/authorized_keys"); fs::create_dir_all(ssh_dir)?; fs::set_permissions(ssh_dir, fs::Permissions::from_mode(0o700))?; let mut ak = Vec::new(); for auth in &cfg.auths { writeln!(ak, "{} {}", auth.ssh_key, auth.name)?; } fs::write(authorized_keys, ak)?; Ok(()) }) .await; retry_or_ignore(async || { let mut sshd_args = Vec::new(); sshd_args.extend(["-i", "-E", "/var/log/sshd.log"]); for key_path in cfg.ssh.keys.iter() { if !fs::exists(key_path).is_ok_and(|b| b) { info!("ssh: host key not found (ignored): {key_path}"); continue; } sshd_args.extend(["-h", key_path]); } let sshd_args = sshd_args.into_iter().map(String::from).collect(); // don't pre-start sshd as it should rarely be useful at this stage, use inetd-style. let listen_addr = cfg.ssh.listen.clone(); info!("ssh: starting listener on {listen_addr}"); let listener = net::TcpListener::bind(listen_addr).await?; tokio::spawn(handle_ssh_connections(listener, sshd_args)); Ok(()) }) .await; } async fn handle_ssh_connections(listener: net::TcpListener, sshd_args: Vec) { loop { let (stream, remote) = match listener.accept().await { Ok(v) => v, Err(e) => { warn!("ssh: listener stopped: {e}"); return; } }; info!("ssh: new connection from {remote}"); use std::os::unix::io::{AsRawFd, FromRawFd}; let fd = stream.as_raw_fd(); let mut cmd = Command::new("/usr/sbin/sshd"); cmd.args(&sshd_args); cmd.stdin(unsafe { Stdio::from_raw_fd(fd) }); cmd.stdout(unsafe { Stdio::from_raw_fd(fd) }); cmd.stderr(Stdio::null()); match cmd.spawn() { Ok(mut child) => { tokio::spawn(async move { child.wait().await }); } Err(e) => { warn!("ssh: failed to start server: {e}"); } } } }