configuration options, error handling

This commit is contained in:
Jan Wolff 2020-05-17 10:29:32 +02:00
parent 87d61457bd
commit 4a9c6c11ac
4 changed files with 181 additions and 75 deletions

View file

@ -1,18 +1,68 @@
mod server; mod server;
use std::env; use std::env;
use std::path::Path;
fn read_config() -> server::ServerConfig { fn help() {
let mut config = server::ServerConfig::new( let version = match option_env!("CARGO_PKG_VERSION") {
"klockenschooster.de".to_string(), Some(v) => v,
"/home/jw/code/projects/sheldond/doc".to_string(), None => "",
); };
config.add_addr("127.0.0.1:1965".to_string());
config.add_addr("[::1]:1965".to_string()); println!("usage: sheldond {}", version);
return config; println!(" -h, --help\t\tdisplay this message");
println!(" -l, --listen\t\tadd a listening address (you can define multiple)");
println!(" -d, --default-host\tdefault hostname to listen for");
println!(" -g, --gem-root\tpath to the gemini root, aka the folder to serve files from");
}
fn parse_args() -> Option<server::ServerConfig> {
let mut has_addr = false;
let mut has_host = false;
let mut has_root = false;
let mut config = server::ServerConfig::new();
let mut args = env::args();
loop {
match args.next() {
Some(arg) => {
if arg == "-h" || arg == "--help" {
return None;
}
if arg == "-l" || arg == "--listen" {
let addr = args.next().unwrap();
config.add_addr(addr);
has_addr = true;
}
if arg == "-d" || arg == "--default-host" {
let host = args.next().unwrap();
config.set_default_host(host);
has_host = true;
}
if arg == "-g" || arg == "--gem-root" {
let gem_root = args.next().unwrap();
config.set_gem_root(gem_root);
has_root = true;
}
}
None => break,
}
}
if !has_addr || !has_host || !has_root {
return None;
}
Some(config)
} }
fn main() { fn main() {
let server = server::Server::new(&read_config()); let config = match parse_args() {
Some(config) => config,
None => {
help();
return;
}
};
let server = server::Server::new(&config);
server.serve(); server.serve();
} }

View file

@ -1,32 +1,89 @@
use crate::server::response; use crate::server::response;
use crate::server::ServerConfig; use crate::server::ServerConfig;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream}; use openssl::ssl::SslStream;
use std::fs::File; use std::fs::File;
use std::io::{copy, BufReader, BufWriter}; use std::io::{copy, BufReader, BufWriter};
use std::net::TcpStream; use std::net::TcpStream;
use std::path::Path; use std::path::Path;
use url::Url; use url::Url;
pub fn handle(config: &ServerConfig, url: Url, stream: &mut SslStream<TcpStream>) { fn send_header(stream: &mut SslStream<TcpStream>, header: &response::Header) {
if url.scheme() != "gemini" { match stream.ssl_write(&header.to_vec()) {
stream.ssl_write(&response::invalid_protocol().to_vec()); Ok(_s) => {
return; return;
} }
Err(_e) => {
return;
}
};
}
let rel_path = Path::new(url.path()).strip_prefix("/").unwrap(); pub fn handle_request(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
let path = config.www_root.join(rel_path); let mut buffer = [0; 1026];
match stream.ssl_read(&mut buffer) {
let file = match File::open(&path) { Ok(s) => {
Ok(file) => file, if s == 0 {
send_header(&mut stream, &response::bad_request());
return;
}
}
Err(_) => { Err(_) => {
stream.ssl_write(&response::not_found().to_vec()); send_header(&mut stream, &response::bad_request());
return; return;
} }
}; };
stream.ssl_write(&response::Header::new(response::Status::Success, "text/gemini").to_vec()); let request = match String::from_utf8(buffer.to_vec()) {
Ok(request) => request,
Err(_) => {
send_header(&mut stream, &response::bad_request());
return;
}
};
let location = match Url::parse(&request) {
Ok(url) => url,
Err(_) => config.default_host.join(&request).unwrap(),
};
handle_response(config, location, &mut stream);
}
fn handle_response(config: &ServerConfig, url: Url, mut stream: &mut SslStream<TcpStream>) {
if url.scheme() != "gemini" {
send_header(&mut stream, &response::permanent_failure());
return;
}
if url.host() != config.default_host.host() {
send_header(&mut stream, &response::proxy_request_refused());
return;
}
let rel_path = match Path::new(url.path()).strip_prefix("/") {
Ok(path) => path,
Err(_) => {
send_header(&mut stream, &response::bad_request());
return;
}
};
let path = config.gem_root.join(rel_path);
let file = match File::open(&path) {
Ok(file) => file,
Err(_) => {
send_header(&mut stream, &response::not_found());
return;
}
};
let header = response::Header::new(response::Status::Success, "text/gemini");
send_header(&mut stream, &header);
let mut buf_file = BufReader::new(file); let mut buf_file = BufReader::new(file);
let mut buf_stream = BufWriter::new(stream); let mut buf_stream = BufWriter::new(stream);
copy(&mut buf_file, &mut buf_stream); match copy(&mut buf_file, &mut buf_stream) {
Ok(_s) => {}
Err(_e) => {}
}
} }

View file

@ -1,7 +1,6 @@
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream}; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
use std::collections::HashMap; use std::net::{SocketAddr, TcpListener};
use std::net::{SocketAddr, TcpListener, TcpStream}; use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use std::vec::Vec; use std::vec::Vec;
@ -13,24 +12,38 @@ pub mod response;
#[derive(Clone)] #[derive(Clone)]
pub struct ServerConfig { pub struct ServerConfig {
default_host: Url, default_host: Url,
www_root: PathBuf, gem_root: PathBuf,
addrs: Vec<SocketAddr>, addrs: Vec<SocketAddr>,
} }
impl ServerConfig { impl ServerConfig {
pub fn new(default_host: String, www_root: String) -> ServerConfig { pub fn new() -> ServerConfig {
let mut url = Url::parse("gemini://default").unwrap();
url.set_host(Some(default_host.as_str()));
ServerConfig { ServerConfig {
default_host: url, default_host: Url::parse("gemini://localhost").unwrap(),
www_root: PathBuf::from(www_root), gem_root: PathBuf::from(""),
addrs: Vec::new(), addrs: Vec::new(),
} }
} }
pub fn set_default_host(&mut self, default_host: String) {
let mut url = Url::parse("gemini://default").unwrap();
match url.set_host(Some(default_host.as_str())) {
Ok(_) => {}
Err(e) => panic!(e),
};
self.default_host = url;
}
pub fn set_gem_root(&mut self, gem_root: String) {
self.gem_root = PathBuf::from(gem_root);
}
pub fn add_addr(&mut self, addr: String) { pub fn add_addr(&mut self, addr: String) {
self.addrs.push(addr.parse().unwrap()); self.addrs.push(match addr.parse() {
Ok(addr) => addr,
Err(e) => panic!(e),
});
} }
} }
@ -42,11 +55,21 @@ pub struct Server {
impl Server { impl Server {
pub fn new(config: &ServerConfig) -> Server { pub fn new(config: &ServerConfig) -> Server {
Server { Server {
acceptor: build_acceptor(), acceptor: Server::build_acceptor(),
config: config.clone(), config: config.clone(),
} }
} }
fn build_acceptor() -> std::sync::Arc<SslAcceptor> {
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
acceptor
.set_private_key_file("doc/key.pem", SslFiletype::PEM)
.unwrap();
acceptor.set_certificate_chain_file("doc/cert.pem").unwrap();
acceptor.check_private_key().unwrap();
return Arc::new(acceptor.build());
}
pub fn serve(&self) { pub fn serve(&self) {
let listener = TcpListener::bind(&self.config.addrs[..]).unwrap(); let listener = TcpListener::bind(&self.config.addrs[..]).unwrap();
@ -57,34 +80,11 @@ impl Server {
let config = self.config.clone(); let config = self.config.clone();
thread::spawn(move || { thread::spawn(move || {
let stream = acceptor.accept(stream).unwrap(); let stream = acceptor.accept(stream).unwrap();
handle_client(&config, stream); handler::handle_request(&config, stream);
}); });
} }
Err(e) => { /* connection failed */ } Err(_) => { /* connection failed */ }
} }
} }
} }
} }
fn handle_client(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
let mut buffer = [0; 1026];
stream.ssl_read(&mut buffer);
let request = String::from_utf8(buffer.to_vec()).unwrap();
let location = match Url::parse(&request) {
Ok(url) => url,
Err(e) => config.default_host.join(&request).unwrap(),
};
handler::handle(config, location, &mut stream);
}
fn build_acceptor() -> std::sync::Arc<SslAcceptor> {
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
acceptor
.set_private_key_file("doc/key.pem", SslFiletype::PEM)
.unwrap();
acceptor.set_certificate_chain_file("doc/cert.pem").unwrap();
acceptor.check_private_key().unwrap();
return Arc::new(acceptor.build());
}

View file

@ -2,12 +2,11 @@ use std::vec::Vec;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum Status { pub enum Status {
Input = 10,
Success = 20, Success = 20,
Redirect = 30,
TemporaryFailure = 40,
PermanentFailure = 50, PermanentFailure = 50,
ClientCertificateRequired = 60, NotFound = 51,
ProxyRequestRefused = 53,
BadRequest = 59,
} }
pub struct Header { pub struct Header {
@ -32,18 +31,18 @@ impl Header {
} }
} }
pub fn invalid_protocol() -> Header { pub fn permanent_failure() -> Header {
Header::new(Status::PermanentFailure, "this protocol is not supported") Header::new(Status::PermanentFailure, "permanent failure")
}
pub fn not_understood() -> Header {
Header::new(Status::PermanentFailure, "request not understood")
} }
pub fn not_found() -> Header { pub fn not_found() -> Header {
Header::new(Status::PermanentFailure, "resource not found") Header::new(Status::NotFound, "not found")
} }
pub fn internal_error() -> Header { pub fn proxy_request_refused() -> Header {
Header::new(Status::PermanentFailure, "internal server error") Header::new(Status::ProxyRequestRefused, "proxy request refused")
}
pub fn bad_request() -> Header {
Header::new(Status::BadRequest, "bad request")
} }