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
1174#[cfg(unix)]
1175impl std::os::unix::io::AsRawFd for Session {
1176    fn as_raw_fd(&self) -> RawSocket {
1177        unsafe { sys::ssh_get_fd(**self.lock_session()) }
1178    }
1179}
1180
1181#[cfg(windows)]
1182impl std::os::windows::io::AsRawSocket for Session {
1183    fn as_raw_socket(&self) -> RawSocket {
1184        unsafe { sys::ssh_get_fd(**self.lock_session()) as RawSocket }
1185    }
1186}
1187
1188/// Indicates the disposition of an authentication operation
1189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1190pub enum AuthStatus {
1191    /// You have been fully authenticated and can now move on
1192    /// to opening channels
1193    Success,
1194    /// The authentication attempt failed. Perhaps retry, or
1195    /// try an alternative auth method.
1196    Denied,
1197    /// You've been partially authenticated.  Check `Session::userauth_list`
1198    /// to determine which methods you should continue with.
1199    Partial,
1200    /// There is additional information about how to proceed
1201    /// with authentication.  For keyboard-interactive auth,
1202    /// you will need to obtain auth prompts and provide answers
1203    /// before you can continue.
1204    Info,
1205    /// In non-blocking mode, you will need to try again as
1206    /// the request couldn't be completed without blocking.
1207    Again,
1208}
1209
1210bitflags::bitflags! {
1211    /// bitflags that indicates permitted authentication methods
1212    pub struct AuthMethods : u32 {
1213        /// The `"none"` authentication method is available.
1214        const NONE = sys::SSH_AUTH_METHOD_NONE;
1215        /// The `"password"` authentication method is available.
1216        const PASSWORD = sys::SSH_AUTH_METHOD_PASSWORD;
1217        /// The `"public-key"` authentication method is available.
1218        const PUBLIC_KEY = sys::SSH_AUTH_METHOD_PUBLICKEY;
1219        /// Host-based authentication is available
1220        const HOST_BASED = sys::SSH_AUTH_METHOD_HOSTBASED;
1221        /// keyboard-interactive authentication is available
1222        const INTERACTIVE = sys::SSH_AUTH_METHOD_INTERACTIVE;
1223        /// GSSAPI authentication is available
1224        const GSSAPI_MIC = sys::SSH_AUTH_METHOD_GSSAPI_MIC;
1225    }
1226}
1227
1228/// Represents the public key provided by the remote host
1229pub struct SshKey {
1230    key: sys::ssh_key,
1231}
1232
1233impl Drop for SshKey {
1234    fn drop(&mut self) {
1235        unsafe { sys::ssh_key_free(self.key) }
1236    }
1237}
1238
1239impl SshKey {
1240    /// Returns the public key hash in the requested format.
1241    /// The hash is returned as binary bytes.
1242    /// Consider using [get_public_key_hash_hexa](#method.get_public_key_hash_hexa)
1243    /// to return it in a more human readable format.
1244    pub fn get_public_key_hash(&self, hash_type: PublicKeyHashType) -> SshResult<Vec<u8>> {
1245        let mut bytes = std::ptr::null_mut();
1246        let mut len = 0;
1247        let res = unsafe {
1248            sys::ssh_get_publickey_hash(
1249                self.key,
1250                match hash_type {
1251                    PublicKeyHashType::Sha1 => {
1252                        sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_SHA1
1253                    }
1254                    PublicKeyHashType::Md5 => sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_MD5,
1255                    PublicKeyHashType::Sha256 => {
1256                        sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_SHA256
1257                    }
1258                },
1259                &mut bytes,
1260                &mut len,
1261            )
1262        };
1263
1264        if res != 0 || bytes.is_null() {
1265            Err(Error::fatal("failed to get public key hash"))
1266        } else {
1267            let data = unsafe { std::slice::from_raw_parts(bytes, len).to_vec() };
1268            unsafe {
1269                sys::ssh_clean_pubkey_hash(&mut bytes);
1270            }
1271            Ok(data)
1272        }
1273    }
1274
1275    /// Returns the public key hash in a human readable form
1276    pub fn get_public_key_hash_hexa(&self, hash_type: PublicKeyHashType) -> SshResult<String> {
1277        let bytes = self.get_public_key_hash(hash_type)?;
1278        let hexa = unsafe { sys::ssh_get_hexa(bytes.as_ptr(), bytes.len()) };
1279        if hexa.is_null() {
1280            Err(Error::fatal(
1281                "failed to allocate bytes for hexa representation",
1282            ))
1283        } else {
1284            let res = unsafe { CStr::from_ptr(hexa) }
1285                .to_string_lossy()
1286                .to_string();
1287            unsafe { sys::ssh_string_free_char(hexa) };
1288            Ok(res)
1289        }
1290    }
1291
1292    pub fn from_privkey_base64(b64_key: &str, passphrase: Option<&str>) -> SshResult<SshKey> {
1293        let b64_key = CString::new(b64_key)
1294            .map_err(|e| Error::Fatal(format!("Failed to process ssh key: {:?}", e)))?;
1295        let passphrase = opt_str_to_cstring(passphrase);
1296        unsafe {
1297            let mut key = sys::ssh_key_new();
1298            if sys::ssh_pki_import_privkey_base64(
1299                b64_key.as_ptr(),
1300                opt_cstring_to_cstr(&passphrase),
1301                None,
1302                null_mut(),
1303                &mut key,
1304            ) != sys::SSH_OK as i32
1305            {
1306                sys::ssh_key_free(key);
1307                return Err(Error::Fatal(format!("Failed to parse ssh key")));
1308            }
1309            return Ok(SshKey { key });
1310        }
1311    }
1312
1313    pub fn from_privkey_file(filename: &str, passphrase: Option<&str>) -> SshResult<SshKey> {
1314        let filename_cstr = CString::new(filename).map_err(|e| {
1315            Error::Fatal(format!(
1316                "Could not make CString from filename '{filename}': {e:#}"
1317            ))
1318        })?;
1319        let passphrase = opt_str_to_cstring(passphrase);
1320        unsafe {
1321            let mut key = sys::ssh_key_new();
1322            if sys::ssh_pki_import_privkey_file(
1323                filename_cstr.as_ptr(),
1324                opt_cstring_to_cstr(&passphrase),
1325                None,
1326                null_mut(),
1327                &mut key,
1328            ) != sys::SSH_OK as i32
1329            {
1330                sys::ssh_key_free(key);
1331                return Err(Error::Fatal(format!(
1332                    "Failed to parse ssh key from file '{filename}'"
1333                )));
1334            }
1335            return Ok(SshKey { key });
1336        }
1337    }
1338}
1339
1340/// Allows configuring the underlying `libssh` debug logging level
1341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1342pub enum LogLevel {
1343    NoLogging,
1344    Warning,
1345    Protocol,
1346    Packet,
1347    Functions,
1348}
1349
1350/// Allows configuring different aspects of a `Session`.
1351/// You always need to set at least `SshOption::Hostname`.
1352#[derive(Debug)]
1353pub enum SshOption {
1354    /// The hostname or ip address to connect to
1355    Hostname(String),
1356
1357    /// The port to connect to
1358    Port(u16),
1359
1360    LogLevel(LogLevel),
1361
1362    /// The pre-opened socket.
1363    /// You don't typically need to provide this.
1364    /// Don't forget to set the hostname as the hostname is used as a
1365    /// key in the known_host mechanism.
1366    Socket(RawSocket),
1367
1368    /// The address to bind the client to
1369    BindAddress(String),
1370
1371    /// The username for authentication
1372    /// If the value is None, the username is set to the default username.
1373    User(Option<String>),
1374
1375    /// Set the ssh directory
1376    /// If the value is None, the directory is set to the default ssh directory.
1377    /// 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.
1378    SshDir(Option<String>),
1379
1380    /// Set the known hosts file name
1381    /// If the value is None, the directory is set to the default known hosts file, normally ~/.ssh/known_hosts.
1382    /// 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.
1383    KnownHosts(Option<String>),
1384
1385    /// Configures the ProxyCommand ssh option, which is used to establish an
1386    /// alternative transport to using a direct TCP connection
1387    ProxyCommand(Option<String>),
1388
1389    /// Add a new identity file (const char *, format string) to the identity list.
1390    /// By default identity, id_dsa and id_rsa are checked.
1391    /// 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.
1392    AddIdentity(String),
1393
1394    /// Set a timeout for the connection
1395    Timeout(Duration),
1396
1397    IdentityAgent(Option<String>),
1398    /// Set the key exchange method to be used. ex:
1399    /// ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
1400    KeyExchange(String),
1401    /// Set the preferred server host key types. ex:
1402    /// ssh-rsa,rsa-sha2-256,ssh-dss,ecdh-sha2-nistp256
1403    HostKeys(String),
1404    /// Set the preferred public key algorithms to be used for
1405    /// authentication as a comma-separated list. ex:
1406    /// ssh-rsa,rsa-sha2-256,ssh-dss,ecdh-sha2-nistp256
1407    PublicKeyAcceptedTypes(String),
1408    ///Set the symmetric cipher client to server as a comma-separated list.
1409    CiphersCS(String),
1410    ///Set the symmetric cipher server to client as a comma-separated list.
1411    CiphersSC(String),
1412    /// Set the MAC algorithm client to server as a comma-separated list.
1413    HmacCS(String),
1414    /// Set the MAC algorithm server to client as a comma-separated list.
1415    HmacSC(String),
1416    /// Set it to false to disable automatic processing of per-user and system-wide OpenSSH configuration files.
1417    ProcessConfig(bool),
1418    /// Set the global known hosts file name
1419    /// If the value is None, the directory is set to the default known hosts file, normally /etc/ssh/ssh_known_hosts.
1420    /// The known hosts file is used to certify remote hosts are genuine.
1421    GlobalKnownHosts(Option<String>),
1422}
1423
1424/// Indicates the state of known-host matching, an important set
1425/// to detect and avoid MITM attacks.
1426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1427pub enum KnownHosts {
1428    /// The known host file does not exist. The host is thus unknown. File will be created if host key is accepted.
1429    NotFound,
1430    /// The server is unknown. User should confirm the public key hash is correct.
1431    Unknown,
1432    /// The server is known and has not changed.
1433    Ok,
1434    /// 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.
1435    Changed,
1436    /// The server gave use a key of a type while we had an other type recorded. It is a possible attack.
1437    Other,
1438}
1439
1440/// The type of hash to use when inspecting a public key fingerprint
1441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1442pub enum PublicKeyHashType {
1443    Sha1,
1444    Md5,
1445    Sha256,
1446}
1447
1448/// Represents a question prompt in keyboard-interactive auth
1449#[derive(Debug, Clone, PartialEq, Eq)]
1450pub struct InteractiveAuthPrompt {
1451    /// The prompt to show to the user
1452    pub prompt: String,
1453    /// If `true`, echo the user's answer to the screen.
1454    /// If `false`, conceal it, as it is secret/sensitive.
1455    pub echo: bool,
1456}
1457
1458/// Represents the overall set of instructions in keyboard-interactive auth
1459#[derive(Debug, Clone, PartialEq, Eq)]
1460pub struct InteractiveAuthInfo {
1461    /// An overall set of instructions.
1462    /// May be empty.
1463    pub instruction: String,
1464    /// The session name.
1465    /// May be empty.
1466    pub name: String,
1467    /// The set of prompts for information that need answers before
1468    /// authentication can succeed.
1469    pub prompts: Vec<InteractiveAuthPrompt>,
1470}
1471
1472/// A utility function that will prompt the user for input
1473/// via the console/tty.
1474///
1475/// `prompt` is the text to show to the user.
1476/// `default_value` can be used to pre-set the answer, allowing the
1477/// user to simply press enter.
1478///
1479/// `echo`, if `true`, means to show the user's answer on the screen
1480/// as they type it.  If `false`, means to conceal it.
1481///
1482/// `verify`, if `true`, will ask the user for their input twice in
1483/// order to confirm that they provided the same text both times.
1484/// This is useful when creating a password and `echo == false`.
1485pub fn get_input(
1486    prompt: &str,
1487    default_value: Option<&str>,
1488    echo: bool,
1489    verify: bool,
1490) -> Option<String> {
1491    const BUF_LEN: usize = 128;
1492    let mut buf = [0u8; BUF_LEN];
1493
1494    if let Some(def) = default_value {
1495        let def = def.as_bytes();
1496        let len = buf.len().min(def.len());
1497        buf[0..len].copy_from_slice(&def[0..len]);
1498    }
1499
1500    let prompt = CString::new(prompt).ok()?;
1501
1502    let res = unsafe {
1503        sys::ssh_getpass(
1504            prompt.as_ptr(),
1505            buf.as_mut_ptr() as *mut _,
1506            buf.len(),
1507            if echo { 1 } else { 0 },
1508            if verify { 1 } else { 0 },
1509        )
1510    };
1511
1512    if res == 0 {
1513        Some(
1514            unsafe { CStr::from_ptr(buf.as_ptr() as *const _) }
1515                .to_string_lossy()
1516                .to_string(),
1517        )
1518    } else {
1519        None
1520    }
1521}
1522
1523fn opt_str_to_cstring(s: Option<&str>) -> Option<CString> {
1524    s.and_then(|s| CString::new(s).ok())
1525}
1526
1527fn opt_string_to_cstring(s: Option<String>) -> Option<CString> {
1528    s.and_then(|s| CString::new(s).ok())
1529}
1530
1531fn opt_cstring_to_cstr(s: &Option<CString>) -> *const ::std::os::raw::c_char {
1532    match s {
1533        Some(s) => s.as_ptr(),
1534        None => std::ptr::null(),
1535    }
1536}
1537
1538#[cfg(test)]
1539mod test {
1540    use super::*;
1541
1542    #[test]
1543    fn init() {
1544        let sess = Session::new().unwrap();
1545        assert!(!sess.is_connected());
1546        assert_eq!(sess.connect(), Err(Error::fatal("Hostname required")));
1547    }
1548}