diff --git a/src/main.rs b/src/main.rs index 9d1fdcf..a381e28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,68 @@ mod server; use std::env; -use std::path::Path; -fn read_config() -> server::ServerConfig { - let mut config = server::ServerConfig::new( - "klockenschooster.de".to_string(), - "/home/jw/code/projects/sheldond/doc".to_string(), - ); - config.add_addr("127.0.0.1:1965".to_string()); - config.add_addr("[::1]:1965".to_string()); - return config; +fn help() { + let version = match option_env!("CARGO_PKG_VERSION") { + Some(v) => v, + None => "", + }; + + println!("usage: sheldond {}", version); + 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 { + 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() { - 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(); } diff --git a/src/server/handler.rs b/src/server/handler.rs index 4a593e8..d27d292 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -1,32 +1,89 @@ use crate::server::response; use crate::server::ServerConfig; -use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream}; +use openssl::ssl::SslStream; use std::fs::File; use std::io::{copy, BufReader, BufWriter}; use std::net::TcpStream; use std::path::Path; use url::Url; -pub fn handle(config: &ServerConfig, url: Url, stream: &mut SslStream) { - if url.scheme() != "gemini" { - stream.ssl_write(&response::invalid_protocol().to_vec()); - return; - } +fn send_header(stream: &mut SslStream, header: &response::Header) { + match stream.ssl_write(&header.to_vec()) { + Ok(_s) => { + return; + } + Err(_e) => { + return; + } + }; +} - let rel_path = Path::new(url.path()).strip_prefix("/").unwrap(); - let path = config.www_root.join(rel_path); - - let file = match File::open(&path) { - Ok(file) => file, +pub fn handle_request(config: &ServerConfig, mut stream: SslStream) { + let mut buffer = [0; 1026]; + match stream.ssl_read(&mut buffer) { + Ok(s) => { + if s == 0 { + send_header(&mut stream, &response::bad_request()); + return; + } + } Err(_) => { - stream.ssl_write(&response::not_found().to_vec()); + send_header(&mut stream, &response::bad_request()); 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) { + 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_stream = BufWriter::new(stream); - copy(&mut buf_file, &mut buf_stream); + match copy(&mut buf_file, &mut buf_stream) { + Ok(_s) => {} + Err(_e) => {} + } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 545fbeb..696a3a5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,7 +1,6 @@ -use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream}; -use std::collections::HashMap; -use std::net::{SocketAddr, TcpListener, TcpStream}; -use std::path::{Path, PathBuf}; +use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; +use std::net::{SocketAddr, TcpListener}; +use std::path::PathBuf; use std::sync::Arc; use std::thread; use std::vec::Vec; @@ -13,24 +12,38 @@ pub mod response; #[derive(Clone)] pub struct ServerConfig { default_host: Url, - www_root: PathBuf, + gem_root: PathBuf, addrs: Vec, } impl ServerConfig { - pub fn new(default_host: String, www_root: String) -> ServerConfig { - let mut url = Url::parse("gemini://default").unwrap(); - url.set_host(Some(default_host.as_str())); - + pub fn new() -> ServerConfig { ServerConfig { - default_host: url, - www_root: PathBuf::from(www_root), + default_host: Url::parse("gemini://localhost").unwrap(), + gem_root: PathBuf::from(""), 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) { - 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 { pub fn new(config: &ServerConfig) -> Server { Server { - acceptor: build_acceptor(), + acceptor: Server::build_acceptor(), config: config.clone(), } } + fn build_acceptor() -> std::sync::Arc { + 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) { let listener = TcpListener::bind(&self.config.addrs[..]).unwrap(); @@ -57,34 +80,11 @@ impl Server { let config = self.config.clone(); thread::spawn(move || { 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) { - 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 { - 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()); -} diff --git a/src/server/response.rs b/src/server/response.rs index 8b087ba..913d881 100644 --- a/src/server/response.rs +++ b/src/server/response.rs @@ -2,12 +2,11 @@ use std::vec::Vec; #[derive(Copy, Clone)] pub enum Status { - Input = 10, Success = 20, - Redirect = 30, - TemporaryFailure = 40, PermanentFailure = 50, - ClientCertificateRequired = 60, + NotFound = 51, + ProxyRequestRefused = 53, + BadRequest = 59, } pub struct Header { @@ -32,18 +31,18 @@ impl Header { } } -pub fn invalid_protocol() -> Header { - Header::new(Status::PermanentFailure, "this protocol is not supported") -} - -pub fn not_understood() -> Header { - Header::new(Status::PermanentFailure, "request not understood") +pub fn permanent_failure() -> Header { + Header::new(Status::PermanentFailure, "permanent failure") } pub fn not_found() -> Header { - Header::new(Status::PermanentFailure, "resource not found") + Header::new(Status::NotFound, "not found") } -pub fn internal_error() -> Header { - Header::new(Status::PermanentFailure, "internal server error") +pub fn proxy_request_refused() -> Header { + Header::new(Status::ProxyRequestRefused, "proxy request refused") +} + +pub fn bad_request() -> Header { + Header::new(Status::BadRequest, "bad request") }