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