diff --git a/Cargo.lock b/Cargo.lock index eee7009..43ba615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "const-oid" version = "0.9.1" @@ -554,7 +565,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" +dependencies = [ + "dirs-sys 0.4.0", ] [[package]] @@ -568,6 +588,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +dependencies = [ + "libc", + "redox_users", + "windows-sys 0.45.0", +] + [[package]] name = "dotenvy" version = "0.15.6" @@ -1393,6 +1424,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -2094,6 +2134,19 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +[[package]] +name = "simple_logger" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78beb34673091ccf96a8816fce8bfd30d1292c7621ca2bcb5f2ba0fae4f558d" +dependencies = [ + "atty", + "colored", + "log", + "time 0.3.17", + "windows-sys 0.42.0", +] + [[package]] name = "slab" version = "0.4.7" @@ -2170,7 +2223,7 @@ dependencies = [ "bytes", "crc", "crossbeam-queue", - "dirs", + "dirs 4.0.0", "dotenvy", "either", "event-listener", @@ -2349,11 +2402,14 @@ name = "tfclient" version = "0.1.0" dependencies = [ "clap", + "dirs 5.0.0", "flate2", "hex", + "log", "reqwest", "serde", "sha2", + "simple_logger", "tar", "tempfile", "trifid-pki", @@ -2406,6 +2462,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", + "libc", + "num_threads", "serde", "time-core", "time-macros", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 3291b5f..4976a67 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -9,6 +9,11 @@ description = "An open-source reimplementation of a Defined Networking-compatibl [dependencies] clap = { version = "4.1.10", features = ["derive"] } trifid-pki = { version = "0.1.5", path = "../trifid-pki" } +dirs = "5.0.0" +log = "0.4.17" +simple_logger = "4.1.0" +sha2 = "0.10.6" +hex = "0.4.3" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/dirs.rs b/tfclient/src/dirs.rs new file mode 100644 index 0000000..038984a --- /dev/null +++ b/tfclient/src/dirs.rs @@ -0,0 +1,5 @@ +use std::path::PathBuf; + +pub fn get_data_dir() -> Option { + dirs::data_dir().map(|f| f.join("tfclient/")) +} \ No newline at end of file diff --git a/tfclient/src/embedded_nebula.rs b/tfclient/src/embedded_nebula.rs new file mode 100644 index 0000000..adaaed2 --- /dev/null +++ b/tfclient/src/embedded_nebula.rs @@ -0,0 +1,104 @@ +use std::error::Error; +use std::fs; +use std::fs::File; +use std::io::Write; +use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; +use std::process::{Child, Command, Output}; +use log::debug; +use crate::dirs::get_data_dir; +use crate::util::sha256; + +pub fn extract_embedded_nebula() -> Result> { + let data_dir = get_data_dir().ok_or("Unable to get platform-specific data dir")?; + if !data_dir.exists() { + fs::create_dir_all(&data_dir)?; + debug!("Created data directory {}", data_dir.as_path().display()); + } + + let bin_dir = data_dir.join("cache/"); + let hash_dir = bin_dir.join(format!("{}/", sha256(crate::nebula_bin::NEBULA_BIN))); + + if !hash_dir.exists() { + fs::create_dir_all(&hash_dir)?; + debug!("Created directory {}", hash_dir.as_path().display()); + } + + let executable_postfix = if cfg!(windows) { ".exe" } else { "" }; + let executable_name = format!("nebula-{}{}", crate::nebula_bin::NEBULA_VERSION, executable_postfix); + + let file_path = hash_dir.join(executable_name); + + if file_path.exists() { + // Already extracted + return Ok(file_path); + } + let mut file = File::create(&file_path)?; + file.write_all(crate::nebula_bin::NEBULA_BIN)?; + + debug!("Extracted nebula to {}", file_path.as_path().display()); + + Ok(file_path) +} + +pub fn extract_embedded_nebula_cert() -> Result> { + let data_dir = get_data_dir().ok_or("Unable to get platform-specific data dir")?; + if !data_dir.exists() { + fs::create_dir_all(&data_dir)?; + debug!("Created data directory {}", data_dir.as_path().display()); + } + + let bin_dir = data_dir.join("cache/"); + let hash_dir = bin_dir.join(format!("{}/", sha256(crate::nebula_cert_bin::NEBULA_CERT_BIN))); + + if !hash_dir.exists() { + fs::create_dir_all(&hash_dir)?; + debug!("Created directory {}", hash_dir.as_path().display()); + } + + let executable_postfix = if cfg!(windows) { ".exe" } else { "" }; + let executable_name = format!("nebula-cert-{}{}", crate::nebula_cert_bin::NEBULA_CERT_VERSION, executable_postfix); + + let file_path = hash_dir.join(executable_name); + + if file_path.exists() { + // Already extracted + return Ok(file_path); + } + + let mut file = File::create(&file_path)?; + file.write_all(crate::nebula_cert_bin::NEBULA_CERT_BIN)?; + + debug!("Extracted nebula-cert to {}", file_path.as_path().display()); + + Ok(file_path) +} + +#[cfg(unix)] +pub fn _setup_permissions(path: &PathBuf) -> Result<(), Box> { + let meta = path.metadata()?; + let mut perms = meta.permissions(); + perms.set_mode(0o0755); + debug!("Setting permissions on {} to 755", path.as_path().display()); + fs::set_permissions(path, perms)?; + Ok(()) +} + +#[cfg(windows)] +pub fn _setup_permissions() -> Result<(), Box> { + Ok(()) +} + +pub fn run_embedded_nebula(args: &[String]) -> Result> { + let path = extract_embedded_nebula()?; + debug!("Running {} with args {:?}", path.as_path().display(), args); + _setup_permissions(&path)?; + Ok(Command::new(path).args(args).spawn()?) +} + +pub fn run_embedded_nebula_cert(args: &[String]) -> Result> { + let path = extract_embedded_nebula_cert()?; + debug!("Running {} with args {:?}", path.as_path().display(), args); + _setup_permissions(&path)?; + Ok(Command::new(path).args(args).spawn()?) +} \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index d4d2582..8d07da2 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +pub mod embedded_nebula; +pub mod dirs; +pub mod util; + pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); } @@ -21,7 +25,14 @@ pub mod nebula_cert_bin { include!(concat!(env!("OUT_DIR"), "/nebula_cert.bin.rs")); } -use clap::{Parser, ArgAction}; +use std::error::Error; +use std::fs; +use std::process::Child; +use clap::{Parser, ArgAction, Subcommand}; +use log::{error, info}; +use simple_logger::SimpleLogger; +use crate::dirs::get_data_dir; +use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert}; #[derive(Parser)] #[command(author = "c0repwn3r", version, about, long_about = None)] @@ -29,17 +40,121 @@ use clap::{Parser, ArgAction}; struct Cli { #[arg(short = 'v', long = "version", action = ArgAction::SetTrue)] #[clap(global = true)] - version: bool + version: bool, + #[command(subcommand)] + subcommand: Commands +} +#[derive(Subcommand)] +enum Commands { + /// Run the `nebula` binary. This is useful if you want to do debugging with tfclient's internal nebula. + RunNebula { + /// Arguments to pass to the `nebula` binary + #[clap(trailing_var_arg=true, allow_hyphen_values=true)] + args: Vec + }, + /// Run the `nebula-cert` binary. This is useful if you want to mess with certificates. Note: tfclient does not actually use nebula-cert for certificate operations, and instead uses trifid-pki internally + RunNebulaCert { + /// Arguments to pass to the `nebula-cert` binary + #[clap(trailing_var_arg=true, allow_hyphen_values=true)] + args: Vec + }, + /// Clear any cached data that tfclient may have added + ClearCache {} } fn main() { + SimpleLogger::new().init().unwrap(); + let args = Cli::parse(); if args.version { print_version(); } + + match args.subcommand { + Commands::RunNebula { args } => { + match run_embedded_nebula(&args) { + Ok(mut c) => { + match c.wait() { + Ok(stat) => { + match stat.code() { + Some(code) => { + if code != 0 { + error!("Nebula process exited with nonzero status code {}", code); + } + std::process::exit(code); + }, + None => { + info!("Nebula process terminated by signal"); + std::process::exit(0); + } + } + }, + Err(e) => { + error!("Unable to wait for child to exit: {}", e); + std::process::exit(1); + } + } + }, + Err(e) => { + error!("Unable to start nebula binary: {}", e); + std::process::exit(1); + } + } + }, + Commands::ClearCache { .. } => { + let data_dir = match get_data_dir() { + Some(dir) => dir, + None => { + error!("Unable to get platform-specific data dir"); + std::process::exit(1); + } + }; + match fs::remove_dir_all(&data_dir) { + Ok(_) => (), + Err(e) => { + error!("Unable to delete data dir: {}", e); + std::process::exit(0); + } + } + info!("Removed data dir {}", data_dir.as_path().display()); + + info!("Removed all cached data."); + std::process::exit(0); + }, + Commands::RunNebulaCert { args } => { + match run_embedded_nebula_cert(&args) { + Ok(mut c) => { + match c.wait() { + Ok(stat) => { + match stat.code() { + Some(code) => { + if code != 0 { + error!("nebula-cert process exited with nonzero status code {}", code); + } + std::process::exit(code); + }, + None => { + info!("nebula-cert process terminated by signal"); + std::process::exit(0); + } + } + }, + Err(e) => { + error!("Unable to wait for child to exit: {}", e); + std::process::exit(1); + } + } + }, + Err(e) => { + error!("Unable to start nebula-cert binary: {}", e); + std::process::exit(1); + } + } + } + } } fn print_version() { diff --git a/tfclient/src/util.rs b/tfclient/src/util.rs new file mode 100644 index 0000000..a87effb --- /dev/null +++ b/tfclient/src/util.rs @@ -0,0 +1,9 @@ +use sha2::Sha256; +use sha2::Digest; + +pub fn sha256(bytes: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(bytes); + let digest = hasher.finalize(); + hex::encode(digest) +} \ No newline at end of file