kochab/src/lib.rs

327 lines
9.3 KiB
Rust
Raw Normal View History

2020-10-31 19:53:03 +00:00
#[macro_use] extern crate log;
use std::{
panic::AssertUnwindSafe,
convert::TryFrom,
io::BufReader,
sync::Arc,
time::Duration,
};
2020-10-31 19:53:03 +00:00
use futures::{future::BoxFuture, FutureExt};
use tokio::{
prelude::*,
io::{self, BufStream},
net::{TcpStream, ToSocketAddrs},
time::timeout,
2020-10-31 19:53:03 +00:00
};
use tokio::net::TcpListener;
2020-11-13 19:20:59 +00:00
use rustls::ClientCertVerifier;
2020-10-31 19:53:03 +00:00
use tokio_rustls::{rustls, TlsAcceptor};
use rustls::*;
use anyhow::*;
2020-11-13 23:48:50 +00:00
use lazy_static::lazy_static;
2020-10-31 19:53:03 +00:00
pub mod types;
pub mod util;
pub use mime;
pub use uriparse as uri;
pub use types::*;
pub const REQUEST_URI_MAX_LEN: usize = 1024;
pub const GEMINI_PORT: u16 = 1965;
type Handler = Arc<dyn Fn(Request) -> HandlerResponse + Send + Sync>;
2020-10-31 19:53:03 +00:00
type HandlerResponse = BoxFuture<'static, Result<Response>>;
#[derive(Clone)]
pub struct Server {
tls_acceptor: TlsAcceptor,
listener: Arc<TcpListener>,
handler: Handler,
timeout: Duration,
2020-10-31 19:53:03 +00:00
}
impl Server {
pub fn bind<A: ToSocketAddrs>(addr: A) -> Builder<A> {
Builder::bind(addr)
}
async fn serve(self) -> Result<()> {
loop {
2020-11-14 02:56:50 +00:00
let (stream, _addr) = self.listener.accept().await
.context("Failed to accept client")?;
2020-10-31 19:53:03 +00:00
let this = self.clone();
tokio::spawn(async move {
if let Err(err) = this.serve_client(stream).await {
2020-11-14 02:56:50 +00:00
error!("{:?}", err);
2020-10-31 19:53:03 +00:00
}
});
}
}
async fn serve_client(self, stream: TcpStream) -> Result<()> {
let fut_accept_request = async {
let stream = self.tls_acceptor.accept(stream).await
.context("Failed to establish TLS session")?;
let mut stream = BufStream::new(stream);
let request = receive_request(&mut stream).await
.context("Failed to receive request")?;
Result::<_, anyhow::Error>::Ok((request, stream))
};
// Use a timeout for interacting with the client
let fut_accept_request = timeout(self.timeout, fut_accept_request);
let (mut request, mut stream) = fut_accept_request.await
.context("Client timed out while waiting for response")??;
2020-10-31 19:53:03 +00:00
debug!("Client requested: {}", request.uri());
2020-11-13 19:20:59 +00:00
// Identify the client certificate from the tls stream. This is the first
// certificate in the certificate chain.
let client_cert = stream.get_ref()
.get_ref()
.1
.get_peer_certificates()
.and_then(|mut v| if v.is_empty() {None} else {Some(v.remove(0))});
request.set_cert(client_cert);
let handler = (self.handler)(request);
2020-10-31 19:53:03 +00:00
let handler = AssertUnwindSafe(handler);
let response = handler.catch_unwind().await
.unwrap_or_else(|_| Response::server_error(""))
.or_else(|err| {
2020-11-14 02:56:50 +00:00
error!("Handler failed: {:?}", err);
2020-10-31 19:53:03 +00:00
Response::server_error("")
2020-11-14 02:56:50 +00:00
})
.context("Request handler failed")?;
2020-10-31 19:53:03 +00:00
// Use a timeout for sending the response
let fut_send_and_flush = async {
send_response(response, &mut stream).await
.context("Failed to send response")?;
stream.flush()
.await
.context("Failed to flush response data")
};
timeout(self.timeout, fut_send_and_flush)
.await
.context("Client timed out receiving response data")??;
2020-10-31 19:53:03 +00:00
Ok(())
}
}
pub struct Builder<A> {
addr: A,
timeout: Duration,
2020-10-31 19:53:03 +00:00
}
impl<A: ToSocketAddrs> Builder<A> {
fn bind(addr: A) -> Self {
Self { addr, timeout: Duration::from_secs(1) }
}
/// Set the timeout on incoming requests
///
/// Note that this timeout is applied twice, once for the delivery of the request, and
/// once for sending the client's response. This means that for a 1 second timeout,
/// the client will have 1 second to complete the TLS handshake and deliver a request
/// header, then your API will have as much time as it needs to handle the request,
/// before the client has another second to receive the response.
///
/// If you would like a timeout for your code itself, please use
/// ['tokio::time::Timeout`] to implement it internally.
///
/// The default timeout is 1 second.
pub fn set_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
2020-10-31 19:53:03 +00:00
}
pub async fn serve<F>(self, handler: F) -> Result<()>
where
F: Fn(Request) -> HandlerResponse + Send + Sync + 'static,
2020-10-31 19:53:03 +00:00
{
2020-11-14 02:56:50 +00:00
let config = tls_config()
.context("Failed to create TLS config")?;
let listener = TcpListener::bind(self.addr).await
.context("Failed to create socket")?;
2020-10-31 19:53:03 +00:00
let server = Server {
tls_acceptor: TlsAcceptor::from(config),
2020-11-14 02:56:50 +00:00
listener: Arc::new(listener),
2020-10-31 19:53:03 +00:00
handler: Arc::new(handler),
timeout: self.timeout,
2020-10-31 19:53:03 +00:00
};
2020-11-13 19:20:59 +00:00
2020-10-31 19:53:03 +00:00
server.serve().await
}
}
async fn receive_request(stream: &mut (impl AsyncBufRead + Unpin)) -> Result<Request> {
let limit = REQUEST_URI_MAX_LEN + "\r\n".len();
let mut stream = stream.take(limit as u64);
let mut uri = Vec::new();
stream.read_until(b'\n', &mut uri).await?;
if !uri.ends_with(b"\r\n") {
if uri.len() < REQUEST_URI_MAX_LEN {
bail!("Request header not terminated with CRLF")
} else {
bail!("Request URI too long")
}
}
// Strip CRLF
uri.pop();
uri.pop();
2020-11-14 02:56:50 +00:00
let uri = URIReference::try_from(&*uri)
.context("Request URI is invalid")?
.into_owned();
let request = Request::from_uri(uri)
.context("Failed to create request from URI")?;
2020-10-31 19:53:03 +00:00
Ok(request)
}
async fn send_response(mut response: Response, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
2020-11-14 02:56:50 +00:00
send_response_header(response.header(), stream).await
.context("Failed to send response header")?;
2020-10-31 19:53:03 +00:00
if let Some(body) = response.take_body() {
2020-11-14 02:56:50 +00:00
send_response_body(body, stream).await
.context("Failed to send response body")?;
2020-10-31 19:53:03 +00:00
}
Ok(())
}
async fn send_response_header(header: &ResponseHeader, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
let header = format!(
"{status} {meta}\r\n",
status = header.status.code(),
meta = header.meta.as_str(),
);
stream.write_all(header.as_bytes()).await?;
Ok(())
}
async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
match body {
Body::Bytes(bytes) => stream.write_all(&bytes).await?,
Body::Reader(mut reader) => { io::copy(&mut reader, stream).await?; },
}
Ok(())
}
fn tls_config() -> Result<Arc<ServerConfig>> {
2020-11-13 19:20:59 +00:00
let mut config = ServerConfig::new(AllowAnonOrSelfsignedClient::new());
2020-10-31 19:53:03 +00:00
2020-11-14 02:56:50 +00:00
let cert_chain = load_cert_chain()
.context("Failed to load TLS certificate")?;
let key = load_key()
.context("Failed to load TLS key")?;
config.set_single_cert(cert_chain, key)
.context("Failed to use loaded TLS certificate")?;
2020-10-31 19:53:03 +00:00
Ok(config.into())
}
fn load_cert_chain() -> Result<Vec<Certificate>> {
2020-11-14 02:56:50 +00:00
let cert_path = "cert/cert.pem";
let certs = std::fs::File::open(cert_path)
.with_context(|| format!("Failed to open `{}`", cert_path))?;
2020-10-31 19:53:03 +00:00
let mut certs = BufReader::new(certs);
let certs = rustls::internal::pemfile::certs(&mut certs)
2020-11-14 02:56:50 +00:00
.map_err(|_| anyhow!("failed to load certs `{}`", cert_path))?;
2020-10-31 19:53:03 +00:00
Ok(certs)
}
fn load_key() -> Result<PrivateKey> {
2020-11-14 02:56:50 +00:00
let key_path = "cert/key.pem";
let keys = std::fs::File::open(key_path)
.with_context(|| format!("Failed to open `{}`", key_path))?;
let mut keys = BufReader::new(keys);
2020-10-31 19:53:03 +00:00
let mut keys = rustls::internal::pemfile::pkcs8_private_keys(&mut keys)
2020-11-14 02:56:50 +00:00
.map_err(|_| anyhow!("failed to load key `{}`", key_path))?;
2020-11-13 19:20:59 +00:00
2020-10-31 19:53:03 +00:00
ensure!(!keys.is_empty(), "no key found");
let key = keys.swap_remove(0);
Ok(key)
}
2020-11-13 23:53:54 +00:00
/// Mime for Gemini documents
pub const GEMINI_MIME_STR: &str = "text/gemini";
2020-10-31 19:53:03 +00:00
2020-11-13 23:48:50 +00:00
lazy_static! {
2020-11-13 23:53:54 +00:00
/// Mime for Gemini documents ("text/gemini")
2020-11-13 23:48:50 +00:00
pub static ref GEMINI_MIME: Mime = GEMINI_MIME_STR.parse().expect("northstar BUG");
}
#[deprecated(note = "Use `GEMINI_MIME` instead", since = "0.3.0")]
2020-10-31 19:53:03 +00:00
pub fn gemini_mime() -> Result<Mime> {
2020-11-13 23:48:50 +00:00
Ok(GEMINI_MIME.clone())
2020-10-31 19:53:03 +00:00
}
2020-11-13 19:20:59 +00:00
/// A client cert verifier that accepts all connections
///
/// Unfortunately, rustls doesn't provide a ClientCertVerifier that accepts self-signed
/// certificates, so we need to implement this ourselves.
struct AllowAnonOrSelfsignedClient { }
impl AllowAnonOrSelfsignedClient {
/// Create a new verifier
fn new() -> Arc<Self> {
Arc::new(Self {})
}
}
impl ClientCertVerifier for AllowAnonOrSelfsignedClient {
fn client_auth_root_subjects(
&self,
_: Option<&webpki::DNSName>
) -> Option<DistinguishedNames> {
Some(Vec::new())
}
2020-10-31 19:53:03 +00:00
2020-11-13 19:20:59 +00:00
fn client_auth_mandatory(&self, _sni: Option<&webpki::DNSName>) -> Option<bool> {
Some(false)
}
fn verify_client_cert(
&self,
_: &[Certificate],
_: Option<&webpki::DNSName>
) -> Result<ClientCertVerified, TLSError> {
Ok(ClientCertVerified::assertion())
}
}
2020-11-13 23:48:50 +00:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gemini_mime_parses() {
let _: &Mime = &GEMINI_MIME;
}
}