1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use crate::args::DaemonArgs;
use crate::chall;
use crate::config::Config;
use crate::errors::*;
use crate::http_responses::*;
use crate::sandbox;
use actix_web::{get, web, HttpRequest, HttpResponse, Responder};
use actix_web::{middleware, App, HttpServer};
use std::env;
use std::fs;
use std::net::TcpListener;
use std::path::Path;
fn get_host(req: &HttpRequest) -> Option<&str> {
if let Some(host) = req.headers().get("Host") {
if let Ok(host) = host.to_str() {
Some(host)
} else {
None
}
} else {
None
}
}
#[inline]
fn bad_request() -> HttpResponse {
HttpResponse::BadRequest().body(BAD_REQUEST)
}
#[inline]
fn not_found() -> HttpResponse {
HttpResponse::NotFound().body(NOT_FOUND)
}
#[get("/{p:.*}")]
async fn redirect(req: HttpRequest) -> impl Responder {
debug!("REQ: {:?}", req);
let host = if let Some(host) = get_host(&req) {
host
} else {
return bad_request();
};
debug!("host: {:?}", host);
let path = req.uri();
debug!("path: {:?}", path);
let url = format!("https://{}{}", host, path);
if url.chars().any(|c| c == '\n' || c == '\r') {
return bad_request();
}
HttpResponse::MovedPermanently()
.header("Location", url)
.body(REDIRECT)
}
#[get("/.well-known/acme-challenge/{chall}")]
async fn acme(token: web::Path<String>, req: HttpRequest) -> impl Responder {
debug!("REQ: {:?}", req);
info!("acme: {:?}", token);
if !chall::valid_token(&token) {
return bad_request();
}
let path = Path::new("challs").join(token.as_ref());
debug!("Reading challenge proof: {:?}", path);
if let Ok(proof) = fs::read(path) {
HttpResponse::Ok().body(proof)
} else {
not_found()
}
}
#[actix_rt::main]
pub async fn spawn(socket: TcpListener) -> Result<()> {
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.service(acme)
.service(redirect)
})
.listen(socket)
.context("Failed to bind socket")?
.run()
.await
.context("Failed to start http daemon")?;
Ok(())
}
pub fn run(config: Config, args: DaemonArgs) -> Result<()> {
env::set_current_dir(&config.chall_dir)?;
let socket = TcpListener::bind(&args.bind_addr).context("Failed to bind socket")?;
sandbox::init(&args).context("Failed to drop privileges")?;
spawn(socket)
}