From e062c4e8cc09b2fe9bb75689f2eb8fbe17e91a0f Mon Sep 17 00:00:00 2001 From: Riley Apeldoorn Date: Thu, 21 Jul 2022 00:20:59 +0200 Subject: [PATCH] Implement config parsing --- .gitignore | 1 + Cargo.lock | 451 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 11 ++ src/main.rs | 275 ++++++++++++++++++++++++++++++++ 4 files changed, 738 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1ee086d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,451 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bytes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxima" +version = "0.1.0" +dependencies = [ + "hyper", + "nom", + "tokio", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "once_cell", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[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 = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..82d6700 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "proxima" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hyper = { version = "^0.14", features = ["full"] } +tokio = { version = "^1", features = ["macros"] } +nom = "^7" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ad9cdbc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,275 @@ +use std::{net::SocketAddr, convert::Infallible}; + +use hyper::{Client, Server, service::{make_service_fn, service_fn}, body::HttpBody, Body, Response, Request, Method, http, upgrade::{self, Upgraded}}; +use tokio::net::TcpStream; + +// https://github.com/hyperium/hyper/blob/master/examples/gateway.rs +// https://en.wikipedia.org/wiki/Gateway_(telecommunications) + +#[tokio::main(flavor = "current_thread")] +async fn main() { + + // Load and parse the configuration + let config = { + + // Prefer ./config over /etc/proxima + let file = unsafe { + ["./config", "/etc/proxima"] + .iter() + .map(load) + .reduce(Result::or) + .unwrap_unchecked() + }; + + file.map(parse) + .expect("Loading the config failed.") + + }; + + let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); +} + +pub struct Rule (Pattern, Effect); + +impl Rule { + /// Get the domain of the pattern. + pub fn domain (&self) -> &str { + &self.0.domain + } + + /// Get the portspec + pub fn ports (&self) -> &Ports { + &self.0.ports + } + + pub fn effect (&self) -> &Effect { + &self.1 + } +} + +pub struct Pattern { + domain: String, + ports: Ports, +} + +pub enum Effect { + Redirect (String), + Proxy { + port: u16, + ssl: bool, + }, +} + +impl Effect { + pub async fn perform (&self) -> std::io::Result<()> { + todo!() + } +} + +pub enum Ports { + Single (u16), + Either (Vec), + Any +} + +impl Ports { + /// Whether this set of ports includes the given port. + pub fn includes (&self, p: u16) -> bool { + match self { + Ports::Single (x) => *x == p, + Ports::Either (l) => l.contains(&p), + Ports::Any => true, + } + } +} + +pub mod parse { + + use super::{ Ports, Effect, Pattern, Rule }; + use nom::{ + sequence as seq, + multi as mul, + character::complete as chr, + bytes::complete::{self as byt, tag, take_till}, + Parser, error::ParseError, combinator::opt, + }; + + pub type PResult<'i, T> = nom::IResult<&'i str, T>; + + fn around (a: A, b: B) -> impl Parser + where E: ParseError, + A: Parser + Clone, + B: Parser, + { + seq::delimited(a.clone(), b, a) + } + + /// Parse a [`Portspec`]. + pub (super) fn portspec (s: &str) -> PResult<'_, Ports> { + let single = chr::u16; + let either = { + let delim = around(chr::space1, chr::char('|')); + seq::delimited( + chr::char('('), + mul::separated_list1(delim, single), + chr::char(')'), + ) + }; + let any = byt::tag("any"); + + let single = single.map(Ports::Single); + let either = either.map(Ports::Either); + let any = any.map(|_| Ports::Any); + + single.or(either) + .or(any) + .parse(s) + } + + /// Parse an [`Effect`]. + pub fn effect (s: &str) -> PResult<'_, Effect> { + + let redirect = { + let spaced = |x| seq::delimited(chr::space1, x, chr::space1); + let internal = domain; + + seq::preceded( + spaced(tag("==>")), + internal, + ).map(Effect::Redirect) + }; + + let proxy = { + let spaced = |x| seq::delimited(chr::space1, x, chr::space1); + let ssl = opt(seq::delimited( + seq::preceded(chr::space1, chr::char('[')), + tag("ssl"), + chr::char(']'), + )).map(|o| o.is_some()); + let internal = seq::terminated(chr::u16, chr::space0); + + seq::preceded( + spaced(tag("-->")), + internal.and(ssl), + ).map(|(port, ssl)| Effect::Proxy { ssl, port }) + }; + + redirect.or(proxy) + .parse(s) + + } + + fn domain (s: &str) -> PResult<'_, String> { + take_till(|c: char| !(c == '.' || c.is_alphanumeric())) + .map(str::to_string) + .parse(s) + } + + /// Parse a [`Pattern`]. + /// + /// ``` + /// use proxima::parse; + /// + /// # fn main () -> parse::PResult<'static, ()> { + /// let (_, pattern) = parse::pattern("example.com : any")?; + /// # Ok ("", ()) + /// # } + /// ``` + pub fn pattern (s: &str) -> PResult<'_, Pattern> { + let spaced = |x| seq::delimited(chr::char(' '), x, chr::char(' ')); + seq::separated_pair(domain, spaced(chr::char(':')), portspec) + .map(|(domain, ports)| Pattern { domain, ports }) + .parse(s) + } + + /// Parse a [`Rule`]. + pub fn rule (s: &str) -> PResult<'_, Rule> { + pattern.and(effect) + .map(|(p, e)| Rule (p, e)) + .parse(s) + } + + #[cfg(test)] + mod tests { + + use super::*; + + /// Test whether a pattern containing an Any portspec gets parsed + /// correctly. + #[test] + fn simple_pattern () { + let input = "example.com : any"; + let (_, Pattern { domain, ports }) = pattern(input).unwrap(); + assert!(domain == "example.com"); + assert!(match ports { + Ports::Any => true, + _ => false, + }) + } + + /// Test whether an Either portspec is parsed correctly. + #[test] + fn either_portspec () { + let input = "(69 | 420)"; + assert!(match portspec(input).unwrap() { + ("", Ports::Either(p)) => p == [69, 420], + _ => false, + }) + } + + /// Test whether domain names are parsed correctly. + #[test] + fn domains () { + let inputs = ["example.com", "im.badat.dev", "riley.lgbt", "toot.site", "a.b.c.d.e.f.g.h"]; + // Each of these should be considered valid + for input in inputs { + domain(input).unwrap(); + } + } + + /// Test whether a simple rule gets parsed correctly. + #[test] + fn simple_rule () { + let input = "example.gay : any --> 3000"; + let (_, Rule (_, effect)) = rule(input).unwrap(); + assert!(match effect { + Effect::Proxy { port: 3000, ssl } if !ssl => true, + _ => false, + }); + } + } + +} + +/// A config consists of a set if [`Rule`]. +pub struct Config (Vec); + +/// Load a config from a path. +pub fn load (p: impl AsRef) -> std::io::Result { + std::fs::read_to_string(p.as_ref()) +} + +/// Parse a config string. +/// +/// Example config string: +/// +/// ```text +/// hmt.riley.lgbt : (80 | 443) --> 6000 # --> is proxy_pass +/// riley.lgbt : (80 | 443) --> 3000 [ssl] # add [ssl] to automate ssl for this domain +/// rly.cx : any ==> riley.lgbt # ==> is HTTP redirect +/// ``` +pub fn parse (data: String) -> Config { + let rules = data + .lines() + .map(parse::rule) + .filter_map(|x| match x { + Ok ((_, rule)) => Some (rule), + Err (e) => { + eprintln!("Error parsing rule: {:?}", e); + None + }, + }) + .collect(); + + Config (rules) +}