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

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) => {
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(());

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

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]);
@@ -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<String, ServerCfg> {
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
}
}
}

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
{
for listen in self.listener {
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,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))
}
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;
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));
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 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<DistinguishedNames> {
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<ClientCertVerified, Error> {
Ok(ClientCertVerified::assertion())
}
Ok(())
});
fn offer_client_auth(&self) -> bool {
true
}
Ok(acceptor.build())
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() {
"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) {

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,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<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))?);
}
},
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(());
}

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