mirror of
https://github.com/wez/wezterm.git
synced 2024-12-24 13:52:55 +03:00
ssh: use a single window for authenticating a session
This makes it so that we preserve context while showing the connection status and authentication prompts.
This commit is contained in:
parent
1ef95b917a
commit
fd8f28960f
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -539,6 +539,20 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-channel 0.4.0",
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-queue",
|
||||||
|
"crossbeam-utils 0.7.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -3401,7 +3415,7 @@ dependencies = [
|
|||||||
"core-foundation 0.7.0",
|
"core-foundation 0.7.0",
|
||||||
"core-graphics 0.19.0",
|
"core-graphics 0.19.0",
|
||||||
"core-text 15.0.0",
|
"core-text 15.0.0",
|
||||||
"crossbeam-channel 0.3.9",
|
"crossbeam",
|
||||||
"daemonize",
|
"daemonize",
|
||||||
"dirs 1.0.5",
|
"dirs 1.0.5",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
|
@ -21,7 +21,7 @@ base64 = "0.10"
|
|||||||
base91 = { path = "base91" }
|
base91 = { path = "base91" }
|
||||||
rangeset = { path = "rangeset" }
|
rangeset = { path = "rangeset" }
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
crossbeam-channel = "0.3"
|
crossbeam = "0.7"
|
||||||
dirs = "1.0"
|
dirs = "1.0"
|
||||||
downcast-rs = "1.0"
|
downcast-rs = "1.0"
|
||||||
euclid = "0.20"
|
euclid = "0.20"
|
||||||
|
@ -6,7 +6,7 @@ use crate::mux::window::WindowId;
|
|||||||
use crate::mux::Mux;
|
use crate::mux::Mux;
|
||||||
use crate::server::listener::spawn_listener;
|
use crate::server::listener::spawn_listener;
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use crossbeam_channel::{unbounded as channel, Receiver};
|
use crossbeam::channel::{unbounded as channel, Receiver};
|
||||||
use log::info;
|
use log::info;
|
||||||
use promise::*;
|
use promise::*;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -9,7 +9,7 @@ use crate::server::tab::ClientTab;
|
|||||||
use crate::server::UnixStream;
|
use crate::server::UnixStream;
|
||||||
use crate::ssh::ssh_connect;
|
use crate::ssh::ssh_connect;
|
||||||
use anyhow::{anyhow, bail, Context, Error};
|
use anyhow::{anyhow, bail, Context, Error};
|
||||||
use crossbeam_channel::TryRecvError;
|
use crossbeam::channel::TryRecvError;
|
||||||
use filedescriptor::{pollfd, AsRawSocketDescriptor};
|
use filedescriptor::{pollfd, AsRawSocketDescriptor};
|
||||||
use log::info;
|
use log::info;
|
||||||
use portable_pty::{CommandBuilder, NativePtySystem, PtySystem};
|
use portable_pty::{CommandBuilder, NativePtySystem, PtySystem};
|
||||||
|
@ -4,7 +4,7 @@ use crate::mux::{Mux, MuxNotification, MuxSubscriber};
|
|||||||
use crate::server::codec::*;
|
use crate::server::codec::*;
|
||||||
use crate::server::pollable::*;
|
use crate::server::pollable::*;
|
||||||
use anyhow::{anyhow, bail, Context, Error};
|
use anyhow::{anyhow, bail, Context, Error};
|
||||||
use crossbeam_channel::TryRecvError;
|
use crossbeam::channel::TryRecvError;
|
||||||
use log::error;
|
use log::error;
|
||||||
use portable_pty::PtySize;
|
use portable_pty::PtySize;
|
||||||
use promise::spawn::spawn_into_main_thread;
|
use promise::spawn::spawn_into_main_thread;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::server::UnixStream;
|
use crate::server::UnixStream;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use crossbeam_channel::{unbounded as channel, Receiver, Sender, TryRecvError};
|
use crossbeam::channel::{unbounded as channel, Receiver, Sender, TryRecvError};
|
||||||
use filedescriptor::*;
|
use filedescriptor::*;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
292
src/ssh.rs
292
src/ssh.rs
@ -6,6 +6,7 @@ use crate::mux::Mux;
|
|||||||
use crate::termwiztermtab;
|
use crate::termwiztermtab;
|
||||||
use anyhow::{anyhow, bail, Context, Error};
|
use anyhow::{anyhow, bail, Context, Error};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use crossbeam::channel::{bounded, Receiver, Sender};
|
||||||
use portable_pty::cmdbuilder::CommandBuilder;
|
use portable_pty::cmdbuilder::CommandBuilder;
|
||||||
use portable_pty::{PtySize, PtySystem};
|
use portable_pty::{PtySize, PtySystem};
|
||||||
use promise::{Future, Promise};
|
use promise::{Future, Promise};
|
||||||
@ -14,41 +15,24 @@ use std::io::Write;
|
|||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use termwiz::cell::{unicode_column_width, AttributeChange, Intensity};
|
use std::time::Duration;
|
||||||
|
use termwiz::cell::unicode_column_width;
|
||||||
use termwiz::lineedit::*;
|
use termwiz::lineedit::*;
|
||||||
use termwiz::surface::Change;
|
use termwiz::surface::Change;
|
||||||
use termwiz::terminal::*;
|
use termwiz::terminal::*;
|
||||||
|
|
||||||
fn password_prompt(
|
#[derive(Default)]
|
||||||
instructions: &str,
|
struct PasswordPromptHost {
|
||||||
prompt: &str,
|
|
||||||
username: &str,
|
|
||||||
remote_address: &str,
|
|
||||||
) -> Option<String> {
|
|
||||||
let title = "🔐 wezterm: SSH authentication".to_string();
|
|
||||||
let text = format!(
|
|
||||||
"🔐 SSH Authentication for {} @ {}\n{}\n",
|
|
||||||
username, remote_address, instructions
|
|
||||||
)
|
|
||||||
.replace("\n", "\r\n");
|
|
||||||
let prompt = prompt.to_string();
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct PasswordPromptHost {
|
|
||||||
history: BasicHistory,
|
history: BasicHistory,
|
||||||
}
|
}
|
||||||
impl LineEditorHost for PasswordPromptHost {
|
impl LineEditorHost for PasswordPromptHost {
|
||||||
fn history(&mut self) -> &mut dyn History {
|
fn history(&mut self) -> &mut dyn History {
|
||||||
&mut self.history
|
&mut self.history
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewrite the input so that we can obscure the password
|
// Rewrite the input so that we can obscure the password
|
||||||
// characters when output to the terminal widget
|
// characters when output to the terminal widget
|
||||||
fn highlight_line(
|
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
|
||||||
&self,
|
|
||||||
line: &str,
|
|
||||||
cursor_position: usize,
|
|
||||||
) -> (Vec<OutputElement>, usize) {
|
|
||||||
let placeholder = "🔑";
|
let placeholder = "🔑";
|
||||||
let grapheme_count = unicode_column_width(line);
|
let grapheme_count = unicode_column_width(line);
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
@ -57,53 +41,73 @@ fn password_prompt(
|
|||||||
}
|
}
|
||||||
(output, unicode_column_width(placeholder) * cursor_position)
|
(output, unicode_column_width(placeholder) * cursor_position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match promise::spawn::block_on(termwiztermtab::run(60, 10, move |mut term| {
|
|
||||||
term.render(&[
|
|
||||||
// Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
|
|
||||||
Change::Title(title.to_string()),
|
|
||||||
Change::Text(text.to_string()),
|
|
||||||
Change::Attribute(AttributeChange::Intensity(Intensity::Normal)),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
let mut editor = LineEditor::new(term);
|
enum UIRequest {
|
||||||
editor.set_prompt(&format!("{}: ", prompt));
|
/// Display something
|
||||||
|
Output(Vec<Change>),
|
||||||
|
/// Request input
|
||||||
|
Input {
|
||||||
|
prompt: String,
|
||||||
|
echo: bool,
|
||||||
|
respond: Promise<String>,
|
||||||
|
},
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SshUIImpl {
|
||||||
|
term: termwiztermtab::TermWizTerminal,
|
||||||
|
rx: Receiver<UIRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SshUIImpl {
|
||||||
|
fn run(&mut self) -> anyhow::Result<()> {
|
||||||
|
let title = "🔐 wezterm: SSH authentication".to_string();
|
||||||
|
self.term.render(&[Change::Title(title)])?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.rx.recv_timeout(Duration::from_millis(200)) {
|
||||||
|
Ok(UIRequest::Close) => break,
|
||||||
|
Ok(UIRequest::Output(changes)) => self.term.render(&changes)?,
|
||||||
|
Ok(UIRequest::Input {
|
||||||
|
prompt,
|
||||||
|
echo: true,
|
||||||
|
mut respond,
|
||||||
|
}) => {
|
||||||
|
respond.result(self.input_prompt(&prompt));
|
||||||
|
}
|
||||||
|
Ok(UIRequest::Input {
|
||||||
|
prompt,
|
||||||
|
echo: false,
|
||||||
|
mut respond,
|
||||||
|
}) => {
|
||||||
|
respond.result(self.password_prompt(&prompt));
|
||||||
|
}
|
||||||
|
Err(err) if err.is_timeout() => {}
|
||||||
|
Err(err) => bail!("recv_timeout: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::new(2, 0));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn password_prompt(&mut self, prompt: &str) -> anyhow::Result<String> {
|
||||||
|
let mut editor = LineEditor::new(&mut self.term);
|
||||||
|
editor.set_prompt(prompt);
|
||||||
|
|
||||||
let mut host = PasswordPromptHost::default();
|
let mut host = PasswordPromptHost::default();
|
||||||
if let Some(line) = editor.read_line(&mut host)? {
|
if let Some(line) = editor.read_line(&mut host)? {
|
||||||
Ok(line)
|
Ok(line)
|
||||||
} else {
|
} else {
|
||||||
bail!("prompt cancelled");
|
bail!("password entry was cancelled");
|
||||||
}
|
|
||||||
})) {
|
|
||||||
Ok(p) => Some(p),
|
|
||||||
Err(p) => {
|
|
||||||
log::error!("failed to prompt for pw: {}", p);
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn input_prompt(
|
fn input_prompt(&mut self, prompt: &str) -> anyhow::Result<String> {
|
||||||
instructions: &str,
|
let mut editor = LineEditor::new(&mut self.term);
|
||||||
prompt: &str,
|
editor.set_prompt(prompt);
|
||||||
username: &str,
|
|
||||||
remote_address: &str,
|
|
||||||
) -> Option<String> {
|
|
||||||
let title = "🔐 wezterm: SSH authentication".to_string();
|
|
||||||
let text = format!(
|
|
||||||
"SSH Authentication for {} @ {}\n{}\n{}\n",
|
|
||||||
username, remote_address, instructions, prompt
|
|
||||||
)
|
|
||||||
.replace("\n", "\r\n");
|
|
||||||
match promise::spawn::block_on(termwiztermtab::run(60, 10, move |mut term| {
|
|
||||||
term.render(&[
|
|
||||||
Change::Title(title.to_string()),
|
|
||||||
Change::Text(text.to_string()),
|
|
||||||
Change::Attribute(AttributeChange::Intensity(Intensity::Normal)),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
let mut editor = LineEditor::new(term);
|
|
||||||
|
|
||||||
let mut host = NopLineEditorHost::default();
|
let mut host = NopLineEditorHost::default();
|
||||||
if let Some(line) = editor.read_line(&mut host)? {
|
if let Some(line) = editor.read_line(&mut host)? {
|
||||||
@ -111,21 +115,72 @@ fn input_prompt(
|
|||||||
} else {
|
} else {
|
||||||
bail!("prompt cancelled");
|
bail!("prompt cancelled");
|
||||||
}
|
}
|
||||||
})) {
|
|
||||||
Ok(p) => Some(p),
|
|
||||||
Err(p) => {
|
|
||||||
log::error!("failed to prompt for pw: {}", p);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Prompt<'a> {
|
struct SshUI {
|
||||||
username: &'a str,
|
tx: Sender<UIRequest>,
|
||||||
remote_address: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ssh2::KeyboardInteractivePrompt for Prompt<'a> {
|
impl SshUI {
|
||||||
|
fn new() -> Self {
|
||||||
|
let (tx, rx) = bounded(16);
|
||||||
|
promise::spawn::spawn_into_main_thread(termwiztermtab::run(70, 15, move |term| {
|
||||||
|
let mut ui = SshUIImpl { term, rx };
|
||||||
|
ui.run()
|
||||||
|
}));
|
||||||
|
Self { tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output(&self, changes: Vec<Change>) {
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Output(changes))
|
||||||
|
.expect("send to SShUI failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_str(&self, s: &str) {
|
||||||
|
let s = s.replace("\n", "\r\n");
|
||||||
|
self.output(vec![Change::Text(s)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input(&self, prompt: &str) -> anyhow::Result<String> {
|
||||||
|
let mut promise = Promise::new();
|
||||||
|
let future = promise.get_future().unwrap();
|
||||||
|
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Input {
|
||||||
|
prompt: prompt.replace("\n", "\r\n"),
|
||||||
|
echo: true,
|
||||||
|
respond: promise,
|
||||||
|
})
|
||||||
|
.expect("send to SshUI failed");
|
||||||
|
|
||||||
|
future.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn password(&self, prompt: &str) -> anyhow::Result<String> {
|
||||||
|
let mut promise = Promise::new();
|
||||||
|
let future = promise.get_future().unwrap();
|
||||||
|
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Input {
|
||||||
|
prompt: prompt.replace("\n", "\r\n"),
|
||||||
|
echo: false,
|
||||||
|
respond: promise,
|
||||||
|
})
|
||||||
|
.expect("send to SshUI failed");
|
||||||
|
|
||||||
|
future.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&self) {
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Close)
|
||||||
|
.expect("send to SshUI failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ssh2::KeyboardInteractivePrompt for SshUI {
|
||||||
fn prompt<'b>(
|
fn prompt<'b>(
|
||||||
&mut self,
|
&mut self,
|
||||||
_username: &str,
|
_username: &str,
|
||||||
@ -135,14 +190,13 @@ impl<'a> ssh2::KeyboardInteractivePrompt for Prompt<'a> {
|
|||||||
prompts
|
prompts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
let func = if p.echo {
|
self.output_str(&format!("{}\n", instructions));
|
||||||
input_prompt
|
if p.echo {
|
||||||
|
self.input(&p.text)
|
||||||
} else {
|
} else {
|
||||||
password_prompt
|
self.password(&p.text)
|
||||||
};
|
}
|
||||||
|
.unwrap_or_else(|_| String::new())
|
||||||
func(instructions, &p.text, &self.username, &self.remote_address)
|
|
||||||
.unwrap_or_else(String::new)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@ -157,7 +211,11 @@ pub fn async_ssh_connect(remote_address: &str, username: &str) -> Future<ssh2::S
|
|||||||
future
|
future
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2::Session> {
|
fn ssh_connect_with_ui(
|
||||||
|
remote_address: &str,
|
||||||
|
username: &str,
|
||||||
|
ui: &mut SshUI,
|
||||||
|
) -> anyhow::Result<ssh2::Session> {
|
||||||
let mut sess = ssh2::Session::new()?;
|
let mut sess = ssh2::Session::new()?;
|
||||||
|
|
||||||
let (remote_address, remote_host_name, port) = {
|
let (remote_address, remote_host_name, port) = {
|
||||||
@ -170,8 +228,11 @@ pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ui.output_str(&format!("Connecting to {}\n", remote_address));
|
||||||
|
|
||||||
let tcp = TcpStream::connect(&remote_address)
|
let tcp = TcpStream::connect(&remote_address)
|
||||||
.with_context(|| format!("ssh connecting to {}", remote_address))?;
|
.with_context(|| format!("ssh connecting to {}", remote_address))?;
|
||||||
|
ui.output_str("Connected OK!\n");
|
||||||
tcp.set_nodelay(true)?;
|
tcp.set_nodelay(true)?;
|
||||||
sess.set_tcp_stream(tcp);
|
sess.set_tcp_stream(tcp);
|
||||||
sess.handshake()
|
sess.handshake()
|
||||||
@ -221,38 +282,22 @@ pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2:
|
|||||||
match known_hosts.check_port(&remote_host_name, port, key) {
|
match known_hosts.check_port(&remote_host_name, port, key) {
|
||||||
CheckResult::Match => {}
|
CheckResult::Match => {}
|
||||||
CheckResult::NotFound => {
|
CheckResult::NotFound => {
|
||||||
let message = format!(
|
ui.output_str(&format!(
|
||||||
"SSH host {} is not yet trusted.\r\n\
|
"SSH host {} is not yet trusted.\n\
|
||||||
{:?} Fingerprint: {}.\r\n\
|
{:?} Fingerprint: {}.\n\
|
||||||
Trust and continue connecting?\r\n",
|
Trust and continue connecting?\n",
|
||||||
remote_address, key_type, fingerprint
|
remote_address, key_type, fingerprint
|
||||||
);
|
));
|
||||||
|
|
||||||
let allow =
|
|
||||||
promise::spawn::block_on(termwiztermtab::run(80, 10, move |mut term| {
|
|
||||||
let title = "🔐 wezterm: SSH authentication".to_string();
|
|
||||||
term.render(&[Change::Title(title), Change::Text(message.to_string())])?;
|
|
||||||
|
|
||||||
let mut editor = LineEditor::new(term);
|
|
||||||
editor.set_prompt("Enter [Y/n]> ");
|
|
||||||
|
|
||||||
let mut host = NopLineEditorHost::default();
|
|
||||||
loop {
|
loop {
|
||||||
let line = match editor.read_line(&mut host) {
|
let line = ui.input("Enter [Y/n]> ")?;
|
||||||
Ok(Some(line)) => line,
|
|
||||||
Ok(None) | Err(_) => return Ok(false),
|
|
||||||
};
|
|
||||||
match line.as_ref() {
|
match line.as_ref() {
|
||||||
"y" | "Y" | "yes" | "YES" => return Ok(true),
|
"y" | "Y" | "yes" | "YES" => break,
|
||||||
"n" | "N" | "no" | "NO" => return Ok(false),
|
"n" | "N" | "no" | "NO" => bail!("user declined to trust host"),
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))?;
|
|
||||||
|
|
||||||
if !allow {
|
|
||||||
bail!("user declined to trust host");
|
|
||||||
}
|
|
||||||
|
|
||||||
known_hosts
|
known_hosts
|
||||||
.add(remote_host_name, key, &remote_address, key_type.into())
|
.add(remote_host_name, key, &remote_address, key_type.into())
|
||||||
@ -263,11 +308,11 @@ pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2:
|
|||||||
.with_context(|| format!("writing known_hosts file {}", file.display()))?;
|
.with_context(|| format!("writing known_hosts file {}", file.display()))?;
|
||||||
}
|
}
|
||||||
CheckResult::Mismatch => {
|
CheckResult::Mismatch => {
|
||||||
termwiztermtab::message_box_ok(&format!(
|
ui.output_str(&format!(
|
||||||
"🛑 host key mismatch for ssh server {}.\n\
|
"🛑 host key mismatch for ssh server {}.\n\
|
||||||
Got fingerprint {} instead of expected value from known_hosts\n\
|
Got fingerprint {} instead of expected value from known_hosts\n\
|
||||||
file {}.\n\
|
file {}.\n\
|
||||||
Refusing to connect.",
|
Refusing to connect.\n",
|
||||||
remote_address,
|
remote_address,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
file.display()
|
file.display()
|
||||||
@ -275,7 +320,7 @@ pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2:
|
|||||||
bail!("host mismatch, man in the middle attack?!");
|
bail!("host mismatch, man in the middle attack?!");
|
||||||
}
|
}
|
||||||
CheckResult::Failure => {
|
CheckResult::Failure => {
|
||||||
termwiztermtab::message_box_ok("🛑 Failed to load and check known ssh hosts");
|
ui.output_str("🛑 Failed to load and check known ssh hosts\n");
|
||||||
bail!("failed to check the known hosts");
|
bail!("failed to check the known hosts");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,24 +340,24 @@ pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2:
|
|||||||
if !sess.authenticated() && methods.contains("publickey") {
|
if !sess.authenticated() && methods.contains("publickey") {
|
||||||
if let Err(err) = sess.userauth_agent(&username) {
|
if let Err(err) = sess.userauth_agent(&username) {
|
||||||
log::info!("while attempting agent auth: {}", err);
|
log::info!("while attempting agent auth: {}", err);
|
||||||
|
} else if sess.authenticated() {
|
||||||
|
ui.output_str("publickey auth successful!\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sess.authenticated() && methods.contains("password") {
|
if !sess.authenticated() && methods.contains("password") {
|
||||||
let pass = password_prompt("", "Password", username, &remote_address)
|
ui.output_str(&format!(
|
||||||
.ok_or_else(|| anyhow!("password entry was cancelled"))?;
|
"Password authentication for {}@{}\n",
|
||||||
|
username, remote_address
|
||||||
|
));
|
||||||
|
let pass = ui.password("Password: ")?;
|
||||||
if let Err(err) = sess.userauth_password(username, &pass) {
|
if let Err(err) = sess.userauth_password(username, &pass) {
|
||||||
log::error!("while attempting password auth: {}", err);
|
log::error!("while attempting password auth: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sess.authenticated() && methods.contains("keyboard-interactive") {
|
if !sess.authenticated() && methods.contains("keyboard-interactive") {
|
||||||
let mut prompt = Prompt {
|
if let Err(err) = sess.userauth_keyboard_interactive(&username, ui) {
|
||||||
username,
|
|
||||||
remote_address: &remote_address,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = sess.userauth_keyboard_interactive(&username, &mut prompt) {
|
|
||||||
log::error!("while attempting keyboard-interactive auth: {}", err);
|
log::error!("while attempting keyboard-interactive auth: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,6 +370,21 @@ pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2:
|
|||||||
Ok(sess)
|
Ok(sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2::Session> {
|
||||||
|
let mut ui = SshUI::new();
|
||||||
|
let res = ssh_connect_with_ui(remote_address, username, &mut ui);
|
||||||
|
match res {
|
||||||
|
Ok(sess) => {
|
||||||
|
ui.close();
|
||||||
|
Ok(sess)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
ui.output_str(&format!("\nFailed: {}", err));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RemoteSshDomain {
|
pub struct RemoteSshDomain {
|
||||||
pty_system: Box<dyn PtySystem>,
|
pty_system: Box<dyn PtySystem>,
|
||||||
id: DomainId,
|
id: DomainId,
|
||||||
|
@ -13,7 +13,7 @@ use crate::mux::window::WindowId;
|
|||||||
use crate::mux::Mux;
|
use crate::mux::Mux;
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use crossbeam_channel::{unbounded as channel, Receiver, Sender};
|
use crossbeam::channel::{unbounded as channel, Receiver, Sender};
|
||||||
use filedescriptor::Pipe;
|
use filedescriptor::Pipe;
|
||||||
use portable_pty::*;
|
use portable_pty::*;
|
||||||
use rangeset::RangeSet;
|
use rangeset::RangeSet;
|
||||||
@ -370,6 +370,48 @@ impl termwiz::terminal::Terminal for TermWizTerminal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl termwiz::terminal::Terminal for &mut TermWizTerminal {
|
||||||
|
fn set_raw_mode(&mut self) -> anyhow::Result<()> {
|
||||||
|
(**self).set_raw_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cooked_mode(&mut self) -> anyhow::Result<()> {
|
||||||
|
(**self).set_cooked_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_alternate_screen(&mut self) -> anyhow::Result<()> {
|
||||||
|
(**self).enter_alternate_screen()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_alternate_screen(&mut self) -> anyhow::Result<()> {
|
||||||
|
(**self).exit_alternate_screen()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_screen_size(&mut self) -> anyhow::Result<ScreenSize> {
|
||||||
|
(**self).get_screen_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_screen_size(&mut self, size: ScreenSize) -> anyhow::Result<()> {
|
||||||
|
(**self).set_screen_size(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, changes: &[Change]) -> anyhow::Result<()> {
|
||||||
|
(**self).render(changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> anyhow::Result<()> {
|
||||||
|
(**self).flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_input(&mut self, wait: Option<Duration>) -> anyhow::Result<Option<InputEvent>> {
|
||||||
|
(**self).poll_input(wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn waker(&self) -> TerminalWaker {
|
||||||
|
(**self).waker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This function spawns a thread and constructs a GUI window with an
|
/// This function spawns a thread and constructs a GUI window with an
|
||||||
/// associated termwiz Terminal object to execute the provided function.
|
/// associated termwiz Terminal object to execute the provided function.
|
||||||
/// The function is expected to run in a loop to manage input and output
|
/// The function is expected to run in a loop to manage input and output
|
||||||
@ -378,7 +420,7 @@ impl termwiz::terminal::Terminal for TermWizTerminal {
|
|||||||
/// the return value from the function.
|
/// the return value from the function.
|
||||||
pub async fn run<
|
pub async fn run<
|
||||||
T: Send + 'static,
|
T: Send + 'static,
|
||||||
F: Send + 'static + Fn(TermWizTerminal) -> anyhow::Result<T>,
|
F: Send + 'static + FnOnce(TermWizTerminal) -> anyhow::Result<T>,
|
||||||
>(
|
>(
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
@ -457,6 +499,7 @@ pub async fn run<
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
pub fn message_box_ok(message: &str) {
|
pub fn message_box_ok(message: &str) {
|
||||||
let title = "wezterm";
|
let title = "wezterm";
|
||||||
let message = message.to_string();
|
let message = message.to_string();
|
||||||
@ -497,7 +540,7 @@ pub fn show_configuration_error_message(err: &str) {
|
|||||||
])
|
])
|
||||||
.map_err(Error::msg)?;
|
.map_err(Error::msg)?;
|
||||||
|
|
||||||
let mut editor = LineEditor::new(term);
|
let mut editor = LineEditor::new(&mut term);
|
||||||
editor.set_prompt("(press enter to close this window)");
|
editor.set_prompt("(press enter to close this window)");
|
||||||
|
|
||||||
let mut host = NopLineEditorHost::default();
|
let mut host = NopLineEditorHost::default();
|
||||||
|
Loading…
Reference in New Issue
Block a user