]> git.proxmox.com Git - rustc.git/blob - vendor/gix-transport/src/client/blocking_io/ssh/mod.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / vendor / gix-transport / src / client / blocking_io / ssh / mod.rs
1 use std::process::Stdio;
2
3 use crate::{client::blocking_io, Protocol};
4
5 /// The error used in [`connect()`].
6 #[derive(Debug, thiserror::Error)]
7 #[allow(missing_docs)]
8 pub enum Error {
9 #[error("The scheme in \"{}\" is not usable for an ssh connection", .0.to_bstring())]
10 UnsupportedScheme(gix_url::Url),
11 #[error("Host name '{host}' could be mistaken for a command-line argument")]
12 AmbiguousHostName { host: String },
13 }
14
15 impl crate::IsSpuriousError for Error {}
16
17 /// The kind of SSH programs we have built-in support for.
18 ///
19 /// Various different programs exists with different capabilities, and we have a few built in.
20 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
21 pub enum ProgramKind {
22 /// The standard linux ssh program
23 Ssh,
24 /// The `(plink|putty).exe` binaries, typically only on windows.
25 Plink,
26 /// The `putty.exe` binary, typically only on windows.
27 Putty,
28 /// The `tortoiseplink.exe` binary, only on windows.
29 TortoisePlink,
30 /// A minimal ssh client that supports on options.
31 Simple,
32 }
33
34 mod program_kind;
35
36 ///
37 pub mod invocation {
38 use std::ffi::OsString;
39
40 /// The error returned when producing ssh invocation arguments based on a selected invocation kind.
41 #[derive(Debug, thiserror::Error)]
42 #[allow(missing_docs)]
43 pub enum Error {
44 #[error("Host name '{host}' could be mistaken for a command-line argument")]
45 AmbiguousHostName { host: String },
46 #[error("The 'Simple' ssh variant doesn't support {function}")]
47 Unsupported {
48 /// The simple command that should have been invoked.
49 command: OsString,
50 /// The function that was unsupported
51 function: &'static str,
52 },
53 }
54 }
55
56 ///
57 pub mod connect {
58 use std::ffi::{OsStr, OsString};
59
60 use crate::client::ssh::ProgramKind;
61
62 /// The options for use when [connecting][super::connect()] via the `ssh` protocol.
63 #[derive(Debug, Clone, Default)]
64 pub struct Options {
65 /// The program or script to use.
66 /// If unset, it defaults to `ssh` or `ssh.exe`, or the program implied by `kind` if that one is set.
67 pub command: Option<OsString>,
68 /// If `true`, a shell must not be used to execute `command`.
69 /// This defaults to `false`, and a shell can then be used if `command` seems to require it, but won't be
70 /// used unnecessarily.
71 pub disallow_shell: bool,
72 /// The ssh variant further identifying `program`. This determines which arguments will be used
73 /// when invoking the program.
74 /// If unset, the `program` basename determines the variant, or an invocation of the `command` itself.
75 pub kind: Option<ProgramKind>,
76 }
77
78 impl Options {
79 /// Return the configured ssh command, defaulting to `ssh` if neither the `command` nor the `kind` fields are set.
80 pub fn ssh_command(&self) -> &OsStr {
81 self.command
82 .as_deref()
83 .or_else(|| self.kind.and_then(|kind| kind.exe()))
84 .unwrap_or_else(|| OsStr::new("ssh"))
85 }
86 }
87 }
88
89 /// Connect to `host` using the ssh program to obtain data from the repository at `path` on the remote.
90 ///
91 /// The optional `user` identifies the user's account to which to connect, while `port` allows to specify non-standard
92 /// ssh ports.
93 ///
94 /// The `desired_version` is the preferred protocol version when establishing the connection, but note that it can be
95 /// downgraded by servers not supporting it.
96 /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.
97 #[allow(clippy::result_large_err)]
98 pub fn connect(
99 url: gix_url::Url,
100 desired_version: Protocol,
101 options: connect::Options,
102 trace: bool,
103 ) -> Result<blocking_io::file::SpawnProcessOnDemand, Error> {
104 if url.scheme != gix_url::Scheme::Ssh || url.host().is_none() {
105 return Err(Error::UnsupportedScheme(url));
106 }
107 let ssh_cmd = options.ssh_command();
108 let mut kind = options.kind.unwrap_or_else(|| ProgramKind::from(ssh_cmd));
109 if options.kind.is_none() && kind == ProgramKind::Simple {
110 let mut cmd = std::process::Command::from(
111 gix_command::prepare(ssh_cmd)
112 .stderr(Stdio::null())
113 .stdout(Stdio::null())
114 .stdin(Stdio::null())
115 .with_shell()
116 .arg("-G")
117 .arg(url.host_argument_safe().ok_or_else(|| Error::AmbiguousHostName {
118 host: url.host().expect("set in ssh urls").into(),
119 })?),
120 );
121 gix_features::trace::debug!(cmd = ?cmd, "invoking `ssh` for feature check");
122 kind = if cmd.status().ok().map_or(false, |status| status.success()) {
123 ProgramKind::Ssh
124 } else {
125 ProgramKind::Simple
126 };
127 }
128
129 let path = gix_url::expand_path::for_shell(url.path.clone());
130 Ok(blocking_io::file::SpawnProcessOnDemand::new_ssh(
131 url,
132 ssh_cmd,
133 path,
134 kind,
135 options.disallow_shell,
136 desired_version,
137 trace,
138 ))
139 }
140
141 #[cfg(test)]
142 mod tests;