diff --git a/Cargo.toml b/Cargo.toml index 849f67b..c8fe0c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,15 @@ repository = "https://git.cscherr.de/PlexSheep/wooly-vault" [dependencies] anyhow = "1.0.86" async-trait = "0.1.82" -libpt = { version = "0.6.0", features = ["log"] } -tokio = { version = "1.40.0", features = ["macros", "net", "time", "io-util", "rt", "sync"] } +clap = { version = "4.5.17", features = ["derive"] } +libpt = { version = "0.7.1", features = ["cli", "log"] } +serde = { version = "1.0.209", features = ["derive"] } +serde_json = "1.0.128" +tokio = { version = "1.40.0", features = [ + "macros", + "net", + "time", + "io-util", + "rt", + "sync", +] } diff --git a/src/challenge/c1.rs b/src/challenge/c1.rs index c2ca865..1d6a861 100644 --- a/src/challenge/c1.rs +++ b/src/challenge/c1.rs @@ -4,8 +4,9 @@ use tokio::io::AsyncWriteExt; use tokio::net::TcpListener; use super::Challenge; +use crate::config::Config; use crate::has_won; -use crate::vault::{Config, VaultRef}; +use crate::vault::VaultRef; pub struct C1 { config: Config, @@ -19,9 +20,15 @@ impl Challenge for C1 { fn new(config: Config, vault: VaultRef) -> Self { Self { config, vault } } + fn hints() -> Vec { - vec![String::from("TCP connect to 1337")] + vec![String::from("TCP connect to 1337.")] } + + fn solution() -> String { + String::from("Connect by TCP, then the secret will be sent to you.") + } + async fn serve(self) -> anyhow::Result<()> { info!("serving challenge 1"); let listener = TcpListener::bind(self.config.addr).await?; diff --git a/src/challenge/c2.rs b/src/challenge/c2.rs index 95b7c3f..9283b4a 100644 --- a/src/challenge/c2.rs +++ b/src/challenge/c2.rs @@ -5,8 +5,9 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use super::Challenge; +use crate::config::Config; use crate::has_won; -use crate::vault::{Config, VaultRef}; +use crate::vault::VaultRef; pub struct C2 { config: Config, @@ -37,11 +38,17 @@ impl Challenge for C2 { fn new(config: Config, vault: VaultRef) -> Self { Self { config, vault } } + fn hints() -> Vec { vec![String::from( "TCP connect to 1337 and give me a special u16", )] } + + fn solution() -> String { + String::from("Connect by TCP, send 1337 as bytes (not text).") + } + async fn serve(self) -> anyhow::Result<()> { info!("serving challenge 2"); let listener = TcpListener::bind(self.config.addr).await?; diff --git a/src/challenge/mod.rs b/src/challenge/mod.rs index 89cb531..0e90fca 100644 --- a/src/challenge/mod.rs +++ b/src/challenge/mod.rs @@ -1,7 +1,8 @@ use anyhow::anyhow; use async_trait::async_trait; -use crate::vault::{Config, VaultRef}; +use crate::config::Config; +use crate::vault::VaultRef; pub mod c1; pub mod c2; @@ -13,6 +14,7 @@ where { fn new(config: Config, vault: VaultRef) -> Self; fn hints() -> Vec; + fn solution() -> String; async fn serve(self) -> anyhow::Result<()>; } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..541d47d --- /dev/null +++ b/src/config.rs @@ -0,0 +1,69 @@ +use std::fmt::{Debug, Display}; +use std::net::SocketAddr; +use std::str::FromStr; + +use clap::Parser; +use libpt::cli::args::VerbosityLevel; +use serde::Serialize; + +pub const ENV_SECRET: &str = "WOOLY_SECRET"; + +/// Wooly Vault --- A few small hacking challenges +/// +/// # Configuration +/// Each challenge requires some basic information to host: +/// +/// * secret - a string that if found signifies that the challenge was solved +/// +/// * addr - the network adress plus port the challenge should run on +/// +/// * verbosity - how verbose the logging should be +/// +/// * challenge - which challenge to host (there are a few) +/// +/// The secret cannot be inputted with a command line argument, because the arguments can often be +/// seen in the process view. You may either specify a secret in the 'WOOLY_SECRET' environment +/// variable or input it interactively on start. +/// +/// # Challenges +/// +/// There are a number of challenges: +/// +/// 1. Connect by TCP +/// +/// 2. Connect by TCP and send a special u16 +#[derive(Parser, Clone, PartialEq, Eq, Hash, Serialize)] +#[command( + author, + version, + about, + long_about, + help_template = libpt::cli::args::HELP_TEMPLATE)] + +pub struct Config { + /// Index of the challenge + pub challenge: u16, + /// Network address to host the challenge on + pub addr: SocketAddr, + #[command(flatten)] + pub verbosity: VerbosityLevel, +} + +impl Default for Config { + fn default() -> Self { + Self { + challenge: 1, + addr: SocketAddr::from_str("127.0.0.1:1337").unwrap(), + verbosity: VerbosityLevel::default(), + } + } +} + +impl Debug for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Config") + .field("addr", &self.addr) + .field("verbosity", &self.verbosity.level()) + .finish() + } +} diff --git a/src/lib.rs b/src/lib.rs index b5db9de..143f2b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod challenge; +pub mod config; pub mod vault; #[inline] diff --git a/src/main.rs b/src/main.rs index 5632dd5..c38aff0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,41 @@ -use wooly_vault::challenge::select_and_start; -use wooly_vault::vault::{Config, Vault}; +use anyhow::Result; +use clap::Parser; +use libpt::log::{debug, info}; + +use wooly_vault::config::ENV_SECRET; +use wooly_vault::{challenge::select_and_start, config::Config, vault::Vault}; #[tokio::main(flavor = "current_thread")] -async fn main() -> anyhow::Result<()> { +async fn main() -> Result<()> { + let conf = Config::parse(); + let _logger = libpt::log::Logger::builder() - .set_level(libpt::log::Level::TRACE) + .set_level(conf.verbosity.level()) .display_time(false) .build()?; + debug!("logger active"); + info!("Configuration: {conf:?}"); - println!("Input the secret and press enter"); - let mut buf: String = String::new(); - std::io::stdin().read_line(&mut buf)?; - let secret = buf.trim(); + let secret = get_secret()?; + let v = Vault::new(&secret); - let conf = Config::default(); - - let v = Vault::new(secret); - - println!("What challenge to serve?"); - let i = select_challenge()?; - select_and_start(i, conf, v).await?; + select_and_start(conf.challenge, conf, v).await?; Ok(()) } -fn select_challenge() -> anyhow::Result { +fn get_secret() -> Result { + if let Ok(v) = std::env::var(ENV_SECRET) { + return Ok(v); + } + + println!("Input the secret and press enter"); + let mut buf: String = String::new(); + std::io::stdin().read_line(&mut buf)?; + Ok(buf.trim().to_string()) +} + +fn select_challenge() -> Result { let mut buf: String = String::new(); std::io::stdin().read_line(&mut buf)?; Ok(buf.trim().parse()?) diff --git a/src/vault.rs b/src/vault.rs index 8217adb..44d786a 100644 --- a/src/vault.rs +++ b/src/vault.rs @@ -1,22 +1,7 @@ -use std::net::SocketAddr; -use std::str::FromStr; use std::sync::Arc; pub type VaultRef = Arc; -#[derive(Debug, Clone, PartialEq, Copy)] -pub struct Config { - pub addr: SocketAddr, -} - -impl Default for Config { - fn default() -> Self { - Self { - addr: SocketAddr::from_str("127.0.0.1:1337").unwrap(), - } - } -} - #[derive(Debug, Clone, PartialEq)] pub struct Vault { secret: String,