1mod algos;
6
7use std::collections::HashMap;
8
9pub use self::algos::Algorithms;
10pub(crate) use self::algos::AlgorithmsRule;
11use super::{Duration, PathBuf};
12use crate::DefaultAlgorithms;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct HostParams {
19 pub add_keys_to_agent: Option<bool>,
21 pub bind_address: Option<String>,
23 pub bind_interface: Option<String>,
25 pub ca_signature_algorithms: Algorithms,
27 pub certificate_file: Option<PathBuf>,
29 pub ciphers: Algorithms,
31 pub compression: Option<bool>,
33 pub connection_attempts: Option<usize>,
35 pub connect_timeout: Option<Duration>,
37 pub forward_agent: Option<bool>,
39 pub host_key_algorithms: Algorithms,
41 pub host_name: Option<String>,
43 pub identity_file: Option<Vec<PathBuf>>,
48 pub ignore_unknown: Option<Vec<String>>,
50 pub kex_algorithms: Algorithms,
52 pub mac: Algorithms,
54 pub port: Option<u16>,
56 pub proxy_jump: Option<Vec<String>>,
58 pub pubkey_accepted_algorithms: Algorithms,
60 pub pubkey_authentication: Option<bool>,
62 pub remote_forward: Option<u16>,
64 pub server_alive_interval: Option<Duration>,
66 pub tcp_keep_alive: Option<bool>,
68 #[cfg(target_os = "macos")]
69 pub use_keychain: Option<bool>,
71 pub user: Option<String>,
73 pub ignored_fields: HashMap<String, Vec<String>>,
75 pub unsupported_fields: HashMap<String, Vec<String>>,
77}
78
79impl HostParams {
80 pub fn new(default_algorithms: &DefaultAlgorithms) -> Self {
82 Self {
83 add_keys_to_agent: None,
84 bind_address: None,
85 bind_interface: None,
86 ca_signature_algorithms: Algorithms::new(&default_algorithms.ca_signature_algorithms),
87 certificate_file: None,
88 ciphers: Algorithms::new(&default_algorithms.ciphers),
89 compression: None,
90 connection_attempts: None,
91 connect_timeout: None,
92 forward_agent: None,
93 host_key_algorithms: Algorithms::new(&default_algorithms.host_key_algorithms),
94 host_name: None,
95 identity_file: None,
96 ignore_unknown: None,
97 kex_algorithms: Algorithms::new(&default_algorithms.kex_algorithms),
98 mac: Algorithms::new(&default_algorithms.mac),
99 port: None,
100 proxy_jump: None,
101 pubkey_accepted_algorithms: Algorithms::new(
102 &default_algorithms.pubkey_accepted_algorithms,
103 ),
104 pubkey_authentication: None,
105 remote_forward: None,
106 server_alive_interval: None,
107 tcp_keep_alive: None,
108 #[cfg(target_os = "macos")]
109 use_keychain: None,
110 user: None,
111 ignored_fields: HashMap::new(),
112 unsupported_fields: HashMap::new(),
113 }
114 }
115
116 pub(crate) fn ignored(&self, param: &str) -> bool {
118 self.ignore_unknown
119 .as_ref()
120 .map(|x| x.iter().any(|x| x.as_str() == param))
121 .unwrap_or(false)
122 }
123
124 pub fn overwrite_if_none(&mut self, b: &Self) {
126 self.add_keys_to_agent = self.add_keys_to_agent.or(b.add_keys_to_agent);
127 self.bind_address = self.bind_address.clone().or_else(|| b.bind_address.clone());
128 self.bind_interface = self
129 .bind_interface
130 .clone()
131 .or_else(|| b.bind_interface.clone());
132 self.certificate_file = self
133 .certificate_file
134 .clone()
135 .or_else(|| b.certificate_file.clone());
136 self.compression = self.compression.or(b.compression);
137 self.connection_attempts = self.connection_attempts.or(b.connection_attempts);
138 self.connect_timeout = self.connect_timeout.or(b.connect_timeout);
139 self.forward_agent = self.forward_agent.or(b.forward_agent);
140 self.host_name = self.host_name.clone().or_else(|| b.host_name.clone());
141 match (&mut self.identity_file, &b.identity_file) {
143 (Some(existing), Some(other)) => existing.extend(other.clone()),
144 (None, Some(other)) => self.identity_file = Some(other.clone()),
145 _ => {}
146 }
147 self.ignore_unknown = self
148 .ignore_unknown
149 .clone()
150 .or_else(|| b.ignore_unknown.clone());
151 self.port = self.port.or(b.port);
152 self.proxy_jump = self.proxy_jump.clone().or_else(|| b.proxy_jump.clone());
153 self.pubkey_authentication = self.pubkey_authentication.or(b.pubkey_authentication);
154 self.remote_forward = self.remote_forward.or(b.remote_forward);
155 self.server_alive_interval = self.server_alive_interval.or(b.server_alive_interval);
156 #[cfg(target_os = "macos")]
157 {
158 self.use_keychain = self.use_keychain.or(b.use_keychain);
159 }
160 self.tcp_keep_alive = self.tcp_keep_alive.or(b.tcp_keep_alive);
161 self.user = self.user.clone().or_else(|| b.user.clone());
162 for (ignored_field, args) in &b.ignored_fields {
163 if !self.ignored_fields.contains_key(ignored_field) {
164 self.ignored_fields
165 .insert(ignored_field.to_owned(), args.to_owned());
166 }
167 }
168 for (unsupported_field, args) in &b.unsupported_fields {
169 if !self.unsupported_fields.contains_key(unsupported_field) {
170 self.unsupported_fields
171 .insert(unsupported_field.to_owned(), args.to_owned());
172 }
173 }
174
175 if self.ca_signature_algorithms.is_default() && !b.ca_signature_algorithms.is_default() {
177 self.ca_signature_algorithms = b.ca_signature_algorithms.clone();
178 }
179 if self.ciphers.is_default() && !b.ciphers.is_default() {
180 self.ciphers = b.ciphers.clone();
181 }
182 if self.host_key_algorithms.is_default() && !b.host_key_algorithms.is_default() {
183 self.host_key_algorithms = b.host_key_algorithms.clone();
184 }
185 if self.kex_algorithms.is_default() && !b.kex_algorithms.is_default() {
186 self.kex_algorithms = b.kex_algorithms.clone();
187 }
188 if self.mac.is_default() && !b.mac.is_default() {
189 self.mac = b.mac.clone();
190 }
191 if self.pubkey_accepted_algorithms.is_default()
192 && !b.pubkey_accepted_algorithms.is_default()
193 {
194 self.pubkey_accepted_algorithms = b.pubkey_accepted_algorithms.clone();
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201
202 use std::str::FromStr;
203
204 use pretty_assertions::assert_eq;
205
206 use super::*;
207 use crate::params::algos::AlgorithmsRule;
208
209 #[test]
210 fn should_initialize_params() {
211 let params = HostParams::new(&DefaultAlgorithms::default());
212 assert!(params.add_keys_to_agent.is_none());
213 assert!(params.bind_address.is_none());
214 assert!(params.bind_interface.is_none());
215 assert_eq!(
216 params.ca_signature_algorithms.algorithms(),
217 DefaultAlgorithms::default().ca_signature_algorithms
218 );
219 assert!(params.certificate_file.is_none());
220 assert_eq!(
221 params.ciphers.algorithms(),
222 DefaultAlgorithms::default().ciphers
223 );
224 assert!(params.compression.is_none());
225 assert!(params.connection_attempts.is_none());
226 assert!(params.connect_timeout.is_none());
227 assert!(params.forward_agent.is_none());
228 assert_eq!(
229 params.host_key_algorithms.algorithms(),
230 DefaultAlgorithms::default().host_key_algorithms
231 );
232 assert!(params.host_name.is_none());
233 assert!(params.identity_file.is_none());
234 assert!(params.ignore_unknown.is_none());
235 assert_eq!(
236 params.kex_algorithms.algorithms(),
237 DefaultAlgorithms::default().kex_algorithms
238 );
239 assert_eq!(params.mac.algorithms(), DefaultAlgorithms::default().mac);
240 assert!(params.port.is_none());
241 assert!(params.proxy_jump.is_none());
242 assert_eq!(
243 params.pubkey_accepted_algorithms.algorithms(),
244 DefaultAlgorithms::default().pubkey_accepted_algorithms
245 );
246 assert!(params.pubkey_authentication.is_none());
247 assert!(params.remote_forward.is_none());
248 assert!(params.server_alive_interval.is_none());
249 #[cfg(target_os = "macos")]
250 assert!(params.use_keychain.is_none());
251 assert!(params.tcp_keep_alive.is_none());
252 }
253
254 #[test]
255 fn test_should_overwrite_if_none() {
256 let mut params = HostParams::new(&DefaultAlgorithms::default());
257 params.bind_address = Some(String::from("pippo"));
258
259 let mut b = HostParams::new(&DefaultAlgorithms::default());
260 b.bind_address = Some(String::from("pluto"));
261 b.bind_interface = Some(String::from("tun0"));
262 b.ciphers
263 .apply(AlgorithmsRule::from_str("c,d").expect("parse error"));
264
265 params.overwrite_if_none(&b);
266 assert_eq!(params.bind_address.unwrap(), "pippo");
267 assert_eq!(params.bind_interface.unwrap(), "tun0");
268
269 assert_eq!(
271 params.ciphers.algorithms(),
272 vec!["c".to_string(), "d".to_string()]
273 );
274 }
275
276 #[test]
277 fn test_ignored_returns_false_when_none() {
278 let params = HostParams::new(&DefaultAlgorithms::default());
279 assert!(!params.ignored("SomeParam"));
280 }
281
282 #[test]
283 fn test_ignored_returns_false_when_not_in_list() {
284 let mut params = HostParams::new(&DefaultAlgorithms::default());
285 params.ignore_unknown = Some(vec!["Param1".to_string(), "Param2".to_string()]);
286 assert!(!params.ignored("OtherParam"));
287 }
288
289 #[test]
290 fn test_ignored_returns_true_when_in_list() {
291 let mut params = HostParams::new(&DefaultAlgorithms::default());
292 params.ignore_unknown = Some(vec!["Param1".to_string(), "Param2".to_string()]);
293 assert!(params.ignored("Param1"));
294 assert!(params.ignored("Param2"));
295 }
296
297 #[test]
298 fn test_overwrite_if_none_all_fields() {
299 let mut params = HostParams::new(&DefaultAlgorithms::empty());
300
301 let mut b = HostParams::new(&DefaultAlgorithms::empty());
302 b.add_keys_to_agent = Some(true);
303 b.bind_address = Some(String::from("addr"));
304 b.bind_interface = Some(String::from("iface"));
305 b.certificate_file = Some(std::path::PathBuf::from("/cert"));
306 b.compression = Some(true);
307 b.connection_attempts = Some(5);
308 b.connect_timeout = Some(Duration::from_secs(30));
309 b.forward_agent = Some(true);
310 b.host_name = Some(String::from("host"));
311 b.identity_file = Some(vec![std::path::PathBuf::from("/id")]);
312 b.ignore_unknown = Some(vec!["field".to_string()]);
313 b.port = Some(22);
314 b.proxy_jump = Some(vec!["proxy".to_string()]);
315 b.pubkey_authentication = Some(true);
316 b.remote_forward = Some(8080);
317 b.server_alive_interval = Some(Duration::from_secs(60));
318 b.tcp_keep_alive = Some(true);
319 #[cfg(target_os = "macos")]
320 {
321 b.use_keychain = Some(true);
322 }
323 b.user = Some(String::from("user"));
324 b.ignored_fields
325 .insert("custom".to_string(), vec!["value".to_string()]);
326 b.unsupported_fields
327 .insert("unsupported".to_string(), vec!["val".to_string()]);
328
329 params.overwrite_if_none(&b);
330
331 assert_eq!(params.add_keys_to_agent, Some(true));
332 assert_eq!(params.bind_address, Some(String::from("addr")));
333 assert_eq!(params.bind_interface, Some(String::from("iface")));
334 assert_eq!(
335 params.certificate_file,
336 Some(std::path::PathBuf::from("/cert"))
337 );
338 assert_eq!(params.compression, Some(true));
339 assert_eq!(params.connection_attempts, Some(5));
340 assert_eq!(params.connect_timeout, Some(Duration::from_secs(30)));
341 assert_eq!(params.forward_agent, Some(true));
342 assert_eq!(params.host_name, Some(String::from("host")));
343 assert_eq!(
344 params.identity_file,
345 Some(vec![std::path::PathBuf::from("/id")])
346 );
347 assert_eq!(params.ignore_unknown, Some(vec!["field".to_string()]));
348 assert_eq!(params.port, Some(22));
349 assert_eq!(params.proxy_jump, Some(vec!["proxy".to_string()]));
350 assert_eq!(params.pubkey_authentication, Some(true));
351 assert_eq!(params.remote_forward, Some(8080));
352 assert_eq!(params.server_alive_interval, Some(Duration::from_secs(60)));
353 assert_eq!(params.tcp_keep_alive, Some(true));
354 #[cfg(target_os = "macos")]
355 assert_eq!(params.use_keychain, Some(true));
356 assert_eq!(params.user, Some(String::from("user")));
357 assert!(params.ignored_fields.contains_key("custom"));
358 assert!(params.unsupported_fields.contains_key("unsupported"));
359 }
360
361 #[test]
362 fn test_overwrite_if_none_does_not_overwrite_existing() {
363 let mut params = HostParams::new(&DefaultAlgorithms::empty());
364 params.add_keys_to_agent = Some(false);
365 params.bind_address = Some(String::from("original"));
366 params.compression = Some(false);
367 params.port = Some(2222);
368 params.user = Some(String::from("original_user"));
369 params
370 .ignored_fields
371 .insert("existing".to_string(), vec!["val1".to_string()]);
372 params
373 .unsupported_fields
374 .insert("existing_unsup".to_string(), vec!["val1".to_string()]);
375
376 let mut b = HostParams::new(&DefaultAlgorithms::empty());
377 b.add_keys_to_agent = Some(true);
378 b.bind_address = Some(String::from("new"));
379 b.compression = Some(true);
380 b.port = Some(22);
381 b.user = Some(String::from("new_user"));
382 b.ignored_fields
383 .insert("existing".to_string(), vec!["val2".to_string()]);
384 b.unsupported_fields
385 .insert("existing_unsup".to_string(), vec!["val2".to_string()]);
386
387 params.overwrite_if_none(&b);
388
389 assert_eq!(params.add_keys_to_agent, Some(false));
391 assert_eq!(params.bind_address, Some(String::from("original")));
392 assert_eq!(params.compression, Some(false));
393 assert_eq!(params.port, Some(2222));
394 assert_eq!(params.user, Some(String::from("original_user")));
395 assert_eq!(
396 params.ignored_fields.get("existing"),
397 Some(&vec!["val1".to_string()])
398 );
399 assert_eq!(
400 params.unsupported_fields.get("existing_unsup"),
401 Some(&vec!["val1".to_string()])
402 );
403 }
404
405 #[test]
406 fn test_overwrite_if_none_algorithms_when_self_is_default() {
407 let mut params = HostParams::new(&DefaultAlgorithms::empty());
408
409 let mut b = HostParams::new(&DefaultAlgorithms::empty());
410 b.ca_signature_algorithms
411 .apply(AlgorithmsRule::from_str("ca-algo").expect("parse error"));
412 b.host_key_algorithms
413 .apply(AlgorithmsRule::from_str("hk-algo").expect("parse error"));
414 b.kex_algorithms
415 .apply(AlgorithmsRule::from_str("kex-algo").expect("parse error"));
416 b.mac
417 .apply(AlgorithmsRule::from_str("mac-algo").expect("parse error"));
418 b.pubkey_accepted_algorithms
419 .apply(AlgorithmsRule::from_str("pk-algo").expect("parse error"));
420
421 params.overwrite_if_none(&b);
422
423 assert_eq!(
424 params.ca_signature_algorithms.algorithms(),
425 &["ca-algo".to_string()]
426 );
427 assert_eq!(
428 params.host_key_algorithms.algorithms(),
429 &["hk-algo".to_string()]
430 );
431 assert_eq!(
432 params.kex_algorithms.algorithms(),
433 &["kex-algo".to_string()]
434 );
435 assert_eq!(params.mac.algorithms(), &["mac-algo".to_string()]);
436 assert_eq!(
437 params.pubkey_accepted_algorithms.algorithms(),
438 &["pk-algo".to_string()]
439 );
440 }
441
442 #[test]
443 fn test_overwrite_if_none_algorithms_when_self_is_not_default() {
444 let mut params = HostParams::new(&DefaultAlgorithms::empty());
445 params
446 .ciphers
447 .apply(AlgorithmsRule::from_str("self-cipher").expect("parse error"));
448
449 let mut b = HostParams::new(&DefaultAlgorithms::empty());
450 b.ciphers
451 .apply(AlgorithmsRule::from_str("other-cipher").expect("parse error"));
452
453 params.overwrite_if_none(&b);
454
455 assert_eq!(params.ciphers.algorithms(), &["self-cipher".to_string()]);
457 }
458
459 #[test]
460 fn test_overwrite_if_none_accumulates_identity_files() {
461 let mut params = HostParams::new(&DefaultAlgorithms::empty());
462 params.identity_file = Some(vec![std::path::PathBuf::from("/path/to/key1")]);
463
464 let mut b = HostParams::new(&DefaultAlgorithms::empty());
465 b.identity_file = Some(vec![
466 std::path::PathBuf::from("/path/to/key2"),
467 std::path::PathBuf::from("/path/to/key3"),
468 ]);
469
470 params.overwrite_if_none(&b);
471
472 assert_eq!(
474 params.identity_file,
475 Some(vec![
476 std::path::PathBuf::from("/path/to/key1"),
477 std::path::PathBuf::from("/path/to/key2"),
478 std::path::PathBuf::from("/path/to/key3"),
479 ])
480 );
481 }
482
483 #[test]
484 fn test_overwrite_if_none_identity_files_when_self_is_none() {
485 let mut params = HostParams::new(&DefaultAlgorithms::empty());
486
487 let mut b = HostParams::new(&DefaultAlgorithms::empty());
488 b.identity_file = Some(vec![std::path::PathBuf::from("/path/to/key1")]);
489
490 params.overwrite_if_none(&b);
491
492 assert_eq!(
493 params.identity_file,
494 Some(vec![std::path::PathBuf::from("/path/to/key1")])
495 );
496 }
497}