libssh_rs/
lib.rs

1//! This crate provides ergonomic bindings to the functions
2//! provided by [libssh](https://libssh.org), a library that provides
3//! an implementation of the SSH 2 protocol.  It is distinct from the
4//! `ssh2` rust crate which uses [libssh2](https://www.libssh2.org),
5//! which is an unrelated project that implements similar functionality!
6
7// This is a bad lint
8#![allow(clippy::wildcard_in_or_patterns)]
9
10/// Re-exporting the underlying unsafe API, should you need it
11pub use libssh_rs_sys as sys;
12
13use std::ffi::{CStr, CString};
14use std::os::raw::{c_int, c_uint, c_ulong};
15#[cfg(unix)]
16use std::os::unix::io::RawFd as RawSocket;
17#[cfg(windows)]
18use std::os::windows::io::RawSocket;
19use std::ptr::null_mut;
20use std::sync::LazyLock;
21use std::sync::{Arc, Mutex, MutexGuard};
22use std::time::Duration;
23
24mod channel;
25mod error;
26mod sftp;
27
28pub use crate::channel::*;
29pub use crate::error::*;
30pub use crate::sftp::*;
31
32struct LibraryState {}
33impl LibraryState {
34    pub fn new() -> Option<Self> {
35        // Force openssl to initialize.
36        // In theory, we don't need this, but in practice we do because of
37        // this bug:
38        // <https://github.com/openssl/openssl/issues/6214>
39        // which weirdly requires that *all* openssl threads be joined before
40        // the process exits, which is an unrealistic expectation on behalf
41        // of that library.
42        // That was worked around in openssl_sys:
43        // <https://github.com/sfackler/rust-openssl/pull/1324>
44        // which tells openssl to skip the process-wide shutdown.
45        openssl_sys::init();
46        let res = unsafe { sys::ssh_init() };
47        if res != sys::SSH_OK as i32 {
48            None
49        } else {
50            Some(Self {})
51        }
52    }
53}
54impl Drop for LibraryState {
55    fn drop(&mut self) {
56        unsafe { sys::ssh_finalize() };
57    }
58}
59
60static LIB: LazyLock<Option<LibraryState>> = LazyLock::new(|| LibraryState::new());
61
62fn initialize() -> SshResult<()> {
63    if LIB.is_none() {
64        Err(Error::fatal("ssh_init failed"))
65    } else {
66        Ok(())
67    }
68}
69
70pub(crate) struct SessionHolder {
71    sess: sys::ssh_session,
72    callbacks: sys::ssh_callbacks_struct,
73    auth_callback: Option<Box<dyn FnMut(&str, bool, bool, Option<String>) -> SshResult<String>>>,
74    pending_agent_forward_channels: Vec<sys::ssh_channel>,
75}
76unsafe impl Send for SessionHolder {}
77
78impl std::ops::Deref for SessionHolder {
79    type Target = sys::ssh_session;
80    fn deref(&self) -> &sys::ssh_session {
81        &self.sess
82    }
83}
84
85impl Drop for SessionHolder {
86    fn drop(&mut self) {
87        self.clear_pending_agent_forward_channels();
88        unsafe {
89            sys::ssh_free(self.sess);
90        }
91    }
92}
93
94impl SessionHolder {
95    pub fn is_blocking(&self) -> bool {
96        unsafe { sys::ssh_is_blocking(self.sess) != 0 }
97    }
98
99    fn last_error(&self) -> Option<Error> {
100        let code = unsafe { sys::ssh_get_error_code(self.sess as _) } as sys::ssh_error_types_e;
101        if code == sys::ssh_error_types_e_SSH_NO_ERROR {
102            return None;
103        }
104
105        let reason = unsafe { sys::ssh_get_error(self.sess as _) };
106        let reason = if reason.is_null() {
107            String::new()
108        } else {
109            unsafe { CStr::from_ptr(reason) }
110                .to_string_lossy()
111                .to_string()
112        };
113
114        if code == sys::ssh_error_types_e_SSH_REQUEST_DENIED {
115            Some(Error::RequestDenied(reason))
116        } else {
117            Some(Error::Fatal(reason))
118        }
119    }
120
121    fn basic_status(&self, res: i32, what: &str) -> SshResult<()> {
122        if res == sys::SSH_OK as i32 {
123            Ok(())
124        } else if res == sys::SSH_AGAIN {
125            Err(Error::TryAgain)
126        } else if let Some(err) = self.last_error() {
127            Err(err)
128        } else {
129            Err(Error::fatal(what))
130        }
131    }
132
133    fn blocking_flush(&self, timeout: Option<Duration>) -> SshResult<()> {
134        let timeout = match timeout {
135            Some(t) => t.as_millis() as c_int,
136            None => -1,
137        };
138        let res = unsafe { sys::ssh_blocking_flush(self.sess, timeout) };
139        self.basic_status(res, "blocking_flush")
140    }
141
142    fn auth_result(&self, res: sys::ssh_auth_e, what: &str) -> SshResult<AuthStatus> {
143        match res {
144            sys::ssh_auth_e_SSH_AUTH_SUCCESS => Ok(AuthStatus::Success),
145            sys::ssh_auth_e_SSH_AUTH_DENIED => Ok(AuthStatus::Denied),
146            sys::ssh_auth_e_SSH_AUTH_PARTIAL => Ok(AuthStatus::Partial),
147            sys::ssh_auth_e_SSH_AUTH_INFO => Ok(AuthStatus::Info),
148            sys::ssh_auth_e_SSH_AUTH_AGAIN => Ok(AuthStatus::Again),
149            sys::ssh_auth_e_SSH_AUTH_ERROR | _ => {
150                if let Some(err) = self.last_error() {
151                    Err(err)
152                } else {
153                    Err(Error::fatal(what))
154                }
155            }
156        }
157    }
158
159    fn clear_pending_agent_forward_channels(&mut self) {
160        for chan in self.pending_agent_forward_channels.drain(..) {
161            unsafe {
162                // We have no callbacks on these channels, no need to cleanup.
163                sys::ssh_channel_free(chan);
164            }
165        }
166    }
167}
168
169/// A Session represents the state needed to make a connection to
170/// a remote host.
171///
172/// You need at least one Session per target host.
173/// A given session can open multiple `Channel`s to perform multiple actions
174/// on a given target host.
175///
176/// # Thread Safety
177///
178/// libssh doesn't allow using anything associated with a given `Session`
179/// from multiple threads concurrently.  These Rust bindings encapsulate
180/// the underlying `Session` in an internal mutex, which allows you to
181/// safely operate on the various elements of the session and even move
182/// them to other threads, but you need to be aware that calling methods
183/// on any of those structs will attempt to lock the underlying session,
184/// and this can lead to blocking in surprising situations.
185pub struct Session {
186    sess: Arc<Mutex<SessionHolder>>,
187}
188
189impl Session {
190    /// Create a new Session.
191    pub fn new() -> SshResult<Self> {
192        initialize()?;
193        let sess = unsafe { sys::ssh_new() };
194        if sess.is_null() {
195            Err(Error::fatal("ssh_new failed"))
196        } else {
197            let callbacks = sys::ssh_callbacks_struct {
198                size: std::mem::size_of::<sys::ssh_callbacks_struct>(),
199                userdata: std::ptr::null_mut(),
200                auth_function: None,
201                log_function: None,
202                connect_status_function: None,
203                global_request_function: None,
204                channel_open_request_x11_function: None,
205                channel_open_request_auth_agent_function: None,
206                channel_open_request_forwarded_tcpip_function: None,
207            };
208            let sess = Arc::new(Mutex::new(SessionHolder {
209                sess,
210                callbacks,
211                auth_callback: None,
212                pending_agent_forward_channels: Vec::new(),
213            }));
214
215            {
216                let mut sess = sess.lock().unwrap();
217                let ptr: *mut SessionHolder = &mut *sess;
218                sess.callbacks.userdata = ptr as _;
219
220                unsafe {
221                    sys::ssh_set_callbacks(**sess, &mut sess.callbacks);
222                }
223            }
224
225            Ok(Self { sess })
226        }
227    }
228
229    unsafe extern "C" fn bridge_auth_callback(
230        prompt: *const ::std::os::raw::c_char,
231        buf: *mut ::std::os::raw::c_char,
232        len: usize,
233        echo: ::std::os::raw::c_int,
234        verify: ::std::os::raw::c_int,
235        userdata: *mut ::std::os::raw::c_void,
236    ) -> ::std::os::raw::c_int {
237        let prompt = CStr::from_ptr(prompt).to_string_lossy().to_string();
238        let echo = if echo == 0 { false } else { true };
239        let verify = if verify == 0 { false } else { true };
240
241        let result = std::panic::catch_unwind(|| {
242            let sess: &mut SessionHolder = &mut *(userdata as *mut SessionHolder);
243
244            let identity = {
245                let mut value = std::ptr::null_mut();
246                sys::ssh_userauth_publickey_auto_get_current_identity(**sess, &mut value);
247                if value.is_null() {
248                    None
249                } else {
250                    let s = CStr::from_ptr(value).to_string_lossy().to_string();
251                    sys::ssh_string_free_char(value);
252                    Some(s)
253                }
254            };
255
256            let cb = sess.auth_callback.as_mut().unwrap();
257            let response = (cb)(&prompt, echo, verify, identity)?;
258            if response.len() > len {
259                return Err(Error::Fatal(format!(
260                    "passphrase is larger than buffer allows {} vs available {}",
261                    response.len(),
262                    len
263                )));
264            }
265
266            let len = response.len().min(len);
267            let buf = std::slice::from_raw_parts_mut(buf as *mut u8, len);
268            buf.copy_from_slice(response.as_bytes());
269
270            Ok(())
271        });
272
273        match result {
274            Err(err) => {
275                eprintln!("Error in auth callback: {:?}", err);
276                sys::SSH_ERROR
277            }
278            Ok(Err(err)) => {
279                eprintln!("Error in auth callback: {:#}", err);
280                sys::SSH_ERROR
281            }
282            Ok(Ok(())) => sys::SSH_OK as c_int,
283        }
284    }
285
286    unsafe extern "C" fn channel_open_request_auth_agent_callback(
287        session: sys::ssh_session,
288        userdata: *mut ::std::os::raw::c_void,
289    ) -> sys::ssh_channel {
290        let sess: &mut SessionHolder = &mut *(userdata as *mut SessionHolder);
291        let chan = sys::ssh_channel_new(session);
292        if chan.is_null() {
293            eprintln!("ssh_channel_new failed: {:?}", sess.last_error());
294            return std::ptr::null_mut();
295        }
296        // We are guarenteed to be holding a session lock here.
297        sess.pending_agent_forward_channels.push(chan);
298        chan
299    }
300
301    /// Sets a callback that is used by libssh when it needs to prompt
302    /// for the passphrase during public key authentication.
303    /// This is NOT used for password or keyboard interactive authentication.
304    /// The callback has the signature:
305    ///
306    /// ```no_run
307    /// use libssh_rs::SshResult;
308    /// fn callback(prompt: &str, echo: bool, verify: bool,
309    ///             identity: Option<String>) -> SshResult<String> {
310    ///  unimplemented!()
311    /// }
312    /// ```
313    ///
314    /// The `prompt` parameter is the prompt text to show to the user.
315    /// The `identity` parameter, if not None, will hold the identity that
316    /// is currently being tried by the `userauth_public_key_auto` method,
317    /// which is helpful to show to the user so that they can input the
318    /// correct passphrase.
319    ///
320    /// The `echo` parameter, if `true`, means that the input entered by
321    /// the user should be visible on screen. If `false`, it should not be
322    /// shown on screen because it is deemed sensitive in some way.
323    ///
324    /// The `verify` parameter, if `true`, means that the user should be
325    /// prompted twice to make sure they entered the same text both times.
326    ///
327    /// The function should return the user's input as a string, or an
328    /// `Error` indicating what went wrong.
329    ///
330    /// You can use the `get_input` function to satisfy the auth callback:
331    ///
332    /// ```
333    /// use libssh_rs::*;
334    /// let sess = Session::new().unwrap();
335    /// sess.set_auth_callback(|prompt, echo, verify, identity| {
336    ///     let prompt = match identity {
337    ///         Some(ident) => format!("{} ({}): ", prompt, ident),
338    ///         None => prompt.to_string(),
339    ///     };
340    ///     get_input(&prompt, None, echo, verify)
341    ///         .ok_or_else(|| Error::Fatal("reading password".to_string()))
342    /// });
343    /// ```
344    pub fn set_auth_callback<F>(&self, callback: F)
345    where
346        F: FnMut(&str, bool, bool, Option<String>) -> SshResult<String> + 'static,
347    {
348        let mut sess = self.lock_session();
349        sess.auth_callback.replace(Box::new(callback));
350        sess.callbacks.auth_function = Some(Self::bridge_auth_callback);
351    }
352
353    /// Enable or disable creating channels when the remote side requests a new channel for SSH
354    /// agent forwarding.
355    /// You are supposed to periodically check whether there's pending channels (already bound to
356    /// remote side's agent client) by using the `accept_agent_forward` function.
357    pub fn enable_accept_agent_forward(&self, enable: bool) {
358        let mut sess = self.lock_session();
359        sess.callbacks.channel_open_request_auth_agent_function = if enable {
360            Some(Self::channel_open_request_auth_agent_callback)
361        } else {
362            sess.clear_pending_agent_forward_channels();
363            // libssh denies auth agent channel requests with no callback set.
364            None
365        }
366    }
367
368    // Accept an auth agent forward channel.
369    // Returns a `Channel` bound to the remote side SSH agent client, or `None` if no pending
370    // request from the server.
371    pub fn accept_agent_forward(&self) -> Option<Channel> {
372        let mut sess = self.lock_session();
373        let chan = sess.pending_agent_forward_channels.pop()?;
374        Some(Channel::new(&self.sess, chan))
375    }
376
377    /// Create a new channel.
378    /// Channels are used to handle I/O for commands and forwarded streams.
379    pub fn new_channel(&self) -> SshResult<Channel> {
380        let sess = self.lock_session();
381        let chan = unsafe { sys::ssh_channel_new(**sess) };
382        if chan.is_null() {
383            if let Some(err) = sess.last_error() {
384                Err(err)
385            } else {
386                Err(Error::fatal("ssh_channel_new failed"))
387            }
388        } else {
389            Ok(Channel::new(&self.sess, chan))
390        }
391    }
392
393    fn lock_session(&self) -> MutexGuard<SessionHolder> {
394        self.sess.lock().unwrap()
395    }
396
397    /// Blocking flush of the outgoing buffer.
398    pub fn blocking_flush(&self, timeout: Option<Duration>) -> SshResult<()> {
399        let sess = self.lock_session();
400        sess.blocking_flush(timeout)
401    }
402
403    /// Disconnect from a session (client or server).
404    /// The session can then be reused to open a new session.
405    pub fn disconnect(&self) {
406        let sess = self.lock_session();
407        unsafe { sys::ssh_disconnect(**sess) };
408    }
409
410    /// Connect to the configured remote host
411    pub fn connect(&self) -> SshResult<()> {
412        let sess = self.lock_session();
413        let res = unsafe { sys::ssh_connect(**sess) };
414        sess.basic_status(res, "ssh_connect failed")
415    }
416
417    /// Check if the servers public key for the connected session is known.
418    /// This checks if we already know the public key of the server we want
419    /// to connect to. This allows to detect if there is a MITM attack going
420    /// on of if there have been changes on the server we don't know about.
421    pub fn is_known_server(&self) -> SshResult<KnownHosts> {
422        let sess = self.lock_session();
423        match unsafe { sys::ssh_session_is_known_server(**sess) } {
424            sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_NOT_FOUND => Ok(KnownHosts::NotFound),
425            sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_UNKNOWN => Ok(KnownHosts::Unknown),
426            sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_OK => Ok(KnownHosts::Ok),
427            sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_CHANGED => Ok(KnownHosts::Changed),
428            sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_OTHER => Ok(KnownHosts::Other),
429            sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_ERROR | _ => {
430                if let Some(err) = sess.last_error() {
431                    Err(err)
432                } else {
433                    Err(Error::fatal("unknown error in ssh_session_is_known_server"))
434                }
435            }
436        }
437    }
438
439    /// Add the current connected server to the user known_hosts file.
440    /// This adds the currently connected server to the known_hosts file
441    /// by appending a new line at the end. The global known_hosts file
442    /// is considered read-only so it is not touched by this function.
443    pub fn update_known_hosts_file(&self) -> SshResult<()> {
444        let sess = self.lock_session();
445        let res = unsafe { sys::ssh_session_update_known_hosts(**sess) };
446
447        if res == sys::SSH_OK as i32 {
448            Ok(())
449        } else if let Some(err) = sess.last_error() {
450            Err(err)
451        } else {
452            Err(Error::fatal("error updating known hosts file"))
453        }
454    }
455
456    /// Parse the ssh config file.
457    /// This should be the last call of all options, it may overwrite options
458    /// which are already set.
459    /// It requires that the `SshOption::Hostname` is already set.
460    /// if `file_name` is None the default `~/.ssh/config` will be used.
461    pub fn options_parse_config(&self, file_name: Option<&str>) -> SshResult<()> {
462        let sess = self.lock_session();
463        let file_name = opt_str_to_cstring(file_name);
464        let res = unsafe { sys::ssh_options_parse_config(**sess, opt_cstring_to_cstr(&file_name)) };
465        if res == 0 {
466            Ok(())
467        } else if let Some(err) = sess.last_error() {
468            Err(err)
469        } else {
470            Err(Error::Fatal(format!(
471                "error parsing config file: {:?}",
472                file_name
473            )))
474        }
475    }
476
477    /// Get the issue banner from the server.
478    /// This is the banner showing a disclaimer to users who log in,
479    /// typically their right or the fact that they will be monitored.
480    pub fn get_issue_banner(&self) -> SshResult<String> {
481        let sess = self.lock_session();
482        let banner = unsafe { sys::ssh_get_issue_banner(**sess) };
483        if banner.is_null() {
484            if let Some(err) = sess.last_error() {
485                Err(err)
486            } else {
487                Err(Error::fatal("failed to get issue banner"))
488            }
489        } else {
490            let banner_text = unsafe { CStr::from_ptr(banner) }
491                .to_string_lossy()
492                .to_string();
493            unsafe { sys::ssh_string_free_char(banner) };
494            Ok(banner_text)
495        }
496    }
497
498    /// Gets the server banner.
499    /// This typically holds the server version information
500    pub fn get_server_banner(&self) -> SshResult<String> {
501        let sess = self.lock_session();
502        let banner = unsafe { sys::ssh_get_serverbanner(**sess) };
503        if banner.is_null() {
504            if let Some(err) = sess.last_error() {
505                Err(err)
506            } else {
507                Err(Error::fatal("failed to get server banner"))
508            }
509        } else {
510            let banner_text = unsafe { CStr::from_ptr(banner) }
511                .to_string_lossy()
512                .to_string();
513            Ok(banner_text)
514        }
515    }
516
517    /// Returns the user name that will be used to authenticate with the remote host
518    pub fn get_user_name(&self) -> SshResult<String> {
519        let sess = self.lock_session();
520        let mut name = std::ptr::null_mut();
521        let res = unsafe {
522            sys::ssh_options_get(**sess, sys::ssh_options_e::SSH_OPTIONS_USER, &mut name)
523        };
524        if res != sys::SSH_OK as i32 || name.is_null() {
525            if let Some(err) = sess.last_error() {
526                Err(err)
527            } else {
528                Err(Error::fatal("error getting user name"))
529            }
530        } else {
531            let user_name = unsafe { CStr::from_ptr(name) }
532                .to_string_lossy()
533                .to_string();
534            unsafe { sys::ssh_string_free_char(name) };
535            Ok(user_name)
536        }
537    }
538
539    /// Returns the public key as string
540    pub fn get_pubkey(&self) -> SshResult<String> {
541        let sess = self.lock_session();
542        let sstring = unsafe { sys::ssh_get_pubkey(**sess) };
543        if sstring.is_null() {
544            if let Some(err) = sess.last_error() {
545                Err(err)
546            } else {
547                Err(Error::fatal("failed to get pubkey"))
548            }
549        } else {
550            let key = unsafe { sys::ssh_string_to_char(sstring) };
551            let key_text = unsafe { CStr::from_ptr(key) }.to_string_lossy().to_string();
552            unsafe { sys::ssh_string_free_char(key) };
553            Ok(key_text)
554        }
555    }
556
557    /// Configures the session.
558    /// You will need to set at least `SshOption::Hostname` prior to
559    /// connecting, in order for libssh to know where to connect.
560    pub fn set_option(&self, option: SshOption) -> SshResult<()> {
561        let sess = self.lock_session();
562        let res = match option {
563            SshOption::LogLevel(level) => unsafe {
564                let level = match level {
565                    LogLevel::NoLogging => sys::SSH_LOG_NOLOG,
566                    LogLevel::Warning => sys::SSH_LOG_WARNING,
567                    LogLevel::Protocol => sys::SSH_LOG_PROTOCOL,
568                    LogLevel::Packet => sys::SSH_LOG_PACKET,
569                    LogLevel::Functions => sys::SSH_LOG_FUNCTIONS,
570                } as u32 as c_int;
571                sys::ssh_options_set(
572                    **sess,
573                    sys::ssh_options_e::SSH_OPTIONS_LOG_VERBOSITY,
574                    &level as *const _ as _,
575                )
576            },
577            SshOption::Hostname(name) => unsafe {
578                let name = CString::new(name)?;
579                sys::ssh_options_set(
580                    **sess,
581                    sys::ssh_options_e::SSH_OPTIONS_HOST,
582                    name.as_ptr() as _,
583                )
584            },
585            SshOption::BindAddress(name) => unsafe {
586                let name = CString::new(name)?;
587                sys::ssh_options_set(
588                    **sess,
589                    sys::ssh_options_e::SSH_OPTIONS_BINDADDR,
590                    name.as_ptr() as _,
591                )
592            },
593            SshOption::KeyExchange(name) => unsafe {
594                let name = CString::new(name)?;
595                sys::ssh_options_set(
596                    **sess,
597                    sys::ssh_options_e::SSH_OPTIONS_KEY_EXCHANGE,
598                    name.as_ptr() as _,
599                )
600            },
601            SshOption::HostKeys(name) => unsafe {
602                let name = CString::new(name)?;
603                sys::ssh_options_set(
604                    **sess,
605                    sys::ssh_options_e::SSH_OPTIONS_HOSTKEYS,
606                    name.as_ptr() as _,
607                )
608            },
609            SshOption::PublicKeyAcceptedTypes(name) => unsafe {
610                let name = CString::new(name)?;
611                sys::ssh_options_set(
612                    **sess,
613                    sys::ssh_options_e::SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES,
614                    name.as_ptr() as _,
615                )
616            },
617            SshOption::AddIdentity(name) => unsafe {
618                let name = CString::new(name)?;
619                sys::ssh_options_set(
620                    **sess,
621                    sys::ssh_options_e::SSH_OPTIONS_ADD_IDENTITY,
622                    name.as_ptr() as _,
623                )
624            },
625            SshOption::IdentityAgent(name) => unsafe {
626                let name = opt_string_to_cstring(name);
627                sys::ssh_options_set(
628                    **sess,
629                    sys::ssh_options_e::SSH_OPTIONS_IDENTITY_AGENT,
630                    opt_cstring_to_cstr(&name) as _,
631                )
632            },
633            SshOption::User(name) => unsafe {
634                let name = opt_string_to_cstring(name);
635                sys::ssh_options_set(
636                    **sess,
637                    sys::ssh_options_e::SSH_OPTIONS_USER,
638                    opt_cstring_to_cstr(&name) as _,
639                )
640            },
641            SshOption::SshDir(name) => unsafe {
642                let name = opt_string_to_cstring(name);
643                sys::ssh_options_set(
644                    **sess,
645                    sys::ssh_options_e::SSH_OPTIONS_SSH_DIR,
646                    opt_cstring_to_cstr(&name) as _,
647                )
648            },
649            SshOption::KnownHosts(known_hosts) => unsafe {
650                let known_hosts = opt_string_to_cstring(known_hosts);
651                sys::ssh_options_set(
652                    **sess,
653                    sys::ssh_options_e::SSH_OPTIONS_KNOWNHOSTS,
654                    opt_cstring_to_cstr(&known_hosts) as _,
655                )
656            },
657            SshOption::ProxyCommand(cmd) => unsafe {
658                let cmd = opt_string_to_cstring(cmd);
659                sys::ssh_options_set(
660                    **sess,
661                    sys::ssh_options_e::SSH_OPTIONS_PROXYCOMMAND,
662                    opt_cstring_to_cstr(&cmd) as _,
663                )
664            },
665            SshOption::Port(port) => {
666                let port: c_uint = port.into();
667                unsafe {
668                    sys::ssh_options_set(
669                        **sess,
670                        sys::ssh_options_e::SSH_OPTIONS_PORT,
671                        &port as *const _ as _,
672                    )
673                }
674            }
675            SshOption::Socket(socket) => unsafe {
676                sys::ssh_options_set(
677                    **sess,
678                    sys::ssh_options_e::SSH_OPTIONS_FD,
679                    &socket as *const _ as _,
680                )
681            },
682            SshOption::Timeout(duration) => unsafe {
683                let micros: c_ulong = duration.as_micros() as c_ulong;
684                sys::ssh_options_set(
685                    **sess,
686                    sys::ssh_options_e::SSH_OPTIONS_TIMEOUT_USEC,
687                    &micros as *const _ as _,
688                )
689            },
690            SshOption::CiphersCS(name) => unsafe {
691                let name = CString::new(name)?;
692                sys::ssh_options_set(
693                    **sess,
694                    sys::ssh_options_e::SSH_OPTIONS_CIPHERS_C_S,
695                    name.as_ptr() as _,
696                )
697            },
698            SshOption::CiphersSC(name) => unsafe {
699                let name = CString::new(name)?;
700                sys::ssh_options_set(
701                    **sess,
702                    sys::ssh_options_e::SSH_OPTIONS_CIPHERS_S_C,
703                    name.as_ptr() as _,
704                )
705            },
706            SshOption::HmacCS(name) => unsafe {
707                let name = CString::new(name)?;
708                sys::ssh_options_set(
709                    **sess,
710                    sys::ssh_options_e::SSH_OPTIONS_HMAC_C_S,
711                    name.as_ptr() as _,
712                )
713            },
714            SshOption::HmacSC(name) => unsafe {
715                let name = CString::new(name)?;
716                sys::ssh_options_set(
717                    **sess,
718                    sys::ssh_options_e::SSH_OPTIONS_HMAC_S_C,
719                    name.as_ptr() as _,
720                )
721            },
722            SshOption::ProcessConfig(value) => unsafe {
723                let value: c_uint = value.into();
724                sys::ssh_options_set(
725                    **sess,
726                    sys::ssh_options_e::SSH_OPTIONS_PROCESS_CONFIG,
727                    &value as *const _ as _,
728                )
729            },
730            SshOption::GlobalKnownHosts(known_hosts) => unsafe {
731                let known_hosts = opt_string_to_cstring(known_hosts);
732                sys::ssh_options_set(
733                    **sess,
734                    sys::ssh_options_e::SSH_OPTIONS_GLOBAL_KNOWNHOSTS,
735                    opt_cstring_to_cstr(&known_hosts) as _,
736                )
737            },
738        };
739
740        if res == 0 {
741            Ok(())
742        } else if let Some(err) = sess.last_error() {
743            Err(err)
744        } else {
745            Err(Error::fatal("failed to set option"))
746        }
747    }
748
749    /// This function allows you to get a hash of the public key.
750    /// You can then print this hash in a human-readable form to the user
751    /// so that he is able to verify it.
752    /// It is very important that you verify at some moment that the hash
753    /// matches a known server. If you don't do it, cryptography wont help
754    /// you at making things secure. OpenSSH uses SHA1 to print public key digests.
755    pub fn get_server_public_key(&self) -> SshResult<SshKey> {
756        let sess = self.lock_session();
757        let mut key = std::ptr::null_mut();
758        let res = unsafe { sys::ssh_get_server_publickey(**sess, &mut key) };
759        if res == sys::SSH_OK as i32 && !key.is_null() {
760            Ok(SshKey { key })
761        } else if let Some(err) = sess.last_error() {
762            Err(err)
763        } else {
764            Err(Error::fatal("failed to get server public key"))
765        }
766    }
767
768    /// Try to authenticate with the given public key.
769    ///
770    /// To avoid unnecessary processing and user interaction, the following
771    /// method is provided for querying whether authentication using the
772    /// 'pubkey' would be possible.
773    /// On success, you want now to use [userauth_publickey](#method.userauth_publickey).
774    /// `username` should almost always be `None` to use the username as
775    /// previously configured via [set_option](#method.set_option) or that
776    /// was loaded from the ssh configuration prior to calling
777    /// [connect](#method.connect), as most ssh server implementations
778    /// do not allow changing the username during authentication.
779    ///
780    pub fn userauth_try_publickey(
781        &self,
782        username: Option<&str>,
783        pubkey: &SshKey,
784    ) -> SshResult<AuthStatus> {
785        let sess = self.lock_session();
786
787        let username = opt_str_to_cstring(username);
788
789        let res = unsafe {
790            sys::ssh_userauth_try_publickey(**sess, opt_cstring_to_cstr(&username), pubkey.key)
791        };
792
793        sess.auth_result(res, "failed authenticating with public key ")
794    }
795
796    /// Authenticate with public/private key or certificate.
797    ///
798    /// `username` should almost always be `None` to use the username as
799    /// previously configured via [set_option](#method.set_option) or that
800    /// was loaded from the ssh configuration prior to calling
801    /// [connect](#method.connect), as most ssh server implementations
802    /// do not allow changing the username during authentication.
803    pub fn userauth_publickey(
804        &self,
805        username: Option<&str>,
806        privkey: &SshKey,
807    ) -> SshResult<AuthStatus> {
808        let sess = self.lock_session();
809
810        let username = opt_str_to_cstring(username);
811
812        let res = unsafe {
813            sys::ssh_userauth_publickey(**sess, opt_cstring_to_cstr(&username), privkey.key)
814        };
815
816        sess.auth_result(res, "authentication error")
817    }
818
819    /// Try to authenticate using an ssh agent.
820    ///
821    /// `username` should almost always be `None` to use the username as
822    /// previously configured via [set_option](#method.set_option) or that
823    /// was loaded from the ssh configuration prior to calling
824    /// [connect](#method.connect), as most ssh server implementations
825    /// do not allow changing the username during authentication.
826    pub fn userauth_agent(&self, username: Option<&str>) -> SshResult<AuthStatus> {
827        let sess = self.lock_session();
828
829        let username = opt_str_to_cstring(username);
830
831        let res = unsafe { sys::ssh_userauth_agent(**sess, opt_cstring_to_cstr(&username)) };
832
833        sess.auth_result(res, "authentication error")
834    }
835
836    /// Try to automatically authenticate using public key authentication.
837    ///
838    /// This will attempt to use an ssh agent if available, and will then
839    /// attempt to use your keys/identities from your `~/.ssh` dir.
840    ///
841    /// `username` should almost always be `None` to use the username as
842    /// previously configured via [set_option](#method.set_option) or that
843    /// was loaded from the ssh configuration prior to calling
844    /// [connect](#method.connect), as most ssh server implementations
845    /// do not allow changing the username during authentication.
846    ///
847    /// The `password` parameter can be used to pre-fill a password to
848    /// unlock the private key(s).  Leaving it set to `None` will cause
849    /// libssh to prompt for the passphrase if you have previously
850    /// used [set_auth_callback](#method.set_auth_callback)
851    /// to configure a callback.  If you haven't set the callback and
852    /// a key is password protected, this authentication method will fail.
853    pub fn userauth_public_key_auto(
854        &self,
855        username: Option<&str>,
856        password: Option<&str>,
857    ) -> SshResult<AuthStatus> {
858        let sess = self.lock_session();
859
860        let username = opt_str_to_cstring(username);
861        let password = opt_str_to_cstring(password);
862
863        let res = unsafe {
864            sys::ssh_userauth_publickey_auto(
865                **sess,
866                opt_cstring_to_cstr(&username),
867                opt_cstring_to_cstr(&password),
868            )
869        };
870
871        sess.auth_result(res, "authentication error")
872    }
873
874    /// Try to perform `"none"` authentication.
875    ///
876    /// Typically, the server will not allow `none` auth to succeed, but it has
877    /// the side effect of informing the client which authentication methods
878    /// are available, so a full-featured client will call this prior to calling
879    /// `userauth_list`.
880    ///
881    /// `username` should almost always be `None` to use the username as
882    /// previously configured via [set_option](#method.set_option) or that
883    /// was loaded from the ssh configuration prior to calling
884    /// [connect](#method.connect), as most ssh server implementations
885    /// do not allow changing the username during authentication.
886    pub fn userauth_none(&self, username: Option<&str>) -> SshResult<AuthStatus> {
887        let sess = self.lock_session();
888        let username = opt_str_to_cstring(username);
889        let res = unsafe { sys::ssh_userauth_none(**sess, opt_cstring_to_cstr(&username)) };
890
891        sess.auth_result(res, "authentication error")
892    }
893
894    /// Returns the permitted `AuthMethods`.
895    ///
896    /// The list is not available until after [userauth_none](#method.userauth_none)
897    /// has been called at least once.
898    ///
899    /// The list can change in response to authentication events; for example,
900    /// after successfully completing pubkey auth, the server may then require
901    /// keyboard interactive auth to enter a second authentication factor.
902    ///
903    /// `username` should almost always be `None` to use the username as
904    /// previously configured via [set_option](#method.set_option) or that
905    /// was loaded from the ssh configuration prior to calling
906    /// [connect](#method.connect), as most ssh server implementations
907    /// do not allow changing the username during authentication.
908    pub fn userauth_list(&self, username: Option<&str>) -> SshResult<AuthMethods> {
909        let sess = self.lock_session();
910        let username = opt_str_to_cstring(username);
911        Ok(unsafe {
912            AuthMethods::from_bits_unchecked(sys::ssh_userauth_list(
913                **sess,
914                opt_cstring_to_cstr(&username),
915            ) as u32)
916        })
917    }
918
919    /// After [userauth_keyboard_interactive](#method.userauth_keyboard_interactive)
920    /// has been called and returned `AuthStatus::Info`, this method must be called
921    /// to discover the prompts to questions that the server needs answered in order
922    /// to authenticate the session.
923    ///
924    /// It is then up to your application to obtain those answers and set them via
925    /// [userauth_keyboard_interactive_set_answers](#method.userauth_keyboard_interactive_set_answers).
926    pub fn userauth_keyboard_interactive_info(&self) -> SshResult<InteractiveAuthInfo> {
927        let sess = self.lock_session();
928        let name = unsafe { sys::ssh_userauth_kbdint_getname(**sess) };
929        let name = unsafe { CStr::from_ptr(name) }
930            .to_string_lossy()
931            .to_string();
932
933        let instruction = unsafe { sys::ssh_userauth_kbdint_getinstruction(**sess) };
934        let instruction = unsafe { CStr::from_ptr(instruction) }
935            .to_string_lossy()
936            .to_string();
937
938        let n_prompts = unsafe { sys::ssh_userauth_kbdint_getnprompts(**sess) };
939        assert!(n_prompts >= 0);
940        let n_prompts = n_prompts as u32;
941        let mut prompts = vec![];
942        for i in 0..n_prompts {
943            let mut echo = 0;
944            let prompt = unsafe { sys::ssh_userauth_kbdint_getprompt(**sess, i, &mut echo) };
945
946            prompts.push(InteractiveAuthPrompt {
947                prompt: unsafe { CStr::from_ptr(prompt) }
948                    .to_string_lossy()
949                    .to_string(),
950                echo: echo != 0,
951            });
952        }
953
954        Ok(InteractiveAuthInfo {
955            name,
956            instruction,
957            prompts,
958        })
959    }
960
961    /// After [userauth_keyboard_interactive_info](#method.userauth_keyboard_interactive_info)
962    /// has been called, and your application has produced the answers to the prompts,
963    /// you must call this method to record those answers.
964    ///
965    /// You will then need to call
966    /// [userauth_keyboard_interactive](#method.userauth_keyboard_interactive) to present
967    /// those answers to the server and discover the next stage of authentication.
968    pub fn userauth_keyboard_interactive_set_answers(&self, answers: &[String]) -> SshResult<()> {
969        let sess = self.lock_session();
970        for (idx, answer) in answers.iter().enumerate() {
971            let answer = CString::new(answer.as_bytes())?;
972
973            let res =
974                unsafe { sys::ssh_userauth_kbdint_setanswer(**sess, idx as u32, answer.as_ptr()) };
975
976            if res != 0 {
977                if let Some(err) = sess.last_error() {
978                    return Err(err);
979                }
980                return Err(Error::fatal("error setting answer"));
981            }
982        }
983        Ok(())
984    }
985
986    /// Initiates keyboard-interactive authentication.
987    ///
988    /// This appears similar to, but is not the same as password authentication.
989    /// You should prefer using keyboard-interactive authentication over password
990    /// auth.
991    ///
992    /// `username` should almost always be `None` to use the username as
993    /// previously configured via [set_option](#method.set_option) or that
994    /// was loaded from the ssh configuration prior to calling
995    /// [connect](#method.connect), as most ssh server implementations
996    /// do not allow changing the username during authentication.
997    ///
998    /// `sub_methods` is not documented in the underlying libssh and
999    /// should almost always be `None`.
1000    ///
1001    /// If the returned `AuthStatus` is `Info`, then your application
1002    /// should use [userauth_keyboard_interactive_info](#method.userauth_keyboard_interactive_info)
1003    /// and use the results of that method to prompt the user to answer
1004    /// the questions sent by the server, then
1005    /// [userauth_keyboard_interactive_set_answers](#method.userauth_keyboard_interactive_set_answers)
1006    /// to record the answers, before again calling this method to
1007    /// present them to the server and determine the next steps.
1008    pub fn userauth_keyboard_interactive(
1009        &self,
1010        username: Option<&str>,
1011        sub_methods: Option<&str>,
1012    ) -> SshResult<AuthStatus> {
1013        let sess = self.lock_session();
1014
1015        let username = opt_str_to_cstring(username);
1016        let sub_methods = opt_str_to_cstring(sub_methods);
1017
1018        let res = unsafe {
1019            sys::ssh_userauth_kbdint(
1020                **sess,
1021                opt_cstring_to_cstr(&username),
1022                opt_cstring_to_cstr(&sub_methods),
1023            )
1024        };
1025        sess.auth_result(res, "authentication error")
1026    }
1027
1028    /// Initiates password based authentication.
1029    ///
1030    /// This appears similar to, but is not the same as keyboard-interactive
1031    /// authentication. You should prefer using keyboard-interactive
1032    /// authentication over password auth.
1033    ///
1034    /// `username` should almost always be `None` to use the username as
1035    /// previously configured via [set_option](#method.set_option) or that
1036    /// was loaded from the ssh configuration prior to calling
1037    /// [connect](#method.connect), as most ssh server implementations
1038    /// do not allow changing the username during authentication.
1039    ///
1040    /// `password` should be a password entered by the user, or otherwise
1041    /// securely communicated to your application.
1042    pub fn userauth_password(
1043        &self,
1044        username: Option<&str>,
1045        password: Option<&str>,
1046    ) -> SshResult<AuthStatus> {
1047        let sess = self.lock_session();
1048        let username = opt_str_to_cstring(username);
1049        let password = opt_str_to_cstring(password);
1050        let res = unsafe {
1051            sys::ssh_userauth_password(
1052                **sess,
1053                opt_cstring_to_cstr(&username),
1054                opt_cstring_to_cstr(&password),
1055            )
1056        };
1057        sess.auth_result(res, "authentication error")
1058    }
1059
1060    /// Sends the "tcpip-forward" global request to ask the server
1061    /// to begin listening for inbound connections; this is for
1062    /// *remote (or reverse) port forwarding*.
1063    ///
1064    /// If `bind_address` is None then bind to all interfaces on
1065    /// the server side.  Otherwise, bind only to the specified address.
1066    /// If `port` is `0` then the server will pick a port to bind to,
1067    /// otherwise, will attempt to use the requested port.
1068    /// Returns the bound port number.
1069    ///
1070    /// Later in your program, you will use `Session::accept_forward` to
1071    /// wait for a forwarded connection from the address you specified.
1072    pub fn listen_forward(&self, bind_address: Option<&str>, port: u16) -> SshResult<u16> {
1073        let sess = self.lock_session();
1074        let bind_address = opt_str_to_cstring(bind_address);
1075        let mut bound_port = 0;
1076        let res = unsafe {
1077            sys::ssh_channel_listen_forward(
1078                **sess,
1079                opt_cstring_to_cstr(&bind_address),
1080                port as i32,
1081                &mut bound_port,
1082            )
1083        };
1084        if res == sys::SSH_OK as i32 {
1085            Ok(bound_port as u16)
1086        } else if let Some(err) = sess.last_error() {
1087            Err(err)
1088        } else {
1089            Err(Error::fatal("error in ssh_channel_listen_forward"))
1090        }
1091    }
1092
1093    /// Accept a remote forwarded connection.
1094    /// You must have called `Session::listen_forward` previously to set up
1095    /// remote port forwarding.
1096    /// Returns a tuple `(destination_port, Channel)`.
1097    /// The destination port is so that you can distinguish between multiple
1098    /// remote forwards and corresponds to the port returned from `listen_forward`.
1099    pub fn accept_forward(&self, timeout: Duration) -> SshResult<(u16, Channel)> {
1100        let mut port = 0;
1101        let sess = self.lock_session();
1102        let chan =
1103            unsafe { sys::ssh_channel_accept_forward(**sess, timeout.as_millis() as _, &mut port) };
1104        if chan.is_null() {
1105            if let Some(err) = sess.last_error() {
1106                Err(err)
1107            } else {
1108                Err(Error::TryAgain)
1109            }
1110        } else {
1111            let channel = Channel::new(&self.sess, chan);
1112
1113            Ok((port as u16, channel))
1114        }
1115    }
1116
1117    /// Returns a tuple of `(read_pending, write_pending)`.
1118    /// If `read_pending` is true, then your OS polling mechanism
1119    /// should request a wakeup when the socket is readable.
1120    /// If `write_pending` is true, then your OS polling mechanism
1121    /// should request a wakeup when the socket is writable.
1122    ///
1123    /// You can use the `AsRawFd` or `AsRawSocket` trait impl
1124    /// to obtain the socket descriptor for polling purposes.
1125    pub fn get_poll_state(&self) -> (bool, bool) {
1126        let state = unsafe { sys::ssh_get_poll_flags(**self.lock_session()) };
1127        let read_pending = (state & sys::SSH_READ_PENDING as i32) != 0;
1128        let write_pending = (state & sys::SSH_WRITE_PENDING as i32) != 0;
1129        (read_pending, write_pending)
1130    }
1131
1132    /// Returns `true` if the session is in blocking mode, `false` otherwise.
1133    pub fn is_blocking(&self) -> bool {
1134        self.lock_session().is_blocking()
1135    }
1136
1137    /// If `blocking == true` then set the session to block mode, otherwise
1138    /// set it to non-blocking mode.
1139    /// In non-blocking mode, a number of methods in the objects associated
1140    /// with the session can return `Error::TryAgain`.
1141    pub fn set_blocking(&self, blocking: bool) {
1142        unsafe { sys::ssh_set_blocking(**self.lock_session(), if blocking { 1 } else { 0 }) }
1143    }
1144
1145    /// Returns `true` if this session is in the connected state, `false`
1146    /// otherwise.
1147    pub fn is_connected(&self) -> bool {
1148        unsafe { sys::ssh_is_connected(**self.lock_session()) != 0 }
1149    }
1150
1151    pub fn sftp(&self) -> SshResult<Sftp> {
1152        let sftp = {
1153            let sess = self.lock_session();
1154            let sftp = unsafe { sys::sftp_new(**sess) };
1155            if sftp.is_null() {
1156                return if let Some(err) = sess.last_error() {
1157                    Err(err)
1158                } else {
1159                    Err(Error::fatal("failed to allocate sftp session"))
1160                };
1161            }
1162
1163            Sftp {
1164                sess: Arc::clone(&self.sess),
1165                sftp_inner: sftp,
1166            }
1167        };
1168
1169        sftp.init()?;
1170        Ok(sftp)
1171    }
1172
1173    /// Send a message that should be ignored by the peer
1174    pub fn send_ignore(&self, data: &[u8]) -> SshResult<()> {
1175        let sess = self.lock_session();
1176        let status = unsafe { sys::ssh_send_ignore(**sess, data.as_ptr() as _) };
1177        if status != sys::SSH_OK as i32 {
1178            if let Some(err) = sess.last_error() {
1179                Err(err)
1180            } else {
1181                Err(Error::TryAgain)
1182            }
1183        } else {
1184            Ok(())
1185        }
1186    }
1187}
1188
1189#[cfg(unix)]
1190impl std::os::unix::io::AsRawFd for Session {
1191    fn as_raw_fd(&self) -> RawSocket {
1192        unsafe { sys::ssh_get_fd(**self.lock_session()) }
1193    }
1194}
1195
1196#[cfg(windows)]
1197impl std::os::windows::io::AsRawSocket for Session {
1198    fn as_raw_socket(&self) -> RawSocket {
1199        unsafe { sys::ssh_get_fd(**self.lock_session()) as RawSocket }
1200    }
1201}
1202
1203/// Indicates the disposition of an authentication operation
1204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1205pub enum AuthStatus {
1206    /// You have been fully authenticated and can now move on
1207    /// to opening channels
1208    Success,
1209    /// The authentication attempt failed. Perhaps retry, or
1210    /// try an alternative auth method.
1211    Denied,
1212    /// You've been partially authenticated.  Check `Session::userauth_list`
1213    /// to determine which methods you should continue with.
1214    Partial,
1215    /// There is additional information about how to proceed
1216    /// with authentication.  For keyboard-interactive auth,
1217    /// you will need to obtain auth prompts and provide answers
1218    /// before you can continue.
1219    Info,
1220    /// In non-blocking mode, you will need to try again as
1221    /// the request couldn't be completed without blocking.
1222    Again,
1223}
1224
1225bitflags::bitflags! {
1226    /// bitflags that indicates permitted authentication methods
1227    pub struct AuthMethods : u32 {
1228        /// The `"none"` authentication method is available.
1229        const NONE = sys::SSH_AUTH_METHOD_NONE;
1230        /// The `"password"` authentication method is available.
1231        const PASSWORD = sys::SSH_AUTH_METHOD_PASSWORD;
1232        /// The `"public-key"` authentication method is available.
1233        const PUBLIC_KEY = sys::SSH_AUTH_METHOD_PUBLICKEY;
1234        /// Host-based authentication is available
1235        const HOST_BASED = sys::SSH_AUTH_METHOD_HOSTBASED;
1236        /// keyboard-interactive authentication is available
1237        const INTERACTIVE = sys::SSH_AUTH_METHOD_INTERACTIVE;
1238        /// GSSAPI authentication is available
1239        const GSSAPI_MIC = sys::SSH_AUTH_METHOD_GSSAPI_MIC;
1240    }
1241}
1242
1243/// Represents the public key provided by the remote host
1244pub struct SshKey {
1245    key: sys::ssh_key,
1246}
1247
1248impl Drop for SshKey {
1249    fn drop(&mut self) {
1250        unsafe { sys::ssh_key_free(self.key) }
1251    }
1252}
1253
1254impl SshKey {
1255    /// Returns the public key hash in the requested format.
1256    /// The hash is returned as binary bytes.
1257    /// Consider using [get_public_key_hash_hexa](#method.get_public_key_hash_hexa)
1258    /// to return it in a more human readable format.
1259    pub fn get_public_key_hash(&self, hash_type: PublicKeyHashType) -> SshResult<Vec<u8>> {
1260        let mut bytes = std::ptr::null_mut();
1261        let mut len = 0;
1262        let res = unsafe {
1263            sys::ssh_get_publickey_hash(
1264                self.key,
1265                match hash_type {
1266                    PublicKeyHashType::Sha1 => {
1267                        sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_SHA1
1268                    }
1269                    PublicKeyHashType::Md5 => sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_MD5,
1270                    PublicKeyHashType::Sha256 => {
1271                        sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_SHA256
1272                    }
1273                },
1274                &mut bytes,
1275                &mut len,
1276            )
1277        };
1278
1279        if res != 0 || bytes.is_null() {
1280            Err(Error::fatal("failed to get public key hash"))
1281        } else {
1282            let data = unsafe { std::slice::from_raw_parts(bytes, len).to_vec() };
1283            unsafe {
1284                sys::ssh_clean_pubkey_hash(&mut bytes);
1285            }
1286            Ok(data)
1287        }
1288    }
1289
1290    /// Returns the public key hash in a human readable form
1291    pub fn get_public_key_hash_hexa(&self, hash_type: PublicKeyHashType) -> SshResult<String> {
1292        let bytes = self.get_public_key_hash(hash_type)?;
1293        let hexa = unsafe { sys::ssh_get_hexa(bytes.as_ptr(), bytes.len()) };
1294        if hexa.is_null() {
1295            Err(Error::fatal(
1296                "failed to allocate bytes for hexa representation",
1297            ))
1298        } else {
1299            let res = unsafe { CStr::from_ptr(hexa) }
1300                .to_string_lossy()
1301                .to_string();
1302            unsafe { sys::ssh_string_free_char(hexa) };
1303            Ok(res)
1304        }
1305    }
1306
1307    pub fn from_privkey_base64(b64_key: &str, passphrase: Option<&str>) -> SshResult<SshKey> {
1308        let b64_key = CString::new(b64_key)
1309            .map_err(|e| Error::Fatal(format!("Failed to process ssh key: {:?}", e)))?;
1310        let passphrase = opt_str_to_cstring(passphrase);
1311        unsafe {
1312            let mut key = sys::ssh_key_new();
1313            if sys::ssh_pki_import_privkey_base64(
1314                b64_key.as_ptr(),
1315                opt_cstring_to_cstr(&passphrase),
1316                None,
1317                null_mut(),
1318                &mut key,
1319            ) != sys::SSH_OK as i32
1320            {
1321                sys::ssh_key_free(key);
1322                return Err(Error::Fatal(format!("Failed to parse ssh key")));
1323            }
1324            return Ok(SshKey { key });
1325        }
1326    }
1327
1328    pub fn from_privkey_file(filename: &str, passphrase: Option<&str>) -> SshResult<SshKey> {
1329        let filename_cstr = CString::new(filename).map_err(|e| {
1330            Error::Fatal(format!(
1331                "Could not make CString from filename '{filename}': {e:#}"
1332            ))
1333        })?;
1334        let passphrase = opt_str_to_cstring(passphrase);
1335        unsafe {
1336            let mut key = sys::ssh_key_new();
1337            if sys::ssh_pki_import_privkey_file(
1338                filename_cstr.as_ptr(),
1339                opt_cstring_to_cstr(&passphrase),
1340                None,
1341                null_mut(),
1342                &mut key,
1343            ) != sys::SSH_OK as i32
1344            {
1345                sys::ssh_key_free(key);
1346                return Err(Error::Fatal(format!(
1347                    "Failed to parse ssh key from file '{filename}'"
1348                )));
1349            }
1350            return Ok(SshKey { key });
1351        }
1352    }
1353}
1354
1355/// Allows configuring the underlying `libssh` debug logging level
1356#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1357pub enum LogLevel {
1358    NoLogging,
1359    Warning,
1360    Protocol,
1361    Packet,
1362    Functions,
1363}
1364
1365/// Allows configuring different aspects of a `Session`.
1366/// You always need to set at least `SshOption::Hostname`.
1367#[derive(Debug)]
1368pub enum SshOption {
1369    /// The hostname or ip address to connect to
1370    Hostname(String),
1371
1372    /// The port to connect to
1373    Port(u16),
1374
1375    LogLevel(LogLevel),
1376
1377    /// The pre-opened socket.
1378    /// You don't typically need to provide this.
1379    /// Don't forget to set the hostname as the hostname is used as a
1380    /// key in the known_host mechanism.
1381    Socket(RawSocket),
1382
1383    /// The address to bind the client to
1384    BindAddress(String),
1385
1386    /// The username for authentication
1387    /// If the value is None, the username is set to the default username.
1388    User(Option<String>),
1389
1390    /// Set the ssh directory
1391    /// If the value is None, the directory is set to the default ssh directory.
1392    /// The ssh directory is used for files like known_hosts and identity (private and public key). It may include "%s" which will be replaced by the user home directory.
1393    SshDir(Option<String>),
1394
1395    /// Set the known hosts file name
1396    /// If the value is None, the directory is set to the default known hosts file, normally ~/.ssh/known_hosts.
1397    /// The known hosts file is used to certify remote hosts are genuine. It may include "%d" which will be replaced by the user home directory.
1398    KnownHosts(Option<String>),
1399
1400    /// Configures the ProxyCommand ssh option, which is used to establish an
1401    /// alternative transport to using a direct TCP connection
1402    ProxyCommand(Option<String>),
1403
1404    /// Add a new identity file (const char *, format string) to the identity list.
1405    /// By default identity, id_dsa and id_rsa are checked.
1406    /// The identity used to authenticate with public key will be prepended to the list. It may include "%s" which will be replaced by the user home directory.
1407    AddIdentity(String),
1408
1409    /// Set a timeout for the connection
1410    Timeout(Duration),
1411
1412    IdentityAgent(Option<String>),
1413    /// Set the key exchange method to be used. ex:
1414    /// ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
1415    KeyExchange(String),
1416    /// Set the preferred server host key types. ex:
1417    /// ssh-rsa,rsa-sha2-256,ssh-dss,ecdh-sha2-nistp256
1418    HostKeys(String),
1419    /// Set the preferred public key algorithms to be used for
1420    /// authentication as a comma-separated list. ex:
1421    /// ssh-rsa,rsa-sha2-256,ssh-dss,ecdh-sha2-nistp256
1422    PublicKeyAcceptedTypes(String),
1423    ///Set the symmetric cipher client to server as a comma-separated list.
1424    CiphersCS(String),
1425    ///Set the symmetric cipher server to client as a comma-separated list.
1426    CiphersSC(String),
1427    /// Set the MAC algorithm client to server as a comma-separated list.
1428    HmacCS(String),
1429    /// Set the MAC algorithm server to client as a comma-separated list.
1430    HmacSC(String),
1431    /// Set it to false to disable automatic processing of per-user and system-wide OpenSSH configuration files.
1432    ProcessConfig(bool),
1433    /// Set the global known hosts file name
1434    /// If the value is None, the directory is set to the default known hosts file, normally /etc/ssh/ssh_known_hosts.
1435    /// The known hosts file is used to certify remote hosts are genuine.
1436    GlobalKnownHosts(Option<String>),
1437}
1438
1439/// Indicates the state of known-host matching, an important set
1440/// to detect and avoid MITM attacks.
1441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1442pub enum KnownHosts {
1443    /// The known host file does not exist. The host is thus unknown. File will be created if host key is accepted.
1444    NotFound,
1445    /// The server is unknown. User should confirm the public key hash is correct.
1446    Unknown,
1447    /// The server is known and has not changed.
1448    Ok,
1449    /// The server key has changed. Either you are under attack or the administrator changed the key. You HAVE to warn the user about a possible attack.
1450    Changed,
1451    /// The server gave use a key of a type while we had an other type recorded. It is a possible attack.
1452    Other,
1453}
1454
1455/// The type of hash to use when inspecting a public key fingerprint
1456#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1457pub enum PublicKeyHashType {
1458    Sha1,
1459    Md5,
1460    Sha256,
1461}
1462
1463/// Represents a question prompt in keyboard-interactive auth
1464#[derive(Debug, Clone, PartialEq, Eq)]
1465pub struct InteractiveAuthPrompt {
1466    /// The prompt to show to the user
1467    pub prompt: String,
1468    /// If `true`, echo the user's answer to the screen.
1469    /// If `false`, conceal it, as it is secret/sensitive.
1470    pub echo: bool,
1471}
1472
1473/// Represents the overall set of instructions in keyboard-interactive auth
1474#[derive(Debug, Clone, PartialEq, Eq)]
1475pub struct InteractiveAuthInfo {
1476    /// An overall set of instructions.
1477    /// May be empty.
1478    pub instruction: String,
1479    /// The session name.
1480    /// May be empty.
1481    pub name: String,
1482    /// The set of prompts for information that need answers before
1483    /// authentication can succeed.
1484    pub prompts: Vec<InteractiveAuthPrompt>,
1485}
1486
1487/// A utility function that will prompt the user for input
1488/// via the console/tty.
1489///
1490/// `prompt` is the text to show to the user.
1491/// `default_value` can be used to pre-set the answer, allowing the
1492/// user to simply press enter.
1493///
1494/// `echo`, if `true`, means to show the user's answer on the screen
1495/// as they type it.  If `false`, means to conceal it.
1496///
1497/// `verify`, if `true`, will ask the user for their input twice in
1498/// order to confirm that they provided the same text both times.
1499/// This is useful when creating a password and `echo == false`.
1500pub fn get_input(
1501    prompt: &str,
1502    default_value: Option<&str>,
1503    echo: bool,
1504    verify: bool,
1505) -> Option<String> {
1506    const BUF_LEN: usize = 128;
1507    let mut buf = [0u8; BUF_LEN];
1508
1509    if let Some(def) = default_value {
1510        let def = def.as_bytes();
1511        let len = buf.len().min(def.len());
1512        buf[0..len].copy_from_slice(&def[0..len]);
1513    }
1514
1515    let prompt = CString::new(prompt).ok()?;
1516
1517    let res = unsafe {
1518        sys::ssh_getpass(
1519            prompt.as_ptr(),
1520            buf.as_mut_ptr() as *mut _,
1521            buf.len(),
1522            if echo { 1 } else { 0 },
1523            if verify { 1 } else { 0 },
1524        )
1525    };
1526
1527    if res == 0 {
1528        Some(
1529            unsafe { CStr::from_ptr(buf.as_ptr() as *const _) }
1530                .to_string_lossy()
1531                .to_string(),
1532        )
1533    } else {
1534        None
1535    }
1536}
1537
1538fn opt_str_to_cstring(s: Option<&str>) -> Option<CString> {
1539    s.and_then(|s| CString::new(s).ok())
1540}
1541
1542fn opt_string_to_cstring(s: Option<String>) -> Option<CString> {
1543    s.and_then(|s| CString::new(s).ok())
1544}
1545
1546fn opt_cstring_to_cstr(s: &Option<CString>) -> *const ::std::os::raw::c_char {
1547    match s {
1548        Some(s) => s.as_ptr(),
1549        None => std::ptr::null(),
1550    }
1551}
1552
1553#[cfg(test)]
1554mod test {
1555    use super::*;
1556
1557    #[test]
1558    fn init() {
1559        let sess = Session::new().unwrap();
1560        assert!(!sess.is_connected());
1561        assert_eq!(sess.connect(), Err(Error::fatal("Hostname required")));
1562    }
1563}