thrussh_config/
lib.rs

1use log::debug;
2use std::io::Read;
3use std::net::ToSocketAddrs;
4use std::path::Path;
5use thiserror::*;
6
7#[derive(Debug, Error)]
8/// anyhow::Errors.
9pub enum Error {
10    #[error("Host not found")]
11    HostNotFound,
12    #[error("No home directory")]
13    NoHome,
14    #[error("{}", source)]
15    Io {
16        #[from]
17        source: std::io::Error,
18    },
19}
20
21mod proxy;
22pub use proxy::*;
23
24#[derive(Debug)]
25pub struct Config {
26    pub user: String,
27    pub host_name: String,
28    pub port: u16,
29    pub identity_file: Option<String>,
30    pub proxy_command: Option<String>,
31    pub add_keys_to_agent: AddKeysToAgent,
32}
33
34impl Config {
35    pub fn default(host_name: &str) -> Self {
36        Config {
37            user: whoami::username(),
38            host_name: host_name.to_string(),
39            port: 22,
40            identity_file: None,
41            proxy_command: None,
42            add_keys_to_agent: AddKeysToAgent::default(),
43        }
44    }
45}
46
47impl Config {
48    fn update_proxy_command(&mut self) {
49        if let Some(ref mut prox) = self.proxy_command {
50            *prox = prox.replace("%h", &self.host_name);
51            *prox = prox.replace("%p", &format!("{}", self.port));
52        }
53    }
54
55    pub async fn stream(&mut self) -> Result<Stream, std::io::Error> {
56        self.update_proxy_command();
57        if let Some(ref proxy_command) = self.proxy_command {
58            let cmd: Vec<&str> = proxy_command.split(' ').collect();
59            Stream::proxy_command(cmd[0], &cmd[1..]).await
60        } else {
61            Stream::tcp_connect(
62                &(self.host_name.as_str(), self.port)
63                    .to_socket_addrs()?
64                    .next()
65                    .unwrap(),
66            )
67            .await
68        }
69    }
70}
71
72pub fn parse_home(host: &str) -> Result<Config, Error> {
73    let mut home = if let Some(home) = dirs_next::home_dir() {
74        home
75    } else {
76        return Err(Error::NoHome);
77    };
78    home.push(".ssh");
79    home.push("config");
80    parse_path(&home, host)
81}
82
83pub fn parse_path<P: AsRef<Path>>(path: P, host: &str) -> Result<Config, Error> {
84    let mut s = String::new();
85    let mut b = std::fs::File::open(path)?;
86    b.read_to_string(&mut s)?;
87    parse(&s, host)
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum AddKeysToAgent {
92    Yes,
93    Confirm,
94    Ask,
95    No,
96}
97
98impl Default for AddKeysToAgent {
99    fn default() -> Self {
100        AddKeysToAgent::No
101    }
102}
103
104pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
105    let mut config: Option<Config> = None;
106    for line in file.lines() {
107        let line = line.trim();
108        if let Some(n) = line.find(' ') {
109            let (key, value) = line.split_at(n);
110            let lower = key.to_lowercase();
111            if let Some(ref mut config) = config {
112                match lower.as_str() {
113                    "host" => break,
114                    "user" => {
115                        config.user.clear();
116                        config.user.push_str(value.trim_start());
117                    }
118                    "hostname" => {
119                        config.host_name.clear();
120                        config.host_name.push_str(value.trim_start())
121                    }
122                    "port" => {
123                        if let Ok(port) = value.trim_start().parse() {
124                            config.port = port
125                        }
126                    }
127                    "identityfile" => {
128                        let id = value.trim_start();
129                        if id.starts_with("~/") {
130                            if let Some(mut home) = dirs_next::home_dir() {
131                                home.push(id.split_at(2).1);
132                                config.identity_file = Some(home.to_str().unwrap().to_string());
133                            } else {
134                                return Err(Error::NoHome);
135                            }
136                        } else {
137                            config.identity_file = Some(id.to_string())
138                        }
139                    }
140                    "proxycommand" => config.proxy_command = Some(value.trim_start().to_string()),
141                    "addkeystoagent" => match value.to_lowercase().as_str() {
142                        "yes" => config.add_keys_to_agent = AddKeysToAgent::Yes,
143                        "confirm" => config.add_keys_to_agent = AddKeysToAgent::Confirm,
144                        "ask" => config.add_keys_to_agent = AddKeysToAgent::Ask,
145                        _ => config.add_keys_to_agent = AddKeysToAgent::No,
146                    },
147                    key => {
148                        debug!("{:?}", key);
149                    }
150                }
151            } else {
152                match lower.as_str() {
153                    "host" => {
154                        if value.trim_start() == host {
155                            let mut c = Config::default(host);
156                            c.port = 22;
157                            config = Some(c)
158                        }
159                    }
160                    _ => {}
161                }
162            }
163        }
164    }
165    if let Some(config) = config {
166        Ok(config)
167    } else {
168        Err(Error::HostNotFound)
169    }
170}