1#![crate_name = "ssh2_config"]
2#![crate_type = "lib"]
3
4#![doc(html_playground_url = "https://play.rust-lang.org")]
54
55use std::fs::File;
56use std::io::{self, BufRead, BufReader};
57use std::path::PathBuf;
58use std::time::Duration;
59mod host;
61mod params;
62mod parser;
63
64pub use host::{Host, HostClause};
66pub use params::HostParams;
67pub use parser::{ParseRule, SshParserError, SshParserResult};
68
69#[derive(Debug, Clone, PartialEq, Eq, Default)]
72pub struct SshConfig {
73 hosts: Vec<Host>,
76}
77
78impl SshConfig {
79 pub fn query<S: AsRef<str>>(&self, host: S) -> HostParams {
81 let mut params = HostParams::default();
82 for cfg_host in self.hosts.iter().rev() {
84 if cfg_host.intersects(host.as_ref()) {
85 params.merge(&cfg_host.params);
86 }
87 }
88 params
90 }
91
92 pub fn parse(mut self, reader: &mut impl BufRead, rules: ParseRule) -> SshParserResult<Self> {
94 parser::SshConfigParser::parse(&mut self, reader, rules).map(|_| self)
95 }
96
97 #[cfg(target_family = "unix")]
98 pub fn parse_default_file(rules: ParseRule) -> SshParserResult<Self> {
100 let ssh_folder = dirs::home_dir()
101 .ok_or_else(|| {
102 SshParserError::Io(io::Error::new(
103 io::ErrorKind::NotFound,
104 "Home folder not found",
105 ))
106 })?
107 .join(".ssh");
108
109 let mut reader =
110 BufReader::new(File::open(ssh_folder.join("config")).map_err(SshParserError::Io)?);
111
112 Self::default().parse(&mut reader, rules)
113 }
114
115 pub fn get_hosts(&self) -> &Vec<Host> {
116 &self.hosts
117 }
118}
119
120#[cfg(test)]
121mod test {
122
123 use pretty_assertions::assert_eq;
124
125 use super::*;
126
127 #[test]
128 fn should_init_ssh_config() {
129 let config = SshConfig::default();
130 assert_eq!(config.hosts.len(), 0);
131 assert_eq!(config.query("192.168.1.2"), HostParams::default());
132 }
133
134 #[test]
135 #[cfg(target_family = "unix")]
136 fn should_parse_default_config() -> Result<(), parser::SshParserError> {
137 let _config = SshConfig::parse_default_file(ParseRule::ALLOW_UNKNOWN_FIELDS)?;
138 Ok(())
139 }
140
141 #[test]
142 fn should_parse_config() -> Result<(), parser::SshParserError> {
143 use std::fs::File;
144 use std::io::BufReader;
145 use std::path::Path;
146
147 let mut reader = BufReader::new(
148 File::open(Path::new("./assets/ssh.config"))
149 .expect("Could not open configuration file"),
150 );
151
152 SshConfig::default().parse(&mut reader, ParseRule::STRICT)?;
153
154 Ok(())
155 }
156
157 #[test]
158 fn should_query_ssh_config() {
159 let mut config = SshConfig::default();
160 let mut params1 = HostParams {
162 bind_address: Some(String::from("0.0.0.0")),
163 ..Default::default()
164 };
165 config.hosts.push(Host::new(
166 vec![HostClause::new(String::from("192.168.*.*"), false)],
167 params1.clone(),
168 ));
169 let params2 = HostParams {
170 bind_interface: Some(String::from("tun0")),
171 ..Default::default()
172 };
173 config.hosts.push(Host::new(
174 vec![HostClause::new(String::from("192.168.10.*"), false)],
175 params2.clone(),
176 ));
177 let params3 = HostParams {
178 host_name: Some(String::from("172.26.104.4")),
179 ..Default::default()
180 };
181 config.hosts.push(Host::new(
182 vec![
183 HostClause::new(String::from("172.26.*.*"), false),
184 HostClause::new(String::from("172.26.104.4"), true),
185 ],
186 params3.clone(),
187 ));
188 assert_eq!(config.query("192.168.1.32"), params1);
190 params1.merge(¶ms2);
192 assert_eq!(config.query("192.168.10.1"), params1);
193 assert_eq!(config.query("172.26.254.1"), params3);
195 assert_eq!(config.query("172.26.104.4"), HostParams::default());
196 }
197}