add tests and fixes

This commit is contained in:
nora 2024-08-26 21:40:49 +02:00
parent 688394cac9
commit a59bcb069d
10 changed files with 202 additions and 37 deletions

View file

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
cluelessh-format = { path = "../../lib/cluelessh-format" }
cluelessh-protocol = { path = "../../lib/cluelessh-protocol" }
cluelessh-tokio = { path = "../../lib/cluelessh-tokio" }
cluelessh-transport = { path = "../../lib/cluelessh-transport" }

View file

@ -1,7 +1,13 @@
mod auth;
mod pty;
use std::{io, net::SocketAddr, process::ExitStatus, sync::Arc};
use std::{
io,
net::SocketAddr,
pin::Pin,
process::{ExitStatus, Stdio},
sync::Arc,
};
use auth::AuthError;
use cluelessh_keys::{private::EncryptedPrivateKeys, public::PublicKey};
@ -12,7 +18,7 @@ use pty::Pty;
use rustix::termios::Winsize;
use tokio::{
fs::File,
io::{AsyncReadExt, AsyncWriteExt},
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
net::{TcpListener, TcpStream},
process::Command,
sync::mpsc,
@ -224,8 +230,12 @@ struct SessionState {
envs: Vec<(String, String)>,
writer: Option<File>,
reader: Option<File>,
//// stdin
writer: Option<Pin<Box<dyn AsyncWrite + Send + Sync>>>,
/// stdout
reader: Option<Pin<Box<dyn AsyncRead + Send + Sync>>>,
/// stderr
reader_ext: Option<Pin<Box<dyn AsyncRead + Send + Sync>>>,
}
async fn handle_session_channel(user: String, channel: Channel) -> Result<()> {
@ -240,9 +250,11 @@ async fn handle_session_channel(user: String, channel: Channel) -> Result<()> {
envs: Vec::new(),
writer: None,
reader: None,
reader_ext: None,
};
let mut read_buf = [0; 1024];
let mut read_ext_buf = [0; 1024];
loop {
let read = async {
@ -254,6 +266,15 @@ async fn handle_session_channel(user: String, channel: Channel) -> Result<()> {
},
}
};
let read_ext = async {
match &mut state.reader_ext {
Some(file) => file.read(&mut read_ext_buf).await,
// Ensure that if this is None, the future never finishes so the state update and process exit can progress.
None => loop {
tokio::task::yield_now().await;
},
}
};
tokio::select! {
update = state.channel.next_update() => {
match update {
@ -281,6 +302,12 @@ async fn handle_session_channel(user: String, channel: Channel) -> Result<()> {
};
let _ = state.channel.send(ChannelOperationKind::Data(read_buf[..read].to_vec())).await;
}
read = read_ext => {
let Ok(read) = read else {
bail!("failed to read");
};
let _ = state.channel.send(ChannelOperationKind::ExtendedData(1, read_ext_buf[..read].to_vec())).await;
}
}
}
}
@ -325,7 +352,7 @@ impl SessionState {
}
}
}
ChannelRequest::Shell { want_reply } => match self.shell().await {
ChannelRequest::Shell { want_reply } => match self.shell(None).await {
Ok(()) => {
if want_reply {
self.channel.send(ChannelOperationKind::Success).await?;
@ -338,9 +365,31 @@ impl SessionState {
}
}
},
ChannelRequest::Exec { .. } => {
todo!()
}
ChannelRequest::Exec {
want_reply,
command,
} => match String::from_utf8(command) {
Ok(command) => match self.shell(Some(&command)).await {
Ok(()) => {
if want_reply {
self.channel.send(ChannelOperationKind::Success).await?;
}
}
Err(err) => {
debug!(%err, "Failed to spawn shell");
if want_reply {
self.channel.send(ChannelOperationKind::Failure).await?;
}
}
},
Err(err) => {
debug!(%err, "Exec command is invalid UTF-8");
if want_reply {
self.channel.send(ChannelOperationKind::Failure).await?;
}
}
},
ChannelRequest::Env {
name,
value,
@ -368,10 +417,15 @@ impl SessionState {
writer.write_all(&data).await?;
}
}
ChannelUpdateKind::Eof => {
if let Some(writer) = &mut self.writer {
writer.shutdown().await?;
}
self.writer = None;
}
ChannelUpdateKind::Open(_)
| ChannelUpdateKind::Closed
| ChannelUpdateKind::ExtendedData { .. }
| ChannelUpdateKind::Eof
| ChannelUpdateKind::Success
| ChannelUpdateKind::Failure => { /* ignore */ }
}
@ -383,12 +437,14 @@ impl SessionState {
let controller = pty.controller().try_clone_to_owned()?;
self.pty = Some(pty);
self.writer = Some(File::from_std(std::fs::File::from(controller.try_clone()?)));
self.reader = Some(File::from_std(std::fs::File::from(controller)));
self.writer = Some(Box::pin(File::from_std(std::fs::File::from(
controller.try_clone()?,
))));
self.reader = Some(Box::pin(File::from_std(std::fs::File::from(controller))));
Ok(())
}
async fn shell(&mut self) -> Result<()> {
async fn shell(&mut self, shell_command: Option<&str>) -> Result<()> {
let user = self.user.clone();
let user = tokio::task::spawn_blocking(move || users::get_user_by_name(&user))
.await?
@ -397,10 +453,18 @@ impl SessionState {
let shell = user.shell();
let mut cmd = Command::new(shell);
if let Some(shell_command) = shell_command {
cmd.arg("-c");
cmd.arg(shell_command);
}
cmd.env_clear();
if let Some(pty) = &self.pty {
pty.start_session_for_command(&mut cmd)?;
} else {
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
}
// TODO: **user** home directory
@ -417,6 +481,16 @@ impl SessionState {
let mut shell = cmd.spawn()?;
if self.pty.is_none() {
let stdin = shell.stdin.take().unwrap();
let stdout = shell.stdout.take().unwrap();
let stderr = shell.stderr.take().unwrap();
self.writer = Some(Box::pin(stdin));
self.reader = Some(Box::pin(stdout));
self.reader_ext = Some(Box::pin(stderr));
}
let process_exit_send = self.process_exit_send.clone();
tokio::spawn(async move {
let result = shell.wait().await;

View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
ssh -p "$PORT" "$HOST" echo jdklfsjdöklfd | grep "jdklfsjdöklfd"

View file

@ -1,12 +1,12 @@
#!/usr/bin/env bash
# KEX
printf $"exit\r" | ssh -oKexAlgorithms=curve25519-sha256 -p "$PORT" "$HOST"
printf $"exit\r" | ssh -oKexAlgorithms=ecdh-sha2-nistp256 -p "$PORT" "$HOST"
ssh -oKexAlgorithms=curve25519-sha256 -p "$PORT" "$HOST" true
ssh -oKexAlgorithms=ecdh-sha2-nistp256 -p "$PORT" "$HOST" true
# Encryption
printf $"exit\r" | ssh -oCiphers=chacha20-poly1305@openssh.com -p "$PORT" "$HOST"
printf $"exit\r" | ssh -oCiphers=aes256-gcm@openssh.com -p "$PORT" "$HOST"
ssh -oCiphers=chacha20-poly1305@openssh.com -p "$PORT" "$HOST" true
ssh -oCiphers=aes256-gcm@openssh.com -p "$PORT" "$HOST" true
# Host Key
printf $"exit\r" | ssh -oHostKeyAlgorithms=ssh-ed25519 -p "$PORT" "$HOST"
ssh -oHostKeyAlgorithms=ssh-ed25519 -p "$PORT" "$HOST" true

View file

@ -1,6 +1,3 @@
#!/usr/bin/env bash
# disabled, TODO
exit 0
echo | ssh -p "$PORT" "$HOST"
printf $"echo jdklfsjdöklfd" | ssh -p "$PORT" "$HOST" | grep jdklfsjdöklfd

View file

@ -19,7 +19,30 @@ kill_server() {
trap kill_server EXIT
failures=()
export PORT=2223
export HOST=localhost
for script in "$script_dir"/openssh-client/*.sh; do
echo "-------------- Running $script"
PORT=2223 HOST=localhost bash -euo pipefail "$script"
echo "-------------- Running PORT=$PORT HOST=$HOST bash $script"
set +e
bash -euo pipefail "$script"
result=$?
set -e
if [ "$result" -ne "0" ]; then
echo "Test $script failed!"
failures+=("$script")
fi
done
if (( ${#failures[@]} )); then
echo "FAILED"
for failure in "${failures[@]}"; do
echo " failed: PORT=$PORT HOST=$HOST bash $failure"
done
exit 1
fi