1#![allow(clippy::wildcard_in_or_patterns)]
9
10pub 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 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 sys::ssh_channel_free(chan);
164 }
165 }
166 }
167}
168
169pub struct Session {
186 sess: Arc<Mutex<SessionHolder>>,
187}
188
189impl Session {
190 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 sess.pending_agent_forward_channels.push(chan);
298 chan
299 }
300
301 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 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 None
365 }
366 }
367
368 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 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 pub fn blocking_flush(&self, timeout: Option<Duration>) -> SshResult<()> {
399 let sess = self.lock_session();
400 sess.blocking_flush(timeout)
401 }
402
403 pub fn disconnect(&self) {
406 let sess = self.lock_session();
407 unsafe { sys::ssh_disconnect(**sess) };
408 }
409
410 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 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 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 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 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 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 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 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 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 µs 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn is_blocking(&self) -> bool {
1134 self.lock_session().is_blocking()
1135 }
1136
1137 pub fn set_blocking(&self, blocking: bool) {
1142 unsafe { sys::ssh_set_blocking(**self.lock_session(), if blocking { 1 } else { 0 }) }
1143 }
1144
1145 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1205pub enum AuthStatus {
1206 Success,
1209 Denied,
1212 Partial,
1215 Info,
1220 Again,
1223}
1224
1225bitflags::bitflags! {
1226 pub struct AuthMethods : u32 {
1228 const NONE = sys::SSH_AUTH_METHOD_NONE;
1230 const PASSWORD = sys::SSH_AUTH_METHOD_PASSWORD;
1232 const PUBLIC_KEY = sys::SSH_AUTH_METHOD_PUBLICKEY;
1234 const HOST_BASED = sys::SSH_AUTH_METHOD_HOSTBASED;
1236 const INTERACTIVE = sys::SSH_AUTH_METHOD_INTERACTIVE;
1238 const GSSAPI_MIC = sys::SSH_AUTH_METHOD_GSSAPI_MIC;
1240 }
1241}
1242
1243pub 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1357pub enum LogLevel {
1358 NoLogging,
1359 Warning,
1360 Protocol,
1361 Packet,
1362 Functions,
1363}
1364
1365#[derive(Debug)]
1368pub enum SshOption {
1369 Hostname(String),
1371
1372 Port(u16),
1374
1375 LogLevel(LogLevel),
1376
1377 Socket(RawSocket),
1382
1383 BindAddress(String),
1385
1386 User(Option<String>),
1389
1390 SshDir(Option<String>),
1394
1395 KnownHosts(Option<String>),
1399
1400 ProxyCommand(Option<String>),
1403
1404 AddIdentity(String),
1408
1409 Timeout(Duration),
1411
1412 IdentityAgent(Option<String>),
1413 KeyExchange(String),
1416 HostKeys(String),
1419 PublicKeyAcceptedTypes(String),
1423 CiphersCS(String),
1425 CiphersSC(String),
1427 HmacCS(String),
1429 HmacSC(String),
1431 ProcessConfig(bool),
1433 GlobalKnownHosts(Option<String>),
1437}
1438
1439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1442pub enum KnownHosts {
1443 NotFound,
1445 Unknown,
1447 Ok,
1449 Changed,
1451 Other,
1453}
1454
1455#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1457pub enum PublicKeyHashType {
1458 Sha1,
1459 Md5,
1460 Sha256,
1461}
1462
1463#[derive(Debug, Clone, PartialEq, Eq)]
1465pub struct InteractiveAuthPrompt {
1466 pub prompt: String,
1468 pub echo: bool,
1471}
1472
1473#[derive(Debug, Clone, PartialEq, Eq)]
1475pub struct InteractiveAuthInfo {
1476 pub instruction: String,
1479 pub name: String,
1482 pub prompts: Vec<InteractiveAuthPrompt>,
1485}
1486
1487pub 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}