Added Reload on SIGHUP

This commit is contained in:
int 80h
2021-12-29 22:40:01 -05:00
parent f7429f3ad6
commit 0f07e7c2be
9 changed files with 168 additions and 112 deletions

2
Cargo.lock generated
View File

@@ -186,7 +186,7 @@ dependencies = [
[[package]]
name = "gemserv"
version = "0.6.2"
version = "0.6.3"
dependencies = [
"futures-util",
"log",

View File

@@ -1,6 +1,6 @@
[package]
name = "gemserv"
version = "0.6.2"
version = "0.6.3"
authors = ["int 80h <int@80h.dev>"]
edition = "2018"
description = "A gemini server"

13
README
View File

@@ -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.

View File

@@ -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())

View File

@@ -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<String> {
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());
}

View File

@@ -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<std::net::SocketAddr> = 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);

View File

@@ -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<String, config::ServerCfg>,
handler: impl Handler + 'static + Copy,
shutdown: Receiver<bool>,
) -> 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(())
}
}

View File

@@ -17,7 +17,10 @@ pub fn init(loglev: &Option<String>) -> 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(())
}

View File

@@ -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<bool>) -> 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<std::net::SocketAddr> = 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<bool>) -> 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<std::net::SocketAddr> = 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(())
}