ssh2_config/
params.rs

1//! # params
2//!
3//! Ssh config params for host rule
4
5use std::collections::HashMap;
6
7use super::{Duration, PathBuf};
8
9/// Describes the ssh configuration.
10/// Configuration is describes in this document: <http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5>
11/// Only arguments supported by libssh2 are implemented
12#[derive(Debug, Default, Clone, PartialEq, Eq)]
13pub struct HostParams {
14    /// Specifies to use the specified address on the local machine as the source address of the connection
15    pub bind_address: Option<String>,
16    /// Use the specified address on the local machine as the source address of the connection
17    pub bind_interface: Option<String>,
18    /// Specifies which algorithms are allowed for signing of certificates by certificate authorities
19    pub ca_signature_algorithms: Option<Vec<String>>,
20    /// Specifies a file from which the user's certificate is read
21    pub certificate_file: Option<PathBuf>,
22    /// Specifies the ciphers allowed for protocol version 2 in order of preference
23    pub ciphers: Option<Vec<String>>,
24    /// Specifies whether to use compression
25    pub compression: Option<bool>,
26    /// Specifies the number of attempts to make before exiting
27    pub connection_attempts: Option<usize>,
28    /// Specifies the timeout used when connecting to the SSH server
29    pub connect_timeout: Option<Duration>,
30    /// Specifies the host key signature algorithms that the client wants to use in order of preference
31    pub host_key_algorithms: Option<Vec<String>>,
32    /// Specifies the real host name to log into
33    pub host_name: Option<String>,
34    /// Specifies the path of the identity file to be used when authenticating.
35    /// More than one file can be specified.
36    /// If more than one file is specified, they will be read in order
37    pub identity_file: Option<Vec<PathBuf>>,
38    /// Specifies a pattern-list of unknown options to be ignored if they are encountered in configuration parsing
39    pub ignore_unknown: Option<Vec<String>>,
40    /// Specifies the available KEX (Key Exchange) algorithms
41    pub kex_algorithms: Option<Vec<String>>,
42    /// Specifies the MAC (message authentication code) algorithms in order of preference
43    pub mac: Option<Vec<String>>,
44    /// Specifies the port number to connect on the remote host.
45    pub port: Option<u16>,
46    /// Specifies the signature algorithms that will be used for public key authentication
47    pub pubkey_accepted_algorithms: Option<Vec<String>>,
48    /// Specifies whether to try public key authentication using SSH keys
49    pub pubkey_authentication: Option<bool>,
50    /// Specifies that a TCP port on the remote machine be forwarded over the secure channel
51    pub remote_forward: Option<u16>,
52    /// Sets a timeout interval in seconds after which if no data has been received from the server, keep alive will be sent
53    pub server_alive_interval: Option<Duration>,
54    /// Specifies whether to send TCP keepalives to the other side
55    pub tcp_keep_alive: Option<bool>,
56    #[cfg(target_os = "macos")]
57    /// specifies whether the system should search for passphrases in the user's keychain when attempting to use a particular key
58    pub use_keychain: Option<bool>,
59    /// Specifies the user to log in as.
60    pub user: Option<String>,
61    /// fields that the parser wasn't able to parse
62    pub ignored_fields: HashMap<String, Vec<String>>,
63    /// fields that the parser was able to parse but ignored
64    pub unsupported_fields: HashMap<String, Vec<String>>,
65}
66
67impl HostParams {
68    /// Return whether `param` is in ignored list
69    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    /// Override current params with params of `b`
77    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    /// Resolve algorithms list.
192    /// if the first argument starts with `+`, then the provided algorithms are PUSHED onto existing list
193    /// if the first argument starts with `-`, then the provided algorithms are REMOVED from existing list
194    /// otherwise the provided list will JUST replace the existing list
195    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            // Concat
202            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            // Remove
209            let new_first = [first.replacen('-', "", 1)];
210            // Remove algos from current_list
211            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        // merge twices
309        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}