init commit

This commit is contained in:
MeexReay 2024-06-06 00:28:16 +03:00
commit 2f1a435dda
7 changed files with 829 additions and 0 deletions

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "http_rrs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
openssl = { version = "0.10.64", features = ["vendored"] }
yaml-rust = "0.4.5"
log = "0.4.21"
log4rs = "1.3.0"

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# HttpRRS
HTTP request redirection system
Default `conf.yml`:
```yml
http_host: localhost:80
https_host: localhost:443
sites:
# - domain: example.com # Domain with SSL
# host: localhost:8080
# ssl_cert: "/path/to/public/certificate.txt"
# ssl_key: "/path/to/private/key.txt"
# - domain: sub.example.com # Domain with no SSL
# host: localhost:8081
- domain: localhost
host: localhost:8080
```
## How it works
This works as a proxy that redirects based on the Host header
![explaination.png](explaination.png)

14
conf.yml Normal file
View File

@ -0,0 +1,14 @@
http_host: localhost:80
https_host: localhost:443
sites:
# - domain: example.com # Domain with SSL
# host: localhost:8080
# ssl_cert: "/path/to/public/certificate.txt"
# ssl_key: "/path/to/private/key.txt"
# - domain: sub.example.com # Domain with no SSL
# host: localhost:8081
- domain: localhost
host: localhost:8080

BIN
explaination.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

590
src/http_server.rs Normal file
View File

@ -0,0 +1,590 @@
use http_rrs::ThreadPool;
use log::{debug, info, warn};
use openssl::ssl::{
NameType, SniError, SslAcceptor, SslAlert, SslContext, SslFiletype, SslMethod, SslRef,
SslStream,
};
use std::time::Duration;
use std::{
io::{Read, Write},
net::{IpAddr, Shutdown, TcpListener, TcpStream},
sync::Arc,
thread,
};
#[derive(Clone)]
pub struct SslCert {
pub cert_file: String,
pub key_file: String,
pub ctx_index: u8,
pub ctx: Option<SslContext>,
}
impl SslCert {
pub fn generate_ctx(cert_file: &str, key_file: &str) -> Option<SslContext> {
let mut ctx = match SslContext::builder(SslMethod::tls()) {
Ok(i) => i,
Err(_) => return None,
};
match ctx.set_private_key_file(&key_file, SslFiletype::PEM) {
Ok(i) => i,
Err(_) => return None,
};
match ctx.set_certificate_file(&cert_file, SslFiletype::PEM) {
Ok(i) => i,
Err(_) => return None,
};
match ctx.check_private_key() {
Ok(i) => i,
Err(_) => return None,
};
Some(ctx.build())
}
pub fn new(cert_file: &str, key_file: &str) -> Option<SslCert> {
let ctx = match Self::generate_ctx(cert_file, key_file) {
Some(i) => Some(i),
None => {
return None;
}
};
Some(SslCert {
ctx: ctx,
cert_file: cert_file.to_string(),
key_file: key_file.to_string(),
ctx_index: 0,
})
}
pub fn get_ctx(&mut self) -> Option<&SslContext> {
// self.ctx_index += 1;
// if self.ctx_index > 5 {
// self.ctx_index = 0;
// self.ctx = Self::generate_ctx(&self.cert_file, &self.key_file);
// }
self.ctx.as_ref()
}
}
#[derive(Clone)]
pub struct Site {
pub domain: String,
pub host: String,
pub ssl: Option<SslCert>,
}
impl Site {
fn connect(self) -> Result<TcpStream, String> {
match TcpStream::connect(self.host) {
Ok(i) => Ok(i),
Err(_) => Err("server not canacting".to_string()),
}
}
}
#[derive(Clone)]
pub struct SiteServer {
host: String,
sites: Arc<Vec<Site>>,
}
fn split_once<'a>(in_string: &'a str, separator: &str) -> Result<(&'a str, &'a str), String> {
let mut splitter = in_string.splitn(2, &separator);
let first = match splitter.next() {
Some(i) => i,
None => return Err("first split nat foined".to_string()),
};
let second = match splitter.next() {
Some(i) => i,
None => return Err("escond split nat foined".to_string()),
};
Ok((first, second))
}
fn get_site(sites: &Vec<Site>, domain: &str) -> Option<Site> {
for i in sites.iter() {
if i.domain == domain {
return Some(i.clone());
}
}
return None;
}
impl SiteServer {
pub fn new<'a>(host: String, sites: Arc<Vec<Site>>) -> Self {
SiteServer { host, sites }
}
fn get_site(self, domain: &str) -> Option<Site> {
return get_site(&self.sites, domain);
}
}
impl SiteServer {
pub fn start_http(self) {
thread::spawn(move || {
self.run_http();
});
}
pub fn run_http(self) {
let listener: TcpListener = match TcpListener::bind(&self.host) {
Ok(i) => i,
Err(_) => {
info!("Http server listener bind error");
return;
}
};
let pool = ThreadPool::new(10);
info!("HTTP server runned on {}", &self.host);
for stream in listener.incoming() {
let local_self = self.clone();
pool.execute(move || {
let mut stream = match stream {
Ok(i) => i,
Err(e) => {
warn!("{}", e);
return;
}
};
match stream.set_write_timeout(Some(Duration::from_secs(10))) {
Ok(i) => i,
Err(_) => {
return;
}
};
match stream.set_read_timeout(Some(Duration::from_secs(10))) {
Ok(i) => i,
Err(_) => {
return;
}
};
let addr = stream.peer_addr();
match local_self.accept_http(
&mut stream,
match addr {
Ok(v) => v,
Err(_) => {
return;
}
},
) {
Ok(v) => {
let (addr, method, host, page) = v;
info!("{} > {} http://{}{}", addr, method, host, page);
}
Err(_) => {}
}
});
}
}
pub fn run_ssl(self) {
let listener: TcpListener = match TcpListener::bind(&self.host) {
Ok(i) => i,
Err(_) => {
info!("Ssl server listener bind error");
return;
}
};
let mut cert = match SslAcceptor::mozilla_intermediate(SslMethod::tls()) {
Ok(v) => v,
Err(_) => {
info!("Ssl acceptor create error");
return;
}
};
let sites = self.clone().sites.clone();
cert.set_servername_callback(Box::new(
move |_ssl: &mut SslRef, _alert: &mut SslAlert| -> Result<(), SniError> {
debug!("hangs");
let servname = match _ssl.servername(NameType::HOST_NAME) {
Some(i) => i,
None => return Err(SniError::NOACK),
};
let cert = match get_site(&sites, servname) {
Some(i) => i,
None => return Err(SniError::NOACK),
};
match _ssl.set_ssl_context(
match match cert.ssl {
Some(i) => i,
None => return Err(SniError::NOACK),
}
.get_ctx()
{
Some(k) => k,
None => return Err(SniError::NOACK),
},
) {
Ok(i) => i,
Err(_) => return Err(SniError::NOACK),
};
return Ok(());
},
));
let cert = cert.build();
let pool = ThreadPool::new(10);
info!("HTTPS server runned on {}", &self.host);
for stream in listener.incoming() {
let local_self = self.clone();
let local_cert = cert.clone();
pool.execute(move || {
let stream = match stream {
Ok(i) => {
debug!("norm esy");
i
}
Err(_) => {
return;
}
};
match stream.set_write_timeout(Some(Duration::from_secs(10))) {
Ok(i) => i,
Err(_) => {
return;
}
};
match stream.set_read_timeout(Some(Duration::from_secs(10))) {
Ok(i) => i,
Err(_) => {
return;
}
};
let addr = stream.peer_addr();
let mut stream = match local_cert.accept(stream) {
Ok(st) => {
debug!("ssl esy");
st
}
Err(_) => {
return;
}
};
match local_self.accept_ssl(
&mut stream,
match addr {
Ok(v) => v,
Err(_) => {
return;
}
},
) {
Ok(v) => {
let (addr, method, host, page) = v;
info!("{} > {} https://{}{}", addr, method, host, page);
}
Err(_) => {}
}
});
}
}
pub fn accept_http(
self,
stream: &mut TcpStream,
peer_addr: std::net::SocketAddr,
) -> Result<(String, String, String, String), ()> {
let octets = match peer_addr.ip() {
IpAddr::V4(ip) => ip.octets(),
_ => [127, 0, 0, 1],
};
let dot: String = String::from(".");
let ip_str = String::from(
octets[0].to_string()
+ &dot
+ &octets[1].to_string()
+ &dot
+ &octets[2].to_string()
+ &dot
+ &octets[3].to_string(),
);
println!("{}", &ip_str);
let addition: String = ip_str.clone() + ":" + peer_addr.port().to_string().as_str() + "\n";
let mut reqst_data: Vec<u8> = vec![0; 4096];
match stream.read(&mut reqst_data) {
Ok(i) => i,
Err(_) => return Err(()),
};
let reqst = match String::from_utf8(reqst_data) {
Ok(v) => v,
Err(_) => {
return Err(());
}
};
let reqst = reqst.trim_matches(char::from(0));
let (head, body) = match split_once(&reqst, "\r\n\r\n") {
Ok(i) => i,
Err(_) => return Err(()),
};
let mut head_lines = head.split("\r\n");
let status = match head_lines.next() {
Some(i) => i,
None => return Err(()),
};
let status: Vec<&str> = status.split(" ").collect();
let mut host: &str = "honk";
let mut content_length: usize = 0;
for l in head_lines {
let (key, value) = match split_once(&l, ": ") {
Ok(i) => i,
Err(_) => return Err(()),
};
let key = key.to_lowercase().replace("-", "_");
if key == "host" {
host = &value;
}
if key == "content_length" {
content_length = match value.parse() {
Ok(i) => i,
Err(_) => {
return Err(());
}
};
}
}
let site = self.get_site(host);
if site.is_none() {
return Err(());
}
let site = match site {
Some(i) => i,
None => return Err(()),
};
let mut site_stream = match site.connect() {
Ok(i) => i,
Err(_) => return Err(()),
};
match site_stream.write((addition + reqst).as_bytes()) {
Ok(i) => i,
Err(_) => {
return Err(());
}
};
let body_len = body.len();
if body_len < content_length {
let mut body_data: Vec<u8> = vec![0; content_length - body_len];
match stream.read_exact(&mut body_data) {
Ok(i) => i,
Err(_) => return Err(()),
};
match site_stream.write_all(&body_data) {
Ok(i) => i,
Err(_) => return Err(()),
};
}
loop {
let mut buf: Vec<u8> = Vec::new();
match site_stream.read_to_end(&mut buf) {
Ok(i) => i,
Err(_) => return Err(()),
};
if buf.is_empty() {
break;
}
match stream.write_all(&buf) {
Ok(i) => i,
Err(_) => return Err(()),
};
}
let method = status[0];
let page = status[1];
match site_stream.shutdown(Shutdown::Both) {
Ok(i) => i,
Err(_) => {
return Err(());
}
};
Ok((
ip_str.clone(),
method.to_string().clone(),
host.to_string().clone(),
page.to_string().clone(),
))
}
pub fn accept_ssl(
self,
stream: &mut SslStream<TcpStream>,
peer_addr: std::net::SocketAddr,
) -> Result<(String, String, String, String), ()> {
let octets = match peer_addr.ip() {
IpAddr::V4(ip) => ip.octets(),
_ => [127, 0, 0, 1],
};
let dot: String = String::from(".");
let ip_str = String::from(
octets[0].to_string()
+ &dot
+ &octets[1].to_string()
+ &dot
+ &octets[2].to_string()
+ &dot
+ &octets[3].to_string(),
);
let addition: String = ip_str.clone() + ":" + peer_addr.port().to_string().as_str() + "\n";
let mut reqst_data: Vec<u8> = vec![0; 4096];
match stream.read(&mut reqst_data) {
Ok(i) => i,
Err(_) => return Err(()),
};
let reqst = match String::from_utf8(reqst_data) {
Ok(v) => v,
Err(_) => {
return Err(());
}
};
let reqst = reqst.trim_matches(char::from(0));
let (head, body) = match split_once(&reqst, "\r\n\r\n") {
Ok(i) => i,
Err(_) => return Err(()),
};
let mut head_lines = head.split("\r\n");
let status = match head_lines.next() {
Some(i) => i,
None => return Err(()),
};
let status: Vec<&str> = status.split(" ").collect();
let mut host: &str = "honk";
let mut content_length: usize = 0;
for l in head_lines {
let (key, value) = match split_once(&l, ": ") {
Ok(i) => i,
Err(_) => return Err(()),
};
let key = key.to_lowercase().replace("-", "_");
if key == "host" {
host = &value;
}
if key == "content_length" {
content_length = match value.parse() {
Ok(i) => i,
Err(_) => {
return Err(());
}
};
}
}
let site = self.get_site(host);
if site.is_none() {
return Err(());
}
let site = match site {
Some(i) => i,
None => return Err(()),
};
let mut site_stream = match site.connect() {
Ok(i) => i,
Err(_) => return Err(()),
};
match site_stream.write((addition + reqst).as_bytes()) {
Ok(i) => i,
Err(_) => {
return Err(());
}
};
let body_len = body.len();
if body_len < content_length {
let mut body_data: Vec<u8> = vec![0; content_length - body_len];
match stream.read_exact(&mut body_data) {
Ok(i) => i,
Err(_) => return Err(()),
};
match site_stream.write_all(&body_data) {
Ok(i) => i,
Err(_) => return Err(()),
};
}
loop {
let mut buf: Vec<u8> = Vec::new();
match site_stream.read_to_end(&mut buf) {
Ok(i) => i,
Err(_) => return Err(()),
};
if buf.is_empty() {
break;
}
match stream.write_all(&buf) {
Ok(i) => i,
Err(e) => {
info!("{}", e);
return Err(());
}
};
}
let method = status[0];
let page = status[1];
match site_stream.shutdown(Shutdown::Both) {
Ok(i) => i,
Err(_) => {
return Err(());
}
};
Ok((
ip_str.clone(),
method.to_string().clone(),
host.to_string().clone(),
page.to_string().clone(),
))
}
}

71
src/lib.rs Normal file
View File

@ -0,0 +1,71 @@
use std::{
sync::{mpsc, Arc, Mutex},
thread,
};
type Job = Box<dyn FnOnce() + Send + 'static>;
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
pub enum PoolCreationError {
InvalidSize,
}
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for _ in 0..size {
workers.push(Worker::new(Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn build(size: usize) -> Result<ThreadPool, PoolCreationError> {
if size <= 0 {
Err(PoolCreationError::InvalidSize)
} else {
Ok(Self::new(size))
}
}
pub fn join(self) {
for ele in self.workers.into_iter() {
ele.thread.join().unwrap();
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}
struct Worker {
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
while let Ok(job) = receiver.lock().unwrap().recv() {
job();
}
});
Worker { thread }
}
}

116
src/main.rs Normal file
View File

@ -0,0 +1,116 @@
pub mod http_server;
extern crate yaml_rust;
use http_server::*;
use std::sync::Arc;
use std::{fs, thread};
use yaml_rust::YamlLoader;
use log::LevelFilter;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Config, Root};
use log4rs::encode::pattern::PatternEncoder;
struct AppConfig {
sites: Vec<Site>,
http_host: String,
https_host: String,
}
impl AppConfig {
fn parse(filename: &str) -> Option<AppConfig> {
let Ok(file_content) = fs::read_to_string(filename) else {
return None;
};
let Ok(docs) = YamlLoader::load_from_str(file_content.as_str()) else {
return None;
};
let doc = docs.get(0)?;
let http_host = doc["http_host"].as_str()?.to_string();
let https_host = doc["https_host"].as_str()?.to_string();
let mut sites: Vec<Site> = Vec::new();
let sites_yaml = doc["sites"].as_vec()?;
for s in sites_yaml {
let mut cert: Option<SslCert> = None;
if !s["ssl_cert"].is_badvalue() && !s["ssl_cert"].is_null() {
cert = Some(
SslCert::new(
s["ssl_cert"].as_str().unwrap(),
s["ssl_key"].as_str().unwrap(),
)
.unwrap(),
);
}
let site = Site {
domain: s["domain"].as_str().unwrap().to_string(),
host: s["host"].as_str().unwrap().to_string(),
ssl: cert,
};
sites.push(site);
}
Some(AppConfig {
sites,
http_host,
https_host,
})
}
}
fn main() {
log4rs::init_config(
Config::builder()
.appender(
Appender::builder().build(
"logfile",
Box::new(
FileAppender::builder()
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S)} | {l} - {m}\n",
)))
.build("latest.log")
.unwrap(),
),
),
)
.appender(
Appender::builder().build(
"stdout",
Box::new(
ConsoleAppender::builder()
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S)} | {l} - {m}\n",
)))
.build(),
),
),
)
.build(
Root::builder()
.appender("logfile")
.appender("stdout")
.build(LevelFilter::Debug),
)
.unwrap(),
)
.unwrap();
let config = AppConfig::parse("conf.yml").unwrap();
let sites_arc = Arc::new(config.sites);
let sites = sites_arc.clone();
thread::spawn(move || {
SiteServer::new(config.http_host.to_string(), sites).run_http();
});
let sites = sites_arc.clone();
SiteServer::new(config.https_host.to_string(), sites).run_ssl();
}