Skip to content

Commit 76bfb1a

Browse files
committed
fix: Sound impl of Session::userauth_agent
1 parent 4995c38 commit 76bfb1a

3 files changed

Lines changed: 29 additions & 14 deletions

File tree

src/agent.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ impl<C: RuntimeContext> Agent<C> {
3232
}
3333

3434
/// Async wrappers
35+
///
36+
/// [`Agent::userauth`] is the one agent function that can be async-wrapped
37+
/// because it writes to the session.
3538
#[allow(clippy::missing_errors_doc)]
3639
impl<C: RuntimeContext> Agent<C> {
3740
/// See [`ssh2::Agent::userauth`]
@@ -43,21 +46,22 @@ impl<C: RuntimeContext> Agent<C> {
4346
}
4447

4548
/// Sync wrappers
49+
///
50+
/// `libssh2` SSH agent socket connections are inherently synchronous and can't
51+
/// be made async. The internal socket is not set to nonblocking and overriding
52+
/// this is not exposed.
4653
#[allow(clippy::missing_errors_doc)]
4754
impl<C: RuntimeContext> Agent<C> {
48-
// FIXME: Async
4955
/// See [`ssh2::Agent::connect`]
5056
pub fn connect(&mut self) -> Result<(), Error> {
5157
self.inner.connect()
5258
}
5359

54-
// FIXME: Async
5560
/// See [`ssh2::Agent::disconnect`]
5661
pub fn disconnect(&mut self) -> Result<(), Error> {
5762
self.inner.disconnect()
5863
}
5964

60-
// FIXME: Async
6165
/// See [`ssh2::Agent::list_identities`]
6266
pub fn list_identities(&mut self) -> Result<(), Error> {
6367
self.inner.list_identities()

src/lib.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ mod consts {
3434

3535
pub const ERROR_EAGAIN: ErrorCode = ErrorCode::Session(-37);
3636
pub const ERROR_BAD_SOCKET: ErrorCode = ErrorCode::Session(-45);
37+
pub const ERROR_INVAL: ErrorCode = ErrorCode::Session(-34);
3738
}
3839

3940
/// Tests are adapted from the [`ssh2`] crate examples.
@@ -51,7 +52,7 @@ mod tests {
5152
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
5253

5354
#[tokio::test]
54-
async fn inspecting_ssh_agent() -> Result<()> {
55+
async fn inspect_agent() -> Result<()> {
5556
let _agent = testing::agent().await?;
5657

5758
testing::with_server(|server_addr| async move {
@@ -80,7 +81,7 @@ mod tests {
8081
}
8182

8283
#[tokio::test]
83-
async fn authenticating_with_ssh_agent() -> Result<()> {
84+
async fn authenticate_agent() -> Result<()> {
8485
let _agent = testing::agent().await?;
8586

8687
testing::with_server(|server_addr| async move {
@@ -90,11 +91,7 @@ mod tests {
9091
sess.handshake().await?;
9192

9293
// Try to authenticate with the first identity in the agent.
93-
let mut agent = sess.agent()?;
94-
agent.connect()?;
95-
agent.list_identities()?;
96-
let identity = agent.identities()?.remove(0);
97-
agent.userauth("username", &identity).await?;
94+
sess.userauth_agent("username").await?;
9895

9996
// Make sure we succeeded
10097
assert!(sess.authenticated());
@@ -105,7 +102,7 @@ mod tests {
105102
}
106103

107104
#[tokio::test]
108-
async fn authenticate_with_password() -> Result<()> {
105+
async fn authenticate_password() -> Result<()> {
109106
testing::with_server(|server_addr| async move {
110107
// Connect to the SSH server
111108
let tcp = TcpStream::connect(server_addr).await?;

src/session.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::Sftp;
1919
#[cfg(feature = "tokio")]
2020
use crate::TokioContext;
2121
use crate::consts::ERROR_BAD_SOCKET;
22+
use crate::consts::ERROR_INVAL;
2223

2324
/// Async wrapper for [`ssh2::Session`].
2425
pub struct Session<C: RuntimeContext> {
@@ -158,10 +159,23 @@ impl<C: RuntimeContext> Session<C> {
158159
.await
159160
}
160161

162+
// It is unsound to simply wrap `ssh2::Session::userauth_agent` with async
163+
// like the rest of the methods since that would recreate the agent
164+
// connection if called with retries, which is not reentrant safe. Instead
165+
// we mimic the implementation in terms of our own async methods and avoid
166+
// duplicating the agent connection.
161167
pub async fn userauth_agent(&self, username: &str) -> Result<(), Error> {
162-
self.ctx
163-
.with_async(|| self.inner.userauth_agent(username))
164-
.await
168+
let mut agent = self.agent()?;
169+
agent.connect()?;
170+
agent.list_identities()?;
171+
let identities = agent.identities()?;
172+
let Some(identity) = identities.first() else {
173+
return Err(Error::new(
174+
ERROR_INVAL,
175+
"no identities found in the ssh agent",
176+
));
177+
};
178+
agent.userauth(username, identity).await
165179
}
166180

167181
pub async fn auth_methods(&self, username: &str) -> Result<&str, Error> {

0 commit comments

Comments
 (0)