Added Reload on SIGHUP
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -186,7 +186,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gemserv"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
|
||||
@@ -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
13
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.
|
||||
|
||||
20
src/cgi.rs
20
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())
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
98
src/main.rs
98
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<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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user