From 0f07e7c2beaf2374dc534d95dd63ea86b8e765dc Mon Sep 17 00:00:00 2001 From: int 80h Date: Wed, 29 Dec 2021 22:40:01 -0500 Subject: [PATCH] Added Reload on SIGHUP --- Cargo.lock | 2 +- Cargo.toml | 2 +- README | 13 ++++-- src/cgi.rs | 20 +++++----- src/con_handler.rs | 34 +++++++--------- src/config.rs | 14 ++++--- src/lib/server.rs | 92 +++++++++++++++++++++++-------------------- src/logger.rs | 5 ++- src/main.rs | 98 +++++++++++++++++++++++++++++++++------------- 9 files changed, 168 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a490354..549bd50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ dependencies = [ [[package]] name = "gemserv" -version = "0.6.2" +version = "0.6.3" dependencies = [ "futures-util", "log", diff --git a/Cargo.toml b/Cargo.toml index fe32553..83b9d7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gemserv" -version = "0.6.2" +version = "0.6.3" authors = ["int 80h "] edition = "2018" description = "A gemini server" diff --git a/README b/README index cca1d2f..20588f4 100644 --- a/README +++ b/README @@ -10,21 +10,26 @@ A gemini server written in rust. - Reverse proxy - Redirect - SCGI + - Reload config on SIGHUP ## Installation and running To run either run "cargo run /path/to/config" or if no configuration is specified it will look for "/usr/local/etc/gemserv.conf" -## Install from crates.io: +### Prebuilt binaries + +You can download prebuilt binaries for linux on the release page. + +### Install from crates.io: cargo install gemserv -## Install from docker +### Install from docker docker pull 080h/gemserv -## Build from source: +### Build from source: - Clone the repo - If you want to use all features run 'cargo build --release' or if you only @@ -32,7 +37,7 @@ docker pull 080h/gemserv - Modify the config.toml to your needs - Run './target/release/gemserv config.toml' -### Init scripts +## Init scripts In the init-scripts directory there's OpenRC(Courtesy of Tastytea) and systemd service files. diff --git a/src/cgi.rs b/src/cgi.rs index c911dcd..dcc6cf4 100644 --- a/src/cgi.rs +++ b/src/cgi.rs @@ -55,16 +55,16 @@ fn envs( if let Some(cert) = session.peer_certificates() { let cert = tokio_rustls::rustls::Certificate::as_ref(&cert[0]); if let Ok((_, x509)) = x509_parser::parse_x509_certificate(cert) { - let user = x509 - .subject() - .iter_common_name() - .next() - .and_then(|cn| cn.as_str().ok()) - .unwrap(); + let user = x509 + .subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()) + .unwrap(); - envs.insert("AUTH_TYPE".to_string(), "Certificate".to_string()); - envs.insert("REMOTE_USER".to_string(), user.to_string()); - envs.insert("TLS_CLIENT_HASH".to_string(), util::fingerhex(cert)); + envs.insert("AUTH_TYPE".to_string(), "Certificate".to_string()); + envs.insert("REMOTE_USER".to_string(), user.to_string()); + envs.insert("TLS_CLIENT_HASH".to_string(), util::fingerhex(cert)); } } @@ -111,7 +111,7 @@ pub async fn cgi( envs.insert("PATH_INFO".into(), path_info); if let Some(p) = path.parent() { - std::env::set_current_dir(p)?; + std::env::set_current_dir(p)?; } let cmd = Command::new(path.to_str().unwrap()) diff --git a/src/con_handler.rs b/src/con_handler.rs index a0f1cd8..6a11523 100644 --- a/src/con_handler.rs +++ b/src/con_handler.rs @@ -1,7 +1,7 @@ -use tokio::fs::{self, File}; -use tokio::io::{self, BufReader, AsyncWrite, AsyncBufReadExt}; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; +use tokio::fs::{self, File}; +use tokio::io::{self, AsyncBufReadExt, AsyncWrite, BufReader}; use url::Url; #[cfg(any(feature = "cgi", feature = "scgi"))] @@ -36,7 +36,8 @@ fn get_mime(path: &Path) -> String { async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> io::Result<()> { let fd = File::open(path).await?; let mut reader = BufReader::with_capacity(1024 * 1024, fd); - con.send_raw(format!("{} {}\r\n", Status::Success as u8, &meta).as_bytes()).await?; + con.send_raw(format!("{} {}\r\n", Status::Success as u8, &meta).as_bytes()) + .await?; loop { let len = { let buf = reader.fill_buf().await?; @@ -49,9 +50,7 @@ async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> i reader.consume(len); } - futures_util::future::poll_fn(|ctx| { - std::pin::Pin::new(&mut con.stream).poll_shutdown(ctx) - }) + futures_util::future::poll_fn(|ctx| std::pin::Pin::new(&mut con.stream).poll_shutdown(ctx)) .await?; Ok(()) } @@ -82,7 +81,7 @@ async fn gen_dir_list(path: PathBuf, u: &url::Url) -> Result { dirs.push(format!("=> {}/ {}/\r\n", ep, p.display())); } else { files.push(format!("=> {} {}\r\n", ep, p.display())); - } + } } dirs.sort(); @@ -166,9 +165,9 @@ pub async fn handle_connection(mut con: conn::Connection, url: url::Url) -> Resu _ => url.path().trim_end_matches('/'), }; if let Some(r) = re.get(u) { - logger::logger(con.peer_addr, Status::RedirectTemporary, url.as_str()); - con.send_status(Status::RedirectTemporary, Some(r)).await?; - return Ok(()); + logger::logger(con.peer_addr, Status::RedirectTemporary, url.as_str()); + con.send_status(Status::RedirectTemporary, Some(r)).await?; + return Ok(()); } } None => {} @@ -202,7 +201,7 @@ pub async fn handle_connection(mut con: conn::Connection, url: url::Url) -> Resu return Ok(()); } } - }, + } None => {} } @@ -214,8 +213,8 @@ pub async fn handle_connection(mut con: conn::Connection, url: url::Url) -> Resu _ => url.path().trim_end_matches('/'), }; if let Some(r) = sc.get(u) { - cgi::scgi(r.to_string(), url, con).await?; - return Ok(()); + cgi::scgi(r.to_string(), url, con).await?; + return Ok(()); } } None => {} @@ -319,22 +318,19 @@ pub async fn handle_connection(mut con: conn::Connection, url: url::Url) -> Resu } match fs::read_to_string(path).await { Ok(c) => { - con.send_body(Status::Success, Some(&mime), Some(c)) - .await?; + con.send_body(Status::Success, Some(&mime), Some(c)).await?; logger::logger(con.peer_addr, Status::Success, url.as_str()); } Err(e) => { println!("{}", e); - con.send_status(Status::NotFound, None) - .await?; + con.send_status(Status::NotFound, None).await?; logger::logger(con.peer_addr, Status::NotFound, url.as_str()); } } - } else { let dir = gen_dir_list(path, &url).await?; con.send_body(Status::Success, Some(&mime), Some(dir)) - .await?; + .await?; logger::logger(con.peer_addr, Status::Success, url.as_str()); } diff --git a/src/config.rs b/src/config.rs index c06aa5c..0f83cf3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,9 +3,9 @@ extern crate toml; use crate::lib::errors; use std::collections::HashMap; use std::env; -use std::path; use std::net; use std::net::ToSocketAddrs; +use std::path; use tokio::fs; use tokio::io; @@ -91,10 +91,14 @@ impl Config { } else if config.host.is_some() && config.port.is_some() { let mut addr: Vec = Vec::new(); addr.push( - format!("{}:{}", &config.host.to_owned().unwrap(), &config.port.unwrap()) - .to_socket_addrs()? - .next() - .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?, + format!( + "{}:{}", + &config.host.to_owned().unwrap(), + &config.port.unwrap() + ) + .to_socket_addrs()? + .next() + .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?, ); config.interface = Some(addr); return Ok(config); diff --git a/src/lib/server.rs b/src/lib/server.rs index 29545b3..855613b 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -1,6 +1,7 @@ #![allow(unreachable_code)] use tokio::io::AsyncReadExt; use tokio::net::TcpListener; +use tokio::sync::watch::Receiver; use tokio_rustls::server::TlsStream; use tokio_rustls::TlsAcceptor; @@ -71,65 +72,70 @@ impl Server { self, cmap: HashMap, handler: impl Handler + 'static + Copy, + shutdown: Receiver, ) -> Result { for listen in self.listener { let cmap = cmap.clone(); let listen = Arc::new(listen); let acceptor = Arc::new(self.acceptor.clone()); + let mut shutdown = shutdown.clone(); tokio::spawn(async move { loop { - let (stream, peer_addr) = listen.accept().await?; - let local_addr = stream.local_addr().unwrap(); - let acceptor = acceptor.clone(); - let cmap = cmap.clone(); - let mut handler = handler; + tokio::select! { + _ = shutdown.changed() => { + break + } + Ok((stream, peer_addr)) = listen.accept() => { + let local_addr = stream.local_addr().unwrap(); + let acceptor = acceptor.clone(); + let cmap = cmap.clone(); + let mut handler = handler; - tokio::spawn(async move { - let mut stream = match acceptor.accept(stream).await { - Ok(s) => s, - Err(e) => { - log::error!("Error: {}", e); - return Ok(()); + tokio::spawn(async move { + let mut stream = match acceptor.accept(stream).await { + Ok(s) => s, + Err(e) => { + log::error!("Error: {}", e); + return Ok(()); + } + }; + let (_, sni) = TlsStream::get_mut(&mut stream); + let sni = match sni.sni_hostname() { + Some(s) => s, + None => return Ok(()), + }; + + let srv = match cmap.get(sni) { + Some(h) => h, + None => return Ok(()) as io::Result<()>, } - }; - let (_, sni) = TlsStream::get_mut(&mut stream); - let sni = match sni.sni_hostname() { - Some(s) => s, - None => return Ok(()), - }; + .to_owned(); - let srv = match cmap.get(sni) { - Some(h) => h, - None => return Ok(()) as io::Result<()>, - } - .to_owned(); + let con = conn::Connection { + stream, + local_addr, + peer_addr, + srv, + }; + let (con, url) = match get_request(con).await { + Ok((c, u)) => (c, u), + Err(_) => return Ok(()) as io::Result<()>, + }; - let con = conn::Connection { - stream, - local_addr, - peer_addr, - srv, - }; - let (con, url) = match get_request(con).await { - Ok((c, u)) => (c, u), - Err(_) => return Ok(()) as io::Result<()>, - }; + match handler(con, url).await { + Ok(o) => o, + Err(_) => return Ok(()) as io::Result<()>, + } - match handler(con, url).await { - Ok(o) => o, - Err(_) => return Ok(()) as io::Result<()>, - } - - Ok(()) as io::Result<()> - }); + Ok(()) + }); + } + } } - Ok(()) as io::Result<()> + Ok(()) as Result }); } - tokio::signal::ctrl_c() - .await - .expect("failed to listen for event"); Ok(()) } } diff --git a/src/logger.rs b/src/logger.rs index 96cf8bb..e761b1a 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -17,7 +17,10 @@ pub fn init(loglev: &Option) -> errors::Result { } }, }; - simple_logger::SimpleLogger::new().with_level(loglev).with_utc_timestamps().init().unwrap(); + simple_logger::SimpleLogger::new() + .with_level(loglev) + .with_utc_timestamps() + .init()?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 92b1582..a3f7e28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,35 +17,77 @@ use lib::status; use lib::tls::{self, tls_acceptor_conf}; use lib::util; +use tokio::signal::unix; +use tokio::sync::watch; + +async fn run(mut recv: watch::Receiver) -> errors::Result { + loop { + let cfg = match config::Config::new().await { + Ok(c) => c, + Err(e) => { + eprintln!("Config error: {}", e); + return Ok(()); + } + }; + + // This will error because log init only wants to be called once. + // On reload it will allow going from higher to lower logging levels + // however trying to go from a lower lever to higher won't change. + if let Err(_) = logger::init(&cfg.log) {} + + let cmap = cfg.to_map(); + log::info!("Serving {} vhosts", cfg.server.len()); + + let mut addr: Vec = Vec::new(); + if let Some(i) = &cfg.interface { + addr.append(&mut i.to_owned()); + } + + let server = server::Server::bind(addr, tls_acceptor_conf, cfg).await?; + if let Err(e) = server + .serve( + cmap, + server::force_boxed(con_handler::handle_connection), + recv.clone(), + ) + .await + { + return Err(e); + }; + recv.changed().await?; + } +} + +async fn signal_select(send: watch::Sender) -> errors::Result { + let mut hangup = unix::signal(unix::SignalKind::hangup())?; + let mut sigterm = unix::signal(unix::SignalKind::terminate())?; + loop { + tokio::select! { + _ = tokio::signal::ctrl_c() => { + log::info!("Received ctrl-c shutting down!"); + send.send(false)?; + std::process::exit(0); + }, + _ = sigterm.recv() => { + log::info!("Received SIGTERM shutting down"); + send.send(false)?; + std::process::exit(0); + }, + _ = hangup.recv() => { + log::info!("Received SIGHUP reloading config."); + send.send(false)?; + } + } + } +} + #[tokio::main] async fn main() -> errors::Result { - let cfg = match config::Config::new().await { - Ok(c) => c, - Err(e) => { - eprintln!("Config error: {}", e); - return Ok(()); - } - }; - - logger::init(&cfg.log)?; - - let cmap = cfg.to_map(); - println!("Serving {} vhosts", cfg.server.len()); - - let mut addr: Vec = Vec::new(); - if let Some(i) = &cfg.interface { - addr.append(&mut i.to_owned()); - } - - let server = server::Server::bind(addr, tls_acceptor_conf, cfg).await?; - if let Err(e) = server - .serve( - cmap, - server::force_boxed(con_handler::handle_connection), - ) - .await - { - return Err(e); - }; + let (send, recv) = watch::channel(true); + tokio::spawn(async move { + run(recv).await?; + return Ok(()) as errors::Result; + }); + signal_select(send).await?; Ok(()) }