use anyhow::Result; use colored::Colorize; use crate::{ ConfigManager, config::Host, input, platform::{ Platform, PlatformKey } }; use dialoguer::{ Select, Input, Confirm, theme::ColorfulTheme }; use ssh2_config::{ SshConfig, ParseRule }; use ssh_key::PrivateKey; use std::fs; use std::io; use tui::{ backend::TermionBackend, Terminal }; fn new_config(host: String, user: String, port: i16, keyid: PlatformKey) -> String { format!(r#"# Generated by Keyman Host {host} HostName {host} User {user} Port {port} IdentityFile {} "#, keyid.file) } impl Platform { // New host pub fn ssh_new_host(&self, config: &mut ConfigManager) -> Result { println!(" ## Creating new SSH host entry ## "); let keyid = input::get_key(config, self.to_owned())?; let userhost = loop { let inp: String = Input::with_theme(&ColorfulTheme::default()) .with_prompt("User@Host") .interact_text()?; let split = inp.split("@"); let split: Vec = split.map(|a| a.to_string()).collect(); if split.len() < 2 { println!("{} Example: paul@example.com:34 - @[:port]", "Incorrect user@host given.".red()); } else { break split } }; let user: String = userhost.get(0).unwrap().to_string(); let mut host: String = userhost.get(1).unwrap().to_string(); let mut port: i16 = 22; let _host = host.to_owned(); let h: Vec<&str> = _host.split(":").collect(); if h.len() > 1 { let portstr = h.get(1).unwrap(); port = i16::from_str_radix(portstr, 10)?; host = h.get(0).unwrap().to_string(); } let config_dir = config.config_dir.join("hosts/").join(host.to_owned()); fs::create_dir_all(config_dir.to_owned()).expect(&format!("Couldnt create_dir_all on {:?}", config_dir.to_owned())); let config_file = config_dir.join("config"); fs::write(config_file.to_owned(), new_config(host.to_owned(), user.to_owned(), port, keyid.to_owned())).expect(&format!("Couldn't write config at {:?}", config_file.to_owned())); Ok(Host { platform: Platform::SSH, id: keyid.file, user, host, port, config: config_file.to_owned().to_string_lossy().to_string() }) } // Get keys pub fn ssh_get_keys(&self, config: &ConfigManager) -> Vec { let mut keys = vec![]; let Ok(dir) = std::fs::read_dir(config.search_path.to_owned()) else { panic!("Couldn't read {:?}", config.search_path.to_owned()) }; for file in dir { let Ok(f) = file else { continue }; match PrivateKey::read_openssh_file(&f.path()) { Ok(p) => keys.push(PlatformKey { file: format!("{}", f.path().to_string_lossy()), display: format!("{}: {}", p.fingerprint(ssh_key::HashAlg::Sha256), f.file_name().to_string_lossy()) }), // Not a private key Err(_) => continue }; } keys } // Run pub fn ssh_run(&self, host: &mut Host, config: &mut ConfigManager, term: &mut Terminal>) -> Result<()> { Ok(loop { let action = Select::with_theme(&ColorfulTheme::default()) .with_prompt(format!("{}@{}", host.user.to_owned(), host.host.to_owned())) .item("Connect") .item("Edit config") .item("Edit definition") .item("Delete") .item("Back") .default(0) .interact()?; term.clear()?; match action { // Connect 0 => { // TODO: clear screen before connect std::process::Command::new("ssh") .arg("-F") .arg(host.config.to_owned()) .arg(host.host.to_owned()).spawn()?.wait()?; } // Edit config 1 => { let t = input::edit_temp_from_file(&host.config)?; // Validate SSH config let mut file = std::io::BufReader::new(fs::File::open(&t)?); if let Err(e) = SshConfig::default().parse(&mut file, ParseRule::STRICT) { println!(" ## {}: {} ## ", "Rejecting config".red(), e) } else { println!(" ## {} ## ", "Config OK".green()); fs::copy(&t, &host.config)?; } } // Edit definition 2 => { let edited_host = host.edit(config)?; host.clone_from(&edited_host); } // Delete 3 => { let confirm = Confirm::with_theme(&ColorfulTheme::default()) .with_prompt("Are you sure you want to delete this definition?") .default(false) .interact()?; if confirm { let mut conf = std::path::PathBuf::from(host.config.to_owned()); conf.pop(); config.remove(host); fs::remove_dir_all(conf)?; break; } } // Back _ => break } }) } }