1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
use std::{ffi::OsStr, io::ErrorKind};
use bstr::{BString, ByteSlice, ByteVec};
use crate::{
client::{ssh, ssh::ProgramKind},
Protocol,
};
impl ProgramKind {
/// Provide the name of the executable that belongs to this kind, or `None` if the kind is `Simple`.
pub fn exe(&self) -> Option<&'static OsStr> {
Some(OsStr::new(match self {
ProgramKind::Ssh => "ssh",
ProgramKind::Plink => "plink",
ProgramKind::Putty => "putty",
ProgramKind::TortoisePlink => "tortoiseplink.exe",
ProgramKind::Simple => return None,
}))
}
/// Prepare all information needed to invoke the ssh command
pub(crate) fn prepare_invocation(
&self,
ssh_cmd: &OsStr,
url: &gix_url::Url,
desired_version: Protocol,
disallow_shell: bool,
) -> Result<gix_command::Prepare, ssh::invocation::Error> {
let mut prepare = gix_command::prepare(ssh_cmd).with_shell();
if disallow_shell {
prepare.use_shell = false;
}
let host = url.host().expect("present in ssh urls");
match self {
ProgramKind::Ssh => {
if desired_version != Protocol::V1 {
prepare = prepare
.args(["-o", "SendEnv=GIT_PROTOCOL"])
.env("GIT_PROTOCOL", format!("version={}", desired_version as usize))
}
if let Some(port) = url.port {
prepare = prepare.arg(format!("-p{port}"));
}
}
ProgramKind::Plink | ProgramKind::Putty | ProgramKind::TortoisePlink => {
if *self == ProgramKind::TortoisePlink {
prepare = prepare.arg("-batch");
}
if let Some(port) = url.port {
prepare = prepare.arg("-P");
prepare = prepare.arg(port.to_string());
}
}
ProgramKind::Simple => {
if url.port.is_some() {
return Err(ssh::invocation::Error {
command: ssh_cmd.into(),
function: "setting the port",
});
}
}
};
let host_as_ssh_arg = match url.user() {
Some(user) => format!("{user}@{host}"),
None => host.into(),
};
// Try to force ssh to yield english messages (for parsing later)
Ok(prepare.arg(host_as_ssh_arg).env("LANG", "C").env("LC_ALL", "C"))
}
/// Note that the caller has to assure that the ssh program is launched in English by setting the locale.
pub(crate) fn line_to_err(&self, line: BString) -> Result<std::io::Error, BString> {
let kind = match self {
ProgramKind::Ssh | ProgramKind::Simple => {
if line.contains_str(b"Permission denied") || line.contains_str(b"permission denied") {
Some(ErrorKind::PermissionDenied)
} else if line.contains_str(b"resolve hostname") {
Some(ErrorKind::ConnectionRefused)
} else if line.contains_str(b"connect to host")
|| line.contains_str("Connection to ")
|| line.contains_str("Connection closed by ")
{
// TODO: turn this into HostUnreachable when stable, or NetworkUnreachable in 'no route' example.
// It's important that it WON'T be considered spurious, but is considered a permanent failure.
Some(ErrorKind::NotFound)
} else {
None
}
}
ProgramKind::Plink | ProgramKind::Putty | ProgramKind::TortoisePlink => {
if line.contains_str(b"publickey") {
Some(ErrorKind::PermissionDenied)
} else {
None
}
}
};
match kind {
Some(kind) => Ok(std::io::Error::new(kind, Vec::from(line).into_string_lossy())),
None => Err(line),
}
}
}
impl<'a> From<&'a OsStr> for ProgramKind {
fn from(v: &'a OsStr) -> Self {
let p = std::path::Path::new(v);
match p.file_stem().and_then(|s| s.to_str()) {
None => ProgramKind::Simple,
Some(stem) => {
if stem.eq_ignore_ascii_case("ssh") {
ProgramKind::Ssh
} else if stem.eq_ignore_ascii_case("plink") {
ProgramKind::Plink
} else if stem.eq_ignore_ascii_case("putty") {
ProgramKind::Putty
} else if stem.eq_ignore_ascii_case("tortoiseplink") {
ProgramKind::TortoisePlink
} else {
ProgramKind::Simple
}
}
}
}
}
|