From cecc6f5793983487edbff7a3cec42ac354a5b3d9 Mon Sep 17 00:00:00 2001 From: int 80h Date: Fri, 26 Nov 2021 10:41:17 -0500 Subject: [PATCH] Changed host/port to interface and can now listen on multiple interface/ports --- README | 5 +- config.toml | 12 +++-- src/config.rs | 53 +++++++++++++++++---- src/lib/conn.rs | 1 + src/lib/errors.rs | 2 + src/lib/server.rs | 114 +++++++++++++++++++++++++++------------------- src/logger.rs | 7 +-- src/main.rs | 45 +++++++++--------- 8 files changed, 153 insertions(+), 86 deletions(-) diff --git a/README b/README index fd52331..c6f1422 100644 --- a/README +++ b/README @@ -13,7 +13,10 @@ A gemini server written in rust. ## 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: diff --git a/config.toml b/config.toml index fb5207e..dda2dff 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,12 @@ -port = 1965 -# use "::" for ipv6 and ipv4 or "0.0.0.0" for ipv4 only -host = "::" +# interface accepts multiple interface/port combinations. However, due to the +# dual stack nature of linux if you specify "[::]:1965" linux will also listen +# 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 # 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. diff --git a/src/config.rs b/src/config.rs index 8b0ade2..97a7170 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,13 +1,18 @@ extern crate serde_derive; extern crate toml; +use tokio::fs; use std::collections::HashMap; -use std::path::Path; -use toml::de::Error; +use std::path; +use std::env; +use crate::lib::errors; + +type Result = std::result::Result>; #[derive(Debug, Deserialize, Clone)] pub struct Config { - pub port: u16, - pub host: String, + pub port: Option, + pub host: Option, + pub interface: Option>, pub log: Option, pub server: Vec, } @@ -38,18 +43,46 @@ pub struct Server { #[derive(Debug, Clone)] pub struct ServerCfg { - pub port: u16, +// pub port: u16, pub server: Server, } impl Config { - pub fn new(file: &Path) -> Result { - let fd = std::fs::read_to_string(file).unwrap(); + pub async fn new() -> Result { + let args: Vec = 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) { 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 { let mut map = HashMap::new(); @@ -57,7 +90,7 @@ impl Config { map.insert( srv.hostname.clone(), ServerCfg { - port: self.port.clone(), + // port: self.port.clone(), server: srv.clone(), }, ); diff --git a/src/lib/conn.rs b/src/lib/conn.rs index 72dbfe0..6798ec8 100644 --- a/src/lib/conn.rs +++ b/src/lib/conn.rs @@ -11,6 +11,7 @@ use crate::status::Status; pub struct Connection { pub stream: SslStream, + pub local_addr: SocketAddr, pub peer_addr: SocketAddr, pub srv: crate::config::ServerCfg, } diff --git a/src/lib/errors.rs b/src/lib/errors.rs index bb319aa..0a89ee8 100644 --- a/src/lib/errors.rs +++ b/src/lib/errors.rs @@ -1,6 +1,8 @@ use std::error::Error; use std::fmt; +pub type Result = std::result::Result>; + #[derive(Debug)] pub struct GemError(pub String); diff --git a/src/lib/server.rs b/src/lib/server.rs index 2ad2163..25df1bb 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -1,3 +1,4 @@ +#![allow(unreachable_code)] use tokio::net::TcpListener; use tokio::io::AsyncReadExt; use openssl::ssl::SslAcceptor; @@ -8,16 +9,14 @@ use url::Url; use std::io; use std::collections::HashMap; use std::future::Future; -//use std::sync::Arc; +use std::sync::Arc; use std::pin::Pin; use crate::config; use crate::conn; use crate::logger; use crate::status::Status; -use crate::errors; - -type Result = std::result::Result>; +use crate::errors::{GemError, Result}; pub trait Handler: FnMut(conn::Connection, url::Url) -> Pin + Send>> + Send + Sync + Copy {} impl Handler for T @@ -33,64 +32,87 @@ where } pub struct Server { - pub listener: TcpListener, + pub listener: Vec, pub acceptor: SslAcceptor, } impl Server { - pub async fn bind(addr: String, + pub async fn bind(addr: Vec, acceptor: fn(config::Config) -> std::result::Result, cfg: config::Config) -> io::Result { - Ok(Server { - listener: TcpListener::bind(addr).await?, - acceptor: acceptor(cfg)?, - }) + if addr.len() == 1 { + Ok(Server { + listener: vec![TcpListener::bind(addr[0].to_owned()).await?], + acceptor: acceptor(cfg)?, + }) + } else { + let mut listener: Vec = 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, default: String, handler: impl Handler + 'static + Copy) -> Result { - loop { - let (stream, peer_addr) = self.listener.accept().await?; - let acceptor = self.acceptor.clone(); + for listen in self.listener { let cmap = cmap.clone(); let default = default.clone(); - let mut handler = handler.clone(); - - let ssl = openssl::ssl::Ssl::new(acceptor.context()).unwrap(); - let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap(); - + let listen = Arc::new(listen); + let acceptor = Arc::new(self.acceptor.clone()); tokio::spawn(async move { - match Pin::new(&mut stream).accept().await { - 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(); + loop { + let (stream, peer_addr) = listen.accept().await?; + let local_addr = stream.local_addr().unwrap(); + let acceptor = acceptor.clone(); + let cmap = cmap.clone(); + let default = default.clone(); + let mut handler = handler.clone(); - let con = conn::Connection { stream, 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<()>, + let ssl = openssl::ssl::Ssl::new(acceptor.context()).unwrap(); + let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap(); + + tokio::spawn(async move { + match Pin::new(&mut stream).accept().await { + 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<()> }); } + 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() { logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); 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 => {} } - match url.port() { Some(p) => { - if p != con.srv.port { + if p != con.local_addr.port() { logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); con.send_status(Status::ProxyRequestRefused, None) .await.map_err(|e| e.to_string())?; @@ -153,11 +174,10 @@ pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, } None => {} } - if url.scheme() != "gemini" { logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); 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)) diff --git a/src/logger.rs b/src/logger.rs index c353d7a..ea08524 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,8 +1,9 @@ use crate::status; +use crate::lib::errors; use log::{info, warn}; use std::net::SocketAddr; -pub fn init(loglev: &Option) { +pub fn init(loglev: &Option) -> errors::Result { let loglev = match loglev { None => log::Level::Info, Some(l) => { @@ -11,13 +12,13 @@ pub fn init(loglev: &Option) { "warn" => log::Level::Warn, "info" => log::Level::Info, _ => { - eprintln!("Incorrect log level in config file."); - std::process::exit(1); + return Err(Box::new(errors::GemError("Incorrect log level in config file.".to_string()))); }, } }, }; simple_logger::init_with_level(loglev).unwrap(); + return Ok(()) } pub fn logger(addr: SocketAddr, stat: status::Status, req: &str) { diff --git a/src/main.rs b/src/main.rs index 202d533..8d86c8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ #[macro_use] extern crate serde_derive; -use std::env; use std::io; use std::net::ToSocketAddrs; -use std::path::Path; mod lib; mod cgi; @@ -20,22 +18,9 @@ use lib::tls; use lib::server; use lib::errors; -type Result = std::result::Result>; - #[tokio::main] -async fn main() -> Result { - let args: Vec = env::args().collect(); - 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) { +async fn main() -> errors::Result { + let cfg = match config::Config::new().await { Ok(c) => c, Err(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 default = &cfg.server[0].hostname; println!("Serving {} vhosts", cfg.server.len()); - let addr = format!("{}:{}", cfg.host, cfg.port); - addr.to_socket_addrs()? - .next() - .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?; + let mut addr: Vec = Vec::new(); + if cfg.host.is_some() && cfg.port.is_some() { + addr.push(format!("{}:{}", &cfg.host.to_owned().unwrap(), &cfg.port.unwrap()) + .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?; if let Err(e) = server.serve(cmap, default.to_string(), server::force_boxed(con_handler::handle_connection)).await {