feat: add way more proxy handling
This commit is contained in:
parent
716707f1bc
commit
2ab9c5d134
117
Cargo.lock
generated
117
Cargo.lock
generated
@ -128,6 +128,22 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.7"
|
||||
@ -138,12 +154,41 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
@ -169,7 +214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -178,6 +223,18 @@ version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
@ -213,6 +270,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
@ -242,6 +311,12 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@ -353,6 +428,37 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
@ -401,8 +507,11 @@ dependencies = [
|
||||
"anstyle",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"cookie",
|
||||
"ctrlc",
|
||||
"dashmap",
|
||||
"httparse",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"shlex",
|
||||
@ -422,6 +531,12 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
@ -7,8 +7,11 @@ edition = "2024"
|
||||
anstyle = "1"
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
cookie = "0"
|
||||
ctrlc = "3"
|
||||
dashmap = "6"
|
||||
httparse = "1"
|
||||
lazy_static = "1"
|
||||
parking_lot = { version = "0", features = ["arc_lock", "serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
shlex = "1"
|
||||
|
@ -1 +1,3 @@
|
||||
pub const DEFAULT_CONFIG: &[u8] = include_bytes!("../config.default.toml");
|
||||
pub const DOUBLE_CRLF: &[u8] = b"\r\n\r\n";
|
||||
pub const HTTP_HEADERS_NEWLINE: &[u8] = b"\r\n"; // I'm not so sure about this
|
||||
|
155
src/main.rs
155
src/main.rs
@ -10,13 +10,15 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, Read, Write},
|
||||
net::{Shutdown, TcpListener, TcpStream},
|
||||
net::{self, Shutdown, TcpListener, TcpStream},
|
||||
sync::Arc,
|
||||
thread,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use dashmap::{DashMap, mapref::one::Ref};
|
||||
use lazy_static::lazy_static;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::utils::headers::HeadersExt;
|
||||
@ -97,10 +99,95 @@ pub enum ClientError {
|
||||
BackendConnectFail(io::Error),
|
||||
#[error("io error exchanging client and backend: {0}")]
|
||||
ExchangeIoError(io::Error),
|
||||
#[error("io error capturing {0} buffer: {0}")]
|
||||
BufferError(&'static str, io::Error),
|
||||
#[error("io error writing auth headers: {0}")]
|
||||
AuthHeadersIoError(io::Error),
|
||||
}
|
||||
|
||||
// fn(req, (adrr, etc)) -> Action
|
||||
// Action {
|
||||
// CaptureBody?
|
||||
// CaptureResponse?
|
||||
// }
|
||||
//
|
||||
// cases:
|
||||
// * GET login -> get response to access csrf token
|
||||
// * POST login -> get body to handle authentication
|
||||
//
|
||||
// auth headers are just added depending on the ID cookie soo, keep a hashmap of IDs and user
|
||||
// data??
|
||||
//
|
||||
// check authethication first of everything as it changes decissions on eveything and give it to fn
|
||||
// too
|
||||
// fn(req, Option<AuthData>, (adrr, etc)) -> Action
|
||||
//
|
||||
// and also add an action to... hook in the response without even letting backend know (redirect
|
||||
// ppl out and such), also in no case we need multiple actions. NOPE, POST login should capture
|
||||
// body and hijack the response, I'll also just make this flexible too.
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct ProxyAction<'a> {
|
||||
// combining like this eases a lot the implementation
|
||||
capture_body_and_hijack: Option<(
|
||||
fn(&httparse::Request, &[u8]) -> Result<(), ClientError>,
|
||||
fn(&httparse::Request, &mut TcpStream) -> Result<(), ClientError>,
|
||||
)>,
|
||||
capture_response: Option<fn(&httparse::Request, &[u8]) -> Result<(), ClientError>>,
|
||||
auth: Option<Ref<'a, String, AuthData>>,
|
||||
}
|
||||
|
||||
struct AuthData {
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub fullname: Option<String>,
|
||||
}
|
||||
impl AuthData {
|
||||
pub fn write_as_headers(&self, into: &mut impl Write) -> Result<(), ClientError> {
|
||||
use constants::HTTP_HEADERS_NEWLINE as NL;
|
||||
let r: Result<_, io::Error> = try {
|
||||
into.write_all(b"X-WEBAUTH-USER: ")?;
|
||||
into.write_all(self.username.as_bytes())?;
|
||||
into.write_all(NL)?;
|
||||
into.write_all(b"X-WEBAUTH-EMAIL: ")?;
|
||||
into.write_all(self.email.as_bytes())?;
|
||||
into.write_all(NL)?;
|
||||
if let Some(fullname) = self.fullname.as_ref() {
|
||||
into.write_all(b"X-WEBAUTH-FULLNAME: ")?;
|
||||
into.write_all(fullname.as_bytes())?;
|
||||
into.write_all(NL)?;
|
||||
};
|
||||
};
|
||||
r.map_err(ClientError::AuthHeadersIoError)
|
||||
}
|
||||
}
|
||||
|
||||
fn what_to_do<'a>(
|
||||
req: &httparse::Request,
|
||||
id_auth: Option<(String, Option<Ref<'a, String, AuthData>>)>,
|
||||
backend: (net::SocketAddr, String, String),
|
||||
) -> ProxyAction<'a> {
|
||||
let is_auth = id_auth.as_ref().is_some_and(|id_auth| id_auth.1.is_some());
|
||||
match (req.path, req.method, is_auth) {
|
||||
(Some("/user/login"), Some("GET"), false) => todo!(),
|
||||
(Some("/user/login"), Some("POST"), false) => todo!(),
|
||||
_ => ProxyAction {
|
||||
capture_body_and_hijack: None,
|
||||
capture_response: None,
|
||||
auth: id_auth.and_then(|id_auth| id_auth.1),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// TODO: might wanna save this one between runtimes
|
||||
static ref AUTH_MAP: DashMap<String, AuthData> = DashMap::new();
|
||||
static ref CSRF_MAP: DashMap<String, AuthData> = DashMap::new();
|
||||
}
|
||||
|
||||
fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), ClientError> {
|
||||
use ClientError as E;
|
||||
use constants::DOUBLE_CRLF;
|
||||
|
||||
let mut header_buf = [0u8; 1024 * 8];
|
||||
let mut read_pos = 0usize;
|
||||
@ -116,34 +203,74 @@ fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(),
|
||||
read_pos += n;
|
||||
|
||||
if let Ok(httparse::Status::Complete(n)) = req.parse(&header_buf[0..read_pos]) {
|
||||
break (n - b"\r\n\r\n".len(), req);
|
||||
break (n - DOUBLE_CRLF.len(), req);
|
||||
}
|
||||
};
|
||||
|
||||
let theres_body = req.headers.has_any(["conten-length", "transfer-encoding"]);
|
||||
|
||||
let host_header = req.headers.get("host").ok_or(E::HostHeaderNotFound)?;
|
||||
let cookies = req.headers.get("cookie");
|
||||
|
||||
// Now find that header and pas everything
|
||||
// Now find that header and pass everything
|
||||
let read_hosts = config.hosts.read();
|
||||
let (addr, _, _) = read_hosts
|
||||
let (addr, id_keyname, csrf_keyname) = read_hosts
|
||||
.get(host_header.as_ref())
|
||||
.ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?;
|
||||
|
||||
let mut stream = TcpStream::connect(addr).map_err(E::BackendConnectFail)?;
|
||||
.ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?
|
||||
.clone();
|
||||
drop(read_hosts);
|
||||
let r: io::Result<()> = try {
|
||||
stream.write_all(&header_buf[0..pos])?;
|
||||
// here we sent all original headers, append our own (TODO)
|
||||
|
||||
// Try to find auth data
|
||||
let id_auth = if let Some(cookies) = cookies {
|
||||
if let Some(id_ck) = cookie::Cookie::split_parse(cookies)
|
||||
.filter_map(|ck| ck.ok())
|
||||
.find(|ck| ck.name() == id_keyname)
|
||||
{
|
||||
let id = id_ck.value().to_string();
|
||||
let val = AUTH_MAP.get(&id);
|
||||
Some((id, val))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let action = what_to_do(&req, id_auth, (addr, id_keyname, csrf_keyname));
|
||||
if let Some((cb1, cb2)) = action.capture_body_and_hijack {
|
||||
let mut body = header_buf[pos..read_pos].to_vec();
|
||||
if theres_body {
|
||||
io::copy(client, &mut body).map_err(|err| E::BufferError("body", err))?;
|
||||
}
|
||||
cb1(&req, &body[..])?;
|
||||
cb2(&req, client)?;
|
||||
} else {
|
||||
let mut stream = TcpStream::connect(addr).map_err(E::BackendConnectFail)?;
|
||||
stream
|
||||
.write_all(&header_buf[0..pos])
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
if let Some(auth) = action.auth {
|
||||
auth.write_as_headers(&mut stream)?;
|
||||
};
|
||||
|
||||
// send our overhead
|
||||
stream.write_all(&header_buf[pos..read_pos])?;
|
||||
stream
|
||||
.write_all(&header_buf[pos..read_pos])
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
if theres_body {
|
||||
io::copy(client, &mut stream)?;
|
||||
io::copy(client, &mut stream).map_err(E::ExchangeIoError)?;
|
||||
}
|
||||
if let Some(cb) = action.capture_response {
|
||||
let mut buf = Vec::new();
|
||||
stream
|
||||
.read_to_end(&mut buf)
|
||||
.map_err(|err| E::BufferError("response", err))?;
|
||||
cb(&req, &buf[..])?;
|
||||
client.write_all(&buf[..]).map_err(E::ExchangeIoError)?;
|
||||
} else {
|
||||
io::copy(&mut stream, client).map_err(E::ExchangeIoError)?;
|
||||
}
|
||||
io::copy(&mut stream, client)?;
|
||||
};
|
||||
r.map_err(E::ExchangeIoError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user