diff --git a/Cargo.lock b/Cargo.lock index dd553cb..8fb00e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,12 @@ dependencies = [ "safemem", ] +[[package]] +name = "build_const" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" + [[package]] name = "byte-tools" version = "0.3.1" @@ -131,6 +137,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +[[package]] +name = "cc" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" + [[package]] name = "cfg-if" version = "0.1.10" @@ -171,15 +183,56 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + +[[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "cryptohelpers" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfc491baaffd7cbd6acc02ebd23564760d83a2c17e1a47e6a04a8d5a86e7fb5" +dependencies = [ + "crc", + "getrandom", + "hex-literal", + "hmac", + "libc", + "openssl", + "pbkdf2", + "serde", + "serde_derive", + "sha2", + "tokio", +] + [[package]] name = "datse" version = "0.1.0" dependencies = [ "color-eyre", + "cryptohelpers", "futures", + "lazy_static", "log", "pretty_env_logger", "tokio", + "uuid", "warp", ] @@ -242,6 +295,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.0" @@ -465,6 +533,22 @@ dependencies = [ "libc", ] +[[package]] +name = "hex-literal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8" + +[[package]] +name = "hmac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "http" version = "0.2.1" @@ -778,12 +862,54 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" +dependencies = [ + "bitflags", + "cfg-if 0.1.10", + "foreign-types", + "lazy_static", + "libc", + "openssl-sys", +] + +[[package]] +name = "openssl-sys" +version = "0.9.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +dependencies = [ + "autocfg 1.0.1", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "owo-colors" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a1250cdd103eef6bd542b5ae82989f931fc00a41a27f60377338241594410f3" +[[package]] +name = "pbkdf2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170d73bf11f39b4ce1809aabc95bf5c33564cdc16fc3200ddda17a5f6e5e48b" +dependencies = [ + "base64", + "crypto-mac", + "hmac", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2", + "subtle", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -842,6 +968,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -1112,6 +1244,20 @@ name = "serde" version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" @@ -1161,6 +1307,19 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + [[package]] name = "signal-hook-registry" version = "1.2.2" @@ -1188,6 +1347,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "subtle" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" + [[package]] name = "syn" version = "1.0.48" @@ -1453,6 +1618,22 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand 0.7.3", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + [[package]] name = "version_check" version = "0.9.2" diff --git a/Cargo.toml b/Cargo.toml index 5115651..497dc55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,11 @@ client = [] [dependencies] color-eyre = {version = "0.5", default-features=false} +cryptohelpers = {version = "1.5.1", features= ["sha256", "rsa", "serde"]} futures = "0.3.8" +lazy_static = "1.4.0" log = "0.4.11" pretty_env_logger = "0.4.0" tokio = {version = "0.2", features = ["full"]} +uuid = {version = "0.8.1", features = ["v4","serde"]} warp = "0.2.5" diff --git a/src/args/error.rs b/src/args/error.rs new file mode 100644 index 0000000..aa04432 --- /dev/null +++ b/src/args/error.rs @@ -0,0 +1,23 @@ +//! Arg parsing error +use super::*; +use std::{ + error, + fmt, +}; + +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + Unknown, +} +impl error::Error for Error{} +impl fmt::Display for Error +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + _ => write!(f, "unknown error"), + } + } +} + diff --git a/src/args/mod.rs b/src/args/mod.rs index c170227..5ea8d13 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -8,19 +8,21 @@ pub enum Operation #[cfg(feature="client")] Client(client::Config), Help, } - -#[derive(Debug)] -pub struct Error; - -pub fn parse_args() -> impl Future> +/// Name of the program +pub fn program() -> &'static str { - parse(std::env::args().skip(1)) + lazy_static!{ + static ref NAME: String = std::env::args().next().unwrap(); + } + &NAME[..] } -async fn parse(args: I) -> Result -where I: IntoIterator, - T: Into +/// Attempt to parse the args +pub fn parse_args() -> impl Future> { - let mut args = args.into_iter().map(Into::into); - todo!() + parse::parse(std::env::args().skip(1)) } + +mod parse; +mod error; +pub use error::Error; diff --git a/src/args/parse.rs b/src/args/parse.rs new file mode 100644 index 0000000..81e9b5c --- /dev/null +++ b/src/args/parse.rs @@ -0,0 +1,10 @@ +//! Parse args +use super::*; + +pub async fn parse(args: I) -> Result +where I: IntoIterator, + T: Into +{ + let mut args = args.into_iter().map(Into::into); + todo!() +} diff --git a/src/main.rs b/src/main.rs index 6e387c0..4e25779 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #[macro_use] extern crate log; + use color_eyre::{ eyre::{ self, eyre, @@ -10,10 +11,10 @@ use color_eyre::{ }, SectionExt as _, }; - use futures::{ prelude::*, }; +use lazy_static::lazy_static; fn install() -> eyre::Result<()> { diff --git a/src/server/mod.rs b/src/server/mod.rs index 7345904..6674aa3 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -6,6 +6,8 @@ pub struct Config } +mod state; + #[cfg(feature="server-http")] pub mod web; #[cfg(feature="server-tcp")] pub mod tcp; diff --git a/src/server/state.rs b/src/server/state.rs new file mode 100644 index 0000000..9588f6f --- /dev/null +++ b/src/server/state.rs @@ -0,0 +1,8 @@ +//! Server state +use super::*; + +#[derive(Debug, Clone)] +pub struct ServerState +{ + +} diff --git a/src/server/web/auth.rs b/src/server/web/auth.rs new file mode 100644 index 0000000..0fea851 --- /dev/null +++ b/src/server/web/auth.rs @@ -0,0 +1,31 @@ +//! Authentication +use super::*; + +pub struct Sha256Hash(pub sha256::Sha256Hash); +type RsaSignature = rsa::Signature; + +impl str::FromStr for Sha256Hash +{ + type Err = (); + fn from_str(s: &str) -> Result { + todo!() //read encoded base64(?)/hex into `Signature` + } +} + +pub async fn auth_req(who: source::IpAddr, state: Arc) -> Result<(), Infallible> +{ + + Ok(()) +} + +pub async fn auth_key(who: source::IpAddr, state: Arc, req_id: uuid::Uuid, num: usize, body: Bytes) -> Result<(), Infallible> +{ + + Ok(()) +} + +pub async fn auth_pass(who: source::IpAddr, state: Arc, req_id: uuid::Uuid, passhash: sha256::Sha256Hash) -> Result<(), Infallible> +{ + + Ok(()) +} diff --git a/src/server/web/forwarded_list.rs b/src/server/web/forwarded_list.rs new file mode 100644 index 0000000..302be9f --- /dev/null +++ b/src/server/web/forwarded_list.rs @@ -0,0 +1,74 @@ +use std::{ + net::{ + IpAddr, + AddrParseError, + }, + str, + error, + fmt, +}; + +#[derive(Debug)] +pub struct XFormatError; + +impl error::Error for XFormatError{} + +impl fmt::Display for XFormatError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "X-Forwarded-For was not in the correct format") + } +} + +#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Default)] +pub struct XForwardedFor(Vec); + +impl XForwardedFor +{ + pub fn new() -> Self + { + Self(Vec::new()) + } + pub fn single(ip: impl Into) -> Self + { + Self(vec![ip.into()]) + } + pub fn addrs(&self) -> &[IpAddr] + { + &self.0[..] + } + + pub fn into_first(self) -> Option + { + self.0.into_iter().next() + } + + pub fn into_addrs(self) -> Vec + { + self.0 + } +} + +impl str::FromStr for XForwardedFor +{ + type Err = XFormatError; + + fn from_str(s: &str) -> Result { + let mut output = Vec::new(); + for next in s.split(',') + { + output.push(next.trim().parse()?) + } + Ok(Self(output)) + } +} + +impl From for XFormatError +{ + #[inline(always)] fn from(_: AddrParseError) -> Self + { + Self + } +} + diff --git a/src/server/web/mod.rs b/src/server/web/mod.rs index 444b459..924e058 100644 --- a/src/server/web/mod.rs +++ b/src/server/web/mod.rs @@ -1,2 +1,80 @@ //! datse server over HTTP use super::*; +use warp::{ + Filter, + hyper::body::Bytes, +}; +use std::{ + str, + sync::Arc, + convert::Infallible, +}; +use cryptohelpers::{ + sha256, + rsa, +}; + +/// Web server config +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Config +{ + +} + +pub mod settings; +mod state; + +mod source; +mod forwarded_list; +mod auth; + +/// Main entry point for web server +pub async fn main(state: server::state::ServerState, cfg: settings::Settings) -> eyre::Result<()> +{ + let state = Arc::new(state::State::new(state, cfg.clone())); + let state = warp::any().map(move || state.clone()); + + // Extract the client IP or fail with custom rejection + let client_ip = warp::addr::remote() + .and(warp::header("X-Forwarded-For")) + .map(source::extract(cfg.trust_x_forwarded_for)) + .and_then(|req: Result| async move {req.map_err(warp::reject::custom)}); + + let auth = { + let req = warp::path("req") + .and(client_ip.clone()).and(state.clone()); //TODO + + let resp = { + let resp_auth_with_state = warp::post() + .and(client_ip.clone()).and(state.clone()) + .and(warp::path::param().map(|req_id: uuid::Uuid| req_id)); + + let resp_auth_key = resp_auth_with_state.clone() + .and(warp::path("si") + .and(warp::path::param().map(|num: usize| std::cmp::max(1, num)) + .or(warp::path::end().map(|| 1usize)).unify())) + .and(warp::body::content_length_limit(cfg.max_body_len.0)) + .and(warp::body::bytes()) + .and_then(auth::auth_key); + + // -- Paths -- + + let resp_auth_pass = resp_auth_with_state + .and(warp::path::param().map(|hash: auth::Sha256Hash| hash.0)) + .and_then(auth::auth_pass); + + let resp = warp::path("resp") + .and(resp_auth_key + .or(resp_auth_pass)); + + + // /resp//pw/ + // /resp//si[/] + resp + }; + + warp::path("auth").and(req) + }; + + todo!() +} diff --git a/src/server/web/settings.rs b/src/server/web/settings.rs new file mode 100644 index 0000000..14d1521 --- /dev/null +++ b/src/server/web/settings.rs @@ -0,0 +1,28 @@ +//! Settings for web server +//! +//! Usually derived from config +use super::*; + +const DEFAULT_MAX_BODY_LEN_ARESP: u64 = 1024 * 4; // 4KB +const DEFAULT_MAX_BODY_LEN: u64 = 1024 * 1024 * 4; // 4MB + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Settings +{ + /// First is max body len for auth responses, 2nd is for data. + pub max_body_len: (u64, u64), + pub trust_x_forwarded_for: bool, +} + +impl Default for Settings +{ + #[inline] + fn default() -> Self + { + Self { + max_body_len: (DEFAULT_MAX_BODY_LEN_ARESP, DEFAULT_MAX_BODY_LEN), + trust_x_forwarded_for: false, + } + } +} + diff --git a/src/server/web/source.rs b/src/server/web/source.rs new file mode 100644 index 0000000..d457ceb --- /dev/null +++ b/src/server/web/source.rs @@ -0,0 +1,58 @@ +//! Contains info about the requester +use super::*; +use std::net::{ + SocketAddr, +}; +use std::{fmt,error}; + +use forwarded_list::XForwardedFor; + +#[derive(Debug)] +pub struct Requester +{ + source: Option, + x_forwarded_for: XForwardedFor, +} + +pub use std::net::IpAddr; + +impl Requester +{ + pub fn new(source: Option, x_forwarded_for: XForwardedFor) -> Result + { + if source.is_none() && x_forwarded_for.addrs().len() == 0 { + Err(NoIpError) + } else { + Ok(Self{ + source, + x_forwarded_for, + }) + } + } +} + + +#[derive(Debug)] +pub struct NoIpError; + +impl warp::reject::Reject for NoIpError{} +impl error::Error for NoIpError{} +impl fmt::Display for NoIpError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "no remote host IP address could be found") + } +} + +/// Extract the IP using the specified settings for trusting the `X-Forwarded-For` header. +pub fn extract(trust_x: bool) -> impl Fn(Option, XForwardedFor) -> Result + Clone +{ + move |opt, x| { + if trust_x { + x.into_first().ok_or(NoIpError) + } else { + opt.map(|x| x.ip()).ok_or(NoIpError) + } + } +} diff --git a/src/server/web/state.rs b/src/server/web/state.rs new file mode 100644 index 0000000..2325ec4 --- /dev/null +++ b/src/server/web/state.rs @@ -0,0 +1,30 @@ +//! Web server state +use super::*; +use tokio::{ + sync::{ + RwLock, + }, +}; + +#[derive(Debug)] +pub struct State +{ + backend: RwLock, + settings: settings::Settings, +} + +impl State +{ + pub fn new(backend: server::state::ServerState, settings: settings::Settings) -> Self + { + Self { + backend: RwLock::new(backend), + settings, + } + } + + pub fn cfg(&self) -> &settings::Settings + { + &self.settings + } +}