|
|
@ -9,10 +9,10 @@ use hyper::{Body, Method, Request, Response}; |
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
|
|
|
|
|
|
|
|
use std::str::FromStr; |
|
|
|
use std::str::FromStr; |
|
|
|
use std::{future, sync}; |
|
|
|
use std::{future, net, sync}; |
|
|
|
|
|
|
|
|
|
|
|
use crate::error::unexpected; |
|
|
|
use crate::error::unexpected; |
|
|
|
use crate::{domain, service, util}; |
|
|
|
use crate::{domain, origin, service, util}; |
|
|
|
|
|
|
|
|
|
|
|
pub struct Service { |
|
|
|
pub struct Service { |
|
|
|
domain_manager: sync::Arc<dyn domain::manager::Manager>, |
|
|
|
domain_manager: sync::Arc<dyn domain::manager::Manager>, |
|
|
@ -158,8 +158,14 @@ impl<'svc> Service { |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn serve_origin(&self, domain: domain::Name, path: &str) -> Response<Body> { |
|
|
|
async fn serve_origin( |
|
|
|
|
|
|
|
&self, |
|
|
|
|
|
|
|
client_ip: net::IpAddr, |
|
|
|
|
|
|
|
domain: domain::Name, |
|
|
|
|
|
|
|
req: Request<Body>, |
|
|
|
|
|
|
|
) -> Response<Body> { |
|
|
|
let mut path_owned; |
|
|
|
let mut path_owned; |
|
|
|
|
|
|
|
let path = req.uri().path(); |
|
|
|
|
|
|
|
|
|
|
|
let path = match path.ends_with('/') { |
|
|
|
let path = match path.ends_with('/') { |
|
|
|
true => { |
|
|
|
true => { |
|
|
@ -178,6 +184,13 @@ impl<'svc> Service { |
|
|
|
Err(domain::manager::GetFileError::FileNotFound) => { |
|
|
|
Err(domain::manager::GetFileError::FileNotFound) => { |
|
|
|
self.render_error_page(404, "File not found") |
|
|
|
self.render_error_page(404, "File not found") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Err(domain::manager::GetFileError::OriginIsProxy { url }) => { |
|
|
|
|
|
|
|
origin::proxy::serve_http_request(client_ip, &url, req) |
|
|
|
|
|
|
|
.await |
|
|
|
|
|
|
|
.unwrap_or_else(|e| { |
|
|
|
|
|
|
|
self.internal_error(format!("proxying {domain} to {url}: {e}").as_str()) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
Err(domain::manager::GetFileError::Unexpected(e)) => { |
|
|
|
Err(domain::manager::GetFileError::Unexpected(e)) => { |
|
|
|
self.internal_error(format!("failed to fetch file {path}: {e}").as_str()) |
|
|
|
self.internal_error(format!("failed to fetch file {path}: {e}").as_str()) |
|
|
|
} |
|
|
|
} |
|
|
@ -366,15 +379,13 @@ impl<'svc> Service { |
|
|
|
self.render_page("/domains.html", Response { domains }) |
|
|
|
self.render_page("/domains.html", Response { domains }) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async fn handle_request(&self, req: Request<Body>) -> Response<Body> { |
|
|
|
async fn handle_request(&self, client_ip: net::IpAddr, req: Request<Body>) -> Response<Body> { |
|
|
|
let (req, body) = req.into_parts(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let maybe_host = match ( |
|
|
|
let maybe_host = match ( |
|
|
|
req.headers |
|
|
|
req.headers() |
|
|
|
.get("Host") |
|
|
|
.get("Host") |
|
|
|
.and_then(|v| v.to_str().ok()) |
|
|
|
.and_then(|v| v.to_str().ok()) |
|
|
|
.map(strip_port), |
|
|
|
.map(strip_port), |
|
|
|
req.uri.host().map(strip_port), |
|
|
|
req.uri().host().map(strip_port), |
|
|
|
) { |
|
|
|
) { |
|
|
|
(Some(h), _) if h != self.config.primary_domain.as_str() => Some(h), |
|
|
|
(Some(h), _) if h != self.config.primary_domain.as_str() => Some(h), |
|
|
|
(_, Some(h)) if h != self.config.primary_domain.as_str() => Some(h), |
|
|
|
(_, Some(h)) if h != self.config.primary_domain.as_str() => Some(h), |
|
|
@ -382,32 +393,34 @@ impl<'svc> Service { |
|
|
|
} |
|
|
|
} |
|
|
|
.and_then(|h| domain::Name::from_str(h).ok()); |
|
|
|
.and_then(|h| domain::Name::from_str(h).ok()); |
|
|
|
|
|
|
|
|
|
|
|
let path = req.uri.path(); |
|
|
|
{ |
|
|
|
|
|
|
|
let path = req.uri().path(); |
|
|
|
|
|
|
|
|
|
|
|
// Serving acme challenges always takes priority. We serve them from the same store no
|
|
|
|
// Serving acme challenges always takes priority. We serve them from the same store no
|
|
|
|
// matter the domain, presumably they are cryptographically random enough that it doesn't
|
|
|
|
// matter the domain, presumably they are cryptographically random enough that it doesn't
|
|
|
|
// matter.
|
|
|
|
// matter.
|
|
|
|
if req.method == Method::GET && path.starts_with("/.well-known/acme-challenge/") { |
|
|
|
if req.method() == Method::GET && path.starts_with("/.well-known/acme-challenge/") { |
|
|
|
let token = path.trim_start_matches("/.well-known/acme-challenge/"); |
|
|
|
let token = path.trim_start_matches("/.well-known/acme-challenge/"); |
|
|
|
|
|
|
|
|
|
|
|
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) { |
|
|
|
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) { |
|
|
|
return self.serve(200, "token.txt", key.into()); |
|
|
|
return self.serve(200, "token.txt", key.into()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Serving domani challenges similarly takes priority.
|
|
|
|
// Serving domani challenges similarly takes priority.
|
|
|
|
if req.method == Method::GET && path == "/.well-known/domani-challenge" { |
|
|
|
if req.method() == Method::GET && path == "/.well-known/domani-challenge" { |
|
|
|
if let Some(ref domain) = maybe_host { |
|
|
|
if let Some(ref domain) = maybe_host { |
|
|
|
match self |
|
|
|
match self |
|
|
|
.domain_manager |
|
|
|
.domain_manager |
|
|
|
.get_domain_checker_challenge_token(domain) |
|
|
|
.get_domain_checker_challenge_token(domain) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Ok(Some(token)) => return self.serve(200, "token.txt", token.into()), |
|
|
|
Ok(Some(token)) => return self.serve(200, "token.txt", token.into()), |
|
|
|
Ok(None) => return self.render_error_page(404, "Token not found"), |
|
|
|
Ok(None) => return self.render_error_page(404, "Token not found"), |
|
|
|
Err(e) => { |
|
|
|
Err(e) => { |
|
|
|
return self.internal_error( |
|
|
|
return self.internal_error( |
|
|
|
format!("failed to get token for domain {}: {e}", domain).as_str(), |
|
|
|
format!("failed to get token for domain {}: {e}", domain).as_str(), |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -415,10 +428,12 @@ impl<'svc> Service { |
|
|
|
|
|
|
|
|
|
|
|
// If a managed domain was given then serve that from its origin
|
|
|
|
// If a managed domain was given then serve that from its origin
|
|
|
|
if let Some(domain) = maybe_host { |
|
|
|
if let Some(domain) = maybe_host { |
|
|
|
return self.serve_origin(domain, req.uri.path()); |
|
|
|
return self.serve_origin(client_ip, domain, req).await; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Serve main domani site
|
|
|
|
// Serve main domani site
|
|
|
|
|
|
|
|
let (req, body) = req.into_parts(); |
|
|
|
|
|
|
|
let path = req.uri.path(); |
|
|
|
|
|
|
|
|
|
|
|
if req.method == Method::GET && path.starts_with("/static/") { |
|
|
|
if req.method == Method::GET && path.starts_with("/static/") { |
|
|
|
return self.render(200, path, ()); |
|
|
|
return self.render(200, path, ()); |
|
|
|