mirror of https://git.sr.ht/~garritfra/taurus
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
3.6 KiB
124 lines
3.6 KiB
extern crate native_tls; |
|
extern crate url; |
|
|
|
mod config; |
|
mod error; |
|
mod gemini; |
|
mod io; |
|
mod logger; |
|
|
|
use error::{TaurusError, TaurusResult}; |
|
use gemini::{GeminiRequest, GeminiResponse}; |
|
use native_tls::{TlsAcceptor, TlsStream}; |
|
use std::{ |
|
io::Read, |
|
net::{TcpListener, TcpStream}, |
|
path, |
|
sync::Arc, |
|
thread, |
|
}; |
|
|
|
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; |
|
|
|
fn main() { |
|
if let Err(e) = run() { |
|
logger::error(e); |
|
std::process::exit(1); |
|
} |
|
} |
|
|
|
fn run() -> TaurusResult<()> { |
|
// CLI |
|
let matches = App::new(crate_name!()) |
|
.version(crate_version!()) |
|
.author(crate_authors!()) |
|
.about(crate_description!()) |
|
.arg( |
|
Arg::with_name("config") |
|
.long("config") |
|
.short("c") |
|
.help("Specify an alternative config file") |
|
.default_value("/etc/taurus/taurus.toml") |
|
.next_line_help(true) |
|
.value_name("FILE"), |
|
) |
|
.get_matches(); |
|
|
|
let config_path = matches.value_of("config").unwrap(); |
|
let config: config::Config = |
|
config::Config::load(config_path).map_err(TaurusError::InvalidConfig)?; |
|
|
|
// Defaults for configuration file |
|
let port = config.port.unwrap_or(1965); |
|
let cert_file = config |
|
.certificate_file |
|
.unwrap_or_else(|| "/etc/taurus/identity.pfx".to_owned()); |
|
let static_root = config |
|
.static_root |
|
.unwrap_or_else(|| "/var/www/gemini".to_owned()); |
|
|
|
// Read certificate |
|
let identity = crate::io::load_cert(&cert_file, &config.certificate_password)?; |
|
|
|
let address = format!("0.0.0.0:{}", port); |
|
let listener = TcpListener::bind(address).map_err(TaurusError::BindFailed)?; |
|
let acceptor = TlsAcceptor::new(identity).unwrap(); |
|
let acceptor = Arc::new(acceptor); |
|
|
|
logger::info(format!("Listening on port {}", port)); |
|
|
|
for stream in listener.incoming() { |
|
match stream { |
|
Ok(stream) => { |
|
let acceptor = acceptor.clone(); |
|
let static_root = static_root.clone(); |
|
|
|
thread::spawn(move || match acceptor.accept(stream) { |
|
Ok(stream) => { |
|
if let Err(e) = handle_client(stream, &static_root) { |
|
logger::error(format!("Can't handle client: {}", e)); |
|
} |
|
} |
|
Err(e) => { |
|
logger::error(format!("Can't handle stream: {}", e)); |
|
} |
|
}); |
|
} |
|
Err(err) => logger::error(err), |
|
} |
|
} |
|
|
|
Ok(()) |
|
} |
|
|
|
/// Send file as a response |
|
fn handle_client(mut stream: TlsStream<TcpStream>, static_root: &str) -> TaurusResult<usize> { |
|
let mut buffer = [0; 1024]; |
|
|
|
stream |
|
.read(&mut buffer) |
|
.map_err(TaurusError::StreamReadFailed)?; |
|
|
|
let raw_request = String::from_utf8(buffer.to_vec())?; |
|
|
|
let request = GeminiRequest::parse(&raw_request)?; |
|
let url_path = request.file_path(); |
|
let file_path = path::Path::new(url_path); |
|
|
|
if file_path.has_root() { |
|
// File starts with `/` (*nix) or `\\` (Windows), decline it |
|
GeminiResponse::not_found().send(stream) |
|
} else { |
|
let path = path::Path::new(&static_root) |
|
.join(&file_path) |
|
.as_path() |
|
.to_owned(); |
|
|
|
// Check if file/dir exists |
|
if path.exists() { |
|
GeminiResponse::from_file(&crate::io::resolve_path(&path))?.send(stream) |
|
} else { |
|
GeminiResponse::not_found().send(stream) |
|
} |
|
} |
|
}
|
|
|