use log::debug;
use std::io::Read;
use std::net::ToSocketAddrs;
use std::path::Path;
use thiserror::*;
#[derive(Debug, Error)]
pub enum Error {
#[error("Host not found")]
HostNotFound,
#[error("No home directory")]
NoHome,
#[error("{}", source)]
Io {
#[from]
source: std::io::Error,
},
}
mod proxy;
pub use proxy::*;
#[derive(Debug)]
pub struct Config {
pub user: String,
pub host_name: String,
pub port: u16,
pub identity_file: Option<String>,
pub proxy_command: Option<String>,
pub add_keys_to_agent: AddKeysToAgent,
}
impl Config {
pub fn default(host_name: &str) -> Self {
Config {
user: whoami::username(),
host_name: host_name.to_string(),
port: 22,
identity_file: None,
proxy_command: None,
add_keys_to_agent: AddKeysToAgent::default(),
}
}
}
impl Config {
fn update_proxy_command(&mut self) {
if let Some(ref mut prox) = self.proxy_command {
*prox = prox.replace("%h", &self.host_name);
*prox = prox.replace("%p", &format!("{}", self.port));
}
}
pub async fn stream(&mut self) -> Result<Stream, std::io::Error> {
self.update_proxy_command();
if let Some(ref proxy_command) = self.proxy_command {
let cmd: Vec<&str> = proxy_command.split(' ').collect();
Stream::proxy_command(cmd[0], &cmd[1..]).await
} else {
Stream::tcp_connect(
&(self.host_name.as_str(), self.port)
.to_socket_addrs()?
.next()
.unwrap(),
)
.await
}
}
}
pub fn parse_home(host: &str) -> Result<Config, Error> {
let mut home = if let Some(home) = dirs_next::home_dir() {
home
} else {
return Err(Error::NoHome);
};
home.push(".ssh");
home.push("config");
parse_path(&home, host)
}
pub fn parse_path<P: AsRef<Path>>(path: P, host: &str) -> Result<Config, Error> {
let mut s = String::new();
let mut b = std::fs::File::open(path)?;
b.read_to_string(&mut s)?;
parse(&s, host)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddKeysToAgent {
Yes,
Confirm,
Ask,
No,
}
impl Default for AddKeysToAgent {
fn default() -> Self {
AddKeysToAgent::No
}
}
pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
let mut config: Option<Config> = None;
for line in file.lines() {
let line = line.trim();
if let Some(n) = line.find(' ') {
let (key, value) = line.split_at(n);
let lower = key.to_lowercase();
if let Some(ref mut config) = config {
match lower.as_str() {
"host" => break,
"user" => {
config.user.clear();
config.user.push_str(value.trim_start());
}
"hostname" => {
config.host_name.clear();
config.host_name.push_str(value.trim_start())
}
"port" => {
if let Ok(port) = value.trim_start().parse() {
config.port = port
}
}
"identityfile" => {
let id = value.trim_start();
if id.starts_with("~/") {
if let Some(mut home) = dirs_next::home_dir() {
home.push(id.split_at(2).1);
config.identity_file = Some(home.to_str().unwrap().to_string());
} else {
return Err(Error::NoHome);
}
} else {
config.identity_file = Some(id.to_string())
}
}
"proxycommand" => config.proxy_command = Some(value.trim_start().to_string()),
"addkeystoagent" => match value.to_lowercase().as_str() {
"yes" => config.add_keys_to_agent = AddKeysToAgent::Yes,
"confirm" => config.add_keys_to_agent = AddKeysToAgent::Confirm,
"ask" => config.add_keys_to_agent = AddKeysToAgent::Ask,
_ => config.add_keys_to_agent = AddKeysToAgent::No,
},
key => {
debug!("{:?}", key);
}
}
} else {
match lower.as_str() {
"host" => {
if value.trim_start() == host {
let mut c = Config::default(host);
c.port = 22;
config = Some(c)
}
}
_ => {}
}
}
}
}
if let Some(config) = config {
Ok(config)
} else {
Err(Error::HostNotFound)
}
}