Changed host/port to interface and can now listen on multiple interface/ports
This commit is contained in:
parent
a022203074
commit
cecc6f5793
5
README
5
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:
|
||||
|
||||
|
12
config.toml
12
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.
|
||||
|
@ -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(),
|
||||
},
|
||||
);
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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) {
|
||||
|
45
src/main.rs
45
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<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 {
|
||||
|
Loading…
Reference in New Issue
Block a user