diff --git a/Cargo.toml b/Cargo.toml index 7f819ae..e4a0e75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ toml = "*" serde = "*" serde_derive = "*" url = "*" +chrono = "0.4" diff --git a/src/cgi.rs b/src/cgi.rs index 3511375..c41d744 100644 --- a/src/cgi.rs +++ b/src/cgi.rs @@ -1,7 +1,7 @@ +use std::collections::HashMap; use std::io::{self, BufReader}; use std::path::{Path, PathBuf}; use std::process::Command; -use std::collections::HashMap; use tokio::net::TcpStream; use tokio_rustls::server::TlsStream; use url::Url; @@ -9,21 +9,20 @@ use url::Url; use crate::config; use crate::status; use crate::util; +use crate::Connection; -pub async fn cgi( - stream: TlsStream, - path: PathBuf, - url: Url, - cfg: config::Config, -) -> Result<(), io::Error> { - +pub async fn cgi(con: Connection, path: PathBuf, url: Url) -> Result<(), io::Error> { let mut envs = HashMap::new(); envs.insert("GEMINI_URL", url.as_str()); envs.insert("SERVER_NAME", url.host_str().unwrap()); envs.insert("SCRIPT_NAME", path.file_name().unwrap().to_str().unwrap()); envs.insert("SERVER_PROTOCOL", "GEMINI"); - // envs.insert("SERVER_PORT", &(cfg.port as str)); - + let addr = con.peer_addr.ip().to_string(); + envs.insert("REMOTE_ADDR", &addr); + let port = con.peer_addr.port().to_string(); + envs.insert("REMOTE_PORT", &port); + // envs.insert("SERVER_PORT", con.cfg.port.clone().to_string()); + if let Some(q) = url.query() { envs.insert("QUERY_STRING", q); } @@ -34,10 +33,13 @@ pub async fn cgi( .output() .unwrap(); if !cmd.status.success() { - util::send(stream, status::Status::CGIError, "CGI Error!", None).await?; + util::send_status(con.stream, status::Status::CGIError, "CGI Error!").await?; return Ok(()); } let cmd = String::from_utf8(cmd.stdout).unwrap(); - util::send(stream, status::Status::Success, "text/gemini", Some(cmd)).await?; + // if cmd.starts_with("20") { + util::send_raw(con.stream, cmd).await?; + //util::send_body(stream, status::Status::Success, "text/gemini", Some(cmd)).await?; + // } return Ok(()); } diff --git a/src/main.rs b/src/main.rs index e2d0da9..cfde722 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use std::error::Error; use std::fs; use std::fs::File; use std::io::{self, BufReader}; +use std::net::SocketAddr; use std::net::ToSocketAddrs; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; @@ -24,6 +25,7 @@ use tokio_rustls::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig}; use tokio_rustls::server::TlsStream; use tokio_rustls::TlsAcceptor; use url::Url; +use chrono::{DateTime, Utc}; mod cgi; mod config; @@ -31,76 +33,70 @@ mod status; mod tls; mod util; +pub struct Connection { + stream: TlsStream, + peer_addr: SocketAddr, + hostname: String, + dir: String, + cgi: String, +} + fn get_content(path: PathBuf, u: url::Url) -> Result { let meta = fs::metadata(&path).expect("Unable to read metadata"); if meta.is_file() { return Ok(std::fs::read_to_string(path).expect("Unable to read file")); } - let mut list = String::from("# Directory Listing\n\n"); + let mut list = String::from("# Directory Listing\r\n\r\n"); + list.push_str(format!("Path: {}\r\n\r\n", u.path()).as_str()); // needs work for file in fs::read_dir(path)? { if let Ok(file) = file { let f = file.file_name().to_str().unwrap().to_owned(); let p = u.join(&f).unwrap().as_str().to_owned(); - println!("=>\t{} {}\r\n", p, f); - list.push_str(format!("=> {} {}\n", p, f).as_str()); + list.push_str(format!("=> {} {}\r\n", p, f).as_str()); } } return Ok(list); } -async fn handle_connection( - mut stream: TlsStream, - cfg: config::Config, -) -> Result<(), io::Error> { +async fn handle_connection(mut con: Connection) -> Result<(), io::Error> { + let now: DateTime = Utc::now(); + println!("{} New Connection: {}", now, con.peer_addr); let mut buffer = [0; 512]; - stream.read(&mut buffer).await?; + con.stream.read(&mut buffer).await?; let request = String::from_utf8_lossy(&buffer[..]).to_owned(); println!("Request: {}", request); let url = Url::parse(&request).unwrap(); + if Some(con.hostname.as_str()) != url.host_str() { + util::send_status(con.stream, status::Status::PermanentFailure, "Url doesn't match certificate!").await?; + return Ok(()); + } + if url.scheme() != "gemini" { - util::send( - stream, + util::send_status( + con.stream, status::Status::ProxyRequestRefused, "Not a gemini scheme!\r\n", - None, ) .await?; return Ok(()); } if url.path().to_string().contains("..") { - util::send( - stream, - status::Status::PermanentFailure, - "Not in path!", - None, - ) - .await?; + util::send_status(con.stream, status::Status::PermanentFailure, "Not in path!").await?; return Ok(()); } - let mut dir = String::new(); - let mut cgi = String::new(); - for server in &cfg.server { - if Some(server.hostname.as_str()) == url.host_str() { - dir = server.dir.to_string(); - if server.cgi.is_some() { - cgi = server.cgi.as_ref().unwrap().to_string(); - } - } - } - - let mut path = PathBuf::from(dir); + let mut path = PathBuf::from(&con.dir); if url.path() != "" || url.path() != "/" { path.push(url.path().trim_start_matches("/")); } if !path.exists() { - util::send(stream, status::Status::NotFound, "Not found!\r\n", None).await?; + util::send_status(con.stream, status::Status::NotFound, "Not found!\r\n").await?; return Ok(()); } @@ -109,11 +105,10 @@ async fn handle_connection( if meta.is_dir() { if !url.path().ends_with("/") { - util::send( - stream, + util::send_status( + con.stream, status::Status::RedirectPermanent, format!("{}/\r\n", url).as_str(), - None, ) .await?; return Ok(()); @@ -124,14 +119,14 @@ async fn handle_connection( } // add timeout - if cgi.trim_end_matches("/") == path.parent().unwrap().to_str().unwrap() { - cgi::cgi(stream, path, url, cfg).await?; + if con.cgi.trim_end_matches("/") == path.parent().unwrap().to_str().unwrap() { + cgi::cgi(con, path, url).await?; return Ok(()); } let content = get_content(path, url)?; - util::send( - stream, + util::send_body( + con.stream, status::Status::Success, "text/gemini", Some(content), @@ -143,6 +138,7 @@ async fn handle_connection( fn main() -> io::Result<()> { let cfg = config::Config::new("config.toml"); + println!("Serving {} vhosts", cfg.server.len()); let addr = format!("{}:{}", cfg.host, cfg.port); addr.to_socket_addrs()? @@ -167,9 +163,30 @@ fn main() -> io::Result<()> { let cfg = cfg.clone(); let fut = async move { - let stream = acceptor.accept(stream).await?; - handle_connection(stream, cfg).await?; - println!("Hello: {}", peer_addr); + let mut stream = acceptor.accept(stream).await?; + let (_, sni) = TlsStream::get_mut(&mut stream); + let sni = sni.get_sni_hostname(); + let mut dir = String::new(); + let mut cgi = String::new(); + let mut hostname = String::new(); + for server in &cfg.server { + if Some(server.hostname.as_str()) == sni { + hostname = sni.unwrap().to_string(); + dir = server.dir.to_string(); + if server.cgi.is_some() { + cgi = server.cgi.as_ref().unwrap().to_string(); + } + } + } + + let con = Connection { + stream, + peer_addr, + hostname, + dir, + cgi, + }; + handle_connection(con).await?; Ok(()) as io::Result<()> }; diff --git a/src/tls.rs b/src/tls.rs index 88f86fb..9245799 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -1,15 +1,14 @@ use std::collections::HashMap; -use std::error::Error; use std::fs::File; use std::io::{self, BufReader}; use std::sync::Arc; use rustls::sign::CertifiedKey; -use rustls::sign::{RSASigningKey, Signer, SigningKey}; +use rustls::sign::{RSASigningKey, SigningKey}; use rustls::ClientHello; -use rustls::{ResolvesServerCert, SignatureScheme}; +use rustls::ResolvesServerCert; use tokio_rustls::rustls::internal::pemfile::{certs, pkcs8_private_keys}; -use tokio_rustls::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig}; +use tokio_rustls::rustls::{Certificate, PrivateKey}; use crate::config; @@ -30,7 +29,7 @@ pub fn load_certs(path: &String) -> io::Result> { pub fn load_key(path: &String) -> PrivateKey { let keyfile = File::open(path).expect("cannot open private key file"); let mut reader = BufReader::new(keyfile); - let key = pkcs8_private_keys(&mut reader).expect("file contains invalid rsa private key"); + let key = pkcs8_private_keys(&mut reader).expect("file contains invalid pkcs8 private key"); return key[0].clone(); } @@ -46,9 +45,7 @@ impl CertResolver { for server in cfg.server.iter() { let key = load_key(&server.key); - // .chain_err(|| format!("Failed to load private key from {}", server.key))?; let certs = load_certs(&server.cert).unwrap(); - // .chain_err(|| format!("Failed to load certificate from {}", server.cert)); let signing_key = RSASigningKey::new(&key).unwrap(); let signing_key_boxed: Arc> = Arc::new(Box::new(signing_key)); @@ -58,8 +55,6 @@ impl CertResolver { ); } - println!("Successfully loaded {} TLS configurations", map.len()); - Ok(CertResolver { map }) } } diff --git a/src/util.rs b/src/util.rs index ca179df..5ea171f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,7 +5,16 @@ use tokio_rustls::server::TlsStream; use crate::status; -pub async fn send( +pub async fn send_status( + stream: TlsStream, + stat: status::Status, + meta: &str, +) -> Result<(), io::Error> { + send_body(stream, stat, meta, None).await?; + Ok(()) +} + +pub async fn send_body( mut stream: TlsStream, stat: status::Status, meta: &str, @@ -17,7 +26,12 @@ pub async fn send( if let Some(b) = body { s = format!("{}", b); } - stream.write_all(s.as_bytes()).await?; + send_raw(stream, s).await?; + Ok(()) +} + +pub async fn send_raw(mut stream: TlsStream, body: String) -> Result<(), io::Error> { + stream.write_all(body.as_bytes()).await?; stream.flush().await?; Ok(()) }