Implemented broken framed logic

pull/38/head
Chip Senkbeil 3 years ago
parent a52fb82fbf
commit 3c7561bef8
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

147
Cargo.lock generated

@ -121,9 +121,11 @@ dependencies = [
name = "distant"
version = "0.1.0"
dependencies = [
"bytes",
"derive_more",
"directories",
"fork",
"futures",
"hex",
"lazy_static",
"log",
@ -134,6 +136,8 @@ dependencies = [
"stderrlog",
"structopt",
"tokio",
"tokio-stream",
"tokio-util",
"whoami",
]
@ -146,6 +150,100 @@ dependencies = [
"libc",
]
[[package]]
name = "futures"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
[[package]]
name = "futures-executor"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582"
[[package]]
name = "futures-macro"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57"
dependencies = [
"autocfg",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53"
[[package]]
name = "futures-task"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
[[package]]
name = "futures-util"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
dependencies = [
"autocfg",
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.3"
@ -356,6 +454,12 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -380,6 +484,18 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro-nested"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
[[package]]
name = "proc-macro2"
version = "1.0.28"
@ -479,6 +595,12 @@ dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "smallvec"
version = "1.6.1"
@ -613,6 +735,31 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-stream"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"

@ -11,9 +11,11 @@ lto = true
codegen-units = 1
[dependencies]
bytes = "1.0.1"
derive_more = { version = "0.99.16", default-features = false, features = ["display", "from", "error"] }
directories = "3.0.2"
fork = "0.1.18"
futures = "0.3.16"
hex = "0.4.3"
log = "0.4.14"
orion = "0.16.0"
@ -21,6 +23,8 @@ serde = { version = "1.0.126", features = ["derive"] }
serde_cbor = "0.11.1"
serde_json = "1.0.64"
tokio = { version = "1.9.0", features = ["full"] }
tokio-stream = "0.1.7"
tokio-util = { version = "0.6.7", features = ["codec"] }
# Binary-specific dependencies
lazy_static = "1.4.0"

@ -110,6 +110,18 @@ pub enum Operation {
detach: bool,
},
/// Re-connects to a detached process on the remote machine (to receive stdout/stderr)
ProcConnect {
/// Id of the actively-running process
id: usize,
},
/// Kills a process running on the remote machine
ProcKill {
/// Id of the actively-running process
id: usize,
},
/// Sends additional data to stdin of running process
ProcStdin {
/// Id of the actively-running process to send stdin data

@ -1,6 +1,8 @@
mod data;
mod net;
mod opt;
mod subcommand;
mod utils;
pub use opt::Opt;
use std::path::PathBuf;

@ -0,0 +1,84 @@
use bytes::{Buf, BufMut, Bytes, BytesMut};
use derive_more::{Display, Error, From};
use tokio::io;
use tokio_util::codec::{Decoder, Encoder};
/// Represents a marker to indicate the beginning of the next message
static MSG_START: &'static [u8] = b";start;";
/// Represents a marker to indicate the end of the next message
static MSG_END: &'static [u8] = b";end;";
#[inline]
fn packet_size(msg_size: usize) -> usize {
MSG_START.len() + msg_size + MSG_END.len()
}
/// Possible errors that can occur during encoding and decoding
#[derive(Debug, Display, Error, From)]
pub enum DistantCodecError {
#[display(fmt = "Corrupt Marker: {:?}", _0)]
CorruptMarker(#[error(not(source))] Bytes),
IoError(io::Error),
}
/// Represents the codec to encode and decode data for transmission
pub struct DistantCodec;
impl<'a> Encoder<&'a [u8]> for DistantCodec {
type Error = DistantCodecError;
fn encode(&mut self, item: &'a [u8], dst: &mut BytesMut) -> Result<(), Self::Error> {
// Add our full packet to the bytes
dst.reserve(packet_size(item.len()));
dst.put(MSG_START);
dst.put(item);
dst.put(MSG_END);
Ok(())
}
}
impl Decoder for DistantCodec {
type Item = Vec<u8>;
type Error = DistantCodecError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
// First, check if we have more data than just our markers, if not we say that it's okay
// but that we're waiting
if src.len() <= (MSG_START.len() + MSG_END.len()) {
return Ok(None);
}
// Second, verify that our first N bytes match our start marker
let marker_start = &src[..MSG_START.len()];
if marker_start != MSG_START {
return Err(DistantCodecError::CorruptMarker(Bytes::copy_from_slice(
marker_start,
)));
}
// Third, find end of message marker by scanning the available bytes, and
// consume a full packet of bytes
let mut maybe_frame = None;
for i in (MSG_START.len() + 1)..(src.len() - MSG_END.len()) {
let marker_end = &src[i..(i + MSG_END.len())];
if marker_end == MSG_END {
maybe_frame = Some(src.split_to(i + MSG_END.len()));
break;
}
}
// Fourth, return our msg if it's available, stripping it of the start and end markers
if let Some(frame) = maybe_frame {
let data = &frame[MSG_START.len()..(frame.len() - MSG_END.len())];
// Advance so frame is no longer kept around
src.advance(frame.len());
Ok(Some(data.to_vec()))
} else {
Ok(None)
}
}
}

@ -0,0 +1,80 @@
use crate::utils::Session;
use codec::{DistantCodec, DistantCodecError};
use derive_more::{Display, Error, From};
use futures::SinkExt;
use orion::{
aead::{self, SecretKey},
errors::UnknownCryptoError,
};
use serde::{de::DeserializeOwned, Serialize};
use std::sync::Arc;
use tokio::{io, net::TcpStream};
use tokio_stream::StreamExt;
use tokio_util::codec::Framed;
mod codec;
#[derive(Debug, Display, Error, From)]
pub enum TransportError {
CodecError(DistantCodecError),
EncryptError(UnknownCryptoError),
IoError(io::Error),
SerializeError(serde_cbor::Error),
}
/// Represents a transport of data across the network
pub struct Transport {
inner: Framed<TcpStream, DistantCodec>,
key: Arc<SecretKey>,
}
impl Transport {
/// Wraps a `TcpStream` and associated credentials in a transport layer
pub fn new(stream: TcpStream, key: Arc<SecretKey>) -> Self {
Self {
inner: Framed::new(stream, DistantCodec),
key,
}
}
/// Establishes a connection using the provided session
pub async fn connect(session: Session) -> io::Result<Self> {
let stream = TcpStream::connect(session.to_socket_addr().await?).await?;
Ok(Self::new(stream, Arc::new(session.key)))
}
/// Sends some data across the wire
pub async fn send<T: Serialize>(&mut self, data: T) -> Result<(), TransportError> {
// Serialize, encrypt, and then (TODO) sign
let data = serde_cbor::ser::to_vec_packed(&data)?;
let data = aead::seal(&self.key, &data)?;
self.inner
.send(&data)
.await
.map_err(TransportError::CodecError)
}
/// Receives some data from out on the wire, waiting until it's available
pub async fn receive<T: DeserializeOwned>(&mut self) -> Result<T, TransportError> {
loop {
if let Some(data) = self.try_receive().await? {
break Ok(data);
}
}
}
/// Attempts to receive some data from out on the wire, returning that data if available
/// or none if unavailable
pub async fn try_receive<T: DeserializeOwned>(&mut self) -> Result<Option<T>, TransportError> {
if let Some(data) = self.inner.next().await {
// Validate (TODO), decrypt, and then deserialize
let data = data?;
let data = aead::open(&self.key, &data)?;
let data = serde_cbor::from_slice(&data)?;
Ok(Some(data))
} else {
Ok(None)
}
}
}

@ -45,6 +45,9 @@ pub struct CommonOpt {
#[derive(Debug, StructOpt)]
pub enum Subcommand {
/// Clears the global session file
ClearSession,
#[structopt(visible_aliases = &["exec", "x"])]
Execute(ExecuteSubcommand),
Launch(LaunchSubcommand),
@ -55,6 +58,7 @@ impl Subcommand {
/// Runs the subcommand, returning the result
pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::ClearSession => subcommand::clear_session::run()?,
Self::Execute(cmd) => subcommand::execute::run(cmd)?,
Self::Launch(cmd) => subcommand::launch::run(cmd)?,
Self::Listen(cmd) => subcommand::listen::run(cmd)?,

@ -0,0 +1,7 @@
use crate::utils::Session;
use tokio::io;
pub fn run() -> Result<(), io::Error> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async { Session::clear().await })
}

@ -1,26 +1,17 @@
use crate::{opt::ExecuteSubcommand, SESSION_PATH};
use crate::{
data::Response,
net::{Transport, TransportError},
opt::ExecuteSubcommand,
utils::{Session, SessionError},
};
use derive_more::{Display, Error, From};
use orion::aead::SecretKey;
use tokio::io;
#[derive(Debug, Display, Error, From)]
pub enum Error {
#[display(fmt = "Invalid key for session")]
InvalidSessionKey,
#[display(fmt = "Invalid port for session")]
InvalidSessionPort,
IoError(io::Error),
#[display(fmt = "Missing key for session")]
MissingSessionKey,
#[display(fmt = "Missing port for session")]
MissingSessionPort,
#[display(fmt = "No session file: {:?}", SESSION_PATH.as_path())]
NoSessionFile,
SessionError(SessionError),
TransportError(TransportError),
}
pub fn run(cmd: ExecuteSubcommand) -> Result<(), Error> {
@ -30,34 +21,19 @@ pub fn run(cmd: ExecuteSubcommand) -> Result<(), Error> {
}
async fn run_async(cmd: ExecuteSubcommand) -> Result<(), Error> {
let (port, key) = load_session().await?;
let session = Session::load().await?;
let mut transport = Transport::connect(session).await?;
println!(
"PORT:{}; KEY:{}",
port,
hex::encode(key.unprotected_as_bytes())
);
// Send our operation
transport.send(cmd.operation).await?;
println!("FORMAT: {}", cmd.format);
println!("OPERATION: {:?}", cmd.operation);
// Continue to receive and process responses as long as we get them or we decide to end
loop {
let response = transport.receive::<Response>().await?;
println!("RESPONSE: {:?}", response);
}
Ok(())
}
println!("DONE");
async fn load_session() -> Result<(u16, SecretKey), Error> {
let text = tokio::fs::read_to_string(SESSION_PATH.as_path())
.await
.map_err(|_| Error::NoSessionFile)?;
let mut tokens = text.split(' ').take(2);
let port = tokens
.next()
.ok_or(Error::MissingSessionPort)?
.parse::<u16>()
.map_err(|_| Error::InvalidSessionPort)?;
let key = SecretKey::from_slice(
&hex::decode(tokens.next().ok_or(Error::MissingSessionKey)?.to_string())
.map_err(|_| Error::InvalidSessionKey)?,
)
.map_err(|_| Error::InvalidSessionKey)?;
Ok((port, key))
Ok(())
}

@ -1,4 +1,4 @@
use crate::{opt::LaunchSubcommand, PROJECT_DIRS, SESSION_PATH};
use crate::{opt::LaunchSubcommand, utils::Session};
use derive_more::{Display, Error, From};
use hex::FromHexError;
use orion::{aead::SecretKey, errors::UnknownCryptoError};
@ -30,7 +30,7 @@ async fn run_async(cmd: LaunchSubcommand) -> Result<(), Error> {
"{} -o StrictHostKeyChecking=no ssh://{}@{}:{} {} {}",
cmd.ssh_program,
cmd.username,
cmd.host,
cmd.host.as_str(),
cmd.port,
cmd.identity_file
.map(|f| format!("-i {}", f.as_path().display()))
@ -75,18 +75,17 @@ async fn run_async(cmd: LaunchSubcommand) -> Result<(), Error> {
// Write a session file containing our data for use in subsequent calls
let (port, key) = result?;
let key_hex_str = hex::encode(key.unprotected_as_bytes());
let session = Session {
host: cmd.host,
port,
key,
};
session.save().await?;
if cmd.print_startup_data {
println!("DISTANT DATA {} {}", port, key_hex_str);
println!("DISTANT DATA {} {}", port, session.to_hex_key());
}
// Ensure our cache directory exists
let cache_dir = PROJECT_DIRS.cache_dir();
tokio::fs::create_dir_all(cache_dir).await?;
// Write our session file
tokio::fs::write(SESSION_PATH.as_path(), format!("{} {}", port, key_hex_str)).await?;
Ok(())
}

@ -1,9 +1,13 @@
use crate::opt::{ConvertToIpAddrError, ListenSubcommand};
use crate::{
data::{Operation, Response, ResponsePayload},
net::{Transport, TransportError},
opt::{ConvertToIpAddrError, ListenSubcommand},
};
use derive_more::{Display, Error, From};
use fork::{daemon, Fork};
use orion::aead::SecretKey;
use std::string::FromUtf8Error;
use tokio::io;
use std::{string::FromUtf8Error, sync::Arc};
use tokio::{io, net::TcpListener};
pub type Result = std::result::Result<(), Error>;
@ -36,16 +40,15 @@ pub fn run(cmd: ListenSubcommand) -> Result {
rt.block_on(async { run_async(cmd, false).await })?;
}
// MAC -> Decrypt
Ok(())
}
async fn run_async(cmd: ListenSubcommand, is_forked: bool) -> Result {
let addr = cmd.host.to_ip_addr()?;
let socket_addrs = cmd.port.make_socket_addrs(addr);
let listener = tokio::net::TcpListener::bind(socket_addrs.as_slice()).await?;
let listener = TcpListener::bind(socket_addrs.as_slice()).await?;
let port = listener.local_addr()?.port();
let key = SecretKey::default();
let key = Arc::new(SecretKey::default());
// Print information about port, key, etc. unless told not to
if !cmd.no_print_startup_data {
@ -59,7 +62,38 @@ async fn run_async(cmd: ListenSubcommand, is_forked: bool) -> Result {
}
}
// TODO: Implement server logic
// Begin our listen loop
loop {
// Wait for a client connection
let (client, _) = listener.accept().await?;
// Build a transport around the client
let mut transport = Transport::new(client, Arc::clone(&key));
// Spawn a new task that loops to handle requests from the client
tokio::spawn(async move {
loop {
match transport.receive::<Operation>().await {
Ok(_request) => {
let response = Response::Error {
msg: String::from("Unimplemented"),
};
if let Err(x) = transport.send(response).await {
eprintln!("ERROR: {:?}", x);
break;
}
}
Err(x) => {
eprintln!("ERROR: {:?}", x);
break;
}
}
}
});
}
#[allow(unreachable_code)]
Ok(())
}

@ -1,3 +1,4 @@
pub mod clear_session;
pub mod execute;
pub mod launch;
pub mod listen;

@ -0,0 +1,126 @@
use crate::{PROJECT_DIRS, SESSION_PATH};
use derive_more::{Display, Error, From};
use orion::aead::SecretKey;
use std::net::{IpAddr, SocketAddr};
use tokio::{io, net::lookup_host};
#[derive(Debug, Display, Error, From)]
pub enum SessionError {
#[display(fmt = "Bad hex key for session")]
BadSessionHexKey,
#[display(fmt = "Invalid address for session")]
InvalidSessionAddr,
#[display(fmt = "Invalid key for session")]
InvalidSessionKey,
#[display(fmt = "Invalid port for session")]
InvalidSessionPort,
IoError(io::Error),
#[display(fmt = "Missing address for session")]
MissingSessionAddr,
#[display(fmt = "Missing key for session")]
MissingSessionKey,
#[display(fmt = "Missing port for session")]
MissingSessionPort,
#[display(fmt = "No session file: {:?}", SESSION_PATH.as_path())]
NoSessionFile,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Session {
pub host: String,
pub port: u16,
pub key: SecretKey,
}
impl Session {
/// Returns a string representing the secret key as hex
pub fn to_hex_key(&self) -> String {
hex::encode(self.key.unprotected_as_bytes())
}
/// Returns the ip address associated with the session based on the host
pub async fn to_ip_addr(&self) -> io::Result<IpAddr> {
let addr = match self.host.parse::<IpAddr>() {
Ok(addr) => addr,
Err(_) => lookup_host((self.host.as_str(), self.port))
.await?
.next()
.ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, SessionError::InvalidSessionAddr)
})?
.ip(),
};
Ok(addr)
}
/// Returns socket address associated with the session
pub async fn to_socket_addr(&self) -> io::Result<SocketAddr> {
let addr = self.to_ip_addr().await?;
Ok(SocketAddr::from((addr, self.port)))
}
/// Clears the global session file
pub async fn clear() -> io::Result<()> {
tokio::fs::remove_file(SESSION_PATH.as_path()).await
}
/// Saves a session to disk
pub async fn save(&self) -> io::Result<()> {
let key_hex_str = self.to_hex_key();
// Ensure our cache directory exists
let cache_dir = PROJECT_DIRS.cache_dir();
tokio::fs::create_dir_all(cache_dir).await?;
// Write our session file
let addr = self.to_ip_addr().await?;
tokio::fs::write(
SESSION_PATH.as_path(),
format!("{} {} {}", addr, self.port, key_hex_str),
)
.await?;
Ok(())
}
/// Loads a session's information into memory
pub async fn load() -> Result<Self, SessionError> {
let text = tokio::fs::read_to_string(SESSION_PATH.as_path())
.await
.map_err(|_| SessionError::NoSessionFile)?;
let mut tokens = text.split(' ').take(3);
// First, load up the address without parsing it
let host = tokens
.next()
.ok_or(SessionError::MissingSessionAddr)?
.trim()
.to_string();
// Second, load up the port and parse it into a number
let port = tokens
.next()
.ok_or(SessionError::MissingSessionPort)?
.trim()
.parse::<u16>()
.map_err(|_| SessionError::InvalidSessionPort)?;
// Third, load up the key and convert it back into a secret key from a hex slice
let key = SecretKey::from_slice(
&hex::decode(tokens.next().ok_or(SessionError::MissingSessionKey)?.trim())
.map_err(|_| SessionError::BadSessionHexKey)?,
)
.map_err(|_| SessionError::InvalidSessionKey)?;
Ok(Session { host, port, key })
}
}
Loading…
Cancel
Save