Switched from openssl to Rustls

This commit is contained in:
int 80h
2021-12-01 21:25:37 -05:00
parent 9ece7c3d3b
commit 0223e31622
15 changed files with 837 additions and 352 deletions

408
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "gemserv"
version = "0.5.0"
version = "0.6.0"
authors = ["int 80h <int@80h.dev>"]
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" ]

2
README
View File

@@ -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"

View File

@@ -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<openssl::x509::X509>, srv: &config::ServerCfg, url: &url::Url) -> HashMap<String, String> {
fn envs(
peer_addr: SocketAddr,
session: &ServerConnection,
srv: &config::ServerCfg,
url: &url::Url,
) -> HashMap<String, String> {
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) => {
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();
envs.insert("AUTH_TYPE".to_string(), "Certificate".to_string());
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("REMOTE_USER".to_string(), user.to_string());
envs.insert("TLS_CLIENT_HASH".to_string(), util::fingerhex(&cert));
}
Err(_) => {}
}
envs.insert("TLS_CLIENT_HASH".to_string(), util::fingerhex(&x));
},
None => {},
}
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(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(());
},
}
};
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(());

View File

@@ -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<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
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()?;
@@ -142,23 +143,20 @@ async fn handle_cgi(
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(),
@@ -230,10 +228,9 @@ pub async fn handle_connection(
}
None => {}
}
},
None => {},
}
None => {}
}
let mut path = PathBuf::new();
@@ -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"));
}

View File

@@ -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<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[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]);
@@ -69,20 +71,28 @@ impl Config {
};
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<String, ServerCfg> {
let mut map = HashMap::new();

View File

@@ -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<TcpStream>,
pub stream: TlsStream<TcpStream>,
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<S: AsyncRead + Unpin>(&mut self, reader: &mut S) -> Result<(), io::Error> {
pub async fn send_stream<S: AsyncRead + Unpin>(
&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(())
}
}

View File

@@ -1,7 +1,7 @@
use std::error::Error;
use std::fmt;
pub type Result<T=()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
pub type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[derive(Debug)]
pub struct GemError(pub String);

View File

@@ -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;

View File

@@ -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<Box<dyn Future<Output = Result> + Send>> + Send + Sync + Copy {}
impl<T> Handler for T
where T: FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>> + Send + Sync + Copy
pub trait Handler:
FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>>
+ Send
+ Sync
+ Copy
{
}
impl<T> Handler for T where
T: FnMut(conn::Connection, url::Url) -> Pin<Box<dyn Future<Output = Result> + Send>>
+ Send
+ Sync
+ Copy
{
}
@@ -33,14 +42,15 @@ where
pub struct Server {
pub listener: Vec<TcpListener>,
pub acceptor: SslAcceptor,
pub acceptor: TlsAcceptor,
}
impl Server {
pub async fn bind(addr: Vec<std::net::SocketAddr>,
acceptor: fn(config::Config) -> std::result::Result<SslAcceptor, ErrorStack>,
cfg: config::Config) -> io::Result<Server>
{
pub async fn bind(
addr: Vec<std::net::SocketAddr>,
acceptor: fn(config::Config) -> std::io::Result<TlsAcceptor>,
cfg: config::Config,
) -> Result<Server> {
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<String, config::ServerCfg>, default: String,
handler: impl Handler + 'static + Copy) -> Result
{
pub async fn serve(
self,
cmap: HashMap<String, config::ServerCfg>,
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,26 +89,32 @@ 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<()>,
@@ -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))
return Ok((con, url));
}

View File

@@ -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<SslAcceptor, ErrorStack> {
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;
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;
}
}
pub fn tls_acceptor_conf(cfg: config::Config) -> io::Result<TlsAcceptor> {
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));
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
});
Ok(())
});
Ok(acceptor.build())
Ok(acceptor)
}
pub fn load_certs(path: &String) -> io::Result<Vec<Certificate>> {
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<Vec<PrivateKey>> {
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<ResolvesServerCertUsingSni> {
let mut resolver = rustls::server::ResolvesServerCertUsingSni::new();
for server in cfg.server.iter() {
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<DistinguishedNames> {
Some(Vec::new())
}
fn verify_client_cert(
&self,
_end_entity: &Certificate,
_intermidiates: &[Certificate],
_now: SystemTime,
) -> Result<ClientCertVerified, Error> {
Ok(ClientCertVerified::assertion())
}
fn offer_client_auth(&self) -> bool {
true
}
fn client_auth_mandatory(&self) -> Option<bool> {
Some(false)
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &Certificate,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &Certificate,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
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<Item = &[u8]>,
_ocsp_response: &[u8],
_now: SystemTime,
) -> Result<ServerCertVerified, Error> {
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<String, Box<CertifiedKey>>,
}
impl CertResolver {
pub fn from_config(cfg: config::Config) -> errors::Result<Self> {
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<Box<dyn SigningKey>> = 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<Arc<CertifiedKey>> {
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
}
}
*/

View File

@@ -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

View File

@@ -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<String>) -> errors::Result {
let loglev = match loglev {
None => log::Level::Info,
Some(l) => {
match l.as_str() {
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())));
},
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) {

View File

@@ -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,7 +27,7 @@ async fn main() -> errors::Result {
Err(e) => {
eprintln!("Config error: {}", e);
return Ok(());
},
}
};
logger::init(&cfg.log)?;
@@ -36,28 +38,40 @@ async fn main() -> errors::Result {
let mut addr: Vec<std::net::SocketAddr> = 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))?);
addr.push(
iface
.to_socket_addrs()?
.next()
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?,
);
}
},
None => {},
}
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(());
}

View File

@@ -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?;