|
153 | 153 | #[cfg(not(unix))]
|
154 | 154 | compile_error!("This crate can only be used on unix");
|
155 | 155 |
|
156 |
| -use std::borrow::Cow; |
157 |
| -use std::ffi::OsStr; |
158 |
| -use std::path::Path; |
159 |
| - |
160 | 156 | mod stdio;
|
161 | 157 | pub use stdio::{ChildStderr, ChildStdin, ChildStdout, Stdio};
|
162 | 158 |
|
| 159 | +mod session; |
| 160 | +pub use session::Session; |
| 161 | + |
163 | 162 | mod builder;
|
164 | 163 | pub use builder::{KnownHosts, SessionBuilder};
|
165 | 164 |
|
@@ -189,233 +188,3 @@ pub use port_forwarding::*;
|
189 | 188 | pub mod process {
|
190 | 189 | pub use super::{ChildStderr, ChildStdin, ChildStdout, Command, RemoteChild, Stdio};
|
191 | 190 | }
|
192 |
| - |
193 |
| -#[derive(Debug)] |
194 |
| -pub(crate) enum SessionImp { |
195 |
| - #[cfg(feature = "process-mux")] |
196 |
| - ProcessImpl(process_impl::Session), |
197 |
| - |
198 |
| - #[cfg(feature = "native-mux")] |
199 |
| - NativeMuxImpl(native_mux_impl::Session), |
200 |
| -} |
201 |
| - |
202 |
| -#[cfg(any(feature = "process-mux", feature = "native-mux"))] |
203 |
| -macro_rules! delegate { |
204 |
| - ($impl:expr, $var:ident, $then:block) => {{ |
205 |
| - match $impl { |
206 |
| - #[cfg(feature = "process-mux")] |
207 |
| - SessionImp::ProcessImpl($var) => $then, |
208 |
| - |
209 |
| - #[cfg(feature = "native-mux")] |
210 |
| - SessionImp::NativeMuxImpl($var) => $then, |
211 |
| - } |
212 |
| - }}; |
213 |
| -} |
214 |
| - |
215 |
| -#[cfg(not(any(feature = "process-mux", feature = "native-mux")))] |
216 |
| -macro_rules! delegate { |
217 |
| - ($impl:expr, $var:ident, $then:block) => {{ |
218 |
| - unreachable!("Neither feature process-mux nor native-mux is enabled") |
219 |
| - }}; |
220 |
| -} |
221 |
| - |
222 |
| -/// A single SSH session to a remote host. |
223 |
| -/// |
224 |
| -/// You can use [`command`](Session::command) to start a new command on the connected machine. |
225 |
| -/// |
226 |
| -/// When the `Session` is dropped, the connection to the remote host is severed, and any errors |
227 |
| -/// silently ignored. To disconnect and be alerted to errors, use [`close`](Session::close). |
228 |
| -#[derive(Debug)] |
229 |
| -pub struct Session(SessionImp); |
230 |
| - |
231 |
| -#[cfg(feature = "process-mux")] |
232 |
| -impl From<process_impl::Session> for Session { |
233 |
| - fn from(imp: process_impl::Session) -> Self { |
234 |
| - Self(SessionImp::ProcessImpl(imp)) |
235 |
| - } |
236 |
| -} |
237 |
| - |
238 |
| -#[cfg(feature = "native-mux")] |
239 |
| -impl From<native_mux_impl::Session> for Session { |
240 |
| - fn from(imp: native_mux_impl::Session) -> Self { |
241 |
| - Self(SessionImp::NativeMuxImpl(imp)) |
242 |
| - } |
243 |
| -} |
244 |
| - |
245 |
| -// TODO: UserKnownHostsFile for custom known host fingerprint. |
246 |
| - |
247 |
| -impl Session { |
248 |
| - /// Connect to the host at the given `host` over SSH using process impl, which will |
249 |
| - /// spawn a new ssh process for each `Child` created. |
250 |
| - /// |
251 |
| - /// The format of `destination` is the same as the `destination` argument to `ssh`. It may be |
252 |
| - /// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. |
253 |
| - /// |
254 |
| - /// If connecting requires interactive authentication based on `STDIN` (such as reading a |
255 |
| - /// password), the connection will fail. Consider setting up keypair-based authentication |
256 |
| - /// instead. |
257 |
| - /// |
258 |
| - /// For more options, see [`SessionBuilder`]. |
259 |
| - #[cfg(feature = "process-mux")] |
260 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "process-mux")))] |
261 |
| - pub async fn connect<S: AsRef<str>>(destination: S, check: KnownHosts) -> Result<Self, Error> { |
262 |
| - let mut s = SessionBuilder::default(); |
263 |
| - s.known_hosts_check(check); |
264 |
| - s.connect(destination.as_ref()).await |
265 |
| - } |
266 |
| - |
267 |
| - /// Connect to the host at the given `host` over SSH using native mux impl, which |
268 |
| - /// will create a new socket connection for each `Child` created. |
269 |
| - /// |
270 |
| - /// See the crate-level documentation for more details on the difference between native and process-based mux. |
271 |
| - /// |
272 |
| - /// The format of `destination` is the same as the `destination` argument to `ssh`. It may be |
273 |
| - /// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. |
274 |
| - /// |
275 |
| - /// If connecting requires interactive authentication based on `STDIN` (such as reading a |
276 |
| - /// password), the connection will fail. Consider setting up keypair-based authentication |
277 |
| - /// instead. |
278 |
| - /// |
279 |
| - /// For more options, see [`SessionBuilder`]. |
280 |
| - #[cfg(feature = "native-mux")] |
281 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "native-mux")))] |
282 |
| - pub async fn connect_mux<S: AsRef<str>>( |
283 |
| - destination: S, |
284 |
| - check: KnownHosts, |
285 |
| - ) -> Result<Self, Error> { |
286 |
| - let mut s = SessionBuilder::default(); |
287 |
| - s.known_hosts_check(check); |
288 |
| - s.connect_mux(destination.as_ref()).await |
289 |
| - } |
290 |
| - |
291 |
| - /// Check the status of the underlying SSH connection. |
292 |
| - #[cfg(not(windows))] |
293 |
| - #[cfg_attr(docsrs, doc(cfg(not(windows))))] |
294 |
| - pub async fn check(&self) -> Result<(), Error> { |
295 |
| - delegate!(&self.0, imp, { imp.check().await }) |
296 |
| - } |
297 |
| - |
298 |
| - /// Get the SSH connection's control socket path. |
299 |
| - #[cfg(not(windows))] |
300 |
| - #[cfg_attr(docsrs, doc(cfg(not(windows))))] |
301 |
| - pub fn control_socket(&self) -> &Path { |
302 |
| - delegate!(&self.0, imp, { imp.ctl() }) |
303 |
| - } |
304 |
| - |
305 |
| - /// Constructs a new [`Command`] for launching the program at path `program` on the remote |
306 |
| - /// host. |
307 |
| - /// |
308 |
| - /// Before it is passed to the remote host, `program` is escaped so that special characters |
309 |
| - /// aren't evaluated by the remote shell. If you do not want this behavior, use |
310 |
| - /// [`raw_command`](Session::raw_command). |
311 |
| - /// |
312 |
| - /// The returned `Command` is a builder, with the following default configuration: |
313 |
| - /// |
314 |
| - /// * No arguments to the program |
315 |
| - /// * Empty stdin and dsicard stdout/stderr for `spawn` or `status`, but create output pipes for |
316 |
| - /// `output` |
317 |
| - /// |
318 |
| - /// Builder methods are provided to change these defaults and otherwise configure the process. |
319 |
| - /// |
320 |
| - /// If `program` is not an absolute path, the `PATH` will be searched in an OS-defined way on |
321 |
| - /// the host. |
322 |
| - pub fn command<'a, S: Into<Cow<'a, str>>>(&self, program: S) -> Command<'_> { |
323 |
| - self.raw_command(&*shell_escape::unix::escape(program.into())) |
324 |
| - } |
325 |
| - |
326 |
| - /// Constructs a new [`Command`] for launching the program at path `program` on the remote |
327 |
| - /// host. |
328 |
| - /// |
329 |
| - /// Unlike [`command`](Session::command), this method does not shell-escape `program`, so it may be evaluated in |
330 |
| - /// unforeseen ways by the remote shell. |
331 |
| - /// |
332 |
| - /// The returned `Command` is a builder, with the following default configuration: |
333 |
| - /// |
334 |
| - /// * No arguments to the program |
335 |
| - /// * Empty stdin and dsicard stdout/stderr for `spawn` or `status`, but create output pipes for |
336 |
| - /// `output` |
337 |
| - /// |
338 |
| - /// Builder methods are provided to change these defaults and otherwise configure the process. |
339 |
| - /// |
340 |
| - /// If `program` is not an absolute path, the `PATH` will be searched in an OS-defined way on |
341 |
| - /// the host. |
342 |
| - pub fn raw_command<S: AsRef<OsStr>>(&self, program: S) -> Command<'_> { |
343 |
| - Command::new( |
344 |
| - self, |
345 |
| - delegate!(&self.0, imp, { imp.raw_command(program).into() }), |
346 |
| - ) |
347 |
| - } |
348 |
| - |
349 |
| - /// Constructs a new [`Command`] that runs the provided shell command on the remote host. |
350 |
| - /// |
351 |
| - /// The provided command is passed as a single, escaped argument to `sh -c`, and from that |
352 |
| - /// point forward the behavior is up to `sh`. Since this executes a shell command, keep in mind |
353 |
| - /// that you are subject to the shell's rules around argument parsing, such as whitespace |
354 |
| - /// splitting, variable expansion, and other funkyness. I _highly_ recommend you read |
355 |
| - /// [this article] if you observe strange things. |
356 |
| - /// |
357 |
| - /// While the returned `Command` is a builder, like for [`command`](Session::command), you should not add |
358 |
| - /// additional arguments to it, since the arguments are already passed within the shell |
359 |
| - /// command. |
360 |
| - /// |
361 |
| - /// # Non-standard Remote Shells |
362 |
| - /// |
363 |
| - /// It is worth noting that there are really _two_ shells at work here: the one that sshd |
364 |
| - /// launches for the session, and that launches are command; and the instance of `sh` that we |
365 |
| - /// launch _in_ that session. This method tries hard to ensure that the provided `command` is |
366 |
| - /// passed exactly as-is to `sh`, but this is complicated by the presence of the "outer" shell. |
367 |
| - /// That outer shell may itself perform argument splitting, variable expansion, and the like, |
368 |
| - /// which might produce unintuitive results. For example, the outer shell may try to expand a |
369 |
| - /// variable that is only defined in the inner shell, and simply produce an empty string in the |
370 |
| - /// variable's place by the time it gets to `sh`. |
371 |
| - /// |
372 |
| - /// To counter this, this method assumes that the remote shell (the one launched by `sshd`) is |
373 |
| - /// [POSIX compliant]. This is more or less equivalent to "supports `bash` syntax" if you don't |
374 |
| - /// look too closely. It uses [`shell-escape`] to escape `command` before sending it to the |
375 |
| - /// remote shell, with the expectation that the remote shell will only end up undoing that one |
376 |
| - /// "level" of escaping, thus producing the original `command` as an argument to `sh`. This |
377 |
| - /// works _most of the time_. |
378 |
| - /// |
379 |
| - /// With sufficiently complex or weird commands, the escaping of `shell-escape` may not fully |
380 |
| - /// match the "un-escaping" of the remote shell. This will manifest as escape characters |
381 |
| - /// appearing in the `sh` command that you did not intend to be there. If this happens, try |
382 |
| - /// changing the remote shell if you can, or fall back to [`command`](Session::command) |
383 |
| - /// and do the escaping manually instead. |
384 |
| - /// |
385 |
| - /// [POSIX compliant]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html |
386 |
| - /// [this article]: https://mywiki.wooledge.org/Arguments |
387 |
| - /// [`shell-escape`]: https://crates.io/crates/shell-escape |
388 |
| - pub fn shell<S: AsRef<str>>(&self, command: S) -> Command<'_> { |
389 |
| - let mut cmd = self.command("sh"); |
390 |
| - cmd.arg("-c").arg(command); |
391 |
| - cmd |
392 |
| - } |
393 |
| - |
394 |
| - /// Request to open a local/remote port forwarding. |
395 |
| - /// The `Socket` can be either a unix socket or a tcp socket. |
396 |
| - /// |
397 |
| - /// If `forward_type` == Local, then `listen_socket` on local machine will be |
398 |
| - /// forwarded to `connect_socket` on remote machine. |
399 |
| - /// |
400 |
| - /// Otherwise, `listen_socket` on the remote machine will be forwarded to `connect_socket` |
401 |
| - /// on the local machine. |
402 |
| - /// |
403 |
| - /// Currently, there is no way of stopping a port forwarding due to the fact that |
404 |
| - /// openssh multiplex server/master does not support this. |
405 |
| - pub async fn request_port_forward( |
406 |
| - &self, |
407 |
| - forward_type: ForwardType, |
408 |
| - listen_socket: Socket<'_>, |
409 |
| - connect_socket: Socket<'_>, |
410 |
| - ) -> Result<(), Error> { |
411 |
| - delegate!(&self.0, imp, { |
412 |
| - imp.request_port_forward(forward_type, listen_socket, connect_socket) |
413 |
| - .await |
414 |
| - }) |
415 |
| - } |
416 |
| - |
417 |
| - /// Terminate the remote connection. |
418 |
| - pub async fn close(self) -> Result<(), Error> { |
419 |
| - delegate!(self.0, imp, { imp.close().await }) |
420 |
| - } |
421 |
| -} |
0 commit comments