1use std::collections::HashMap;
6
7use super::{Duration, PathBuf};
8
9#[derive(Debug, Default, Clone, PartialEq, Eq)]
13pub struct HostParams {
14 pub bind_address: Option<String>,
16 pub bind_interface: Option<String>,
18 pub ca_signature_algorithms: Option<Vec<String>>,
20 pub certificate_file: Option<PathBuf>,
22 pub ciphers: Option<Vec<String>>,
24 pub compression: Option<bool>,
26 pub connection_attempts: Option<usize>,
28 pub connect_timeout: Option<Duration>,
30 pub host_key_algorithms: Option<Vec<String>>,
32 pub host_name: Option<String>,
34 pub identity_file: Option<Vec<PathBuf>>,
38 pub ignore_unknown: Option<Vec<String>>,
40 pub kex_algorithms: Option<Vec<String>>,
42 pub mac: Option<Vec<String>>,
44 pub port: Option<u16>,
46 pub pubkey_accepted_algorithms: Option<Vec<String>>,
48 pub pubkey_authentication: Option<bool>,
50 pub remote_forward: Option<u16>,
52 pub server_alive_interval: Option<Duration>,
54 pub tcp_keep_alive: Option<bool>,
56 #[cfg(target_os = "macos")]
57 pub use_keychain: Option<bool>,
59 pub user: Option<String>,
61 pub ignored_fields: HashMap<String, Vec<String>>,
63 pub unsupported_fields: HashMap<String, Vec<String>>,
65}
66
67impl HostParams {
68 pub(crate) fn ignored(&self, param: &str) -> bool {
70 self.ignore_unknown
71 .as_ref()
72 .map(|x| x.iter().any(|x| x.as_str() == param))
73 .unwrap_or(false)
74 }
75
76 pub fn merge(&mut self, b: &Self) {
78 if let Some(bind_address) = b.bind_address.as_deref() {
79 self.bind_address = Some(bind_address.to_owned());
80 }
81 if let Some(bind_interface) = b.bind_interface.as_deref() {
82 self.bind_interface = Some(bind_interface.to_owned());
83 }
84 if let Some(ca_signature_algorithms) = b.ca_signature_algorithms.as_deref() {
85 if self.ca_signature_algorithms.is_none() {
86 self.ca_signature_algorithms = Some(Vec::new());
87 }
88 Self::resolve_algorithms(
89 self.ca_signature_algorithms.as_mut().unwrap(),
90 ca_signature_algorithms,
91 );
92 }
93 if let Some(certificate_file) = b.certificate_file.as_deref() {
94 self.certificate_file = Some(certificate_file.to_owned());
95 }
96 if let Some(ciphers) = b.ciphers.as_deref() {
97 if self.ciphers.is_none() {
98 self.ciphers = Some(Vec::new());
99 }
100 Self::resolve_algorithms(self.ciphers.as_mut().unwrap(), ciphers);
101 }
102 if let Some(compression) = b.compression {
103 self.compression = Some(compression);
104 }
105 if let Some(connection_attempts) = b.connection_attempts {
106 self.connection_attempts = Some(connection_attempts);
107 }
108 trace!(
109 "wait comparing connect timeout: {:?} {:?}",
110 self.connect_timeout, b.connect_timeout
111 );
112 if let Some(connect_timeout) = b.connect_timeout {
113 self.connect_timeout = Some(connect_timeout);
114 }
115 if let Some(host_key_algorithms) = b.host_key_algorithms.as_deref() {
116 if self.host_key_algorithms.is_none() {
117 self.host_key_algorithms = Some(Vec::new());
118 }
119 Self::resolve_algorithms(
120 self.host_key_algorithms.as_mut().unwrap(),
121 host_key_algorithms,
122 );
123 }
124 if let Some(host_name) = b.host_name.as_deref() {
125 self.host_name = Some(host_name.to_owned());
126 }
127 if let Some(identity_file) = b.identity_file.as_deref() {
128 self.identity_file = Some(identity_file.to_owned());
129 }
130 if let Some(ignore_unknown) = b.ignore_unknown.as_deref() {
131 self.ignore_unknown = Some(ignore_unknown.to_owned());
132 }
133 if let Some(kex_algorithms) = b.kex_algorithms.as_deref() {
134 if self.kex_algorithms.is_none() {
135 self.kex_algorithms = Some(Vec::new());
136 }
137 Self::resolve_algorithms(self.kex_algorithms.as_mut().unwrap(), kex_algorithms);
138 }
139 if let Some(mac) = b.mac.as_deref() {
140 if self.mac.is_none() {
141 self.mac = Some(Vec::new());
142 }
143 Self::resolve_algorithms(self.mac.as_mut().unwrap(), mac);
144 }
145 if let Some(port) = b.port {
146 self.port = Some(port);
147 }
148 if let Some(pubkey_accepted_algorithms) = b.pubkey_accepted_algorithms.as_deref() {
149 if self.pubkey_accepted_algorithms.is_none() {
150 self.pubkey_accepted_algorithms = Some(Vec::new());
151 }
152 Self::resolve_algorithms(
153 self.pubkey_accepted_algorithms.as_mut().unwrap(),
154 pubkey_accepted_algorithms,
155 );
156 }
157 if let Some(pubkey_authentication) = b.pubkey_authentication {
158 self.pubkey_authentication = Some(pubkey_authentication);
159 }
160 if let Some(remote_forward) = b.remote_forward {
161 self.remote_forward = Some(remote_forward);
162 }
163 if let Some(server_alive_interval) = b.server_alive_interval {
164 self.server_alive_interval = Some(server_alive_interval);
165 }
166 if let Some(tcp_keep_alive) = b.tcp_keep_alive {
167 self.tcp_keep_alive = Some(tcp_keep_alive);
168 }
169 #[cfg(target_os = "macos")]
170 if let Some(use_keychain) = b.use_keychain {
171 self.use_keychain = Some(use_keychain);
172 }
173 if let Some(user) = b.user.as_deref() {
174 self.user = Some(user.to_owned());
175 }
176 for (ignored_field, args) in &b.ignored_fields {
177 if !self.ignored_fields.contains_key(ignored_field) {
178 self.ignored_fields
179 .insert(ignored_field.to_owned(), args.to_owned());
180 }
181 }
182
183 for (unsupported_field, args) in &b.unsupported_fields {
184 if !self.unsupported_fields.contains_key(unsupported_field) {
185 self.unsupported_fields
186 .insert(unsupported_field.to_owned(), args.to_owned());
187 }
188 }
189 }
190
191 fn resolve_algorithms(current_list: &mut Vec<String>, algos: &[String]) {
196 if algos.is_empty() {
197 return;
198 }
199 let first = algos.first().unwrap();
200 if first.starts_with('+') {
201 for algo in [first.replacen('+', "", 1)].iter().chain(algos[1..].iter()) {
203 if !current_list.contains(algo) {
204 current_list.push(algo.to_owned());
205 }
206 }
207 } else if first.starts_with('-') {
208 let new_first = [first.replacen('-', "", 1)];
210 current_list.retain(|algo| {
212 !new_first
213 .iter()
214 .chain(algos[1..].iter())
215 .any(|remove| remove == algo)
216 });
217 } else {
218 *current_list = algos.to_vec();
219 }
220 }
221}
222
223#[cfg(test)]
224mod test {
225
226 use pretty_assertions::assert_eq;
227
228 use super::*;
229
230 #[test]
231 fn should_initialize_params() {
232 let params = HostParams::default();
233 assert!(params.bind_address.is_none());
234 assert!(params.bind_interface.is_none());
235 assert!(params.ca_signature_algorithms.is_none());
236 assert!(params.certificate_file.is_none());
237 assert!(params.ciphers.is_none());
238 assert!(params.compression.is_none());
239 assert!(params.connection_attempts.is_none());
240 assert!(params.connect_timeout.is_none());
241 assert!(params.host_key_algorithms.is_none());
242 assert!(params.host_name.is_none());
243 assert!(params.identity_file.is_none());
244 assert!(params.ignore_unknown.is_none());
245 assert!(params.kex_algorithms.is_none());
246 assert!(params.mac.is_none());
247 assert!(params.port.is_none());
248 assert!(params.pubkey_accepted_algorithms.is_none());
249 assert!(params.pubkey_authentication.is_none());
250 assert!(params.remote_forward.is_none());
251 assert!(params.server_alive_interval.is_none());
252 #[cfg(target_os = "macos")]
253 assert!(params.use_keychain.is_none());
254 assert!(params.tcp_keep_alive.is_none());
255 }
256
257 #[test]
258 fn should_merge_params() {
259 let mut params = HostParams::default();
260 let mut b = HostParams {
261 bind_address: Some(String::from("pippo")),
262 bind_interface: Some(String::from("tun0")),
263 ca_signature_algorithms: Some(vec![]),
264 certificate_file: Some(PathBuf::default()),
265 ciphers: Some(vec![]),
266 compression: Some(true),
267 connect_timeout: Some(Duration::from_secs(1)),
268 connection_attempts: Some(3),
269 host_key_algorithms: Some(vec![]),
270 host_name: Some(String::from("192.168.1.2")),
271 identity_file: Some(vec![PathBuf::default()]),
272 ignore_unknown: Some(vec![]),
273 kex_algorithms: Some(vec![]),
274 mac: Some(vec![]),
275 port: Some(22),
276 pubkey_accepted_algorithms: Some(vec![]),
277 pubkey_authentication: Some(true),
278 remote_forward: Some(32),
279 server_alive_interval: Some(Duration::from_secs(10)),
280 #[cfg(target_os = "macos")]
281 use_keychain: Some(true),
282 tcp_keep_alive: Some(true),
283 ..Default::default()
284 };
285 params.merge(&b);
286 assert!(params.bind_address.is_some());
287 assert!(params.bind_interface.is_some());
288 assert!(params.ca_signature_algorithms.is_some());
289 assert!(params.certificate_file.is_some());
290 assert!(params.ciphers.is_some());
291 assert!(params.compression.is_some());
292 assert!(params.connection_attempts.is_some());
293 assert!(params.connect_timeout.is_some());
294 assert!(params.host_key_algorithms.is_some());
295 assert!(params.host_name.is_some());
296 assert!(params.identity_file.is_some());
297 assert!(params.ignore_unknown.is_some());
298 assert!(params.kex_algorithms.is_some());
299 assert!(params.mac.is_some());
300 assert!(params.port.is_some());
301 assert!(params.pubkey_accepted_algorithms.is_some());
302 assert!(params.pubkey_authentication.is_some());
303 assert!(params.remote_forward.is_some());
304 assert!(params.server_alive_interval.is_some());
305 #[cfg(target_os = "macos")]
306 assert!(params.use_keychain.is_some());
307 assert!(params.tcp_keep_alive.is_some());
308 b.tcp_keep_alive = None;
310 params.merge(&b);
311 assert_eq!(params.tcp_keep_alive.unwrap(), true);
312 }
313
314 #[test]
315 fn should_resolve_algorithms_list_when_preceeded_by_plus() {
316 let mut list = vec![
317 "a".to_string(),
318 "b".to_string(),
319 "c".to_string(),
320 "d".to_string(),
321 "e".to_string(),
322 ];
323 let algos = [
324 "+1".to_string(),
325 "a".to_string(),
326 "b".to_string(),
327 "3".to_string(),
328 "d".to_string(),
329 ];
330 HostParams::resolve_algorithms(&mut list, &algos);
331 assert_eq!(
332 list,
333 vec![
334 "a".to_string(),
335 "b".to_string(),
336 "c".to_string(),
337 "d".to_string(),
338 "e".to_string(),
339 "1".to_string(),
340 "3".to_string(),
341 ]
342 );
343 }
344
345 #[test]
346 fn should_resolve_algorithms_list_when_preceeded_by_minus() {
347 let mut list = vec![
348 "a".to_string(),
349 "b".to_string(),
350 "c".to_string(),
351 "d".to_string(),
352 "e".to_string(),
353 ];
354 let algos = ["-a".to_string(), "b".to_string(), "3".to_string()];
355 HostParams::resolve_algorithms(&mut list, &algos);
356 assert_eq!(
357 list,
358 vec!["c".to_string(), "d".to_string(), "e".to_string(),]
359 );
360 }
361
362 #[test]
363 fn should_resolve_algorithm_list_when_replacing() {
364 let mut list = vec![
365 "a".to_string(),
366 "b".to_string(),
367 "c".to_string(),
368 "d".to_string(),
369 "e".to_string(),
370 ];
371 let algos = [
372 "1".to_string(),
373 "a".to_string(),
374 "b".to_string(),
375 "3".to_string(),
376 "d".to_string(),
377 ];
378 HostParams::resolve_algorithms(&mut list, &algos);
379 assert_eq!(
380 list,
381 vec![
382 "1".to_string(),
383 "a".to_string(),
384 "b".to_string(),
385 "3".to_string(),
386 "d".to_string(),
387 ]
388 );
389 }
390}