ssh2_config/
params.rs

1//! # params
2//!
3//! Ssh config params for host rule
4
5mod 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/// Describes the ssh configuration.
15/// Configuration is describes in this document: <http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5>
16/// Only arguments supported by libssh2 are implemented
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct HostParams {
19    /// Specifies whether keys should be automatically added to a running ssh-agent(1)
20    pub add_keys_to_agent: Option<bool>,
21    /// Specifies to use the specified address on the local machine as the source address of the connection
22    pub bind_address: Option<String>,
23    /// Use the specified address on the local machine as the source address of the connection
24    pub bind_interface: Option<String>,
25    /// Specifies which algorithms are allowed for signing of certificates by certificate authorities
26    pub ca_signature_algorithms: Algorithms,
27    /// Specifies a file from which the user's certificate is read
28    pub certificate_file: Option<PathBuf>,
29    /// Specifies the ciphers allowed for protocol version 2 in order of preference
30    pub ciphers: Algorithms,
31    /// Specifies whether to use compression
32    pub compression: Option<bool>,
33    /// Specifies the number of attempts to make before exiting
34    pub connection_attempts: Option<usize>,
35    /// Specifies the timeout used when connecting to the SSH server
36    pub connect_timeout: Option<Duration>,
37    /// Specifies whether the connection to the authentication agent (if any) will be forwarded to the remote machine
38    pub forward_agent: Option<bool>,
39    /// Specifies the host key signature algorithms that the client wants to use in order of preference
40    pub host_key_algorithms: Algorithms,
41    /// Specifies the real host name to log into
42    pub host_name: Option<String>,
43    /// Specifies the path of the identity file to be used when authenticating.
44    /// More than one file can be specified.
45    /// If more than one file is specified, they will be read in order
46    pub identity_file: Option<Vec<PathBuf>>,
47    /// Specifies a pattern-list of unknown options to be ignored if they are encountered in configuration parsing
48    pub ignore_unknown: Option<Vec<String>>,
49    /// Specifies the available KEX (Key Exchange) algorithms
50    pub kex_algorithms: Algorithms,
51    /// Specifies the MAC (message authentication code) algorithms in order of preference
52    pub mac: Algorithms,
53    /// Specifies the port number to connect on the remote host.
54    pub port: Option<u16>,
55    /// Specifies one or more jump proxies as either [user@]host[:port] or an ssh URI
56    pub proxy_jump: Option<Vec<String>>,
57    /// Specifies the signature algorithms that will be used for public key authentication
58    pub pubkey_accepted_algorithms: Algorithms,
59    /// Specifies whether to try public key authentication using SSH keys
60    pub pubkey_authentication: Option<bool>,
61    /// Specifies that a TCP port on the remote machine be forwarded over the secure channel
62    pub remote_forward: Option<u16>,
63    /// Sets a timeout interval in seconds after which if no data has been received from the server, keep alive will be sent
64    pub server_alive_interval: Option<Duration>,
65    /// Specifies whether to send TCP keepalives to the other side
66    pub tcp_keep_alive: Option<bool>,
67    #[cfg(target_os = "macos")]
68    /// specifies whether the system should search for passphrases in the user's keychain when attempting to use a particular key
69    pub use_keychain: Option<bool>,
70    /// Specifies the user to log in as.
71    pub user: Option<String>,
72    /// fields that the parser wasn't able to parse
73    pub ignored_fields: HashMap<String, Vec<String>>,
74    /// fields that the parser was able to parse but ignored
75    pub unsupported_fields: HashMap<String, Vec<String>>,
76}
77
78impl HostParams {
79    /// Create a new [`HostParams`] object with the [`DefaultAlgorithms`]
80    pub fn new(default_algorithms: &DefaultAlgorithms) -> Self {
81        Self {
82            add_keys_to_agent: None,
83            bind_address: None,
84            bind_interface: None,
85            ca_signature_algorithms: Algorithms::new(&default_algorithms.ca_signature_algorithms),
86            certificate_file: None,
87            ciphers: Algorithms::new(&default_algorithms.ciphers),
88            compression: None,
89            connection_attempts: None,
90            connect_timeout: None,
91            forward_agent: None,
92            host_key_algorithms: Algorithms::new(&default_algorithms.host_key_algorithms),
93            host_name: None,
94            identity_file: None,
95            ignore_unknown: None,
96            kex_algorithms: Algorithms::new(&default_algorithms.kex_algorithms),
97            mac: Algorithms::new(&default_algorithms.mac),
98            port: None,
99            proxy_jump: None,
100            pubkey_accepted_algorithms: Algorithms::new(
101                &default_algorithms.pubkey_accepted_algorithms,
102            ),
103            pubkey_authentication: None,
104            remote_forward: None,
105            server_alive_interval: None,
106            tcp_keep_alive: None,
107            #[cfg(target_os = "macos")]
108            use_keychain: None,
109            user: None,
110            ignored_fields: HashMap::new(),
111            unsupported_fields: HashMap::new(),
112        }
113    }
114
115    /// Return whether a certain `param` is in the ignored list
116    pub(crate) fn ignored(&self, param: &str) -> bool {
117        self.ignore_unknown
118            .as_ref()
119            .map(|x| x.iter().any(|x| x.as_str() == param))
120            .unwrap_or(false)
121    }
122
123    /// Given a [`HostParams`] object `b`, it will overwrite all the params from `self` only if they are [`None`]
124    pub fn overwrite_if_none(&mut self, b: &Self) {
125        self.add_keys_to_agent = self.add_keys_to_agent.or(b.add_keys_to_agent);
126        self.bind_address = self.bind_address.clone().or_else(|| b.bind_address.clone());
127        self.bind_interface = self
128            .bind_interface
129            .clone()
130            .or_else(|| b.bind_interface.clone());
131        self.certificate_file = self
132            .certificate_file
133            .clone()
134            .or_else(|| b.certificate_file.clone());
135        self.compression = self.compression.or(b.compression);
136        self.connection_attempts = self.connection_attempts.or(b.connection_attempts);
137        self.connect_timeout = self.connect_timeout.or(b.connect_timeout);
138        self.forward_agent = self.forward_agent.or(b.forward_agent);
139        self.host_name = self.host_name.clone().or_else(|| b.host_name.clone());
140        self.identity_file = self
141            .identity_file
142            .clone()
143            .or_else(|| b.identity_file.clone());
144        self.ignore_unknown = self
145            .ignore_unknown
146            .clone()
147            .or_else(|| b.ignore_unknown.clone());
148        self.port = self.port.or(b.port);
149        self.proxy_jump = self.proxy_jump.clone().or_else(|| b.proxy_jump.clone());
150        self.pubkey_authentication = self.pubkey_authentication.or(b.pubkey_authentication);
151        self.remote_forward = self.remote_forward.or(b.remote_forward);
152        self.server_alive_interval = self.server_alive_interval.or(b.server_alive_interval);
153        #[cfg(target_os = "macos")]
154        {
155            self.use_keychain = self.use_keychain.or(b.use_keychain);
156        }
157        self.tcp_keep_alive = self.tcp_keep_alive.or(b.tcp_keep_alive);
158        self.user = self.user.clone().or_else(|| b.user.clone());
159        for (ignored_field, args) in &b.ignored_fields {
160            if !self.ignored_fields.contains_key(ignored_field) {
161                self.ignored_fields
162                    .insert(ignored_field.to_owned(), args.to_owned());
163            }
164        }
165        for (unsupported_field, args) in &b.unsupported_fields {
166            if !self.unsupported_fields.contains_key(unsupported_field) {
167                self.unsupported_fields
168                    .insert(unsupported_field.to_owned(), args.to_owned());
169            }
170        }
171
172        // merge algos if default and b is not default
173        if self.ca_signature_algorithms.is_default() && !b.ca_signature_algorithms.is_default() {
174            self.ca_signature_algorithms = b.ca_signature_algorithms.clone();
175        }
176        if self.ciphers.is_default() && !b.ciphers.is_default() {
177            self.ciphers = b.ciphers.clone();
178        }
179        if self.host_key_algorithms.is_default() && !b.host_key_algorithms.is_default() {
180            self.host_key_algorithms = b.host_key_algorithms.clone();
181        }
182        if self.kex_algorithms.is_default() && !b.kex_algorithms.is_default() {
183            self.kex_algorithms = b.kex_algorithms.clone();
184        }
185        if self.mac.is_default() && !b.mac.is_default() {
186            self.mac = b.mac.clone();
187        }
188        if self.pubkey_accepted_algorithms.is_default()
189            && !b.pubkey_accepted_algorithms.is_default()
190        {
191            self.pubkey_accepted_algorithms = b.pubkey_accepted_algorithms.clone();
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198
199    use std::str::FromStr;
200
201    use pretty_assertions::assert_eq;
202
203    use super::*;
204    use crate::params::algos::AlgorithmsRule;
205
206    #[test]
207    fn should_initialize_params() {
208        let params = HostParams::new(&DefaultAlgorithms::default());
209        assert!(params.add_keys_to_agent.is_none());
210        assert!(params.bind_address.is_none());
211        assert!(params.bind_interface.is_none());
212        assert_eq!(
213            params.ca_signature_algorithms.algorithms(),
214            DefaultAlgorithms::default().ca_signature_algorithms
215        );
216        assert!(params.certificate_file.is_none());
217        assert_eq!(
218            params.ciphers.algorithms(),
219            DefaultAlgorithms::default().ciphers
220        );
221        assert!(params.compression.is_none());
222        assert!(params.connection_attempts.is_none());
223        assert!(params.connect_timeout.is_none());
224        assert!(params.forward_agent.is_none());
225        assert_eq!(
226            params.host_key_algorithms.algorithms(),
227            DefaultAlgorithms::default().host_key_algorithms
228        );
229        assert!(params.host_name.is_none());
230        assert!(params.identity_file.is_none());
231        assert!(params.ignore_unknown.is_none());
232        assert_eq!(
233            params.kex_algorithms.algorithms(),
234            DefaultAlgorithms::default().kex_algorithms
235        );
236        assert_eq!(params.mac.algorithms(), DefaultAlgorithms::default().mac);
237        assert!(params.port.is_none());
238        assert!(params.proxy_jump.is_none());
239        assert_eq!(
240            params.pubkey_accepted_algorithms.algorithms(),
241            DefaultAlgorithms::default().pubkey_accepted_algorithms
242        );
243        assert!(params.pubkey_authentication.is_none());
244        assert!(params.remote_forward.is_none());
245        assert!(params.server_alive_interval.is_none());
246        #[cfg(target_os = "macos")]
247        assert!(params.use_keychain.is_none());
248        assert!(params.tcp_keep_alive.is_none());
249    }
250
251    #[test]
252    fn test_should_overwrite_if_none() {
253        let mut params = HostParams::new(&DefaultAlgorithms::default());
254        params.bind_address = Some(String::from("pippo"));
255
256        let mut b = HostParams::new(&DefaultAlgorithms::default());
257        b.bind_address = Some(String::from("pluto"));
258        b.bind_interface = Some(String::from("tun0"));
259        b.ciphers
260            .apply(AlgorithmsRule::from_str("c,d").expect("parse error"));
261
262        params.overwrite_if_none(&b);
263        assert_eq!(params.bind_address.unwrap(), "pippo");
264        assert_eq!(params.bind_interface.unwrap(), "tun0");
265
266        // algos
267        assert_eq!(
268            params.ciphers.algorithms(),
269            vec!["c".to_string(), "d".to_string()]
270        );
271    }
272}