diff --git a/README b/README index 390558b..ab02acb 100644 --- a/README +++ b/README @@ -1,3 +1,29 @@ -Gemserv is a gemini server written in rust. It supports vhosts and cgi. To run -use "cargo run /path/to/config" +# Gemserv +A gemini server written in rust. + +## Features + + - Vhosts + - CGI + - User directories + +## Installation and running + + - Clone the repo + - Run 'cargo build --release' + - Modify the config.toml to your needs + - Run './target/release/gemserv config.toml' + +## Create server key/cert pairs + + +## CGI + - GEMINI_URL + - SERVER_NAME + - SERVER_PROTOCOL + - SCRIPT_NAME + - REMOTE_ADDR + - REMOTE_HOST + - REMOTE_PORT + - QUERY_STRING diff --git a/config.toml b/config.toml index 63f87a0..b159fad 100644 --- a/config.toml +++ b/config.toml @@ -9,6 +9,8 @@ key = "/path/to/key" cert = "/path/to/cert" # cgi dir is optional cgi = "/path/to/cgi-bin/" +# usrdir is optional. it'll look in /home/~usr/public_gemini +usrdir = true [[server]] hostname = "example.net" diff --git a/src/cgi.rs b/src/cgi.rs index 3cdf550..c27740a 100644 --- a/src/cgi.rs +++ b/src/cgi.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::io::{self, BufReader}; use std::path::{Path, PathBuf}; use std::process::Command; +use std::os::unix::fs::PermissionsExt; use tokio::net::TcpStream; use tokio_rustls::server::TlsStream; use url::Url; diff --git a/src/config.rs b/src/config.rs index 53afdde..6ebc062 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,6 +19,7 @@ pub struct Server { pub key: String, pub cert: String, pub cgi: Option, + pub usrdir: Option, } impl Config { @@ -35,7 +36,8 @@ impl Config { dir: srv.dir.clone(), key: srv.key.clone(), cert: srv.cert.clone(), - cgi: srv.cgi.clone() + cgi: srv.cgi.clone(), + usrdir: srv.usrdir.clone(), }); } map diff --git a/src/conn.rs b/src/conn.rs index a613634..ff0df26 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -12,6 +12,7 @@ pub struct Connection { pub hostname: String, pub dir: String, pub cgi: String, + pub usrdir: bool, } impl Connection { diff --git a/src/main.rs b/src/main.rs index e4df54c..a6279f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,6 +82,10 @@ fn get_content(path: PathBuf, u: url::Url) -> Result { for file in fs::read_dir(&path)? { if let Ok(file) = file { let m = file.metadata()?; + let perm = m.permissions(); + if perm.mode() & 0o0444 != 0o0444 { + continue + } let file = file.path(); let p = file.strip_prefix(&path).unwrap(); if m.is_dir() { @@ -118,9 +122,23 @@ async fn handle_connection(mut con: conn::Connection) -> Result<(), io::Error> { return Ok(()); } - let mut path = PathBuf::from(&con.dir); - if url.path() != "" || url.path() != "/" { - path.push(url.path().trim_start_matches("/")); + let mut path = PathBuf::new(); + + if url.path().starts_with("/~") && con.usrdir == true{ + let usr = url.path().trim_start_matches("/~"); + let usr: Vec<&str> = usr.splitn(2, "/").collect(); + path.push("/home/"); + if usr.len() == 2 { + path.push(format!("{}/{}/{}", usr[0], "public_gemini", usr[1])); + } else { + path.push(format!("{}/{}/",usr[0], "public_gemini")); + } + } else { + path.push(&con.dir); + if url.path() != "" || url.path() != "/" { + path.push(url.path().trim_start_matches("/")); + } + } if !path.exists() { @@ -128,7 +146,8 @@ async fn handle_connection(mut con: conn::Connection) -> Result<(), io::Error> { return Ok(()); } - let meta = fs::metadata(&path).expect("Unable to read metadata"); + let mut meta = fs::metadata(&path).expect("Unable to read metadata"); + let mut perm = meta.permissions(); if meta.is_dir() { if !url.path().ends_with("/") { @@ -141,13 +160,27 @@ async fn handle_connection(mut con: conn::Connection) -> Result<(), io::Error> { } if path.join("index.gemini").exists() { path.push("index.gemini"); + meta = fs::metadata(&path).expect("Unable to read metadata"); + perm = meta.permissions(); } } // add timeout if con.cgi.trim_end_matches("/") == path.parent().unwrap().to_str().unwrap() { + if perm.mode() & 0o0111 == 0o0111 { cgi::cgi(con, path, url).await?; return Ok(()); + } else { + con.send_status( + status::Status::CGIError, "CGI Error!\r\n").await?; + return Ok(()); + } + } + + if perm.mode() & 0o0444 != 0o0444 { + con.send_status( + status::Status::NotFound, "Not Found!\r\n").await?; + return Ok(()); } let mime = get_mime(&path); @@ -209,6 +242,7 @@ fn main() -> io::Result<()> { let mut dir = String::new(); let mut cgi = String::new(); let mut hostname = String::new(); + let mut usrdir: bool = false; for server in &cfg.server { if Some(server.hostname.as_str()) == sni { @@ -217,6 +251,9 @@ fn main() -> io::Result<()> { if server.cgi.is_some() { cgi = server.cgi.as_ref().unwrap().to_string(); } + if server.usrdir.is_some() { + usrdir = server.usrdir.unwrap(); + } break; } } @@ -227,6 +264,7 @@ fn main() -> io::Result<()> { hostname, dir, cgi, + usrdir, }; handle_connection(con).await?;