Skip to main content

ssh2/
agent.rs

1use parking_lot::{Mutex, MutexGuard};
2use std::ffi::{CStr, CString};
3use std::path::{Path, PathBuf};
4use std::ptr::null_mut;
5use std::slice;
6use std::str;
7use std::sync::Arc;
8
9use util;
10use {raw, Error, ErrorCode, SessionInner};
11
12/// A structure representing a connection to an SSH agent.
13///
14/// Agents can be used to authenticate a session.
15pub struct Agent {
16    raw: *mut raw::LIBSSH2_AGENT,
17    sess: Arc<Mutex<SessionInner>>,
18}
19
20// Agent is both Send and Sync; the compiler can't see it because it
21// is pessimistic about the raw pointer.  We use Arc/Mutex to guard accessing
22// the raw pointer so we are safe for both.
23unsafe impl Send for Agent {}
24unsafe impl Sync for Agent {}
25
26/// A public key which is extracted from an SSH agent.
27#[derive(Debug, PartialEq, Eq)]
28pub struct PublicKey {
29    blob: Vec<u8>,
30    comment: String,
31}
32
33impl Agent {
34    pub(crate) fn from_raw_opt(
35        raw: *mut raw::LIBSSH2_AGENT,
36        err: Option<Error>,
37        sess: &Arc<Mutex<SessionInner>>,
38    ) -> Result<Self, Error> {
39        if raw.is_null() {
40            Err(err.unwrap_or_else(Error::unknown))
41        } else {
42            Ok(Self {
43                raw,
44                sess: Arc::clone(sess),
45            })
46        }
47    }
48
49    /// Connect to an ssh-agent running on the system.
50    pub fn connect(&mut self) -> Result<(), Error> {
51        let sess = self.sess.lock();
52        unsafe { sess.rc(raw::libssh2_agent_connect(self.raw)) }
53    }
54
55    /// Close a connection to an ssh-agent.
56    pub fn disconnect(&mut self) -> Result<(), Error> {
57        let sess = self.sess.lock();
58        unsafe { sess.rc(raw::libssh2_agent_disconnect(self.raw)) }
59    }
60
61    /// Request an ssh-agent to list of public keys, and stores them in the
62    /// internal collection of the handle.
63    ///
64    /// Call `identities` to get the public keys.
65    pub fn list_identities(&mut self) -> Result<(), Error> {
66        let sess = self.sess.lock();
67        unsafe { sess.rc(raw::libssh2_agent_list_identities(self.raw)) }
68    }
69
70    /// Get list of the identities of this agent.
71    pub fn identities(&self) -> Result<Vec<PublicKey>, Error> {
72        let sess = self.sess.lock();
73        let mut res = vec![];
74        let mut prev = null_mut();
75        let mut next = null_mut();
76        loop {
77            match unsafe { raw::libssh2_agent_get_identity(self.raw, &mut next, prev) } {
78                0 => {
79                    prev = next;
80                    res.push(unsafe { PublicKey::from_raw(next) });
81                }
82                1 => break,
83                rc => return Err(Error::from_session_error_raw(sess.raw, rc)),
84            }
85        }
86        Ok(res)
87    }
88
89    fn resolve_raw_identity(
90        &self,
91        sess: &MutexGuard<SessionInner>,
92        identity: &PublicKey,
93    ) -> Result<Option<*mut raw::libssh2_agent_publickey>, Error> {
94        let mut prev = null_mut();
95        let mut next = null_mut();
96        loop {
97            match unsafe { raw::libssh2_agent_get_identity(self.raw, &mut next, prev) } {
98                0 => {
99                    prev = next;
100                    let this_ident = unsafe { PublicKey::from_raw(next) };
101                    if this_ident == *identity {
102                        return Ok(Some(next));
103                    }
104                }
105                1 => break,
106                rc => return Err(Error::from_session_error_raw(sess.raw, rc)),
107            }
108        }
109        Ok(None)
110    }
111
112    /// Attempt public key authentication with the help of ssh-agent.
113    pub fn userauth(&self, username: &str, identity: &PublicKey) -> Result<(), Error> {
114        let username = CString::new(username)?;
115        let sess = self.sess.lock();
116        let raw_ident = self.resolve_raw_identity(&sess, identity)?.ok_or_else(|| {
117            Error::new(
118                ErrorCode::Session(raw::LIBSSH2_ERROR_BAD_USE),
119                "Identity not found in agent",
120            )
121        })?;
122        unsafe {
123            sess.rc(raw::libssh2_agent_userauth(
124                self.raw,
125                username.as_ptr(),
126                raw_ident,
127            ))
128        }
129    }
130
131    /// Set a custom agent socket path to connect to.
132    pub fn set_identity_path(&mut self, path: &Path) -> Result<(), Error> {
133        let path = CString::new(util::path2bytes(path)?)?;
134        unsafe {
135            raw::libssh2_agent_set_identity_path(self.raw, path.as_ptr());
136        }
137        Ok(())
138    }
139
140    /// Get the custom agent socket path, if set.
141    pub fn identity_path(&self) -> Option<PathBuf> {
142        unsafe {
143            let ptr = raw::libssh2_agent_get_identity_path(self.raw);
144            if ptr.is_null() {
145                None
146            } else {
147                Some(util::mkpath(CStr::from_ptr(ptr).to_bytes()))
148            }
149        }
150    }
151}
152
153impl Drop for Agent {
154    fn drop(&mut self) {
155        unsafe { raw::libssh2_agent_free(self.raw) }
156    }
157}
158
159impl PublicKey {
160    unsafe fn from_raw(raw: *mut raw::libssh2_agent_publickey) -> Self {
161        let blob = slice::from_raw_parts_mut((*raw).blob, (*raw).blob_len as usize);
162        let comment = (*raw).comment;
163        let comment = if comment.is_null() {
164            String::new()
165        } else {
166            CStr::from_ptr(comment).to_string_lossy().into_owned()
167        };
168        Self {
169            blob: blob.to_vec(),
170            comment,
171        }
172    }
173
174    /// Return the data of this public key.
175    pub fn blob(&self) -> &[u8] {
176        &self.blob
177    }
178
179    /// Returns the comment in a printable format
180    pub fn comment(&self) -> &str {
181        &self.comment
182    }
183}