Update with proper logging; fix bad host parsing

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

126
Cargo.lock generated

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
@ -124,6 +133,7 @@ dependencies = [
"bytes",
"derive_more",
"directories",
"flexi_logger",
"fork",
"futures",
"hex",
@ -133,14 +143,30 @@ dependencies = [
"serde",
"serde_cbor",
"serde_json",
"stderrlog",
"structopt",
"strum",
"tokio",
"tokio-stream",
"tokio-util",
"whoami",
]
[[package]]
name = "flexi_logger"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ba2265890613939b533fa11c3728651531419ac549ccf527896201581f23991"
dependencies = [
"atty",
"chrono",
"glob",
"lazy_static",
"log",
"regex",
"thiserror",
"yansi",
]
[[package]]
name = "fork"
version = "0.1.18"
@ -255,6 +281,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "half"
version = "1.7.1"
@ -533,6 +565,23 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ryu"
version = "1.0.5"
@ -607,19 +656,6 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "stderrlog"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a53e2eff3e94a019afa6265e8ee04cb05b9d33fe9f5078b14e4e391d155a38"
dependencies = [
"atty",
"chrono",
"log",
"termcolor",
"thread_local",
]
[[package]]
name = "strsim"
version = "0.8.0"
@ -650,6 +686,27 @@ dependencies = [
"syn",
]
[[package]]
name = "strum"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "subtle"
version = "2.4.1"
@ -668,30 +725,32 @@ dependencies = [
]
[[package]]
name = "termcolor"
version = "1.1.2"
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"winapi-util",
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.11.0"
name = "thiserror"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
dependencies = [
"unicode-width",
"thiserror-impl",
]
[[package]]
name = "thread_local"
version = "1.0.1"
name = "thiserror-impl"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@ -886,21 +945,18 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yansi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
[[package]]
name = "zeroize"
version = "1.4.1"

@ -14,7 +14,6 @@ codegen-units = 1
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"
@ -22,12 +21,14 @@ orion = "0.16.0"
serde = { version = "1.0.126", features = ["derive"] }
serde_cbor = "0.11.1"
serde_json = "1.0.64"
strum = { version = "0.21.0", features = ["derive"] }
tokio = { version = "1.9.0", features = ["full"] }
tokio-stream = "0.1.7"
tokio-util = { version = "0.6.7", features = ["codec"] }
# Binary-specific dependencies
flexi_logger = "0.18.0"
fork = "0.1.18"
lazy_static = "1.4.0"
stderrlog = "0.5.1"
structopt = "0.3.22"
whoami = "1.1.2"

@ -1,15 +1,17 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use structopt::StructOpt;
use strum::AsRefStr;
/// Represents an operation to be performed on the remote machine
#[derive(Clone, Debug, PartialEq, Eq, StructOpt, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, AsRefStr, StructOpt, Serialize, Deserialize)]
#[serde(
rename_all = "snake_case",
deny_unknown_fields,
tag = "type",
content = "payload"
)]
#[strum(serialize_all = "snake_case")]
pub enum Operation {
/// Reads a file from the specified path on the remote machine
#[structopt(visible_aliases = &["cat"])]
@ -136,13 +138,14 @@ pub enum Operation {
}
/// Represents an response to an operation performed on the remote machine
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, AsRefStr, Serialize, Deserialize)]
#[serde(
rename_all = "snake_case",
deny_unknown_fields,
tag = "status",
content = "payload"
)]
#[strum(serialize_all = "snake_case")]
pub enum Response {
/// Represents a successfully-handled operation
Ok(ResponsePayload),
@ -155,7 +158,7 @@ pub enum Response {
}
/// Represents the payload of a successful response
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, AsRefStr, Serialize, Deserialize)]
#[serde(
rename_all = "snake_case",
deny_unknown_fields,

@ -4,6 +4,7 @@ mod opt;
mod subcommand;
mod utils;
use log::error;
pub use opt::Opt;
use std::path::PathBuf;
@ -21,17 +22,41 @@ lazy_static::lazy_static! {
pub fn run() {
let opt = Opt::load();
init_logging(&opt.common);
if let Err(x) = opt.subcommand.run() {
eprintln!("{}", x);
if let Err(x) = opt.subcommand.run(opt.common) {
error!("{}", x);
}
}
pub fn init_logging(opt: &opt::CommonOpt) {
stderrlog::new()
.module("distant")
.quiet(opt.quiet)
.verbosity(opt.verbose as usize)
.timestamp(stderrlog::Timestamp::Off)
.init()
.unwrap();
use flexi_logger::{FileSpec, LevelFilter, LogSpecification, Logger};
let module = "distant";
// Disable logging for everything but our binary, which is based on verbosity
let mut builder = LogSpecification::builder();
builder.default(LevelFilter::Off).module(
module,
match opt.verbose {
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
},
);
// If quiet, we suppress all output
if opt.quiet {
builder.module(module, LevelFilter::Off);
}
// Create our logger, but don't initialize yet
let logger = Logger::with(builder.build());
// If provided, log to file instead of stderr
let logger = if let Some(path) = opt.log_file.as_ref() {
logger.log_to_file(FileSpec::try_from(path).expect("Failed to create log file spec"))
} else {
logger
};
logger.start().expect("Failed to initialize logger");
}

@ -3,7 +3,7 @@ use derive_more::{Display, Error, From};
use lazy_static::lazy_static;
use std::{
env,
net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr},
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
str::FromStr,
};
@ -41,6 +41,10 @@ pub struct CommonOpt {
/// Quiet mode
#[structopt(short, long, global = true)]
pub quiet: bool,
/// Log output to disk instead of stderr
#[structopt(long, global = true)]
pub log_file: Option<PathBuf>,
}
#[derive(Debug, StructOpt)]
@ -56,12 +60,12 @@ pub enum Subcommand {
impl Subcommand {
/// Runs the subcommand, returning the result
pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
pub fn run(self, opt: CommonOpt) -> 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)?,
Self::Execute(cmd) => subcommand::execute::run(cmd, opt)?,
Self::Launch(cmd) => subcommand::launch::run(cmd, opt)?,
Self::Listen(cmd) => subcommand::listen::run(cmd, opt)?,
}
Ok(())
@ -127,24 +131,28 @@ pub enum BindAddress {
#[derive(Clone, Debug, Display, From, Error, PartialEq, Eq)]
pub enum ConvertToIpAddrError {
ClientIpParseError(AddrParseError),
MissingClientIp,
AddrParseError(AddrParseError),
#[display(fmt = "SSH_CONNECTION missing 3rd argument (host ip)")]
MissingSshAddr,
VarError(env::VarError),
}
impl BindAddress {
/// Converts address into valid IP
pub fn to_ip_addr(&self) -> Result<IpAddr, ConvertToIpAddrError> {
/// Converts address into valid IP; in the case of "any", will leverage the
/// `use_ipv6` flag to determine if binding should use ipv4 or ipv6
pub fn to_ip_addr(&self, use_ipv6: bool) -> Result<IpAddr, ConvertToIpAddrError> {
match self {
Self::Ssh => {
let ssh_connection = env::var("SSH_CONNECTION")?;
let ip_str = ssh_connection
.split(' ')
.skip(2)
.next()
.ok_or(ConvertToIpAddrError::MissingClientIp)?;
.ok_or(ConvertToIpAddrError::MissingSshAddr)?;
let ip = ip_str.parse::<IpAddr>()?;
Ok(ip)
}
Self::Any if use_ipv6 => Ok(IpAddr::V6(Ipv6Addr::UNSPECIFIED)),
Self::Any => Ok(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
Self::Ip(addr) => Ok(*addr),
}
@ -159,7 +167,7 @@ impl FromStr for BindAddress {
"ssh" => Ok(Self::Ssh),
"any" => Ok(Self::Any),
"localhost" => Ok(Self::Ip(IpAddr::V4(Ipv4Addr::LOCALHOST))),
x => x.parse(),
x => Ok(Self::Ip(x.parse::<IpAddr>()?)),
}
}
}
@ -193,6 +201,19 @@ pub struct LaunchSubcommand {
#[structopt(long, value_name = "ssh|any|IP", default_value = "ssh")]
pub bind_server: BindAddress,
/// If specified, will write server logs to a file instead of discarding them
#[structopt(long)]
pub server_log_file: Option<PathBuf>,
/// If specified, will set the server's log level (0 is warning and above, 1 is info, 2 is
/// debug, and 3 or higher is trace)
#[structopt(long, default_value = "0")]
pub server_log_level: u8,
/// If specified, will bind server to the ipv6 interface if host is "any" instead of ipv4
#[structopt(short = "6", long)]
pub use_ipv6: bool,
/// Username to use when sshing into remote machine
#[structopt(short, long, default_value = &USERNAME)]
pub username: String,
@ -211,7 +232,12 @@ pub struct LaunchSubcommand {
}
/// Represents some range of ports
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, Display, PartialEq, Eq)]
#[display(
fmt = "{}{}",
start,
"end.as_ref().map(|end| format!(\"[:{}]\", end)).unwrap_or_default()"
)]
pub struct PortRange {
pub start: u16,
pub end: Option<u16>,
@ -293,6 +319,10 @@ pub struct ListenSubcommand {
#[structopt(short, long, value_name = "ssh|any|IP", default_value = "localhost")]
pub host: BindAddress,
/// If specified, will bind to the ipv6 interface if host is "any" instead of ipv4
#[structopt(short = "6", long)]
pub use_ipv6: bool,
/// Set the port(s) that the server will attempt to bind to
///
/// This can be in the form of PORT1 or PORT1:PORTN to provide a range of ports.

@ -1,7 +1,7 @@
use crate::{
data::Response,
net::{Transport, TransportError},
opt::ExecuteSubcommand,
opt::{CommonOpt, ExecuteSubcommand},
utils::{Session, SessionError},
};
use derive_more::{Display, Error, From};
@ -14,13 +14,13 @@ pub enum Error {
TransportError(TransportError),
}
pub fn run(cmd: ExecuteSubcommand) -> Result<(), Error> {
pub fn run(cmd: ExecuteSubcommand, opt: CommonOpt) -> Result<(), Error> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async { run_async(cmd).await })
rt.block_on(async { run_async(cmd, opt).await })
}
async fn run_async(cmd: ExecuteSubcommand) -> Result<(), Error> {
async fn run_async(cmd: ExecuteSubcommand, _opt: CommonOpt) -> Result<(), Error> {
let session = Session::load().await?;
let mut transport = Transport::connect(session).await?;

@ -1,6 +1,10 @@
use crate::{opt::LaunchSubcommand, utils::Session};
use crate::{
opt::{CommonOpt, LaunchSubcommand},
utils::Session,
};
use derive_more::{Display, Error, From};
use hex::FromHexError;
use log::*;
use orion::{aead::SecretKey, errors::UnknownCryptoError};
use std::string::FromUtf8Error;
use tokio::{io, process::Command};
@ -16,15 +20,25 @@ pub enum Error {
Utf8Error(FromUtf8Error),
}
pub fn run(cmd: LaunchSubcommand) -> Result<(), Error> {
pub fn run(cmd: LaunchSubcommand, opt: CommonOpt) -> Result<(), Error> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async { run_async(cmd).await })
rt.block_on(async { run_async(cmd, opt).await })
}
async fn run_async(cmd: LaunchSubcommand) -> Result<(), Error> {
async fn run_async(cmd: LaunchSubcommand, _opt: CommonOpt) -> Result<(), Error> {
let remote_command = format!(
"{} listen --daemon --host {}",
cmd.remote_program, cmd.bind_server
"{} listen --daemon --host {} {} {} {}",
cmd.remote_program,
cmd.bind_server,
if cmd.use_ipv6 { "-6" } else { "" },
cmd.server_log_file
.as_ref()
.map(|path| format!("--log-file {:?}", path))
.unwrap_or_default(),
match cmd.server_log_level {
0 => String::new(),
n => format!("-{}", "v".repeat(n as usize)),
},
);
let ssh_command = format!(
"{} -o StrictHostKeyChecking=no ssh://{}@{}:{} {} {}",
@ -35,7 +49,7 @@ async fn run_async(cmd: LaunchSubcommand) -> Result<(), Error> {
cmd.identity_file
.map(|f| format!("-i {}", f.as_path().display()))
.unwrap_or_default(),
remote_command,
remote_command.trim(),
);
let out = Command::new("sh")
.arg("-c")
@ -84,7 +98,7 @@ async fn run_async(cmd: LaunchSubcommand) -> Result<(), Error> {
session.save().await?;
if cmd.print_startup_data {
println!("DISTANT DATA {} {}", port, session.to_hex_key());
info!("DISTANT DATA {} {}", port, session.to_hex_key());
}
Ok(())

@ -1,10 +1,11 @@
use crate::{
data::{Operation, Response, ResponsePayload},
net::{Transport, TransportError},
opt::{ConvertToIpAddrError, ListenSubcommand},
data::{Operation, Response},
net::Transport,
opt::{CommonOpt, ConvertToIpAddrError, ListenSubcommand},
};
use derive_more::{Display, Error, From};
use fork::{daemon, Fork};
use log::*;
use orion::aead::SecretKey;
use std::{string::FromUtf8Error, sync::Arc};
use tokio::{io, net::TcpListener};
@ -19,16 +20,16 @@ pub enum Error {
Utf8Error(FromUtf8Error),
}
pub fn run(cmd: ListenSubcommand) -> Result {
pub fn run(cmd: ListenSubcommand, opt: CommonOpt) -> Result {
if cmd.daemon {
// NOTE: We keep the stdin, stdout, stderr open so we can print out the pid with the parent
match daemon(false, true) {
Ok(Fork::Child) => {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async { run_async(cmd, true).await })?;
rt.block_on(async { run_async(cmd, opt, true).await })?;
}
Ok(Fork::Parent(pid)) => {
eprintln!("[distant detached, pid = {}]", pid);
info!("[distant detached, pid = {}]", pid);
if let Err(_) = fork::close_fd() {
return Err(Error::ForkError);
}
@ -37,17 +38,22 @@ pub fn run(cmd: ListenSubcommand) -> Result {
}
} else {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async { run_async(cmd, false).await })?;
rt.block_on(async { run_async(cmd, opt, false).await })?;
}
Ok(())
}
async fn run_async(cmd: ListenSubcommand, is_forked: bool) -> Result {
let addr = cmd.host.to_ip_addr()?;
async fn run_async(cmd: ListenSubcommand, _opt: CommonOpt, is_forked: bool) -> Result {
let addr = cmd.host.to_ip_addr(cmd.use_ipv6)?;
let socket_addrs = cmd.port.make_socket_addrs(addr);
debug!("Binding to {} in range {}", addr, cmd.port);
let listener = TcpListener::bind(socket_addrs.as_slice()).await?;
let port = listener.local_addr()?.port();
debug!("Bound to port: {}", port);
let key = Arc::new(SecretKey::default());
// Print information about port, key, etc. unless told not to
@ -67,6 +73,19 @@ async fn run_async(cmd: ListenSubcommand, is_forked: bool) -> Result {
// Wait for a client connection
let (client, _) = listener.accept().await?;
// Grab the client's remote address for later logging purposes
let addr_string = match client.peer_addr() {
Ok(addr) => {
let addr_string = addr.to_string();
info!("New client from {}", addr_string);
addr_string
}
Err(x) => {
error!("Unable to examine client's peer address: {}", x);
"???".to_string()
}
};
// Build a transport around the client
let mut transport = Transport::new(client, Arc::clone(&key));
@ -74,18 +93,24 @@ async fn run_async(cmd: ListenSubcommand, is_forked: bool) -> Result {
tokio::spawn(async move {
loop {
match transport.receive::<Operation>().await {
Ok(_request) => {
Ok(request) => {
trace!(
"[{}] Received request of type {}",
addr_string.as_str(),
request.as_ref()
);
let response = Response::Error {
msg: String::from("Unimplemented"),
};
if let Err(x) = transport.send(response).await {
eprintln!("ERROR: {:?}", x);
error!("{}", x);
break;
}
}
Err(x) => {
eprintln!("ERROR: {:?}", x);
error!("{}", x);
break;
}
}

Loading…
Cancel
Save