mirror of
https://github.com/wez/wezterm.git
synced 2024-12-27 15:37:29 +03:00
Run with libssh
This works, but on macOS, there is a segfault in openssl when the session is closed... I'm going to try this on Linux to see if it is consistent behavior and ponder next steps.
This commit is contained in:
parent
0bf50924b1
commit
a8b64a2756
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -2413,7 +2413,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libssh-rs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/wez/libssh-rs.git?rev=3b74ea94143725f3411fa649d7f6f6b5178d4b1b#3b74ea94143725f3411fa649d7f6f6b5178d4b1b"
|
||||
source = "git+https://github.com/wez/libssh-rs.git?rev=6894d2f3871344b08652f7fc09761a65d6c992d6#6894d2f3871344b08652f7fc09761a65d6c992d6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libssh-rs-sys",
|
||||
@ -2423,7 +2423,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libssh-rs-sys"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/wez/libssh-rs.git?rev=3b74ea94143725f3411fa649d7f6f6b5178d4b1b#3b74ea94143725f3411fa649d7f6f6b5178d4b1b"
|
||||
source = "git+https://github.com/wez/libssh-rs.git?rev=6894d2f3871344b08652f7fc09761a65d6c992d6#6894d2f3871344b08652f7fc09761a65d6c992d6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libz-sys",
|
||||
|
@ -26,7 +26,8 @@ portable-pty = { version="0.5", path = "../pty" }
|
||||
regex = "1"
|
||||
smol = "1.2"
|
||||
ssh2 = {version="0.9.3", features=["openssl-on-win32"]}
|
||||
libssh-rs = {git="https://github.com/wez/libssh-rs.git", rev="3b74ea94143725f3411fa649d7f6f6b5178d4b1b", features=["vendored", "vendored-openssl"]}
|
||||
libssh-rs = {git="https://github.com/wez/libssh-rs.git", rev="6894d2f3871344b08652f7fc09761a65d6c992d6", features=["vendored", "vendored-openssl"]}
|
||||
#libssh-rs = {path="../../libssh-rs/libssh-rs", features=["vendored", "vendored-openssl"]}
|
||||
thiserror = "1.0"
|
||||
|
||||
# Not used directly, but is used to centralize the openssl vendor feature selection
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::session::SessionEvent;
|
||||
use anyhow::Context;
|
||||
use libssh_rs as libssh;
|
||||
use smol::channel::{bounded, Sender};
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -126,6 +127,118 @@ impl crate::session::SessionInner {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn authenticate_libssh(&mut self, sess: &libssh::Session) -> anyhow::Result<()> {
|
||||
let tx = self.tx_event.clone();
|
||||
|
||||
// Set the callback for pubkey auth
|
||||
sess.set_auth_callback(move |prompt, echo, _verify, identity| {
|
||||
let (reply, answers) = bounded(1);
|
||||
tx.try_send(SessionEvent::Authenticate(AuthenticationEvent {
|
||||
username: "".to_string(),
|
||||
instructions: "".to_string(),
|
||||
prompts: vec![AuthenticationPrompt {
|
||||
prompt: match identity {
|
||||
Some(ident) => format!("{} ({}): ", prompt, ident),
|
||||
None => prompt.to_string(),
|
||||
},
|
||||
echo,
|
||||
}],
|
||||
reply,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let mut answers = smol::block_on(answers.recv())
|
||||
.context("waiting for authentication answers from user")
|
||||
.unwrap();
|
||||
Ok(answers.remove(0))
|
||||
});
|
||||
|
||||
use libssh::{AuthMethods, AuthStatus};
|
||||
match sess.userauth_none(None)? {
|
||||
AuthStatus::Success => return Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
loop {
|
||||
let auth_methods = sess.userauth_list(None)?;
|
||||
|
||||
if auth_methods.contains(AuthMethods::PUBLIC_KEY) {
|
||||
match sess.userauth_public_key_auto(None, None)? {
|
||||
AuthStatus::Success => return Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if auth_methods.contains(AuthMethods::INTERACTIVE) {
|
||||
loop {
|
||||
match sess.userauth_keyboard_interactive(None, None)? {
|
||||
AuthStatus::Success => return Ok(()),
|
||||
AuthStatus::Info => {
|
||||
let info = sess.userauth_keyboard_interactive_info()?;
|
||||
|
||||
let (reply, answers) = bounded(1);
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::Authenticate(AuthenticationEvent {
|
||||
username: sess.get_user_name()?,
|
||||
instructions: info.instruction,
|
||||
prompts: info
|
||||
.prompts
|
||||
.into_iter()
|
||||
.map(|p| AuthenticationPrompt {
|
||||
prompt: p.prompt,
|
||||
echo: p.echo,
|
||||
})
|
||||
.collect(),
|
||||
reply,
|
||||
}))
|
||||
.context("sending Authenticate request to user")?;
|
||||
|
||||
let answers = smol::block_on(answers.recv())
|
||||
.context("waiting for authentication answers from user")?;
|
||||
|
||||
sess.userauth_keyboard_interactive_set_answers(&answers)?;
|
||||
|
||||
continue;
|
||||
}
|
||||
AuthStatus::Denied => {
|
||||
break;
|
||||
}
|
||||
status => {
|
||||
anyhow::bail!("interactive auth status: {:?}", status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if auth_methods.contains(AuthMethods::PASSWORD) {
|
||||
let (reply, answers) = bounded(1);
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::Authenticate(AuthenticationEvent {
|
||||
username: "".to_string(),
|
||||
instructions: "".to_string(),
|
||||
prompts: vec![AuthenticationPrompt {
|
||||
prompt: "Password: ".to_string(),
|
||||
echo: false,
|
||||
}],
|
||||
reply,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let mut answers = smol::block_on(answers.recv())
|
||||
.context("waiting for authentication answers from user")
|
||||
.unwrap();
|
||||
let pw = answers.remove(0);
|
||||
|
||||
match sess.userauth_password(None, Some(&pw))? {
|
||||
AuthStatus::Success => return Ok(()),
|
||||
status => anyhow::bail!("password auth status: {:?}", status),
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("unhandled auth case");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn authenticate(
|
||||
&mut self,
|
||||
sess: &ssh2::Session,
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::session::SessionEvent;
|
||||
use anyhow::{anyhow, Context};
|
||||
use libssh_rs as libssh;
|
||||
use smol::channel::{bounded, Sender};
|
||||
use ssh2::CheckResult;
|
||||
use std::io::Write;
|
||||
@ -8,7 +9,7 @@ use std::path::Path;
|
||||
#[derive(Debug)]
|
||||
pub struct HostVerificationEvent {
|
||||
pub message: String,
|
||||
reply: Sender<bool>,
|
||||
pub(crate) reply: Sender<bool>,
|
||||
}
|
||||
|
||||
impl HostVerificationEvent {
|
||||
@ -21,6 +22,62 @@ impl HostVerificationEvent {
|
||||
}
|
||||
|
||||
impl crate::session::SessionInner {
|
||||
pub fn host_verification_libssh(
|
||||
&mut self,
|
||||
sess: &libssh::Session,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
) -> anyhow::Result<()> {
|
||||
let key = sess
|
||||
.get_server_public_key()?
|
||||
.get_public_key_hash_hexa(libssh::PublicKeyHashType::Sha256)?;
|
||||
|
||||
match sess.is_known_server()? {
|
||||
libssh::KnownHosts::Ok => Ok(()),
|
||||
libssh::KnownHosts::NotFound | libssh::KnownHosts::Unknown => {
|
||||
let (reply, confirm) = bounded(1);
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::HostVerify(HostVerificationEvent {
|
||||
message: format!(
|
||||
"SSH host {}:{} is not yet trusted.\n\
|
||||
Fingerprint: {}.\n\
|
||||
Trust and continue connecting?",
|
||||
hostname, port, key
|
||||
),
|
||||
reply,
|
||||
}))
|
||||
.context("sending HostVerify request to user")?;
|
||||
|
||||
let trusted = smol::block_on(confirm.recv())
|
||||
.context("waiting for host verification confirmation from user")?;
|
||||
|
||||
if !trusted {
|
||||
anyhow::bail!("user declined to trust host");
|
||||
}
|
||||
|
||||
Ok(sess.update_known_hosts_file()?)
|
||||
}
|
||||
libssh::KnownHosts::Changed => {
|
||||
anyhow::bail!(
|
||||
"host key mismatch for ssh server {}:{}.\n\
|
||||
Got fingerprint {} instead of expected value from known_hosts\n\
|
||||
file.\n\
|
||||
Refusing to connect.",
|
||||
hostname,
|
||||
port,
|
||||
key,
|
||||
);
|
||||
}
|
||||
libssh::KnownHosts::Other => {
|
||||
anyhow::bail!(
|
||||
"The host key for this server was not found, but another\n\
|
||||
type of key exists. An attacker might change the default\n\
|
||||
server key to confuse your client into thinking the key\n\
|
||||
does not exist"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn host_verification(
|
||||
&mut self,
|
||||
sess: &ssh2::Session,
|
||||
|
@ -207,6 +207,7 @@ impl crate::session::SessionInner {
|
||||
|
||||
let (write_to_stdin, mut read_from_stdin) = socketpair()?;
|
||||
let (mut write_to_stdout, read_from_stdout) = socketpair()?;
|
||||
let write_to_stderr = write_to_stdout.try_clone()?;
|
||||
|
||||
read_from_stdin.set_non_blocking(true)?;
|
||||
write_to_stdout.set_non_blocking(true)?;
|
||||
@ -242,8 +243,8 @@ impl crate::session::SessionInner {
|
||||
buf: VecDeque::with_capacity(8192),
|
||||
},
|
||||
DescriptorState {
|
||||
fd: None,
|
||||
buf: VecDeque::new(),
|
||||
fd: Some(write_to_stderr),
|
||||
buf: VecDeque::with_capacity(8192),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -82,6 +82,7 @@ pub(crate) struct Exec {
|
||||
pub reply: Sender<ExecResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DescriptorState {
|
||||
pub fd: Option<FileDescriptor>,
|
||||
pub buf: VecDeque<u8>,
|
||||
@ -170,13 +171,6 @@ impl SessionWrap {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_blocking(&mut self) -> bool {
|
||||
match self {
|
||||
Self::Ssh2(sess) => sess.sess.is_blocking(),
|
||||
Self::LibSsh(sess) => sess.is_blocking(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_poll_flags(&self) -> i16 {
|
||||
match self {
|
||||
Self::Ssh2(sess) => match sess.sess.block_directions() {
|
||||
@ -208,8 +202,6 @@ impl SessionWrap {
|
||||
match self {
|
||||
Self::Ssh2(sess) => {
|
||||
let channel = sess.sess.channel_session()?;
|
||||
// FIXME: remove this concept
|
||||
// channel.handle_extended_data(ssh2::ExtendedData::Merge)?;
|
||||
Ok(ChannelWrap::Ssh2(channel))
|
||||
}
|
||||
Self::LibSsh(sess) => {
|
||||
@ -266,9 +258,9 @@ impl ChannelWrap {
|
||||
match self {
|
||||
Self::Ssh2(chan) => Box::new(chan.stream(idx as i32)),
|
||||
Self::LibSsh(chan) => match idx {
|
||||
1 => Box::new(chan.stdout()),
|
||||
2 => Box::new(chan.stderr()),
|
||||
_ => unreachable!(),
|
||||
0 => Box::new(chan.stdout()),
|
||||
1 => Box::new(chan.stderr()),
|
||||
_ => panic!("wanted reader for idx={}", idx),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -390,7 +382,59 @@ impl SessionInner {
|
||||
}
|
||||
|
||||
fn run_impl(&mut self) -> anyhow::Result<()> {
|
||||
self.run_impl_ssh2()
|
||||
if true {
|
||||
self.run_impl_libssh()
|
||||
} else {
|
||||
self.run_impl_ssh2()
|
||||
}
|
||||
}
|
||||
|
||||
fn run_impl_libssh(&mut self) -> anyhow::Result<()> {
|
||||
let hostname = self
|
||||
.config
|
||||
.get("hostname")
|
||||
.ok_or_else(|| anyhow!("hostname not present in config"))?
|
||||
.to_string();
|
||||
let user = self
|
||||
.config
|
||||
.get("user")
|
||||
.ok_or_else(|| anyhow!("username not present in config"))?
|
||||
.to_string();
|
||||
let port = self
|
||||
.config
|
||||
.get("port")
|
||||
.ok_or_else(|| anyhow!("port is always set in config loader"))?
|
||||
.parse::<u16>()?;
|
||||
|
||||
let sess = libssh::Session::new()?;
|
||||
// sess.set_option(libssh::SshOption::LogLevel(libssh::LogLevel::Packet))?;
|
||||
sess.set_option(libssh::SshOption::Hostname(hostname.clone()))?;
|
||||
sess.set_option(libssh::SshOption::User(Some(user)))?;
|
||||
sess.set_option(libssh::SshOption::Port(port))?;
|
||||
sess.options_parse_config(None)?; // FIXME: overridden config path?
|
||||
sess.connect()?;
|
||||
|
||||
let banner = sess.get_server_banner()?;
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::Banner(Some(banner)))
|
||||
.context("notifying user of banner")?;
|
||||
|
||||
self.host_verification_libssh(&sess, &hostname, port)?;
|
||||
self.authenticate_libssh(&sess)?;
|
||||
|
||||
if let Ok(banner) = sess.get_issue_banner() {
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::Banner(Some(banner)))
|
||||
.context("notifying user of banner")?;
|
||||
}
|
||||
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::Authenticated)
|
||||
.context("notifying user that session is authenticated")?;
|
||||
|
||||
sess.set_blocking(false);
|
||||
let mut sess = SessionWrap::with_libssh(sess);
|
||||
self.request_loop(&mut sess)
|
||||
}
|
||||
|
||||
fn run_impl_ssh2(&mut self) -> anyhow::Result<()> {
|
||||
@ -479,16 +523,18 @@ impl SessionInner {
|
||||
.context("notifying user that session is authenticated")?;
|
||||
|
||||
sess.set_blocking(false);
|
||||
self.request_loop(SessionWrap::with_ssh2(sess))
|
||||
|
||||
let mut sess = SessionWrap::with_ssh2(sess);
|
||||
self.request_loop(&mut sess)
|
||||
}
|
||||
|
||||
fn request_loop(&mut self, mut sess: SessionWrap) -> anyhow::Result<()> {
|
||||
fn request_loop(&mut self, sess: &mut SessionWrap) -> anyhow::Result<()> {
|
||||
let mut sleep_delay = Duration::from_millis(100);
|
||||
|
||||
loop {
|
||||
self.tick_io()?;
|
||||
self.drain_request_pipe();
|
||||
self.dispatch_pending_requests(&mut sess)?;
|
||||
self.dispatch_pending_requests(sess)?;
|
||||
|
||||
let mut poll_array = vec![
|
||||
pollfd {
|
||||
|
Loading…
Reference in New Issue
Block a user