Skip to content

Commit de39149

Browse files
authored
Merge pull request #78 from wiktor-k/wiktor/doc-examples
Add doc examples
2 parents 19fce9c + 9536f13 commit de39149

File tree

4 files changed

+185
-27
lines changed

4 files changed

+185
-27
lines changed

src/agent.rs

Lines changed: 182 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
//! Traits for implementing custom SSH agents
1+
//! Traits for implementing custom SSH agents.
2+
//!
3+
//! Agents which store no state or their state is minimal should
4+
//! implement the [`Session`] trait. If a more elaborate state is
5+
//! needed, especially one which depends on the socket making the
6+
//! connection then it is advisable to implement the [`Agent`] trait.
27
38
use std::fmt;
49
use std::io;
@@ -29,6 +34,39 @@ use crate::proto::SignRequest;
2934
use crate::proto::SmartcardKey;
3035

3136
/// Type representing a socket that asynchronously returns a list of streams.
37+
///
38+
/// This trait is implemented for [TCP sockets](TcpListener) on all
39+
/// platforms, Unix sockets on Unix platforms (e.g. Linux, macOS) and
40+
/// Named Pipes on Windows.
41+
///
42+
/// Objects implementing this trait are passed to the [`listen`]
43+
/// function.
44+
///
45+
/// # Examples
46+
///
47+
/// The following example starts listening for connections and
48+
/// processes them with the `MyAgent` struct.
49+
///
50+
/// ```no_run
51+
/// # async fn main_() -> testresult::TestResult {
52+
/// use ssh_agent_lib::agent::{listen, Session};
53+
/// use tokio::net::TcpListener;
54+
///
55+
/// #[derive(Default, Clone)]
56+
/// struct MyAgent;
57+
///
58+
/// impl Session for MyAgent {
59+
/// // implement your agent logic here
60+
/// }
61+
///
62+
/// listen(
63+
/// TcpListener::bind("127.0.0.1:8080").await?,
64+
/// MyAgent::default(),
65+
/// )
66+
/// .await?;
67+
/// # Ok(()) }
68+
/// ```
69+
3270
#[async_trait]
3371
pub trait ListeningSocket {
3472
/// Stream type that represents an accepted socket.
@@ -91,6 +129,43 @@ impl ListeningSocket for NamedPipeListener {
91129
///
92130
/// This type is implemented by agents that want to handle incoming SSH agent
93131
/// connections.
132+
///
133+
/// # Examples
134+
///
135+
/// The following examples shows the most minimal [`Session`]
136+
/// implementation: one that returns a list of public keys that it
137+
/// manages and signs all incoming signing requests.
138+
///
139+
/// Note that the `MyAgent` struct is cloned for all new sessions
140+
/// (incoming connections). If the cloning needs special behavior
141+
/// implementing [`Clone`] manually is a viable approach. If the newly
142+
/// created sessions require information from the underlying socket it
143+
/// is advisable to implement the [`Agent`] trait.
144+
///
145+
/// ```
146+
/// use ssh_agent_lib::{agent::Session, error::AgentError};
147+
/// use ssh_agent_lib::proto::{Identity, SignRequest};
148+
/// use ssh_key::{Algorithm, Signature};
149+
///
150+
/// #[derive(Default, Clone)]
151+
/// struct MyAgent;
152+
///
153+
/// #[ssh_agent_lib::async_trait]
154+
/// impl Session for MyAgent {
155+
/// async fn request_identities(&mut self) -> Result<Vec<Identity>, AgentError> {
156+
/// Ok(vec![ /* public keys that this agent knows of */ ])
157+
/// }
158+
///
159+
/// async fn sign(&mut self, request: SignRequest) -> Result<Signature, AgentError> {
160+
/// // get the signature by signing `request.data`
161+
/// let signature = vec![];
162+
/// Ok(Signature::new(
163+
/// Algorithm::new("algorithm").map_err(AgentError::other)?,
164+
/// signature,
165+
/// ).map_err(AgentError::other)?)
166+
/// }
167+
/// }
168+
/// ```
94169
#[async_trait]
95170
pub trait Session: 'static + Sync + Send + Unpin {
96171
/// Request a list of keys managed by this session.
@@ -251,6 +326,37 @@ where
251326
}
252327

253328
/// Factory of sessions for the given type of sockets.
329+
///
330+
/// An agent implementation is automatically created for types which
331+
/// implement [`Session`] and [`Clone`]: new sessions are created by
332+
/// cloning the agent object. This is usually sufficient for the
333+
/// majority of use cases. In case the information about the
334+
/// underlying socket (connection source) is needed the [`Agent`] can
335+
/// be implemented manually.
336+
///
337+
/// # Examples
338+
///
339+
/// This example shows how to retrieve the connecting process ID on Unix:
340+
///
341+
/// ```
342+
/// use ssh_agent_lib::agent::{Agent, Session};
343+
///
344+
/// #[derive(Debug, Default)]
345+
/// struct AgentSocketInfo;
346+
///
347+
/// #[cfg(unix)]
348+
/// impl Agent<tokio::net::UnixListener> for AgentSocketInfo {
349+
/// fn new_session(&mut self, socket: &tokio::net::UnixStream) -> impl Session {
350+
/// let _socket_info = format!(
351+
/// "unix: addr: {:?} cred: {:?}",
352+
/// socket.peer_addr().unwrap(),
353+
/// socket.peer_cred().unwrap()
354+
/// );
355+
/// Self
356+
/// }
357+
/// }
358+
/// # impl Session for AgentSocketInfo { }
359+
/// ```
254360
pub trait Agent<S>: 'static + Send + Sync
255361
where
256362
S: ListeningSocket + fmt::Debug + Send,
@@ -261,15 +367,40 @@ where
261367

262368
/// Listen for connections on a given socket and use session factory
263369
/// to create new session for each accepted socket.
264-
pub async fn listen<S>(mut socket: S, mut sf: impl Agent<S>) -> Result<(), AgentError>
370+
///
371+
/// # Examples
372+
///
373+
/// The following example starts listening for connections and
374+
/// processes them with the `MyAgent` struct.
375+
///
376+
/// ```no_run
377+
/// # async fn main_() -> testresult::TestResult {
378+
/// use ssh_agent_lib::agent::{listen, Session};
379+
/// use tokio::net::TcpListener;
380+
///
381+
/// #[derive(Default, Clone)]
382+
/// struct MyAgent;
383+
///
384+
/// impl Session for MyAgent {
385+
/// // implement your agent logic here
386+
/// }
387+
///
388+
/// listen(
389+
/// TcpListener::bind("127.0.0.1:8080").await?,
390+
/// MyAgent::default(),
391+
/// )
392+
/// .await?;
393+
/// # Ok(()) }
394+
/// ```
395+
pub async fn listen<S>(mut socket: S, mut agent: impl Agent<S>) -> Result<(), AgentError>
265396
where
266397
S: ListeningSocket + fmt::Debug + Send,
267398
{
268399
log::info!("Listening; socket = {:?}", socket);
269400
loop {
270401
match socket.accept().await {
271402
Ok(socket) => {
272-
let session = sf.new_session(&socket);
403+
let session = agent.new_session(&socket);
273404
tokio::spawn(async move {
274405
let adapter = Framed::new(socket, Codec::<Request, Response>::default());
275406
if let Err(e) = handle_socket::<S>(session, adapter).await {
@@ -317,38 +448,65 @@ where
317448
}
318449
}
319450

320-
/// Bind to a service binding listener.
321451
#[cfg(unix)]
322-
pub async fn bind<SF>(listener: service_binding::Listener, sf: SF) -> Result<(), AgentError>
452+
type PlatformSpecificListener = tokio::net::UnixListener;
453+
454+
#[cfg(windows)]
455+
type PlatformSpecificListener = NamedPipeListener;
456+
457+
/// Bind to a service binding listener.
458+
///
459+
/// # Examples
460+
///
461+
/// The following example uses `clap` to parse the host socket data
462+
/// thus allowing the user to choose at runtime whether they want to
463+
/// use TCP sockets, Unix domain sockets (including systemd socket
464+
/// activation) or Named Pipes (under Windows).
465+
///
466+
/// ```no_run
467+
/// use clap::Parser;
468+
/// use service_binding::Binding;
469+
/// use ssh_agent_lib::agent::{bind, Session};
470+
///
471+
/// #[derive(Debug, Parser)]
472+
/// struct Args {
473+
/// #[clap(long, short = 'H', default_value = "unix:///tmp/ssh.sock")]
474+
/// host: Binding,
475+
/// }
476+
///
477+
/// #[derive(Default, Clone)]
478+
/// struct MyAgent;
479+
///
480+
/// impl Session for MyAgent {}
481+
///
482+
/// #[tokio::main]
483+
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
484+
/// let args = Args::parse();
485+
///
486+
/// bind(args.host.try_into()?, MyAgent::default()).await?;
487+
///
488+
/// Ok(())
489+
/// }
490+
/// ```
491+
pub async fn bind<A>(listener: service_binding::Listener, agent: A) -> Result<(), AgentError>
323492
where
324-
SF: Agent<tokio::net::UnixListener> + Agent<tokio::net::TcpListener>,
493+
A: Agent<PlatformSpecificListener> + Agent<tokio::net::TcpListener>,
325494
{
326495
match listener {
327496
#[cfg(unix)]
328497
service_binding::Listener::Unix(listener) => {
329-
listen(UnixListener::from_std(listener)?, sf).await
498+
listen(UnixListener::from_std(listener)?, agent).await
330499
}
331500
service_binding::Listener::Tcp(listener) => {
332-
listen(TcpListener::from_std(listener)?, sf).await
501+
listen(TcpListener::from_std(listener)?, agent).await
333502
}
503+
#[cfg(windows)]
504+
service_binding::Listener::NamedPipe(pipe) => {
505+
listen(NamedPipeListener::bind(pipe)?, agent).await
506+
}
507+
#[allow(unreachable_patterns)]
334508
_ => Err(AgentError::IO(std::io::Error::other(
335509
"Unsupported type of a listener.",
336510
))),
337511
}
338512
}
339-
340-
/// Bind to a service binding listener.
341-
#[cfg(windows)]
342-
pub async fn bind<SF>(listener: service_binding::Listener, sf: SF) -> Result<(), AgentError>
343-
where
344-
SF: Agent<NamedPipeListener> + Agent<tokio::net::TcpListener>,
345-
{
346-
match listener {
347-
service_binding::Listener::Tcp(listener) => {
348-
listen(TcpListener::from_std(listener)?, sf).await
349-
}
350-
service_binding::Listener::NamedPipe(pipe) => {
351-
listen(NamedPipeListener::bind(pipe)?, sf).await
352-
}
353-
}
354-
}

src/codec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! SSH agent protocol framing codec
1+
//! SSH agent protocol framing codec.
22
33
use std::marker::PhantomData;
44
use std::mem::size_of;

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! SSH agent errors
1+
//! SSH agent errors.
22
33
use std::io;
44

src/proto.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! SSH agent protocol structures
1+
//! SSH agent protocol structures.
22
33
pub mod error;
44
pub mod extension;

0 commit comments

Comments
 (0)