Changed host/port to interface and can now listen on multiple interface/ports

This commit is contained in:
int 80h
2021-11-26 10:41:17 -05:00
parent a022203074
commit cecc6f5793
8 changed files with 153 additions and 86 deletions

5
README
View File

@@ -13,7 +13,10 @@ A gemini server written in rust.
## Installation and running ## Installation and running
It's recommended that you build with Libressl instead of Openssl It's recommended that you build with Libressl instead of Openssl.
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: ## Install from crates.io:

View File

@@ -1,6 +1,12 @@
port = 1965 # interface accepts multiple interface/port combinations. However, due to the
# use "::" for ipv6 and ipv4 or "0.0.0.0" for ipv4 only # dual stack nature of linux if you specify "[::]:1965" linux will also listen
host = "::" # on "0.0.0.0:1965" so if you manually specify both it will fail.
# interface = [ "0.0.0.0:1965, "[::]:1965" ]
interface = [ "[::]:1965" ]
# port and host have been deprecated in favor of interface but will still work
# for now.
# port = 1965
# host = "::"
# log is optional and server wide. It defaults to info if not set. Other levels # log is optional and server wide. It defaults to info if not set. Other levels
# are error, warn, and info. If error is set it will only show error. If warn # are error, warn, and info. If error is set it will only show error. If warn
# is set it will show error and warn. Info shows all three. # is set it will show error and warn. Info shows all three.

View File

@@ -1,13 +1,18 @@
extern crate serde_derive; extern crate serde_derive;
extern crate toml; extern crate toml;
use tokio::fs;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path;
use toml::de::Error; use std::env;
use crate::lib::errors;
type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct Config { pub struct Config {
pub port: u16, pub port: Option<u16>,
pub host: String, pub host: Option<String>,
pub interface: Option<Vec<String>>,
pub log: Option<String>, pub log: Option<String>,
pub server: Vec<Server>, pub server: Vec<Server>,
} }
@@ -38,18 +43,46 @@ pub struct Server {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ServerCfg { pub struct ServerCfg {
pub port: u16, // pub port: u16,
pub server: Server, pub server: Server,
} }
impl Config { impl Config {
pub fn new(file: &Path) -> Result<Config, Error> { pub async fn new() -> Result<Config> {
let fd = std::fs::read_to_string(file).unwrap(); let args: Vec<String> = env::args().collect();
let mut p = path::PathBuf::new();
if args.len() != 2 {
p.push("/usr/local/etc/gemserv.conf");
if !p.exists() {
return Err(Box::new(errors::GemError(
"Please run with the path to the config file. \
Or create the config as /usr/local/etc/gemserv.conf".to_string())));
}
} else {
p.push(&args[1]);
}
let fd = fs::read_to_string(p).await.unwrap();
let config: Config = match toml::from_str(&fd) { let config: Config = match toml::from_str(&fd) {
Ok(c) => c, Ok(c) => c,
Err(e) => return Err(e), Err(e) => return Err(Box::new(e)),
}; };
return Ok(config);
if config.host.is_some() || config.port.is_some() {
eprintln!("The host/port keys are depricated in favor \
of interface and may be removed in the future.");
}
if config.interface.is_some() && (config.host.is_some() || config.port.is_some()) {
return Err(Box::new(errors::GemError("You need to specify either host/port or interface".into())));
} else if config.interface.is_none() && config.host.is_none() && config.port.is_none() {
return Err(Box::new(errors::GemError("You need to specify either host/port or interface".into())));
} else if config.host.is_some() && config.port.is_some() {
return Ok(config);
} else if config.interface.is_some() {
return Ok(config);
}
return Err(Box::new(errors::GemError("You need to specify either host/port or interface".into())));
} }
pub fn to_map(&self) -> HashMap<String, ServerCfg> { pub fn to_map(&self) -> HashMap<String, ServerCfg> {
let mut map = HashMap::new(); let mut map = HashMap::new();
@@ -57,7 +90,7 @@ impl Config {
map.insert( map.insert(
srv.hostname.clone(), srv.hostname.clone(),
ServerCfg { ServerCfg {
port: self.port.clone(), // port: self.port.clone(),
server: srv.clone(), server: srv.clone(),
}, },
); );

View File

@@ -11,6 +11,7 @@ use crate::status::Status;
pub struct Connection { pub struct Connection {
pub stream: SslStream<TcpStream>, pub stream: SslStream<TcpStream>,
pub local_addr: SocketAddr,
pub peer_addr: SocketAddr, pub peer_addr: SocketAddr,
pub srv: crate::config::ServerCfg, pub srv: crate::config::ServerCfg,
} }

View File

@@ -1,6 +1,8 @@
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
pub type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[derive(Debug)] #[derive(Debug)]
pub struct GemError(pub String); pub struct GemError(pub String);

View File

@@ -1,3 +1,4 @@
#![allow(unreachable_code)]
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use openssl::ssl::SslAcceptor; use openssl::ssl::SslAcceptor;
@@ -8,16 +9,14 @@ use url::Url;
use std::io; use std::io;
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future; use std::future::Future;
//use std::sync::Arc; use std::sync::Arc;
use std::pin::Pin; use std::pin::Pin;
use crate::config; use crate::config;
use crate::conn; use crate::conn;
use crate::logger; use crate::logger;
use crate::status::Status; use crate::status::Status;
use crate::errors; use crate::errors::{GemError, Result};
type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
pub trait Handler: FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>> + Send + Sync + Copy {} pub trait Handler: FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>> + Send + Sync + Copy {}
impl<T> Handler for T impl<T> Handler for T
@@ -33,64 +32,87 @@ where
} }
pub struct Server { pub struct Server {
pub listener: TcpListener, pub listener: Vec<TcpListener>,
pub acceptor: SslAcceptor, pub acceptor: SslAcceptor,
} }
impl Server { impl Server {
pub async fn bind(addr: String, pub async fn bind(addr: Vec<std::net::SocketAddr>,
acceptor: fn(config::Config) -> std::result::Result<SslAcceptor, ErrorStack>, acceptor: fn(config::Config) -> std::result::Result<SslAcceptor, ErrorStack>,
cfg: config::Config) -> io::Result<Server> cfg: config::Config) -> io::Result<Server>
{ {
Ok(Server { if addr.len() == 1 {
listener: TcpListener::bind(addr).await?, Ok(Server {
acceptor: acceptor(cfg)?, listener: vec![TcpListener::bind(addr[0].to_owned()).await?],
}) acceptor: acceptor(cfg)?,
})
} else {
let mut listener: Vec<TcpListener> = Vec::new();
for a in addr {
listener.append(&mut vec![TcpListener::bind(a.to_owned()).await?]);
}
Ok(Server {
listener,
acceptor: acceptor(cfg)?,
})
}
} }
pub async fn serve(self, cmap: HashMap<String, config::ServerCfg>, default: String, pub async fn serve(self, cmap: HashMap<String, config::ServerCfg>, default: String,
handler: impl Handler + 'static + Copy) -> Result handler: impl Handler + 'static + Copy) -> Result
{ {
loop { for listen in self.listener {
let (stream, peer_addr) = self.listener.accept().await?;
let acceptor = self.acceptor.clone();
let cmap = cmap.clone(); let cmap = cmap.clone();
let default = default.clone(); let default = default.clone();
let mut handler = handler.clone(); let listen = Arc::new(listen);
let acceptor = Arc::new(self.acceptor.clone());
let ssl = openssl::ssl::Ssl::new(acceptor.context()).unwrap();
let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap();
tokio::spawn(async move { tokio::spawn(async move {
match Pin::new(&mut stream).accept().await { loop {
Ok(s) => s, let (stream, peer_addr) = listen.accept().await?;
Err(e) => { let local_addr = stream.local_addr().unwrap();
log::error!("Error: {}",e); let acceptor = acceptor.clone();
return Ok(()); let cmap = cmap.clone();
}, let default = default.clone();
}; let mut handler = handler.clone();
let srv = match stream.ssl().servername(NameType::HOST_NAME) {
Some(s) => match cmap.get(s) {
Some(ss) => ss,
None => cmap.get(&default).unwrap(),
},
None => cmap.get(&default).unwrap(),
}.to_owned();
let con = conn::Connection { stream, peer_addr, srv }; let ssl = openssl::ssl::Ssl::new(acceptor.context()).unwrap();
let (con, url) = match get_request(con).await { let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap();
Ok((c, u)) => (c, u),
Err(_) => return Ok(()) as io::Result<()>,
};
match handler(con, url).await { tokio::spawn(async move {
Ok(o) => o, match Pin::new(&mut stream).accept().await {
Err(_) => return Ok(()) as io::Result<()>, Ok(s) => s,
Err(e) => {
log::error!("Error: {}",e);
return Ok(());
},
};
let srv = match stream.ssl().servername(NameType::HOST_NAME) {
Some(s) => match cmap.get(s) {
Some(ss) => ss,
None => cmap.get(&default).unwrap(),
},
None => cmap.get(&default).unwrap(),
}.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<()>,
};
match handler(con, url).await {
Ok(o) => o,
Err(_) => return Ok(()) as io::Result<()>,
}
Ok(()) as io::Result<()>
});
} }
Ok(()) as io::Result<()> Ok(()) as io::Result<()>
}); });
} }
tokio::signal::ctrl_c().await.expect("failed to listen for event");
Ok(())
} }
} }
@@ -137,15 +159,14 @@ pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection,
if con.srv.server.hostname.as_str() != h.to_lowercase() { if con.srv.server.hostname.as_str() != h.to_lowercase() {
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?; con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
return Err(Box::new(errors::GemError("Wrong host".into()))); return Err(Box::new(GemError("Wrong host".into())));
} }
}, },
None => {} None => {}
} }
match url.port() { match url.port() {
Some(p) => { Some(p) => {
if p != con.srv.port { if p != con.local_addr.port() {
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
con.send_status(Status::ProxyRequestRefused, None) con.send_status(Status::ProxyRequestRefused, None)
.await.map_err(|e| e.to_string())?; .await.map_err(|e| e.to_string())?;
@@ -153,11 +174,10 @@ pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection,
} }
None => {} None => {}
} }
if url.scheme() != "gemini" { if url.scheme() != "gemini" {
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?; con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
return Err(Box::new(errors::GemError("scheme not gemini".into()))); return Err(Box::new(GemError("scheme not gemini".into())));
} }
return Ok((con, url)) return Ok((con, url))

View File

@@ -1,8 +1,9 @@
use crate::status; use crate::status;
use crate::lib::errors;
use log::{info, warn}; use log::{info, warn};
use std::net::SocketAddr; use std::net::SocketAddr;
pub fn init(loglev: &Option<String>) { pub fn init(loglev: &Option<String>) -> errors::Result {
let loglev = match loglev { let loglev = match loglev {
None => log::Level::Info, None => log::Level::Info,
Some(l) => { Some(l) => {
@@ -11,13 +12,13 @@ pub fn init(loglev: &Option<String>) {
"warn" => log::Level::Warn, "warn" => log::Level::Warn,
"info" => log::Level::Info, "info" => log::Level::Info,
_ => { _ => {
eprintln!("Incorrect log level in config file."); return Err(Box::new(errors::GemError("Incorrect log level in config file.".to_string())));
std::process::exit(1);
}, },
} }
}, },
}; };
simple_logger::init_with_level(loglev).unwrap(); simple_logger::init_with_level(loglev).unwrap();
return Ok(())
} }
pub fn logger(addr: SocketAddr, stat: status::Status, req: &str) { pub fn logger(addr: SocketAddr, stat: status::Status, req: &str) {

View File

@@ -1,10 +1,8 @@
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
use std::env;
use std::io; use std::io;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::path::Path;
mod lib; mod lib;
mod cgi; mod cgi;
@@ -20,22 +18,9 @@ use lib::tls;
use lib::server; use lib::server;
use lib::errors; use lib::errors;
type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[tokio::main] #[tokio::main]
async fn main() -> Result { async fn main() -> errors::Result {
let args: Vec<String> = env::args().collect(); let cfg = match config::Config::new().await {
if args.len() != 2 {
println!("Please run with the path to the config file.");
return Ok(());
}
let p = Path::new(&args[1]);
if !p.exists() {
println!("Config file doesn't exist");
return Ok(());
}
let cfg = match config::Config::new(&p) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
eprintln!("Config error: {}", e); eprintln!("Config error: {}", e);
@@ -43,16 +28,32 @@ async fn main() -> Result {
}, },
}; };
logger::init(&cfg.log); logger::init(&cfg.log)?;
let cmap = cfg.to_map(); let cmap = cfg.to_map();
let default = &cfg.server[0].hostname; let default = &cfg.server[0].hostname;
println!("Serving {} vhosts", cfg.server.len()); println!("Serving {} vhosts", cfg.server.len());
let addr = format!("{}:{}", cfg.host, cfg.port); let mut addr: Vec<std::net::SocketAddr> = Vec::new();
addr.to_socket_addrs()? if cfg.host.is_some() && cfg.port.is_some() {
.next() addr.push(format!("{}:{}", &cfg.host.to_owned().unwrap(), &cfg.port.unwrap())
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?; .to_socket_addrs()?.next()
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?);
} else {
match &cfg.interface {
Some(i) => {
for iface in i {
addr.push(iface
.to_socket_addrs()?.next()
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?);
}
},
None => {},
}
}
addr.sort_by(|a, b| a.port().cmp(&b.port()));
addr.dedup();
let server = server::Server::bind(addr, tls::acceptor_conf, cfg.clone()).await?; let server = server::Server::bind(addr, tls::acceptor_conf, cfg.clone()).await?;
if let Err(e) = server.serve(cmap, default.to_string(), server::force_boxed(con_handler::handle_connection)).await { if let Err(e) = server.serve(cmap, default.to_string(), server::force_boxed(con_handler::handle_connection)).await {