openssh/lib.rs
1//! Scriptable SSH through OpenSSH (**only works on unix**).
2//!
3//! This crate wraps the OpenSSH remote login client (`ssh` on most machines), and provides
4//! a convenient mechanism for running commands on remote hosts. Since all commands are executed
5//! through the `ssh` command, all your existing configuration (e.g., in `.ssh/config`) should
6//! continue to work as expected.
7//!
8//! # Executing remote processes
9//!
10//! The library's API is modeled closely after that of [`std::process::Command`], since `ssh` also
11//! attempts to make the remote process seem as much as possible like a local command. However,
12//! there are some differences.
13//!
14//! First of all, all remote commands are executed in the context of a single ssh
15//! [session](Session). Authentication happens once when the session is
16//! [established](Session::connect), and subsequent command invocations re-use the same connection.
17//!
18//! Note that the maximum number of multiplexed remote commands is 10 by default. This value can be
19//! increased by changing the `MaxSessions` setting in [`sshd_config`].
20//!
21//! Much like with [`std::process::Command`], you have multiple
22//! options when it comes to launching a remote command. You can
23//! [spawn](Command::spawn) the remote command, which just gives you a
24//! handle to the running process, you can run the command and wait
25//! for its [output](Command::output), or you can run it and just
26//! extract its [exit status](Command::status). Unlike its `std`
27//! counterpart though, these methods on [`OwningCommand`] can fail
28//! even if the remote command executed successfully, since there is a
29//! fallible network separating you from it.
30//!
31//! Also unlike its `std` counterpart, [`spawn`](OwningCommand::spawn) gives you a [`Child`] rather
32//! than a [`std::process::Child`]. Behind the scenes, a remote child is really just a process
33//! handle to the _local_ `ssh` instance corresponding to the spawned remote command. The behavior
34//! of the methods of [`RemoteChild`] therefore match the behavior of `ssh`, rather than that of
35//! the remote command directly. Usually, these are the same, though not always, as highlighted in
36//! the documetantation the individual methods. See also the section below on Remote Shells.
37//!
38//! # Connection modes
39//!
40//! This library provides two way to connect to the [`ControlMaster`]:
41//!
42//! One is to spawn a new process, the other is to connect to
43//! the control socket directly.
44//!
45//! The process implementation executes remote commands by invoking
46//! the ssh command locally with arguments that make the invocation
47//! reuse the connections set up by the control master.
48//!
49//! This maximizes compatibility with OpenSSH, but loses out on some fidelity
50//! in information about execution since only the exit code and the output of
51//! the ssh command is available to inspect.
52//!
53//! The native mux implementation on the other hand connects directly to
54//! the ssh control master and executes commands and retrieves the exit codes and
55//! the output of the remote process over its native protocol.
56//!
57//! This gives better access to error information at the cost of introducing
58//! more non-OpenSSH code into the call path.
59//!
60//! The former parses the stdout/stderr of the ssh control master to retrieve the error
61//! for any failed operations, while the later retrieves the error from the control socket
62//! directly.
63//!
64//! Thus, the error handling in the later is more robust.
65//!
66//! Also, the former requires one process to be spawn for every connection while the later only
67//! needs to create one socket, so the later has better performance and consumes less resource.
68//!
69//! Behind the scenes, the crate uses ssh's [`ControlMaster`] feature to multiplex the channels for
70//! the different remote commands. Because of this, each remote command is tied to the lifetime of
71//! the [`Session`] that spawned them. When the session is [closed](Session::close), the connection
72//! is severed, and there can be no outstanding remote clients.
73//!
74//! # Authentication
75//!
76//! This library supports only password-less authentication schemes. If running `ssh` to a target
77//! host requires you to provide input on standard input (`STDIN`), then this crate will not work
78//! for you. You should set up keypair-based authentication instead.
79//!
80//! # Errors
81//!
82//! Since we are wrapping the `ssh`, which in turn runs a remote command that we do not control, we
83//! do not have a reliable way to tell the difference between what is a failure of the SSH
84//! connection itself, and what is a program error from the remote host. We do our best with some
85//! heuristics (like `ssh` exiting with status code 255 if a connection error occurs), but the
86//! errors from this crate will almost necessarily be worse than those of a native SSH
87//! implementation. Sorry in advance :)
88//!
89//! This also means that you may see strange errors when the remote process is terminated by a
90//! signal (such as through `kill` or `pkill`). When this happens, all the local ssh program sees
91//! is that the remote process disappeared, and so it returns with an error. It does not
92//! communicate that the process exited due to a signal. In cases like this, your call will return
93//! [`Error::Disconnected`], because the connection to _that_ remote process was disconnected. The
94//! ssh connection as a whole is likely still intact.
95//!
96//! To check if the connection has truly failed, use [`Session::check`]. It will return `Ok` if the
97//! master connection is still operational, and _may_ provide you with more information than you
98//! got from the failing command (that is, just [`Error::Disconnected`]) if it is not.
99//!
100//! # Remote Shells
101//!
102//! When you invoke a remote command through ssh, the remote command is executed by a shell on the
103//! remote end. That shell _interprets_ anything passed to it — it might evalute words starting
104//! with `$` as variables, split arguments by whitespace, and other things a shell is wont to do.
105//! Since that is _usually_ not what you expect to happen, `.arg("a b")` should pass a _single_
106//! argument with the value `a b`, `openssh` _escapes_ every argument (and the command itself) by
107//! default using [`shell-escape`]. This works well in most cases, but might run into issues when
108//! the remote shell (generally the remote user's login shell) has a different syntax than the
109//! shell `shell-escape` targets (bash). For example, Windows shells have different escaping syntax
110//! than bash does.
111//!
112//! If this applies to you, you can use [`raw_arg`](Command::raw_arg),
113//! [`raw_args`](Command::raw_args), and [`raw_command`](Session::raw_command) to bypass the
114//! escaping that `openssh` normally does for you.
115//!
116//! # Sftp subsystem
117//!
118//! For sftp and other ssh subsystem, check [`Session::subsystem`] for more information.
119//!
120//! # Examples
121//!
122//! ```rust,no_run
123//! # #[cfg(feature = "native-mux")]
124//! # #[tokio::main]
125//! # async fn main() -> Result<(), openssh::Error> {
126//! use openssh::{Session, KnownHosts};
127//!
128//! let session = Session::connect_mux("me@ssh.example.com", KnownHosts::Strict).await?;
129//!
130//! let ls = session.command("ls").output().await?;
131//! eprintln!("{}", String::from_utf8(ls.stdout).expect("server output was not valid UTF-8"));
132//!
133//! let whoami = session.command("whoami").output().await?;
134//! assert_eq!(whoami.stdout, b"me\n");
135//!
136//! session.close().await?;
137//! # Ok(()) }
138//! ```
139//!
140//! [`ControlMaster`]: https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
141//! [`sshd_config`]: https://linux.die.net/man/5/sshd_config
142//! [`shell-escape`]: https://crates.io/crates/shell-escape
143
144#![warn(
145 missing_docs,
146 missing_debug_implementations,
147 rustdoc::broken_intra_doc_links,
148 rust_2018_idioms,
149 unreachable_pub
150)]
151#![cfg_attr(
152 not(any(feature = "process-mux", feature = "native-mux")),
153 allow(unused_variables, unreachable_code, unused_imports, dead_code)
154)]
155// only enables the nightly `doc_cfg` feature when
156// the `docsrs` configuration attribute is defined
157#![cfg_attr(docsrs, feature(doc_cfg))]
158
159#[cfg(not(unix))]
160compile_error!("This crate can only be used on unix");
161
162mod stdio;
163pub use stdio::{ChildStderr, ChildStdin, ChildStdout, Stdio};
164
165mod session;
166pub use session::Session;
167
168mod builder;
169pub use builder::{ControlPersist, KnownHosts, SessionBuilder};
170
171mod command;
172pub use command::{OverSsh, OwningCommand};
173/// Convenience [`OwningCommand`] alias when working with a session reference.
174pub type Command<'s> = OwningCommand<&'s Session>;
175
176mod escape;
177
178mod child;
179pub use child::Child;
180/// Convenience [`Child`] alias when working with a session reference.
181pub type RemoteChild<'a> = Child<&'a Session>;
182
183mod error;
184pub use error::Error;
185
186#[cfg(feature = "process-mux")]
187pub(crate) mod process_impl;
188
189#[cfg(feature = "native-mux")]
190pub(crate) mod native_mux_impl;
191
192#[cfg(doc)]
193/// Changelog for this crate.
194pub mod changelog;
195
196mod port_forwarding;
197pub use port_forwarding::*;
198
199/// Types to create and interact with the Remote Process
200pub mod process {
201 pub use super::{ChildStderr, ChildStdin, ChildStdout, Command, RemoteChild, Stdio};
202}