feat: add csrf scraping
This commit is contained in:
parent
591b30104c
commit
f2902f6175
328
Cargo.lock
generated
328
Cargo.lock
generated
@ -70,6 +70,12 @@ version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
@ -144,6 +150,29 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3"
|
||||
dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser-macros"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.7"
|
||||
@ -177,12 +206,72 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa-short"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ego-tree"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
|
||||
dependencies = [
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@ -201,6 +290,18 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever",
|
||||
"match_token",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
@ -246,12 +347,55 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_token"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
@ -305,12 +449,76 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@ -329,6 +537,21 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.13"
|
||||
@ -344,6 +567,40 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scraper"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527e65d9d888567588db4c12da1087598d0f6f8b346cc2c5abc91f05fc2dffe2"
|
||||
dependencies = [
|
||||
"cssparser",
|
||||
"ego-tree",
|
||||
"getopts",
|
||||
"html5ever",
|
||||
"precomputed-hash",
|
||||
"selectors",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cssparser",
|
||||
"derive_more",
|
||||
"fxhash",
|
||||
"log",
|
||||
"new_debug_unreachable",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"precomputed-hash",
|
||||
"servo_arc",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
@ -373,18 +630,64 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servo_arc"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@ -402,6 +705,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
|
||||
dependencies = [
|
||||
"futf",
|
||||
"mac",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
@ -506,6 +820,8 @@ dependencies = [
|
||||
"dashmap",
|
||||
"httparse",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"scraper",
|
||||
"serde",
|
||||
"shlex",
|
||||
"thiserror",
|
||||
@ -518,6 +834,18 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
|
@ -12,6 +12,8 @@ ctrlc = "3"
|
||||
dashmap = "6"
|
||||
httparse = "1"
|
||||
parking_lot = { version = "0", features = ["arc_lock", "serde"] }
|
||||
paste = "1"
|
||||
scraper = "0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
shlex = "1"
|
||||
thiserror = "2"
|
||||
|
@ -49,6 +49,7 @@ pub fn start_client(config: config::Schema) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc<config::Schema>) {
|
||||
let Ok(stream2) = stream.try_clone() else {
|
||||
return;
|
||||
@ -65,9 +66,15 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc<config::Schema>
|
||||
"help" => {
|
||||
_ = writeln!(
|
||||
stream,
|
||||
"available commands:\n help\n configcount\n confdump\n hosts <list|create|delete> ..."
|
||||
"available commands (in no particular order):\n help\n configcount\n userdump \n csrfdump\n confdump\n hosts <list|create|delete> ..."
|
||||
);
|
||||
}
|
||||
"userdump" => {
|
||||
_ = writeln!(stream, "{:#?}", crate::listener::AUTH_MAP);
|
||||
},
|
||||
"csrfdump" => {
|
||||
_ = writeln!(stream, "{:#?}", crate::listener::CSRF_MAP);
|
||||
},
|
||||
"configcount" => {
|
||||
_ = writeln!(
|
||||
stream,
|
||||
|
502
src/listener/mod.rs
Normal file
502
src/listener/mod.rs
Normal file
@ -0,0 +1,502 @@
|
||||
use core::str;
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
net::{Shutdown, TcpListener, TcpStream},
|
||||
str::Utf8Error,
|
||||
sync::Arc,
|
||||
thread,
|
||||
};
|
||||
|
||||
use dashmap::{DashMap, mapref::one::Ref};
|
||||
use httparse::{Request, Response};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{config, constants, utils::headers::HeadersExt};
|
||||
|
||||
pub fn start_listener_loop(config_arc: &Arc<config::Schema>, listener: &TcpListener) -> ! {
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(mut client) => {
|
||||
let config_arc = config_arc.clone();
|
||||
thread::spawn(|| {
|
||||
if let Err(err) = handle_client(&mut client, config_arc.as_ref()) {
|
||||
eprintln!("err: invalid req head ({err}), closing...");
|
||||
_ = client.shutdown(Shutdown::Both);
|
||||
}
|
||||
drop(client);
|
||||
drop(config_arc);
|
||||
});
|
||||
}
|
||||
Err(err) => eprintln!("error with an incoming listener {err:#?}"),
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!("listener had to be killed unexpectedly");
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error("error reading request head: {0}")]
|
||||
HeadReadError(io::Error),
|
||||
#[error("failed to find \"host\" header")]
|
||||
HostHeaderNotFound,
|
||||
#[error("requested host {0:?}, but it's not registered")]
|
||||
HostNotRegistered(String),
|
||||
#[error("failed to connect to backend: {0}")]
|
||||
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),
|
||||
#[error("failed to parse response head: {0}")]
|
||||
ParseResponseHead(httparse::Error),
|
||||
#[error("attempted to parse an unfinished response")]
|
||||
UnfinishedResponse,
|
||||
#[error("error with text encoding: {0}")]
|
||||
TextEncodingError(Utf8Error),
|
||||
#[error("error building html csrf finder selector: {0}")]
|
||||
SelectorBuildError(String),
|
||||
#[error("couldn't find csrf token in response")]
|
||||
CsrfNotFound,
|
||||
#[error("invalid header {0:?}")]
|
||||
InvalidHeader(&'static str),
|
||||
}
|
||||
|
||||
// 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 authentication first of everything as it changes decisions on everything 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)]
|
||||
#[derive(Debug)]
|
||||
enum ProxyAction<'a> {
|
||||
// combining like this eases a lot the implementation
|
||||
CaptureBodyAndHijack(String),
|
||||
// (
|
||||
// fn(&httparse::Request, &[u8]) -> Result<(), ClientError>,
|
||||
// fn(&httparse::Request, &mut TcpStream) -> Result<(), ClientError>,
|
||||
// ),
|
||||
// ),
|
||||
CaptureResponse(String),
|
||||
//fn(&httparse::Request, &[u8]) -> Result<(), ClientError>),
|
||||
ContWithMaybeAuth(Option<Ref<'a, String, AuthData>>),
|
||||
}
|
||||
|
||||
impl ProxyAction<'_> {
|
||||
// username - password
|
||||
#[allow(dead_code)]
|
||||
type CaptureBodyConclussion = (String, String);
|
||||
|
||||
pub fn capture_body(
|
||||
_req: &httparse::Request,
|
||||
_id: &str,
|
||||
_body: &[u8],
|
||||
) -> Result<Self::CaptureBodyConclussion, ClientError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn hijack(
|
||||
_req: &httparse::Request,
|
||||
_body_conclussion: Self::CaptureBodyConclussion,
|
||||
_stream: &mut TcpStream,
|
||||
) -> Result<(), ClientError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn capture_response(
|
||||
_req: &httparse::Request,
|
||||
_res: &httparse::Response,
|
||||
id: String,
|
||||
csrf_keyname: &str,
|
||||
body: &[u8],
|
||||
) -> Result<(), ClientError> {
|
||||
// Find csrf token and add it to list of things
|
||||
let document = scraper::Html::parse_document(
|
||||
str::from_utf8(body).map_err(ClientError::TextEncodingError)?,
|
||||
);
|
||||
let selector = scraper::Selector::parse(format!("input[name=\"{csrf_keyname}\"]").as_str())
|
||||
.map_err(|err| ClientError::SelectorBuildError(format!("{err}")))?;
|
||||
let csrf = document
|
||||
.select(&selector)
|
||||
.next()
|
||||
.and_then(|elem| elem.attr("value"))
|
||||
.ok_or(ClientError::CsrfNotFound)?;
|
||||
|
||||
println!("inserting {id} - {csrf}");
|
||||
CSRF_MAP.insert(id, csrf.to_string());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub 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>>)>,
|
||||
) -> ProxyAction<'a> {
|
||||
let trimedpath = req
|
||||
.path
|
||||
.and_then(|path| path.split_once('?').map(|spl| spl.0).or(Some(path)));
|
||||
// FIXME: auth might not be set up until gitea replies and sets an ID
|
||||
match (trimedpath, req.method, id_auth) {
|
||||
(Some("/user/login"), Some("GET"), Some((id, None))) => {
|
||||
// We gotta get the gitea response to find the csrf token and save that
|
||||
ProxyAction::CaptureResponse(id)
|
||||
}
|
||||
(Some("/user/login"), Some("POST"), Some((id, None))) => {
|
||||
// Here we gotta get the post body, check csrf, and check passwd
|
||||
// Then get their account data, add it to their ID and redirect back to home
|
||||
ProxyAction::CaptureBodyAndHijack(id)
|
||||
}
|
||||
(_, _, id_auth) => ProxyAction::ContWithMaybeAuth(id_auth.and_then(|id_auth| id_auth.1)),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: might wanna save this one between runtimes
|
||||
pub static AUTH_MAP: std::sync::LazyLock<DashMap<String, AuthData>> =
|
||||
std::sync::LazyLock::new(DashMap::new);
|
||||
// TODO: time expire old keys?
|
||||
pub static CSRF_MAP: std::sync::LazyLock<DashMap<String, String>> =
|
||||
std::sync::LazyLock::new(DashMap::new);
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), ClientError> {
|
||||
use ClientError as E;
|
||||
use ProxyAction as A;
|
||||
|
||||
http_bodies::parse_http!(client_req, client, Request, let (req, _, body_over));
|
||||
|
||||
let theres_body = req.headers.expect_body();
|
||||
|
||||
let host_header = req.headers.header("host").ok_or(E::HostHeaderNotFound)?;
|
||||
let cookies = req.headers.header("cookie");
|
||||
|
||||
// Now find that header and pass everything
|
||||
let read_hosts = config.hosts.read();
|
||||
let (addr, id_keyname, csrf_keyname) = read_hosts
|
||||
.get(host_header.as_ref())
|
||||
.ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?
|
||||
.clone();
|
||||
drop(read_hosts);
|
||||
|
||||
// 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(Result::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);
|
||||
if let A::CaptureBodyAndHijack(id) = action {
|
||||
let mut body = body_over.to_vec();
|
||||
if theres_body {
|
||||
// not sure if this will append or overwrite, wanna append
|
||||
io::copy(client, &mut body).map_err(|err| E::BufferError("body", err))?;
|
||||
}
|
||||
let c = A::capture_body(&req, id.as_ref(), &body[..])?;
|
||||
A::hijack(&req, c, client)?;
|
||||
} else {
|
||||
let mut stream = TcpStream::connect(addr).map_err(E::BackendConnectFail)?;
|
||||
// stream.write_all(body_head).map_err(E::ExchangeIoError)?;
|
||||
stream
|
||||
.write_all(
|
||||
format!(
|
||||
"{} {} HTTP/1.1\r\n",
|
||||
req.method.unwrap_or("GET"),
|
||||
req.path.unwrap_or("/")
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
req.headers
|
||||
.iter()
|
||||
.filter(|header| !header.name.eq_ignore_ascii_case("accept-encoding"))
|
||||
.map(|header| {
|
||||
stream.write_all(
|
||||
&{
|
||||
let mut all = Vec::new();
|
||||
all.extend_from_slice(header.name.as_bytes());
|
||||
all.extend_from_slice(b": ");
|
||||
all.extend_from_slice(header.value);
|
||||
all.extend_from_slice(b"\r\n");
|
||||
all
|
||||
}[..],
|
||||
)
|
||||
})
|
||||
.try_fold((), |(), header| header)
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
if let A::ContWithMaybeAuth(Some(ref auth)) = action {
|
||||
auth.write_as_headers(&mut stream)?;
|
||||
}
|
||||
stream
|
||||
.write_all(constants::HTTP_HEADERS_NEWLINE)
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
|
||||
// send our overhead
|
||||
stream.write_all(body_over).map_err(E::ExchangeIoError)?;
|
||||
if theres_body {
|
||||
io::copy(client, &mut stream).map_err(E::ExchangeIoError)?;
|
||||
}
|
||||
if let A::CaptureResponse(id) = action {
|
||||
http_bodies::parse_http!(stream_res, stream, Response, let (resp, _, res_body_over));
|
||||
|
||||
let buf = http_bodies::capture_body(
|
||||
resp.headers,
|
||||
res_body_over,
|
||||
&mut stream,
|
||||
"response buffer",
|
||||
)?
|
||||
.unwrap_or_else(Vec::new);
|
||||
A::capture_response(&req, &resp, id, csrf_keyname.as_ref(), &buf[..])?;
|
||||
|
||||
client
|
||||
.write_all(
|
||||
format!(
|
||||
"HTTP/1.1 {} {}\r\n",
|
||||
resp.code.unwrap_or(200),
|
||||
resp.reason.unwrap_or("OK")
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
resp.headers
|
||||
.iter()
|
||||
.filter(|header| !header.name.eq_ignore_ascii_case("transfer-encoding"))
|
||||
.map(|header| {
|
||||
client.write_all(
|
||||
&{
|
||||
let mut all = Vec::new();
|
||||
all.extend_from_slice(header.name.as_bytes());
|
||||
all.extend_from_slice(b": ");
|
||||
all.extend_from_slice(header.value);
|
||||
all.extend_from_slice(b"\r\n");
|
||||
all
|
||||
}[..],
|
||||
)
|
||||
})
|
||||
.try_fold((), |(), header| header)
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
|
||||
// FIXME: :grimacing: (append to headers or smth)
|
||||
client
|
||||
.write_all(format!("Content-Length: {}\r\n\r\n", buf.len()).as_bytes())
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
client
|
||||
.write_all(constants::HTTP_HEADERS_NEWLINE)
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
|
||||
client.write_all(&buf[..]).map_err(E::ExchangeIoError)?;
|
||||
} else {
|
||||
io::copy(&mut stream, client).map_err(E::ExchangeIoError)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod http_bodies {
|
||||
use crate::{listener::ClientError, utils::headers::HeadersExt};
|
||||
|
||||
// pub trait HttpMessage<'h, 'b> {
|
||||
// fn new(headers: &'h mut [httparse::Header<'b>]) -> Self;
|
||||
// fn parse(&mut self, buf: &'b [u8]) -> httparse::Result<usize>;
|
||||
// }
|
||||
|
||||
// impl<'h, 'b> HttpMessage<'h, 'b> for httparse::Request<'h, 'b> {
|
||||
// fn new(headers: &'h mut [httparse::Header<'b>]) -> Self {
|
||||
// httparse::Request::new(headers)
|
||||
// }
|
||||
|
||||
// fn parse(&mut self, buf: &'b [u8]) -> httparse::Result<usize> {
|
||||
// self.parse(buf)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<'h, 'b> HttpMessage<'h, 'b> for httparse::Response<'h, 'b> {
|
||||
// fn new(headers: &'h mut [httparse::Header<'b>]) -> Self {
|
||||
// httparse::Response::new(headers)
|
||||
// }
|
||||
|
||||
// fn parse(&mut self, buf: &'b [u8]) -> httparse::Result<usize> {
|
||||
// self.parse(buf)
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct ChunckedTransferEncoding<'a, R: Read> {
|
||||
rem: Vec<u8>,
|
||||
reader: &'a mut R,
|
||||
}
|
||||
// This was a read impl, then tried to go into an interator and ended up being... this
|
||||
impl<R: Read> ChunckedTransferEncoding<'_, R> {
|
||||
fn read(&mut self, mut cb: impl FnMut(&[u8])) -> io::Result<()> {
|
||||
loop {
|
||||
let Some(busize_after_pos) =
|
||||
self.rem.windows(b"\r\n".len()).position(|w| w == b"\r\n")
|
||||
else {
|
||||
let mut tmp = [0u8; 4096];
|
||||
let n = self.reader.read(&mut tmp)?;
|
||||
self.rem.extend_from_slice(&tmp[..n]);
|
||||
continue;
|
||||
};
|
||||
|
||||
let chunk_len = self.rem[0..busize_after_pos]
|
||||
.iter()
|
||||
.try_fold(0usize, |mut acc, i| {
|
||||
acc <<= 4;
|
||||
|
||||
#[allow(clippy::manual_is_ascii_check)]
|
||||
{
|
||||
acc += (if (b'0'..=b'9').contains(i) {
|
||||
i - b'0'
|
||||
} else if (b'A'..=b'F').contains(i) {
|
||||
i - b'A'
|
||||
} else if (b'a'..=b'f').contains(i) {
|
||||
i - b'f'
|
||||
} else {
|
||||
return None;
|
||||
}) as usize;
|
||||
}
|
||||
Some(acc)
|
||||
})
|
||||
.ok_or(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid chunk size head contents",
|
||||
))?;
|
||||
|
||||
if chunk_len == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
while self.rem.len() < busize_after_pos + chunk_len + (b"\r\n".len() * 2) {
|
||||
let mut tmp = [0u8; 4096];
|
||||
let n = self.reader.read(&mut tmp)?;
|
||||
self.rem.extend_from_slice(&tmp[..n]);
|
||||
}
|
||||
cb(&self.rem[(busize_after_pos + b"\r\n".len())
|
||||
..(busize_after_pos + b"\r\n".len() + chunk_len)]);
|
||||
self.rem = self.rem
|
||||
[(busize_after_pos + b"\r\n".len() + chunk_len + b"\r\n".len())..]
|
||||
.to_vec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capture_body(
|
||||
headers: &[httparse::Header<'_>],
|
||||
body_over: &[u8],
|
||||
reader: &mut impl Read,
|
||||
debug_bufname: &'static str,
|
||||
) -> Result<Option<Vec<u8>>, ClientError> {
|
||||
Ok(if let Some(tenc) = headers.header("transfer-encoding") {
|
||||
if tenc != "chunked" {
|
||||
return Err(ClientError::InvalidHeader("transfer-encoding"));
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
(ChunckedTransferEncoding {
|
||||
rem: body_over.to_vec(),
|
||||
reader,
|
||||
})
|
||||
.read(|chunk| {
|
||||
buf.extend_from_slice(chunk);
|
||||
})
|
||||
.map_err(|err| ClientError::BufferError(debug_bufname, err))?;
|
||||
Some(buf)
|
||||
} else if let Some(clen) = headers.header("content-length") {
|
||||
let n: usize = clen
|
||||
.parse()
|
||||
.map_err(|_| ClientError::InvalidHeader("content-length"))?;
|
||||
let mut buf = Vec::with_capacity(n);
|
||||
buf.extend_from_slice(body_over);
|
||||
reader
|
||||
.read_exact(&mut buf[body_over.len()..])
|
||||
.map_err(|err| ClientError::BufferError(debug_bufname, err))?;
|
||||
Some(buf)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
// I hate it too
|
||||
macro_rules! parse_http {
|
||||
($trash_state:tt, $stream:expr, $msg_type:ty, $ret:stmt) => {
|
||||
$crate::listener::http_bodies::parse_http!($trash_state, $stream, $msg_type, 16, {1024 * 8}, $ret);
|
||||
};
|
||||
($trash_state:tt, $stream:expr, $msg_type:ty, $headers_len:expr, $buf_size:expr, $ret:stmt) => {
|
||||
paste::paste! {
|
||||
let mut [<$trash_state _buf>] = [0u8; $buf_size];
|
||||
let mut [<$trash_state _pos>] = 0;
|
||||
let mut [<$trash_state _headers>];
|
||||
|
||||
$ret = loop {
|
||||
[<$trash_state _headers>] = [httparse::EMPTY_HEADER; $headers_len];
|
||||
let mut res = <$msg_type>::new(&mut [<$trash_state _headers>]);
|
||||
|
||||
let n = $stream
|
||||
.read(&mut [<$trash_state _buf>][[<$trash_state _pos>]..])
|
||||
.map_err(ClientError::HeadReadError)?;
|
||||
[<$trash_state _pos>] += n;
|
||||
|
||||
if let Ok(httparse::Status::Complete(n)) = res.parse(&[<$trash_state _buf>][0..[<$trash_state _pos>]]) {
|
||||
break (res, &[<$trash_state _buf>][0..(n - $crate::constants::HTTP_HEADERS_NEWLINE.len())], &[<$trash_state _buf>][n..[<$trash_state _pos>]]);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
use std::io::{self, Read};
|
||||
|
||||
pub(crate) use parse_http;
|
||||
}
|
221
src/main.rs
221
src/main.rs
@ -1,7 +1,11 @@
|
||||
//! # Tuxcord Reverse Proxy Header Authenthication
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(
|
||||
anonymous_lifetime_in_impl_trait,
|
||||
iterator_try_collect,
|
||||
inherent_associated_types,
|
||||
stmt_expr_attributes,
|
||||
read_buf,
|
||||
rwlock_downgrade,
|
||||
try_blocks
|
||||
)]
|
||||
@ -9,24 +13,20 @@
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, Read, Write},
|
||||
net::{self, Shutdown, TcpListener, TcpStream},
|
||||
io::{Read, Write},
|
||||
net::TcpListener,
|
||||
sync::Arc,
|
||||
thread,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use dashmap::{DashMap, mapref::one::Ref};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::utils::headers::HeadersExt;
|
||||
|
||||
pub mod args;
|
||||
pub mod config;
|
||||
pub mod constants;
|
||||
#[cfg(feature = "ipc")]
|
||||
pub mod ipc;
|
||||
pub mod listener;
|
||||
pub mod utils;
|
||||
|
||||
use args::Args;
|
||||
@ -66,210 +66,5 @@ fn main() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "ipc")]
|
||||
ipc::handle_daemon(config_arc.clone());
|
||||
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(mut client) => {
|
||||
let config_arc = config_arc.clone();
|
||||
thread::spawn(|| {
|
||||
if let Err(err) = handle_client(&mut client, config_arc.as_ref()) {
|
||||
eprintln!("err: invalid req head ({err}), closing...");
|
||||
_ = client.shutdown(Shutdown::Both);
|
||||
}
|
||||
drop(client);
|
||||
drop(config_arc);
|
||||
});
|
||||
}
|
||||
Err(err) => eprintln!("error with an incoming listener {err:#?}"),
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!("listener had to be killed unexpectedly");
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error("error reading request head: {0}")]
|
||||
HeadReadError(io::Error),
|
||||
#[error("failed to find \"host\" header")]
|
||||
HostHeaderNotFound,
|
||||
#[error("requested host {0:?}, but it's not registered")]
|
||||
HostNotRegistered(String),
|
||||
#[error("failed to connect to backend: {0}")]
|
||||
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 authentication first of everything as it changes decisions on everything 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_authd = id_auth.as_ref().is_some_and(|id_auth| id_auth.1.is_some());
|
||||
match (req.path, req.method, is_authd) {
|
||||
(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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: might wanna save this one between runtimes
|
||||
static AUTH_MAP: std::sync::LazyLock<DashMap<String, AuthData>> =
|
||||
std::sync::LazyLock::new(DashMap::new);
|
||||
static _CSRF_MAP: std::sync::LazyLock<DashMap<String, String>> =
|
||||
std::sync::LazyLock::new(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;
|
||||
|
||||
let mut headers;
|
||||
let (pos, req) = loop {
|
||||
headers = [httparse::EMPTY_HEADER; 16];
|
||||
let mut req = httparse::Request::new(&mut headers);
|
||||
|
||||
let n = client
|
||||
.read(&mut header_buf[read_pos..])
|
||||
.map_err(E::HeadReadError)?;
|
||||
read_pos += n;
|
||||
|
||||
if let Ok(httparse::Status::Complete(n)) = req.parse(&header_buf[0..read_pos]) {
|
||||
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 pass everything
|
||||
let read_hosts = config.hosts.read();
|
||||
let (addr, id_keyname, csrf_keyname) = read_hosts
|
||||
.get(host_header.as_ref())
|
||||
.ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?
|
||||
.clone();
|
||||
drop(read_hosts);
|
||||
|
||||
// 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(Result::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])
|
||||
.map_err(E::ExchangeIoError)?;
|
||||
if theres_body {
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
listener::start_listener_loop(&config_arc, &listener);
|
||||
}
|
||||
|
18
src/utils.rs
18
src/utils.rs
@ -1,7 +1,10 @@
|
||||
pub mod headers {
|
||||
use std::borrow::Cow;
|
||||
use std::borrow::{Borrow, Cow};
|
||||
|
||||
pub trait HeadersExt<'a> {
|
||||
fn expect_body(&self) -> bool {
|
||||
self.has_any(["conten-length", "transfer-encoding"])
|
||||
}
|
||||
fn has_any(&self, headers: impl IntoIterator<Item = &'_ str>) -> bool {
|
||||
headers.into_iter().any(|h| self.has(h))
|
||||
}
|
||||
@ -9,16 +12,19 @@ pub mod headers {
|
||||
headers.into_iter().all(|h| self.has(h))
|
||||
}
|
||||
fn has(&self, header: &str) -> bool;
|
||||
fn get(&self, header: &str) -> Option<Cow<'a, str>>;
|
||||
fn header(&self, header: &str) -> Option<Cow<'a, str>>;
|
||||
}
|
||||
|
||||
impl<'a> HeadersExt<'a> for &mut [httparse::Header<'a>] {
|
||||
impl<'a, T: Borrow<[httparse::Header<'a>]>> HeadersExt<'a> for T {
|
||||
fn has(&self, header: &str) -> bool {
|
||||
self.iter().any(|h| h.name.eq_ignore_ascii_case(header))
|
||||
self.borrow()
|
||||
.iter()
|
||||
.any(|h| h.name.eq_ignore_ascii_case(header))
|
||||
}
|
||||
|
||||
fn get(&self, header: &str) -> Option<Cow<'a, str>> {
|
||||
self.iter()
|
||||
fn header(&self, header: &str) -> Option<Cow<'a, str>> {
|
||||
self.borrow()
|
||||
.iter()
|
||||
.find(|h| h.name.eq_ignore_ascii_case(header))
|
||||
.map(|h| String::from_utf8_lossy(h.value))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user