mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
add tests and fixes
This commit is contained in:
parent
688394cac9
commit
a59bcb069d
10 changed files with 202 additions and 37 deletions
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
3
bin/cluelesshd/tests/openssh-client/command.sh
Normal file
3
bin/cluelesshd/tests/openssh-client/command.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
ssh -p "$PORT" "$HOST" echo jdklfsjdöklfd | grep "jdklfsjdöklfd"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue