2024-10-31 13:14:07 +02:00
|
|
|
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
|
2024-11-05 18:48:04 +02:00
|
|
|
#![feature(let_chains)]
|
2024-10-31 13:14:07 +02:00
|
|
|
|
|
|
|
use std::{
|
2024-11-06 19:40:27 +02:00
|
|
|
io::{stderr, stdout, IsTerminal, Read, Stderr, Write},
|
2024-11-05 18:48:04 +02:00
|
|
|
ops::{Deref, DerefMut},
|
|
|
|
os::fd::AsRawFd,
|
2024-10-31 13:14:07 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
use clap::Parser;
|
|
|
|
use cross::io::Poll;
|
|
|
|
use rsh::{
|
2024-11-05 18:48:04 +02:00
|
|
|
crypt::{
|
2024-11-06 19:40:27 +02:00
|
|
|
client::{self, ClientSocket, Message},
|
2024-11-05 18:48:04 +02:00
|
|
|
config::{ClientConfig, SimpleClientKeyStore},
|
|
|
|
signature::{SignEd25519, SignatureMethod},
|
|
|
|
},
|
2024-11-06 19:40:27 +02:00
|
|
|
proto::{ServerMessage, StreamIndex},
|
2024-10-31 13:14:07 +02:00
|
|
|
};
|
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
use std::{
|
|
|
|
io::{self, stdin, Stdin, Stdout},
|
|
|
|
net::{IpAddr, SocketAddr},
|
|
|
|
path::PathBuf,
|
|
|
|
process::ExitCode,
|
|
|
|
time::Duration,
|
|
|
|
};
|
|
|
|
|
|
|
|
use libterm::{RawMode, RawTerminal};
|
|
|
|
use rsh::proto::{ClientMessage, TerminalInfo};
|
|
|
|
|
2024-10-31 13:14:07 +02:00
|
|
|
pub const PING_TIMEOUT: Duration = Duration::from_secs(3);
|
|
|
|
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum Error {
|
|
|
|
#[error("I/O error: {0}")]
|
|
|
|
Io(#[from] io::Error),
|
|
|
|
#[error("Terminal error: {0}")]
|
|
|
|
Terminal(#[from] libterm::Error),
|
|
|
|
#[error("Disconnected by the server: {0}")]
|
|
|
|
Disconnected(String),
|
|
|
|
#[error("Timed out")]
|
|
|
|
Timeout,
|
2024-11-05 18:48:04 +02:00
|
|
|
#[error("Socket error: {0}")]
|
|
|
|
Socket(#[from] client::Error),
|
|
|
|
#[error("Aborted by user")]
|
|
|
|
Abort,
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Parser)]
|
|
|
|
struct Args {
|
2024-11-02 19:17:32 +02:00
|
|
|
#[clap(short, long)]
|
|
|
|
key: PathBuf,
|
2024-11-02 20:22:53 +02:00
|
|
|
#[clap(short = 'P', long, default_value_t = 77)]
|
|
|
|
port: u16,
|
2024-10-31 13:14:07 +02:00
|
|
|
remote: IpAddr,
|
2024-11-05 18:48:04 +02:00
|
|
|
command: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct RawStdin {
|
|
|
|
stdin: Stdin,
|
|
|
|
raw: Option<RawMode>,
|
|
|
|
}
|
|
|
|
|
2024-10-31 13:14:07 +02:00
|
|
|
pub struct Client {
|
|
|
|
poll: Poll,
|
2024-11-05 18:48:04 +02:00
|
|
|
socket: ClientSocket,
|
2024-11-06 19:40:27 +02:00
|
|
|
stdin: RawStdin,
|
2024-10-31 13:14:07 +02:00
|
|
|
stdout: Stdout,
|
2024-11-06 19:40:27 +02:00
|
|
|
stderr: Stderr,
|
2024-11-05 18:48:04 +02:00
|
|
|
last0: u8,
|
|
|
|
last1: u8,
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
pub enum Event<'a, 'b> {
|
|
|
|
Data(&'a [u8]),
|
2024-11-01 18:44:41 +02:00
|
|
|
Stdin(&'b [u8]),
|
|
|
|
Disconnected(&'b str),
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
impl RawStdin {
|
|
|
|
pub fn open() -> Result<Self, Error> {
|
2024-10-31 13:14:07 +02:00
|
|
|
let stdin = stdin();
|
2024-11-05 18:48:04 +02:00
|
|
|
let raw = if stdin.is_terminal() {
|
|
|
|
Some(unsafe { RawMode::enter(&stdin) }?)
|
|
|
|
} else {
|
|
|
|
None
|
2024-10-31 13:14:07 +02:00
|
|
|
};
|
2024-11-05 18:48:04 +02:00
|
|
|
Ok(Self { stdin, raw })
|
|
|
|
}
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
impl Deref for RawStdin {
|
|
|
|
type Target = Stdin;
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.stdin
|
|
|
|
}
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
impl DerefMut for RawStdin {
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
&mut self.stdin
|
|
|
|
}
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
impl AsRawFd for RawStdin {
|
|
|
|
fn as_raw_fd(&self) -> std::os::fd::RawFd {
|
|
|
|
self.stdin.as_raw_fd()
|
|
|
|
}
|
|
|
|
}
|
2024-11-02 14:22:01 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
impl Drop for RawStdin {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some(raw) = &mut self.raw {
|
|
|
|
unsafe { raw.leave(&self.stdin) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-11-02 14:22:01 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
impl Client {
|
2024-11-06 19:40:27 +02:00
|
|
|
pub fn connect(remote: SocketAddr, crypto_config: ClientConfig) -> Result<Self, Error> {
|
2024-11-05 18:48:04 +02:00
|
|
|
let mut poll = Poll::new()?;
|
|
|
|
let mut socket = ClientSocket::connect(remote, crypto_config)?;
|
2024-11-06 19:40:27 +02:00
|
|
|
let stdin = RawStdin::open()?;
|
2024-11-05 18:48:04 +02:00
|
|
|
let stdout = stdout();
|
2024-11-06 19:40:27 +02:00
|
|
|
let stderr = stderr();
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-06 19:40:27 +02:00
|
|
|
poll.add(&stdin)?;
|
2024-11-05 18:48:04 +02:00
|
|
|
poll.add(&socket)?;
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
let info = terminal_info(&stdout)?;
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
Self::handshake(&mut socket, info)?;
|
2024-10-31 13:14:07 +02:00
|
|
|
|
|
|
|
Ok(Self {
|
2024-11-06 19:40:27 +02:00
|
|
|
stdin,
|
2024-10-31 13:14:07 +02:00
|
|
|
stdout,
|
2024-11-06 19:40:27 +02:00
|
|
|
stderr,
|
2024-11-05 18:48:04 +02:00
|
|
|
socket,
|
|
|
|
poll,
|
|
|
|
last0: 0,
|
|
|
|
last1: 0,
|
2024-10-31 13:14:07 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
fn update_last(&mut self, buffer: &[u8]) -> Result<(), Error> {
|
|
|
|
if buffer.len() >= 2 {
|
|
|
|
self.last0 = buffer[buffer.len() - 2];
|
|
|
|
self.last1 = buffer[buffer.len() - 1];
|
|
|
|
} else if buffer.len() >= 1 {
|
|
|
|
self.last0 = self.last1;
|
|
|
|
self.last1 = buffer[buffer.len() - 1];
|
|
|
|
} else {
|
|
|
|
self.last0 = self.last1;
|
|
|
|
self.last1 = 0;
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
if self.last0 == b'\x1B' && self.last1 == b'~' {
|
|
|
|
Err(Error::Abort)
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run(mut self) -> Result<String, Error> {
|
2024-11-05 18:48:04 +02:00
|
|
|
let mut recv_buf = [0; 512];
|
|
|
|
|
2024-10-31 13:14:07 +02:00
|
|
|
loop {
|
2024-11-05 18:48:04 +02:00
|
|
|
if let Some(message) = self.socket.read(&mut recv_buf)? {
|
|
|
|
match message {
|
|
|
|
ServerMessage::Bye(reason) => return Ok(reason.into()),
|
2024-11-06 19:40:27 +02:00
|
|
|
ServerMessage::Output(StreamIndex::Stdout, data) => {
|
2024-11-05 18:48:04 +02:00
|
|
|
self.stdout.write_all(data).ok();
|
|
|
|
self.stdout.flush().ok();
|
|
|
|
continue;
|
|
|
|
}
|
2024-11-06 19:40:27 +02:00
|
|
|
ServerMessage::Output(StreamIndex::Stderr, data) => {
|
|
|
|
self.stderr.write_all(data).ok();
|
|
|
|
self.stderr.flush().ok();
|
|
|
|
continue;
|
|
|
|
}
|
2024-11-05 18:48:04 +02:00
|
|
|
_ => continue,
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
let fd = self.poll.wait(None)?.unwrap();
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
if fd == self.socket.as_raw_fd() {
|
|
|
|
if self.socket.poll()? == 0 {
|
|
|
|
return Ok("".into());
|
|
|
|
}
|
2024-11-06 19:40:27 +02:00
|
|
|
} else if self.stdin.as_raw_fd() == fd {
|
|
|
|
let len = self.stdin.read(&mut recv_buf)?;
|
2024-11-05 18:48:04 +02:00
|
|
|
self.update_last(&recv_buf[..len])?;
|
|
|
|
self.socket
|
|
|
|
.write_all(&ClientMessage::Input(&recv_buf[..len]))?;
|
|
|
|
} else {
|
|
|
|
unreachable!()
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
fn handshake(socket: &mut ClientSocket, info: TerminalInfo) -> Result<(), Error> {
|
|
|
|
let mut recv_buf = [0; 256];
|
|
|
|
socket.write_all(&ClientMessage::OpenSession(info))?;
|
|
|
|
loop {
|
|
|
|
if let Some(message) = socket.read(&mut recv_buf)? {
|
|
|
|
match message {
|
|
|
|
ServerMessage::SessionOpen => return Ok(()),
|
|
|
|
_ => return Err(Error::Disconnected("Unexpected server message".into())),
|
|
|
|
}
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
|
2024-11-05 18:48:04 +02:00
|
|
|
if socket.poll()? == 0 {
|
|
|
|
todo!()
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn terminal_info(stdout: &Stdout) -> Result<TerminalInfo, Error> {
|
|
|
|
let (columns, rows) = stdout.raw_size()?;
|
|
|
|
Ok(TerminalInfo {
|
|
|
|
columns: columns as _,
|
|
|
|
rows: rows as _,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-11-06 19:40:27 +02:00
|
|
|
fn run_terminal(remote: SocketAddr, config: ClientConfig) -> Result<(), Error> {
|
|
|
|
let reason = Client::connect(remote, config)?.run()?;
|
|
|
|
if !reason.is_empty() {
|
|
|
|
eprintln!("\nDisconnected: {reason}");
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run_command(
|
|
|
|
remote: SocketAddr,
|
|
|
|
config: ClientConfig,
|
|
|
|
command: Vec<String>,
|
|
|
|
) -> Result<ExitCode, Error> {
|
|
|
|
let mut poll = Poll::new()?;
|
|
|
|
let mut buffer = [0; 512];
|
|
|
|
let mut command_string = String::new();
|
|
|
|
for (i, word) in command.iter().enumerate() {
|
|
|
|
if i != 0 {
|
|
|
|
command_string.push(' ');
|
|
|
|
}
|
|
|
|
command_string.push_str(word);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut stdin = stdin();
|
|
|
|
let mut stdout = stdout();
|
|
|
|
let mut stderr = stderr();
|
|
|
|
|
|
|
|
let mut socket = ClientSocket::connect(remote, config)?;
|
|
|
|
|
|
|
|
poll.add(&socket)?;
|
|
|
|
poll.add(&stdin)?;
|
|
|
|
|
|
|
|
socket.write_all(&ClientMessage::RunCommand(command_string.as_str()))?;
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let fd = poll.wait(None)?.unwrap();
|
|
|
|
|
|
|
|
match fd {
|
|
|
|
_ if fd == socket.as_raw_fd() => {
|
|
|
|
let message = match socket.poll_read(&mut buffer)? {
|
|
|
|
Message::Data(data) => data,
|
|
|
|
Message::Incomplete => continue,
|
2024-11-06 20:05:45 +02:00
|
|
|
Message::Closed => break,
|
2024-11-06 19:40:27 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
match message {
|
|
|
|
ServerMessage::Output(StreamIndex::Stdout, output) => {
|
|
|
|
stdout.write_all(output).ok();
|
|
|
|
stdout.flush().ok();
|
|
|
|
}
|
|
|
|
ServerMessage::Output(StreamIndex::Stderr, output) => {
|
|
|
|
stderr.write_all(output).ok();
|
|
|
|
stderr.flush().ok();
|
|
|
|
}
|
2024-11-06 20:05:45 +02:00
|
|
|
_ => todo!(),
|
2024-11-06 19:40:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ if fd == stdin.as_raw_fd() => {
|
|
|
|
let len = stdin.read(&mut buffer)?;
|
|
|
|
if len == 0 {
|
|
|
|
poll.remove(&stdin)?;
|
|
|
|
socket.write_all(&ClientMessage::CloseStdin)?;
|
|
|
|
} else {
|
|
|
|
socket.write_all(&ClientMessage::Input(&buffer[..len]))?;
|
|
|
|
}
|
|
|
|
}
|
2024-11-06 20:05:45 +02:00
|
|
|
_ => unreachable!(),
|
2024-11-06 19:40:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(ExitCode::SUCCESS)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(args: Args) -> Result<ExitCode, Error> {
|
2024-11-02 20:22:53 +02:00
|
|
|
let remote = SocketAddr::new(args.remote, args.port);
|
2024-11-02 19:17:32 +02:00
|
|
|
let ed25519 = SignEd25519::load_signing_key(args.key).unwrap();
|
|
|
|
let key = SignatureMethod::Ed25519(ed25519);
|
2024-11-05 18:48:04 +02:00
|
|
|
let config = ClientConfig::with_default_algorithms(SimpleClientKeyStore::new(key));
|
2024-11-06 19:40:27 +02:00
|
|
|
if args.command.is_empty() {
|
|
|
|
run_terminal(remote, config).map(|_| ExitCode::SUCCESS)
|
|
|
|
} else {
|
|
|
|
run_command(remote, config, args.command)
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> ExitCode {
|
2024-11-02 14:22:01 +02:00
|
|
|
env_logger::init();
|
2024-10-31 13:14:07 +02:00
|
|
|
let args = Args::parse();
|
|
|
|
|
2024-11-06 19:40:27 +02:00
|
|
|
match run(args) {
|
|
|
|
Ok(status) => status,
|
|
|
|
Err(error) => {
|
|
|
|
eprintln!("Error: {error}");
|
|
|
|
ExitCode::FAILURE
|
|
|
|
}
|
2024-10-31 13:14:07 +02:00
|
|
|
}
|
|
|
|
}
|