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