use std::fs::File; use std::io::{self, BufReader}; use std::path::{Path, PathBuf}; use structopt::StructOpt; use tokio_rustls::rustls::internal::pemfile::{certs, rsa_private_keys}; use tokio_rustls::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig}; #[derive(StructOpt, Debug)] struct Options { /// host to listen on #[structopt(short = "H", long, env = "HOST", default_value = "0.0.0.0")] host: String, /// port to listen on #[structopt(short = "p", long, env = "PORT", default_value = "1965")] port: u16, /// cert file #[structopt(short = "c", long = "cert", env = "CERT_FILE")] cert: PathBuf, /// key file #[structopt(short = "k", long = "key", env = "KEY_FILE")] key: PathBuf, /// server hostname #[structopt( long = "hostname", env = "SERVER_HOSTNAME", default_value = "maj.kahless.cetacean.club" )] hostname: String, } fn load_certs(path: &Path) -> io::Result> { certs(&mut BufReader::new(File::open(path)?)) .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert")) } fn load_keys(path: &Path) -> io::Result> { rsa_private_keys(&mut BufReader::new(File::open(path)?)) .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key")) } #[tokio::main] async fn main() -> Result<(), maj::server::Error> { pretty_env_logger::init(); let opts = Options::from_args(); let certs = load_certs(&opts.cert)?; let mut keys = load_keys(&opts.key)?; log::info!("{:?}", opts); let mut config = ServerConfig::new(NoClientAuth::new()); config .set_single_cert(certs, keys.remove(0)) .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; maj::server::serve( &Handler { hostname: opts.hostname, }, config, opts.host, opts.port, ) .await?; Ok(()) } struct Handler { hostname: String, } fn index() -> Result { let msg = include_bytes!("index.gmi"); Ok(maj::Response { status: maj::StatusCode::Success, meta: "text/gemini".to_string(), body: msg.to_vec(), }) } fn majc() -> Result { let msg = include_bytes!("majc.gmi"); Ok(maj::Response { status: maj::StatusCode::Success, meta: "text/gemini".to_string(), body: msg.to_vec(), }) } #[async_trait::async_trait] impl maj::server::Handler for Handler { async fn handle(&self, r: maj::server::Request) -> Result { if r.url.has_host() && r.url.host_str().unwrap().to_string() != self.hostname { return Ok(maj::Response { status: maj::StatusCode::ProxyRequestRefused, meta: "Wrong host".to_string(), body: vec![], }); } match r.url.path() { "" => Ok(maj::Response { status: maj::StatusCode::PermanentRedirect, meta: format!("gemini://{}/", self.hostname), body: vec![], }), "/" => index(), "/majc" => majc(), _ => Ok(maj::Response { status: maj::StatusCode::NotFound, meta: "Not found".to_string(), body: vec![], }), } } }