1#![crate_name = "ssh2_config"]
2#![crate_type = "lib"]
3
4#![doc(html_playground_url = "https://play.rust-lang.org")]
59
60#[macro_use]
61extern crate log;
62
63use std::fmt;
64use std::fs::File;
65use std::io::{self, BufRead, BufReader};
66use std::path::PathBuf;
67use std::time::Duration;
68mod host;
70mod params;
71mod parser;
72mod serializer;
73
74pub use host::{Host, HostClause};
76pub use params::HostParams;
77pub use parser::{ParseRule, SshParserError, SshParserResult};
78
79#[derive(Debug, Clone, PartialEq, Eq, Default)]
82pub struct SshConfig {
83 hosts: Vec<Host>,
86}
87
88impl fmt::Display for SshConfig {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 serializer::SshConfigSerializer::from(self).serialize(f)
91 }
92}
93
94impl SshConfig {
95 pub fn query<S: AsRef<str>>(&self, host: S) -> HostParams {
97 let mut params = HostParams::default();
98 for cfg_host in self.hosts.iter() {
100 if cfg_host.intersects(host.as_ref()) {
101 params.merge(&cfg_host.params);
102 }
103 }
104 params
106 }
107
108 pub fn parse(mut self, reader: &mut impl BufRead, rules: ParseRule) -> SshParserResult<Self> {
110 parser::SshConfigParser::parse(&mut self, reader, rules).map(|_| self)
111 }
112
113 pub fn parse_default_file(rules: ParseRule) -> SshParserResult<Self> {
115 let ssh_folder = dirs::home_dir()
116 .ok_or_else(|| {
117 SshParserError::Io(io::Error::new(
118 io::ErrorKind::NotFound,
119 "Home folder not found",
120 ))
121 })?
122 .join(".ssh");
123
124 let mut reader =
125 BufReader::new(File::open(ssh_folder.join("config")).map_err(SshParserError::Io)?);
126
127 Self::default().parse(&mut reader, rules)
128 }
129
130 pub fn get_hosts(&self) -> &Vec<Host> {
131 &self.hosts
132 }
133}
134
135#[cfg(test)]
136fn test_log() {
137 use std::sync::Once;
138
139 static INIT: Once = Once::new();
140
141 INIT.call_once(|| {
142 let _ = env_logger::builder()
143 .filter_level(log::LevelFilter::Trace)
144 .is_test(true)
145 .try_init();
146 });
147}
148
149#[cfg(test)]
150mod test {
151
152 use pretty_assertions::assert_eq;
153
154 use super::*;
155
156 #[test]
157 fn should_init_ssh_config() {
158 test_log();
159
160 let config = SshConfig::default();
161 assert_eq!(config.hosts.len(), 0);
162 assert_eq!(config.query("192.168.1.2"), HostParams::default());
163 }
164
165 #[test]
166 fn should_parse_default_config() -> Result<(), parser::SshParserError> {
167 test_log();
168
169 let _config = SshConfig::parse_default_file(ParseRule::ALLOW_UNKNOWN_FIELDS)?;
170 Ok(())
171 }
172
173 #[test]
174 fn should_parse_config() -> Result<(), parser::SshParserError> {
175 test_log();
176
177 use std::fs::File;
178 use std::io::BufReader;
179 use std::path::Path;
180
181 let mut reader = BufReader::new(
182 File::open(Path::new("./assets/ssh.config"))
183 .expect("Could not open configuration file"),
184 );
185
186 SshConfig::default().parse(&mut reader, ParseRule::STRICT)?;
187
188 Ok(())
189 }
190
191 #[test]
192 fn should_query_ssh_config() {
193 test_log();
194
195 let mut config = SshConfig::default();
196 let mut params1 = HostParams {
198 bind_address: Some(String::from("0.0.0.0")),
199 ..Default::default()
200 };
201 config.hosts.push(Host::new(
202 vec![HostClause::new(String::from("192.168.*.*"), false)],
203 params1.clone(),
204 ));
205 let params2 = HostParams {
206 bind_interface: Some(String::from("tun0")),
207 ..Default::default()
208 };
209 config.hosts.push(Host::new(
210 vec![HostClause::new(String::from("192.168.10.*"), false)],
211 params2.clone(),
212 ));
213 let params3 = HostParams {
214 host_name: Some(String::from("172.26.104.4")),
215 ..Default::default()
216 };
217 config.hosts.push(Host::new(
218 vec![
219 HostClause::new(String::from("172.26.*.*"), false),
220 HostClause::new(String::from("172.26.104.4"), true),
221 ],
222 params3.clone(),
223 ));
224 assert_eq!(config.query("192.168.1.32"), params1);
226 params1.merge(¶ms2);
228 assert_eq!(config.query("192.168.10.1"), params1);
229 assert_eq!(config.query("172.26.254.1"), params3);
231 assert_eq!(config.query("172.26.104.4"), HostParams::default());
232 }
233}