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
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:

View File

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

View File

@ -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<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
pub port: u16,
pub host: String,
pub port: Option<u16>,
pub host: Option<String>,
pub interface: Option<Vec<String>>,
pub log: Option<String>,
pub server: Vec<Server>,
}
@ -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<Config, Error> {
let fd = std::fs::read_to_string(file).unwrap();
pub async fn new() -> Result<Config> {
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) {
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> {
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(),
},
);

View File

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

View File

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

View File

@ -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<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
use crate::errors::{GemError, Result};
pub trait Handler: FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>> + Send + Sync + Copy {}
impl<T> Handler for T
@ -33,64 +32,87 @@ where
}
pub struct Server {
pub listener: TcpListener,
pub listener: Vec<TcpListener>,
pub acceptor: SslAcceptor,
}
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>,
cfg: config::Config) -> io::Result<Server>
{
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<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,
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))

View File

@ -1,8 +1,9 @@
use crate::status;
use crate::lib::errors;
use log::{info, warn};
use std::net::SocketAddr;
pub fn init(loglev: &Option<String>) {
pub fn init(loglev: &Option<String>) -> errors::Result {
let loglev = match loglev {
None => log::Level::Info,
Some(l) => {
@ -11,13 +12,13 @@ pub fn init(loglev: &Option<String>) {
"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) {

View File

@ -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<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[tokio::main]
async fn main() -> Result {
let args: Vec<String> = 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<std::net::SocketAddr> = 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 {