diff --git a/Cargo.lock b/Cargo.lock index dde18dc..f2bf774 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,12 +23,33 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + [[package]] name = "bytes" version = "1.1.0" @@ -47,6 +68,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "colored" version = "1.9.3" @@ -59,19 +90,52 @@ dependencies = [ ] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "cpufeatures" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ - "foreign-types-shared", + "libc", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "data-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "der-oid-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436" +dependencies = [ + "num-bigint", + "num-traits", + "syn", +] + +[[package]] +name = "der-parser" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9807efb310ce4ea172924f3a69d82f9fd6c9c3a19336344591153e665b31c43e" +dependencies = [ + "der-oid-macro", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] [[package]] name = "form_urlencoded" @@ -122,20 +186,32 @@ dependencies = [ [[package]] name = "gemserv" -version = "0.5.0" +version = "0.6.0" dependencies = [ "futures-util", "log", "mime", "new_mime_guess", - "openssl", + "rustls-pemfile", "serde", "serde_derive", + "sha2", "simple_logger", "tokio", - "tokio-openssl", + "tokio-rustls", "toml", "url", + "x509-parser", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", ] [[package]] @@ -179,6 +255,15 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -227,6 +312,12 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "0.7.14" @@ -261,6 +352,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -270,6 +372,36 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -280,6 +412,15 @@ dependencies = [ "libc", ] +[[package]] +name = "oid-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe554cb2393bc784fd678c82c84cc0599c31ceadc7f03a594911f822cb8d1815" +dependencies = [ + "der-parser", +] + [[package]] name = "once_cell" version = "1.8.0" @@ -287,31 +428,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] -name = "openssl" -version = "0.10.38" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-sys" -version = "0.9.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parking_lot" @@ -356,12 +476,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" - [[package]] name = "proc-macro2" version = "1.0.32" @@ -389,12 +503,67 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rusticata-macros" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c52377bb2288aa522a0c8208947fada1e0c76397f108cc08f57efe6077b50d" +dependencies = [ + "nom", +] + +[[package]] +name = "rustls" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "serde" version = "1.0.130" @@ -412,6 +581,19 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -446,6 +628,12 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "syn" version = "1.0.82" @@ -457,6 +645,26 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.5" @@ -521,15 +729,14 @@ dependencies = [ ] [[package]] -name = "tokio-openssl" -version = "0.6.3" +name = "tokio-rustls" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +checksum = "4baa378e417d780beff82bf54ceb0d195193ea6a00c14e22359e7f39456b5689" dependencies = [ - "futures-util", - "openssl", - "openssl-sys", + "rustls", "tokio", + "webpki", ] [[package]] @@ -541,6 +748,12 @@ dependencies = [ "serde", ] +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + [[package]] name = "unicase" version = "2.6.0" @@ -571,6 +784,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.2.2" @@ -583,18 +802,86 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "winapi" version = "0.3.9" @@ -616,3 +903,20 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "x509-parser" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" +dependencies = [ + "base64", + "chrono", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", +] diff --git a/Cargo.toml b/Cargo.toml index 5d73c96..297f399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gemserv" -version = "0.5.0" +version = "0.6.0" authors = ["int 80h "] edition = "2018" description = "A gemini server" @@ -15,8 +15,7 @@ license = "MIT" [dependencies] tokio = { version = "1.14", features = [ "full" ] } -openssl = "0.10.38" -tokio-openssl = "0.6.3" +rustls-pemfile = "0.2.1" futures-util = "0.3.18" toml = "0.5.8" serde = "1.0.130" @@ -26,6 +25,13 @@ new_mime_guess = "3.0.2" mime = "0.3.16" log = "0.4.14" simple_logger = "1.15" +sha2 = "0.9.8" +x509-parser = "0.12" + +[dependencies.tokio-rustls] +version = "0.23.1" +optional = false +features = ["dangerous_configuration"] [features] default = [ "cgi", "scgi", "proxy" ] diff --git a/README b/README index c6f1422..417cd35 100644 --- a/README +++ b/README @@ -13,8 +13,6 @@ A gemini server written in rust. ## Installation and running -It's recommended that you build with Libressl instead of Openssl. - To run either run "cargo run /path/to/config" or if no configuration is specified it will look for "/usr/local/etc/gemserv.conf" diff --git a/src/cgi.rs b/src/cgi.rs index 1977f31..15407eb 100644 --- a/src/cgi.rs +++ b/src/cgi.rs @@ -3,10 +3,12 @@ use std::collections::HashMap; use std::io; use std::net::SocketAddr; -#[cfg(feature = "cgi")] -use tokio::process::Command; #[cfg(feature = "cgi")] use std::path::PathBuf; +#[cfg(feature = "cgi")] +use tokio::process::Command; + +use tokio_rustls::rustls::ServerConnection; #[cfg(feature = "scgi")] use std::net::ToSocketAddrs; @@ -22,39 +24,51 @@ use crate::status::Status; use crate::util; #[cfg(any(feature = "cgi", feature = "scgi"))] -fn envs(peer_addr: SocketAddr, x509: Option, srv: &config::ServerCfg, url: &url::Url) -> HashMap { +fn envs( + peer_addr: SocketAddr, + session: &ServerConnection, + srv: &config::ServerCfg, + url: &url::Url, +) -> HashMap { let mut envs = HashMap::new(); envs.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string()); envs.insert("GEMINI_URL".to_string(), url.to_string()); - envs.insert("SERVER_NAME".to_string(), url.host_str().unwrap().to_string()); + envs.insert( + "SERVER_NAME".to_string(), + url.host_str().unwrap().to_string(), + ); envs.insert("SERVER_PROTOCOL".to_string(), "GEMINI".to_string()); let addr = peer_addr.ip().to_string(); envs.insert("REMOTE_ADDR".to_string(), addr.clone()); envs.insert("REMOTE_HOST".to_string(), addr); let port = peer_addr.port().to_string(); envs.insert("REMOTE_PORT".to_string(), port); - envs.insert("SERVER_SOFTWARE".to_string(), env!("CARGO_PKG_NAME").to_string()); + envs.insert( + "SERVER_SOFTWARE".to_string(), + env!("CARGO_PKG_NAME").to_string(), + ); if let Some(q) = url.query() { envs.insert("QUERY_STRING".to_string(), q.to_string()); } - match x509 { - Some(x) => { - envs.insert("AUTH_TYPE".to_string(), "Certificate".to_string()); + if let Some(cert) = session.peer_certificates() { + let cert = tokio_rustls::rustls::Certificate::as_ref(&cert[0]); + match x509_parser::parse_x509_certificate(cert) { + Ok((_, x509)) => { + let user = x509 + .subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()) + .unwrap(); - let cn = x.subject_name().entries_by_nid(openssl::nid::Nid::COMMONNAME); - for c in cn { - let cd = match c.data().as_utf8() { - Ok(n) => n.to_string(), - _ => "".to_string(), - }; - envs.insert("REMOTE_USER".to_string(), cd); + envs.insert("AUTH_TYPE".to_string(), "Certificate".to_string()); + envs.insert("REMOTE_USER".to_string(), user.to_string()); + envs.insert("TLS_CLIENT_HASH".to_string(), util::fingerhex(&cert)); } - - envs.insert("TLS_CLIENT_HASH".to_string(), util::fingerhex(&x)); - }, - None => {}, + Err(_) => {} + } } match &srv.server.cgienv { @@ -73,15 +87,15 @@ fn check(byt: u8, peer_addr: SocketAddr, u: &url::Url) -> bool { match byt { 49 => { logger::logger(peer_addr, Status::Input, u.as_str()); - }, + } 50 => { logger::logger(peer_addr, Status::Success, u.as_str()); - }, + } 51..=54 => {} _ => { logger::logger(peer_addr, Status::CGIError, u.as_str()); return false; - }, + } } true } @@ -92,19 +106,18 @@ pub async fn cgi( path: PathBuf, url: &url::Url, script_name: String, - path_info: String + path_info: String, ) -> Result<(), io::Error> { - - let x509 = con.stream.ssl().peer_certificate(); - let mut envs = envs(con.peer_addr, x509, &con.srv, &url); + let (_, session) = con.stream.get_ref(); + let mut envs = envs(con.peer_addr, session, &con.srv, &url); envs.insert("SCRIPT_NAME".into(), script_name); envs.insert("PATH_INFO".into(), path_info); match path.parent() { Some(p) => { std::env::set_current_dir(p)?; - }, - None => {}, + } + None => {} } let cmd = Command::new(path.to_str().unwrap()) @@ -113,22 +126,20 @@ pub async fn cgi( .output(); let cmd = match tokio::time::timeout(tokio::time::Duration::from_secs(5), cmd).await { - Ok(c) => { - match c { - Ok(cc) => cc, + Ok(c) => match c { + Ok(cc) => cc, - Err(_) => { - logger::logger(con.peer_addr, Status::CGIError, url.as_str()); - con.send_status(Status::CGIError, None).await?; - return Ok(()); - }, + Err(_) => { + logger::logger(con.peer_addr, Status::CGIError, url.as_str()); + con.send_status(Status::CGIError, None).await?; + return Ok(()); } }, Err(_) => { logger::logger(con.peer_addr, Status::CGIError, url.as_str()); con.send_status(Status::CGIError, None).await?; return Ok(()); - }, + } }; if !cmd.status.success() { @@ -161,11 +172,15 @@ pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection) -> Resul return Ok(()); } }; - let x509 = con.stream.ssl().peer_certificate(); - let envs = envs(con.peer_addr, x509, &con.srv, &u); + let (_, session) = con.stream.get_ref(); + let envs = envs(con.peer_addr, session, &con.srv, &u); let len = 0usize; - let mut byt = String::from(format!("CONTENT_LENGTH\x00{}\x00SCGI\x001\x00 - RQUEST_METHOD\x00POST\x00REQUEST_URI\x00{}\x00", len, u.path())); + let mut byt = String::from(format!( + "CONTENT_LENGTH\x00{}\x00SCGI\x001\x00 + RQUEST_METHOD\x00POST\x00REQUEST_URI\x00{}\x00", + len, + u.path() + )); for (k, v) in envs.iter() { byt.push_str(&format!("{}\x00{}\x00", k, v)); } @@ -176,7 +191,11 @@ pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection) -> Resul let mut buf = vec![]; if let Err(_) = tokio::time::timeout( - tokio::time::Duration::from_secs(5), stream.read_to_end(&mut buf)).await { + tokio::time::Duration::from_secs(5), + stream.read_to_end(&mut buf), + ) + .await + { logger::logger(con.peer_addr, Status::CGIError, u.as_str()); con.send_status(Status::CGIError, None).await?; return Ok(()); diff --git a/src/con_handler.rs b/src/con_handler.rs index 75b9482..c692f18 100644 --- a/src/con_handler.rs +++ b/src/con_handler.rs @@ -6,14 +6,16 @@ use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; use url::Url; -use crate::util; -use crate::conn; -use crate::status::Status; +#[cfg(any(feature = "cgi", feature = "scgi"))] use crate::cgi; +use crate::conn; use crate::logger; +#[cfg(feature = "proxy")] use crate::revproxy; +use crate::status::Status; +use crate::util; -type Result = std::result::Result>; +type Result = std::result::Result>; fn get_mime(path: &PathBuf) -> String { let mut mime = "text/gemini".to_string(); @@ -36,8 +38,7 @@ fn get_mime(path: &PathBuf) -> String { async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> io::Result<()> { let fd = File::open(path)?; let mut reader = BufReader::with_capacity(1024 * 1024, fd); - con.send_status(Status::Success, Some(&meta)) - .await?; + con.send_status(Status::Success, Some(&meta)).await?; loop { let len = { let buf = reader.fill_buf()?; @@ -132,33 +133,30 @@ async fn handle_cgi( match &con.srv.server.cgipath { Some(c) => { - if path.starts_with(c) { - if perm.mode() & 0o0111 == 0o0111 { - cgi::cgi(con, path, url, script_name, path_info).await?; - return Ok(true); - } else { - logger::logger(con.peer_addr, Status::CGIError, request); - con.send_status(Status::CGIError, None).await?; - return Ok(true); + if path.starts_with(c) { + if perm.mode() & 0o0111 == 0o0111 { + cgi::cgi(con, path, url, script_name, path_info).await?; + return Ok(true); + } else { + logger::logger(con.peer_addr, Status::CGIError, request); + con.send_status(Status::CGIError, None).await?; + return Ok(true); + } } } - }, None => { if meta.is_file() && perm.mode() & 0o0111 == 0o0111 { cgi::cgi(con, path, url, script_name, path_info).await?; return Ok(true); } - }, + } } } Ok(false) } // TODO Rewrite this monster. -pub async fn handle_connection( - mut con: conn::Connection, - url: url::Url -) -> Result { +pub async fn handle_connection(mut con: conn::Connection, url: url::Url) -> Result { let index = match &con.srv.server.index { Some(i) => i.clone(), None => "index.gemini".to_string(), @@ -223,18 +221,17 @@ pub async fn handle_connection( "/" => "/", _ => url.path().trim_end_matches("/"), }; - match sc.get(u) { - Some(r) => { - cgi::scgi(r.to_string(), url, con).await?; - return Ok(()); + match sc.get(u) { + Some(r) => { + cgi::scgi(r.to_string(), url, con).await?; + return Ok(()); + } + None => {} } - None => {} } - }, - None => {}, + None => {} } - let mut path = PathBuf::new(); if url.path().starts_with("/~") && con.srv.server.usrdir.unwrap_or(false) { @@ -246,7 +243,12 @@ pub async fn handle_connection( path.push("/home/"); } if usr.len() == 2 { - path.push(format!("{}/{}/{}", usr[0], "public_gemini", util::url_decode(usr[1].as_bytes()))); + path.push(format!( + "{}/{}/{}", + usr[0], + "public_gemini", + util::url_decode(usr[1].as_bytes()) + )); } else { path.push(format!("{}/{}/", usr[0], "public_gemini")); } @@ -304,13 +306,13 @@ pub async fn handle_connection( return Ok(()); } - if meta.is_file() && perm.mode() & 0o0111 == 0o0111 { + if meta.is_file() && perm.mode() & 0o0111 == 0o0111 { logger::logger(con.peer_addr, Status::NotFound, &url.as_str()); con.send_status(Status::NotFound, None).await?; return Ok(()); } - if perm.mode() & 0o0444 != 0o0444 { + if perm.mode() & 0o0444 != 0o0444 { logger::logger(con.peer_addr, Status::NotFound, &url.as_str()); con.send_status(Status::NotFound, None).await?; return Ok(()); diff --git a/src/config.rs b/src/config.rs index 97a7170..7f83119 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,12 @@ extern crate serde_derive; extern crate toml; -use tokio::fs; -use std::collections::HashMap; -use std::path; -use std::env; use crate::lib::errors; +use std::collections::HashMap; +use std::env; +use std::path; +use tokio::fs; -type Result = std::result::Result>; +type Result = std::result::Result>; #[derive(Debug, Deserialize, Clone)] pub struct Config { @@ -43,7 +43,7 @@ pub struct Server { #[derive(Debug, Clone)] pub struct ServerCfg { -// pub port: u16, + // pub port: u16, pub server: Server, } @@ -56,7 +56,9 @@ impl Config { if !p.exists() { return Err(Box::new(errors::GemError( "Please run with the path to the config file. \ - Or create the config as /usr/local/etc/gemserv.conf".to_string()))); + Or create the config as /usr/local/etc/gemserv.conf" + .to_string(), + ))); } } else { p.push(&args[1]); @@ -67,22 +69,30 @@ impl Config { Ok(c) => c, Err(e) => return Err(Box::new(e)), }; - + if config.host.is_some() || config.port.is_some() { - eprintln!("The host/port keys are depricated in favor \ - of interface and may be removed in the future."); + eprintln!( + "The host/port keys are depricated in favor \ + of interface and may be removed in the future." + ); } - + if config.interface.is_some() && (config.host.is_some() || config.port.is_some()) { - return Err(Box::new(errors::GemError("You need to specify either host/port or interface".into()))); + return Err(Box::new(errors::GemError( + "You need to specify either host/port or interface".into(), + ))); } else if config.interface.is_none() && config.host.is_none() && config.port.is_none() { - return Err(Box::new(errors::GemError("You need to specify either host/port or interface".into()))); + return Err(Box::new(errors::GemError( + "You need to specify either host/port or interface".into(), + ))); } else if config.host.is_some() && config.port.is_some() { return Ok(config); } else if config.interface.is_some() { return Ok(config); - } - return Err(Box::new(errors::GemError("You need to specify either host/port or interface".into()))); + } + return Err(Box::new(errors::GemError( + "You need to specify either host/port or interface".into(), + ))); } pub fn to_map(&self) -> HashMap { let mut map = HashMap::new(); @@ -90,11 +100,11 @@ impl Config { map.insert( srv.hostname.clone(), ServerCfg { - // port: self.port.clone(), + // port: self.port.clone(), server: srv.clone(), }, ); } map } -} \ No newline at end of file +} diff --git a/src/lib/conn.rs b/src/lib/conn.rs index 6798ec8..3ea90b0 100644 --- a/src/lib/conn.rs +++ b/src/lib/conn.rs @@ -2,15 +2,15 @@ use std::io; use std::marker::Unpin; use std::net::SocketAddr; -use tokio::net::TcpStream; use tokio::io::AsyncRead; use tokio::io::{AsyncWrite, AsyncWriteExt}; -use tokio_openssl::SslStream; +use tokio::net::TcpStream; +use tokio_rustls::server::TlsStream; use crate::status::Status; pub struct Connection { - pub stream: SslStream, + pub stream: TlsStream, pub local_addr: SocketAddr, pub peer_addr: SocketAddr, pub srv: crate::config::ServerCfg, @@ -38,7 +38,11 @@ impl Connection { self.send_raw(b.as_bytes()).await?; } - futures_util::future::poll_fn(|ctx| std::pin::Pin::new(&mut self.stream).poll_shutdown(ctx)).await.unwrap(); + futures_util::future::poll_fn(|ctx| { + std::pin::Pin::new(&mut self.stream).poll_shutdown(ctx) + }) + .await + .unwrap(); Ok(()) } @@ -49,9 +53,16 @@ impl Connection { Ok(()) } - pub async fn send_stream(&mut self, reader: &mut S) -> Result<(), io::Error> { + pub async fn send_stream( + &mut self, + reader: &mut S, + ) -> Result<(), io::Error> { tokio::io::copy(reader, &mut self.stream).await?; - futures_util::future::poll_fn(|ctx| std::pin::Pin::new(&mut self.stream).poll_shutdown(ctx)).await.unwrap(); + futures_util::future::poll_fn(|ctx| { + std::pin::Pin::new(&mut self.stream).poll_shutdown(ctx) + }) + .await + .unwrap(); Ok(()) } } diff --git a/src/lib/errors.rs b/src/lib/errors.rs index 0a89ee8..b0a62b2 100644 --- a/src/lib/errors.rs +++ b/src/lib/errors.rs @@ -1,7 +1,7 @@ use std::error::Error; use std::fmt; -pub type Result = std::result::Result>; +pub type Result = std::result::Result>; #[derive(Debug)] pub struct GemError(pub String); diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 2cc8caf..a4ea256 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -1,6 +1,6 @@ -pub mod util; -pub mod status; pub mod conn; -pub mod tls; -pub mod server; pub mod errors; +pub mod server; +pub mod status; +pub mod tls; +pub mod util; diff --git a/src/lib/server.rs b/src/lib/server.rs index 25df1bb..d25de77 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -1,26 +1,35 @@ #![allow(unreachable_code)] -use tokio::net::TcpListener; use tokio::io::AsyncReadExt; -use openssl::ssl::SslAcceptor; -use openssl::error::ErrorStack; -use openssl::ssl::NameType; +use tokio::net::TcpListener; +use tokio_rustls::server::TlsStream; +use tokio_rustls::TlsAcceptor; + //use futures_util::future::TryFutureExt; -use url::Url; -use std::io; use std::collections::HashMap; use std::future::Future; -use std::sync::Arc; +use std::io; use std::pin::Pin; +use std::sync::Arc; +use url::Url; use crate::config; use crate::conn; +use crate::errors::{GemError, Result}; use crate::logger; use crate::status::Status; -use crate::errors::{GemError, Result}; -pub trait Handler: FnMut(conn::Connection, url::Url) -> Pin + Send>> + Send + Sync + Copy {} -impl Handler for T - where T: FnMut(conn::Connection, url::Url) -> Pin + Send>> + Send + Sync + Copy +pub trait Handler: + FnMut(conn::Connection, url::Url) -> Pin + Send>> + + Send + + Sync + + Copy +{ +} +impl Handler for T where + T: FnMut(conn::Connection, url::Url) -> Pin + Send>> + + Send + + Sync + + Copy { } @@ -33,14 +42,15 @@ where pub struct Server { pub listener: Vec, - pub acceptor: SslAcceptor, + pub acceptor: TlsAcceptor, } impl Server { - pub async fn bind(addr: Vec, - acceptor: fn(config::Config) -> std::result::Result, - cfg: config::Config) -> io::Result - { + pub async fn bind( + addr: Vec, + acceptor: fn(config::Config) -> std::io::Result, + cfg: config::Config, + ) -> Result { if addr.len() == 1 { Ok(Server { listener: vec![TcpListener::bind(addr[0].to_owned()).await?], @@ -58,14 +68,18 @@ impl Server { } } - pub async fn serve(self, cmap: HashMap, default: String, - handler: impl Handler + 'static + Copy) -> Result - { - for listen in self.listener { + pub async fn serve( + self, + cmap: HashMap, + default: String, + handler: impl Handler + 'static + Copy, + ) -> Result { + for listen in self.listener { let cmap = cmap.clone(); let default = default.clone(); let listen = Arc::new(listen); let acceptor = Arc::new(self.acceptor.clone()); + tokio::spawn(async move { loop { let (stream, peer_addr) = listen.accept().await?; @@ -75,31 +89,37 @@ impl Server { let default = default.clone(); let mut handler = handler.clone(); - let ssl = openssl::ssl::Ssl::new(acceptor.context()).unwrap(); - let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap(); - tokio::spawn(async move { - match Pin::new(&mut stream).accept().await { + let mut stream = match acceptor.accept(stream).await { Ok(s) => s, Err(e) => { - log::error!("Error: {}",e); + log::error!("Error: {}", e); return Ok(()); - }, + } + }; + let (_, sni) = TlsStream::get_mut(&mut stream); + let sni = match sni.sni_hostname() { + Some(s) => s, + None => return Ok(()), }; - let srv = match stream.ssl().servername(NameType::HOST_NAME) { - Some(s) => match cmap.get(s) { - Some(ss) => ss, - None => cmap.get(&default).unwrap(), - }, - None => cmap.get(&default).unwrap(), - }.to_owned(); - let con = conn::Connection { stream, local_addr, peer_addr, srv }; + let srv = match cmap.get(sni) { + Some(h) => h, + None => cmap.get(&default).unwrap(), + } + .to_owned(); + + let con = conn::Connection { + stream, + local_addr, + peer_addr, + srv, + }; let (con, url) = match get_request(con).await { Ok((c, u)) => (c, u), Err(_) => return Ok(()) as io::Result<()>, }; - + match handler(con, url).await { Ok(o) => o, Err(_) => return Ok(()) as io::Result<()>, @@ -111,18 +131,27 @@ impl Server { Ok(()) as io::Result<()> }); } - tokio::signal::ctrl_c().await.expect("failed to listen for event"); + tokio::signal::ctrl_c() + .await + .expect("failed to listen for event"); Ok(()) } } pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, url::Url)> { let mut buffer = [0; 1024]; - let len = match tokio::time::timeout(tokio::time::Duration::from_secs(5), con.stream.read(&mut buffer)).await { + let len = match tokio::time::timeout( + tokio::time::Duration::from_secs(5), + con.stream.read(&mut buffer), + ) + .await + { Ok(result) => result.unwrap(), Err(e) => { logger::logger(con.peer_addr, Status::BadRequest, ""); - con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?; + con.send_status(Status::BadRequest, None) + .await + .map_err(|e| e.to_string())?; return Err(Box::new(e)); } }; @@ -130,7 +159,9 @@ pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, Ok(request) => request, Err(e) => { logger::logger(con.peer_addr, Status::BadRequest, ""); - con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?; + con.send_status(Status::BadRequest, None) + .await + .map_err(|e| e.to_string())?; return Err(Box::new(e)); } }; @@ -149,7 +180,9 @@ pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, Ok(url) => url, Err(e) => { logger::logger(con.peer_addr, Status::BadRequest, &request); - con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?; + con.send_status(Status::BadRequest, None) + .await + .map_err(|e| e.to_string())?; return Err(Box::new(e)); } }; @@ -158,10 +191,12 @@ pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, Some(h) => { if con.srv.server.hostname.as_str() != h.to_lowercase() { logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); - con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?; + con.send_status(Status::ProxyRequestRefused, None) + .await + .map_err(|e| e.to_string())?; return Err(Box::new(GemError("Wrong host".into()))); } - }, + } None => {} } match url.port() { @@ -169,16 +204,19 @@ pub async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, if p != con.local_addr.port() { logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); con.send_status(Status::ProxyRequestRefused, None) - .await.map_err(|e| e.to_string())?; + .await + .map_err(|e| e.to_string())?; } } None => {} } if url.scheme() != "gemini" { logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str()); - con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?; + con.send_status(Status::ProxyRequestRefused, None) + .await + .map_err(|e| e.to_string())?; return Err(Box::new(GemError("scheme not gemini".into()))); } - - return Ok((con, url)) -} \ No newline at end of file + + return Ok((con, url)); +} diff --git a/src/lib/tls.rs b/src/lib/tls.rs index e778d30..6df53a3 100644 --- a/src/lib/tls.rs +++ b/src/lib/tls.rs @@ -1,69 +1,163 @@ -extern crate openssl; -extern crate tokio_openssl; -use std::collections::HashMap; +use std::fs::File; +use std::io::{self, BufReader}; +use std::sync::Arc; +use std::time::SystemTime; -use openssl::error::ErrorStack; -use openssl::ssl::NameType; -use openssl::ssl::SniError; -use openssl::ssl::SslContextBuilder; -use openssl::ssl::SslVersion; -use openssl::ssl::SslVerifyMode; -use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; +use rustls::client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier, ServerName}; +use rustls::internal::msgs::enums::SignatureScheme; +use rustls::internal::msgs::handshake::DigitallySignedStruct; +use rustls::internal::msgs::handshake::DistinguishedNames; +use rustls::server::{ClientCertVerified, ClientCertVerifier, ResolvesServerCertUsingSni}; +use rustls::sign::{self, CertifiedKey}; +use rustls::{Certificate, Error, PrivateKey}; +use rustls_pemfile::{certs, pkcs8_private_keys}; +use tokio_rustls::rustls; +use tokio_rustls::TlsAcceptor; use crate::config; -pub fn acceptor_conf(cfg: config::Config) -> Result { - let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls_server())?; - acceptor.set_min_proto_version(Some(SslVersion::TLS1_2))?; - let mut map = HashMap::new(); - let mut num = 1; +pub fn tls_acceptor_conf(cfg: config::Config) -> io::Result { + let resolver = load_keypair(cfg)?; + let config = rustls::server::ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(Arc::new(GeminiClientAuth)) + .with_cert_resolver(Arc::new(resolver)); + let acceptor = TlsAcceptor::from(Arc::new(config)); + + Ok(acceptor) +} + +pub fn load_certs(path: &String) -> io::Result> { + certs(&mut BufReader::new(File::open(path)?)) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert")) + .map(|mut certs| certs.drain(..).map(Certificate).collect()) +} + +fn load_key(path: &String) -> io::Result> { + pkcs8_private_keys(&mut std::io::BufReader::new(std::fs::File::open(path)?)) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key")) + .map(|mut keys| keys.drain(..).map(PrivateKey).collect()) +} + +fn load_keypair(cfg: config::Config) -> io::Result { + let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); + for server in cfg.server.iter() { - let mut ctx = SslContextBuilder::new(SslMethod::tls_server())?; - ctx.set_verify(SslVerifyMode::NONE); - match ctx.set_private_key_file(&server.key, SslFiletype::PEM) { - Ok(c) => c, - Err(e) => { - log::error!("Error: Can't load key file"); - return Err(e); - } - }; - match ctx.set_certificate_chain_file(&server.cert) { - Ok(c) => c, - Err(e) => { - log::error!("Error: Can't load cert file"); - return Err(e); - } - }; - let ctx = ctx.build(); - map.insert(server.hostname.clone(), ctx.clone()); - if num == 1 { - map.insert("default".to_string(), ctx); - num += 1; - } + let key = load_key(&server.key)?.remove(0); + let certs = load_certs(&server.cert)?; + let signing_key = sign::any_supported_type(&key).expect("error loading key"); + + resolver + .add( + &server.hostname.clone(), + CertifiedKey::new(certs, signing_key), + ) + .expect("error loading key"); + } + Ok(resolver) +} + +struct GeminiClientAuth; + +impl ClientCertVerifier for GeminiClientAuth { + fn client_auth_root_subjects(&self) -> Option { + Some(Vec::new()) } - let ctx_builder = &mut *acceptor; - ctx_builder.set_servername_callback(move |ssl, _alert| -> Result<(), SniError> { - ssl.set_ssl_context({ - let hostname = ssl.servername(NameType::HOST_NAME); - if let Some(host) = hostname { - if let Some(ctx) = map.get(host) { - &ctx - } else { - &map.get(&"default".to_string()).expect("Can't get default") - } - } else { - &map.get(&"default".to_string()).expect("Can't get default") - } - }) - .expect("Can't get sni"); - // for client certs we don't have anything to verify right now? - ssl.set_verify_callback(SslVerifyMode::PEER, |_ver, _store| -> bool { - return true - }); + fn verify_client_cert( + &self, + _end_entity: &Certificate, + _intermidiates: &[Certificate], + _now: SystemTime, + ) -> Result { + Ok(ClientCertVerified::assertion()) + } - Ok(()) - }); + fn offer_client_auth(&self) -> bool { + true + } - Ok(acceptor.build()) + fn client_auth_mandatory(&self) -> Option { + Some(false) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + tokio_rustls::rustls::client::WebPkiVerifier::verification_schemes() + } } + +pub struct GeminiServerAuth; + +impl ServerCertVerifier for GeminiServerAuth { + fn verify_server_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } +} +// This was pull out of the depths of git. +// Rustls won't let self signed certs be used with sni which gemini requires. +// At 1.4.2 in https://gemini.circumlunar.space/docs/spec-spec.txt +/* +pub struct CertResolver { + map: HashMap>, +} + +impl CertResolver { + pub fn from_config(cfg: config::Config) -> errors::Result { + let mut map = HashMap::new(); + + for server in cfg.server.iter() { + let key = load_key(&server.key)?; + let certs = load_certs(&server.cert)?; + let signing_key = RsaSigningKey::new(&key).unwrap(); + + //let signing_key_boxed: Arc> = Arc::new(Box::new(signing_key)); + let signing_key_boxed = Arc::new(signing_key); + map.insert( + server.hostname.clone(), + Box::new(CertifiedKey::new(certs, signing_key_boxed)), + ); + } + + Ok(CertResolver { map }) + } +} + +impl ResolvesServerCert for CertResolver { + fn resolve(&self, client_hello: ClientHello<'_>) -> Option> { + if let Some(hostname) = client_hello.server_name() { + if let Some(cert) = self.map.get(hostname.into()) { + let cert_box = Arc::new(cert); + return Some(&cert_box); + } + } + + None + } +} +*/ diff --git a/src/lib/util.rs b/src/lib/util.rs index e3a1061..fd1463d 100644 --- a/src/lib/util.rs +++ b/src/lib/util.rs @@ -1,19 +1,18 @@ +use sha2::Digest; use url::form_urlencoded; pub fn url_decode(url: &[u8]) -> String { let decoded: String = form_urlencoded::parse(url) .map(|(key, val)| [key, val].concat()) .collect(); - return decoded + return decoded; } - -pub fn fingerhex(x509: &openssl::x509::X509) -> String { - let finger = match x509.digest(openssl::hash::MessageDigest::sha256()) { - Ok(f) => f, - _ => return "".to_string(), - }; +pub fn fingerhex(x509: &[u8]) -> String { + let mut finger = sha2::Sha256::new(); + finger.update(&x509); + let finger = finger.finalize(); let mut hex: String = String::from("SHA256:"); - for f in finger.as_ref() { + for f in finger { hex.push_str(&format!("{:02X}", f)); } hex diff --git a/src/logger.rs b/src/logger.rs index ea08524..f8cb652 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,24 +1,24 @@ -use crate::status; use crate::lib::errors; +use crate::status; use log::{info, warn}; use std::net::SocketAddr; pub fn init(loglev: &Option) -> errors::Result { let loglev = match loglev { None => log::Level::Info, - Some(l) => { - match l.as_str() { - "error" => log::Level::Error, - "warn" => log::Level::Warn, - "info" => log::Level::Info, - _ => { - return Err(Box::new(errors::GemError("Incorrect log level in config file.".to_string()))); - }, + Some(l) => match l.as_str() { + "error" => log::Level::Error, + "warn" => log::Level::Warn, + "info" => log::Level::Info, + _ => { + return Err(Box::new(errors::GemError( + "Incorrect log level in config file.".to_string(), + ))); } }, }; simple_logger::init_with_level(loglev).unwrap(); - return Ok(()) + return Ok(()); } pub fn logger(addr: SocketAddr, stat: status::Status, req: &str) { diff --git a/src/main.rs b/src/main.rs index 8d86c8e..da82285 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,19 +4,21 @@ extern crate serde_derive; use std::io; use std::net::ToSocketAddrs; -mod lib; +#[cfg(any(feature = "cgi", feature = "scgi"))] mod cgi; -mod config; -mod logger; -mod revproxy; mod con_handler; +mod config; +mod lib; +mod logger; +#[cfg(feature = "proxy")] +mod revproxy; -use lib::util; use lib::conn; -use lib::status; -use lib::tls; -use lib::server; use lib::errors; +use lib::server; +use lib::status; +use lib::tls::{self, tls_acceptor_conf}; +use lib::util; #[tokio::main] async fn main() -> errors::Result { @@ -25,39 +27,51 @@ async fn main() -> errors::Result { Err(e) => { eprintln!("Config error: {}", e); return Ok(()); - }, + } }; - + logger::init(&cfg.log)?; - + let cmap = cfg.to_map(); let default = &cfg.server[0].hostname; println!("Serving {} vhosts", cfg.server.len()); let mut addr: Vec = Vec::new(); if cfg.host.is_some() && cfg.port.is_some() { - addr.push(format!("{}:{}", &cfg.host.to_owned().unwrap(), &cfg.port.unwrap()) - .to_socket_addrs()?.next() - .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?); + addr.push( + format!("{}:{}", &cfg.host.to_owned().unwrap(), &cfg.port.unwrap()) + .to_socket_addrs()? + .next() + .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?, + ); } else { match &cfg.interface { Some(i) => { for iface in i { - addr.push(iface - .to_socket_addrs()?.next() - .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?); - } - }, - None => {}, + addr.push( + iface + .to_socket_addrs()? + .next() + .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?, + ); + } + } + None => {} } } addr.sort_by(|a, b| a.port().cmp(&b.port())); addr.dedup(); - - let server = server::Server::bind(addr, tls::acceptor_conf, cfg.clone()).await?; - if let Err(e) = server.serve(cmap, default.to_string(), server::force_boxed(con_handler::handle_connection)).await { - return Err(e) + let server = server::Server::bind(addr, tls_acceptor_conf, cfg.clone()).await?; + if let Err(e) = server + .serve( + cmap, + default.to_string(), + server::force_boxed(con_handler::handle_connection), + ) + .await + { + return Err(e); }; - return Ok(()) + return Ok(()); } diff --git a/src/revproxy.rs b/src/revproxy.rs index de8e49a..5396cd8 100644 --- a/src/revproxy.rs +++ b/src/revproxy.rs @@ -1,14 +1,18 @@ #![cfg(feature = "proxy")] -use openssl::ssl::{SslConnector, SslMethod}; +use std::convert::TryFrom; use std::io; use std::net::ToSocketAddrs; -use std::pin::Pin; +use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; +use tokio_rustls::rustls; +use tokio_rustls::TlsConnector; + use crate::conn; use crate::logger; use crate::status::Status; +use crate::tls; pub async fn proxy(addr: String, u: url::Url, mut con: conn::Connection) -> Result<(), io::Error> { let p: Vec<&str> = u.path().trim_start_matches("/").splitn(2, "/").collect(); @@ -22,32 +26,25 @@ pub async fn proxy(addr: String, u: url::Url, mut con: conn::Connection) -> Resu con.send_status(Status::NotFound, None).await?; return Ok(()); } + let domain = &addr; let addr = addr .to_socket_addrs()? .next() .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?; - let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); - connector.set_verify(openssl::ssl::SslVerifyMode::NONE); - let config = connector.build().configure().unwrap().into_ssl("localhost").unwrap(); + let config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(Arc::new(tls::GeminiServerAuth)) + .with_no_client_auth(); + let connector = TlsConnector::from(Arc::new(config)); + + let stream = TcpStream::connect(&addr).await?; + + let domain = rustls::ServerName::try_from(domain.as_str()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?; + + let mut stream = connector.connect(domain, stream).await?; - let stream = match TcpStream::connect(&addr).await { - Ok(s) => s, - Err(_) => { - logger::logger(con.peer_addr, Status::ProxyError, u.as_str()); - con.send_status(Status::ProxyError, None).await?; - return Ok(()); - } - }; - let mut stream = tokio_openssl::SslStream::new(config, stream).unwrap(); - match Pin::new(&mut stream).connect().await { - Ok(s) => s, - Err(_) => { - logger::logger(con.peer_addr, Status::ProxyError, u.as_str()); - con.send_status(Status::ProxyError, None).await?; - return Ok(()); - } - }; stream.write_all(p[1].as_bytes()).await?; stream.flush().await?; @@ -58,32 +55,25 @@ pub async fn proxy(addr: String, u: url::Url, mut con: conn::Connection) -> Resu Ok(()) } -pub async fn proxy_all(addr: &str, u: url::Url, mut con: conn::Connection) -> Result<(), io::Error> { - let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); - connector.set_verify(openssl::ssl::SslVerifyMode::NONE); +pub async fn proxy_all( + addr: &str, + u: url::Url, + mut con: conn::Connection, +) -> Result<(), io::Error> { let domain = addr.splitn(2, ':').next().unwrap(); - let config = connector.build().configure().unwrap().into_ssl(domain).unwrap(); - // TCP handshake - let stream = match TcpStream::connect(&addr).await { - Ok(s) => s, - Err(_) => { - logger::logger(con.peer_addr, Status::ProxyError, u.as_str()); - con.send_status(Status::ProxyError, None).await?; - return Ok(()); - } - }; + let config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(Arc::new(tls::GeminiServerAuth)) + .with_no_client_auth(); + let connector = TlsConnector::from(Arc::new(config)); - // TLS handshake with SNI - let mut stream = tokio_openssl::SslStream::new(config, stream).unwrap(); - match Pin::new(&mut stream).connect().await { - Ok(s) => s, - Err(_) => { - logger::logger(con.peer_addr, Status::ProxyError, u.as_str()); - con.send_status(Status::ProxyError, None).await?; - return Ok(()); - } - }; + let stream = TcpStream::connect(&addr).await?; + + let domain = rustls::ServerName::try_from(domain) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?; + + let mut stream = connector.connect(domain, stream).await?; // send request: URL + CRLF stream.write_all(u.as_ref().as_bytes()).await?;