feat: added a basic interactivity to the client

This commit is contained in:
Andrew Rioux
2023-09-04 01:11:55 -04:00
parent 7876e00dc8
commit 7f1f43a601
10 changed files with 381 additions and 65 deletions

View File

@@ -0,0 +1 @@
pub mod sysinfo;

View File

@@ -0,0 +1,71 @@
use sparse_05_common::messages::{Capabilities, OperatingSystem};
pub fn print_capabilities(capabilities: &Capabilities) {
use ansi_term::Colour as Color;
println!("Capabilities of remote host:");
println!(
"\tOperating system: \t{}",
match &capabilities.operating_system {
OperatingSystem::Linux => "Linux",
OperatingSystem::Windows => "Windows",
}
);
println!(
"\tInside a container: \t{}",
if capabilities.docker_container {
"yes"
} else {
"no"
}
);
if capabilities.docker_container {
println!(
"\tContainer breakout: \t{}",
if capabilities.docker_breakout {
Color::Green.paint("successful")
} else {
Color::Red.paint("unsuccessful")
}
);
}
println!(
"\tRemote user: \t\t{}",
match &capabilities.userent {
Some(user) => Color::White.paint(user),
None => Color::Red.paint("unknown"),
}
);
match capabilities.operating_system {
OperatingSystem::Linux => {
println!(
"\tAdmin user: \t\t{}",
match (capabilities.root, capabilities.setuid) {
(false, false) => Color::Red.paint("no"),
(_, _) => Color::Green.paint("yes"),
}
);
println!(
"\tsetuid capability: \t{}",
match capabilities.setuid {
true => Color::Green.paint("yes"),
false => Color::Red.paint("no"),
}
);
}
OperatingSystem::Windows => {
println!(
"\tAdmin user: \t\t{}",
match capabilities.root {
true => Color::Green.paint("yes"),
false => Color::Red.paint("no"),
}
);
}
}
println!("\tTransport type: \t{:?}", capabilities.transport);
println!(
"\tHost name: \t\t{}",
capabilities.hostname.as_deref().unwrap_or("<unknown>")
);
}

View File

@@ -1,24 +1,28 @@
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
use std::{
io::{self, Write},
net::SocketAddr,
path::PathBuf,
sync::Arc,
};
use anyhow::{bail, Context};
use ed25519_dalek::{Keypair, Signature, Signer, Verifier};
use rmp_serde::decode::ReadSlice;
use sparse_05_common::messages::{Capabilities, Response, CONNECTED_MESSAGE, CONNECT_MESSAGE};
use sparse_05_common::messages::{
Capabilities, Command, OperatingSystem, Response, CONNECTED_MESSAGE, CONNECT_MESSAGE,
};
use tokio::{fs, net::UdpSocket};
use crate::configs::ClientConfig;
enum State {
Authenticating,
Ready,
UploadingFile,
DownloadingFile,
}
mod commands;
mod shell;
struct Connection {
config: ClientConfig,
foreign_sign_pubkey: ed25519_dalek::PublicKey,
foreign_enc_pubkey: ecies_ed25519::PublicKey,
socket: Arc<UdpSocket>,
ip: SocketAddr,
}
impl Connection {
@@ -44,6 +48,27 @@ impl Connection {
Ok([&signature.to_bytes(), &*data].concat())
}
async fn send_command(&self, command: Command) -> anyhow::Result<()> {
let cmd = rmp_serde::to_vec(&command)?;
self.socket
.send_to(&self.encrypt_and_sign(&cmd)?, self.ip)
.await?;
Ok(())
}
async fn get_response(&self) -> anyhow::Result<Response> {
let mut buffer = [0u8; 2000];
let (read, from) = self.socket.recv_from(&mut buffer).await?;
if from != self.ip {
bail!("received packet from wrong computer");
}
self.decrypt_and_verify(&buffer[..read])
}
}
pub async fn connect(config: PathBuf, ip: SocketAddr) -> anyhow::Result<()> {
@@ -58,10 +83,20 @@ pub async fn connect(config: PathBuf, ip: SocketAddr) -> anyhow::Result<()> {
let mut counter = 0;
let (foreign_sign_pubkey, foreign_enc_pubkey, capabilities) = loop {
if counter > 5 {
if counter > 25 {
bail!("could not connect to server");
}
print!(
"\rConnecting{}",
match counter % 3 {
2 => ".. ",
1 => ". ",
_ => "...",
}
);
io::stdout().flush().unwrap();
remote.send_to(connect_msg, ip).await?;
let mut buf = [0u8; 2000];
@@ -122,17 +157,21 @@ pub async fn connect(config: PathBuf, ip: SocketAddr) -> anyhow::Result<()> {
}
};
let connection = Connection {
let connection = Arc::new(Connection {
config,
foreign_enc_pubkey,
foreign_sign_pubkey,
};
socket: Arc::clone(&remote),
ip,
});
remote
.send_to(&connection.encrypt_and_sign(CONNECTED_MESSAGE)?, ip)
.await?;
println!("connected! {:?}", capabilities);
println!("\rConnected! ");
shell::shell(Arc::clone(&connection), capabilities).await?;
Ok(())
}

View File

@@ -0,0 +1,102 @@
use std::{
io::{self, Read, Write},
os::fd::AsRawFd,
path::PathBuf,
sync::Arc,
};
use sparse_05_common::messages::Capabilities;
use tokio::net::UdpSocket;
use super::{commands, Connection};
macro_rules! libc_try {
($expr:expr) => {
if unsafe { $expr } == -1 {
return Err(std::io::Error::last_os_error())?;
}
};
}
#[cfg(unix)]
fn get_term_attrs<F: AsRawFd>(fd: &F) -> anyhow::Result<libc::termios> {
let mut termios = unsafe { std::mem::zeroed() };
libc_try!(libc::tcgetattr(fd.as_raw_fd(), &mut termios));
Ok(termios)
}
#[cfg(unix)]
fn set_term_attrs<F: AsRawFd>(fd: &mut F, attrs: &libc::termios) -> anyhow::Result<()> {
libc_try!(libc::tcsetattr(fd.as_raw_fd(), 0, attrs));
Ok(())
}
#[cfg(unix)]
fn convert_termios_raw(attrs: &mut libc::termios) -> anyhow::Result<()> {
unsafe { libc::cfmakeraw(attrs) };
Ok(())
}
pub(super) async fn shell(
connection: Arc<Connection>,
mut capabilities: Capabilities,
) -> anyhow::Result<()> {
println!("Type #help to view a list of sparse commands\n");
let mut stdin = io::stdin();
let mut stdout = io::stdout();
let backup_term_attrs = get_term_attrs(&stdin)?;
let mut raw_term_attrs = get_term_attrs(&stdin)?;
convert_termios_raw(&mut raw_term_attrs)?;
macro_rules! cmd_matcher {
($input:expr, $(($check:ident, $matcher:expr) => {$($body:tt)*}),+, _ => {$($ebody:tt)*}) => {
match &$input[..] {
$($check if $check.len() >= $matcher.len() && $check[..$matcher.len()] == $matcher[..] => {
let $check = &$check[$matcher.len()..];
$($body)*
}),+
_ => {
$($ebody)*
}
}
}
}
let mut cwd = "/".to_string();
loop {
print!(
"{}@{}:{} {} ",
capabilities.userent.as_deref().unwrap_or("unknown user!"),
match &capabilities.hostname {
Some(n) => n.clone(),
None => format!("{}", connection.ip.ip()),
},
cwd,
if capabilities.root { "#" } else { "$" }
);
stdout.flush().unwrap();
let mut cmd = [0u8; 256];
let amount = stdin.read(&mut cmd)?;
if amount == 0 {
break;
}
cmd_matcher!(
cmd[..amount],
(_sysinfo, b"#sysinfo") => {
commands::sysinfo::print_capabilities(&capabilities)
},
(_help, b"#help") => {},
(_exit, b"#exit") => {
break;
},
_ => {}
);
}
Ok(())
}