2020-10-30 23:02:57 +00:00
|
|
|
use std::{
|
|
|
|
error::Error,
|
2020-10-30 23:36:32 +00:00
|
|
|
path::PathBuf,
|
2020-10-30 23:02:57 +00:00
|
|
|
sync::Arc,
|
|
|
|
time::Duration,
|
|
|
|
};
|
|
|
|
|
2020-11-06 05:03:33 +00:00
|
|
|
use futures::FutureExt;
|
2020-10-31 02:31:03 +00:00
|
|
|
use handlebars::Handlebars;
|
2020-10-30 23:02:57 +00:00
|
|
|
use hyper::{
|
|
|
|
StatusCode,
|
|
|
|
};
|
|
|
|
use reqwest::Client;
|
2020-11-02 03:34:50 +00:00
|
|
|
use serde::Deserialize;
|
2020-10-30 23:02:57 +00:00
|
|
|
use tokio::{
|
2020-11-06 05:03:33 +00:00
|
|
|
sync::oneshot,
|
2020-10-30 23:02:57 +00:00
|
|
|
time::delay_for,
|
|
|
|
};
|
|
|
|
|
2020-11-27 00:03:11 +00:00
|
|
|
use ptth_core::{
|
2020-11-02 04:07:55 +00:00
|
|
|
http_serde,
|
2020-11-08 16:00:31 +00:00
|
|
|
prelude::*,
|
2020-11-02 04:07:55 +00:00
|
|
|
};
|
2020-10-30 23:02:57 +00:00
|
|
|
|
2020-11-29 18:50:51 +00:00
|
|
|
pub mod errors;
|
2020-10-31 01:35:39 +00:00
|
|
|
pub mod file_server;
|
2020-11-26 23:51:10 +00:00
|
|
|
pub mod load_toml;
|
2020-10-30 23:02:57 +00:00
|
|
|
|
2020-11-26 23:41:32 +00:00
|
|
|
// Thanks to https://github.com/robsheldon/bad-passwords-index
|
|
|
|
|
|
|
|
const BAD_PASSWORDS: &[u8] = include_bytes! ("bad_passwords.txt");
|
|
|
|
|
|
|
|
pub fn password_is_bad (mut password: String) -> bool {
|
|
|
|
password.make_ascii_lowercase ();
|
|
|
|
|
|
|
|
let ac = aho_corasick::AhoCorasick::new (&[
|
|
|
|
password
|
|
|
|
]);
|
|
|
|
|
|
|
|
ac.find (BAD_PASSWORDS).is_some ()
|
|
|
|
}
|
|
|
|
|
2020-11-02 04:07:55 +00:00
|
|
|
struct ServerState {
|
2020-11-02 14:41:22 +00:00
|
|
|
config: Config,
|
2020-11-02 04:07:55 +00:00
|
|
|
handlebars: Handlebars <'static>,
|
2020-11-10 00:44:21 +00:00
|
|
|
server_info: file_server::ServerInfo,
|
2020-11-02 04:07:55 +00:00
|
|
|
client: Client,
|
2020-11-08 15:01:15 +00:00
|
|
|
hidden_path: Option <PathBuf>,
|
2020-11-02 04:07:55 +00:00
|
|
|
}
|
|
|
|
|
2020-10-31 02:31:03 +00:00
|
|
|
async fn handle_req_resp <'a> (
|
2020-11-06 20:55:55 +00:00
|
|
|
state: &Arc <ServerState>,
|
2020-10-30 23:02:57 +00:00
|
|
|
req_resp: reqwest::Response
|
2020-11-08 03:16:13 +00:00
|
|
|
) -> Result <(), ()> {
|
2020-10-30 23:02:57 +00:00
|
|
|
//println! ("Step 1");
|
|
|
|
|
2020-10-31 02:31:03 +00:00
|
|
|
let body = req_resp.bytes ().await.unwrap ();
|
2020-11-02 02:07:46 +00:00
|
|
|
let wrapped_reqs: Vec <http_serde::WrappedRequest> = match rmp_serde::from_read_ref (&body)
|
2020-10-30 23:02:57 +00:00
|
|
|
{
|
|
|
|
Ok (x) => x,
|
2020-11-06 20:55:55 +00:00
|
|
|
Err (e) => {
|
|
|
|
error! ("Can't parse wrapped requests: {:?}", e);
|
2020-11-08 03:16:13 +00:00
|
|
|
return Err (());
|
2020-11-06 20:55:55 +00:00
|
|
|
},
|
2020-10-30 23:02:57 +00:00
|
|
|
};
|
|
|
|
|
2020-11-06 20:55:55 +00:00
|
|
|
debug! ("Unwrapped {} requests", wrapped_reqs.len ());
|
|
|
|
|
2020-11-02 02:07:46 +00:00
|
|
|
for wrapped_req in wrapped_reqs.into_iter () {
|
2020-11-02 04:07:55 +00:00
|
|
|
let state = state.clone ();
|
2020-11-02 02:07:46 +00:00
|
|
|
|
|
|
|
tokio::spawn (async move {
|
|
|
|
let (req_id, parts) = (wrapped_req.id, wrapped_req.req);
|
|
|
|
|
2020-11-06 20:55:55 +00:00
|
|
|
debug! ("Handling request {}", req_id);
|
|
|
|
|
2020-11-08 15:53:09 +00:00
|
|
|
let default_root = PathBuf::from ("./");
|
|
|
|
let file_server_root: &std::path::Path = state.config.file_server_root
|
|
|
|
.as_ref ()
|
|
|
|
.unwrap_or (&default_root);
|
|
|
|
|
|
|
|
let response = file_server::serve_all (
|
|
|
|
&state.handlebars,
|
2020-11-10 00:44:21 +00:00
|
|
|
&state.server_info,
|
2020-11-08 15:53:09 +00:00
|
|
|
file_server_root,
|
|
|
|
parts.method,
|
|
|
|
&parts.uri,
|
|
|
|
&parts.headers,
|
2020-11-25 00:16:14 +00:00
|
|
|
state.hidden_path.as_deref ()
|
2020-11-08 15:53:09 +00:00
|
|
|
).await;
|
2020-11-02 02:07:46 +00:00
|
|
|
|
2020-11-02 04:07:55 +00:00
|
|
|
let mut resp_req = state.client
|
2020-11-02 19:17:22 +00:00
|
|
|
.post (&format! ("{}/http_response/{}", state.config.relay_url, req_id))
|
2020-11-27 00:20:18 +00:00
|
|
|
.header (ptth_core::PTTH_MAGIC_HEADER, base64::encode (rmp_serde::to_vec (&response.parts).unwrap ()));
|
2020-11-02 02:07:46 +00:00
|
|
|
|
2020-11-06 18:47:04 +00:00
|
|
|
if let Some (length) = response.content_length {
|
|
|
|
resp_req = resp_req.header ("Content-Length", length.to_string ());
|
|
|
|
}
|
2020-11-02 02:07:46 +00:00
|
|
|
if let Some (body) = response.body {
|
|
|
|
resp_req = resp_req.body (reqwest::Body::wrap_stream (body));
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:47:04 +00:00
|
|
|
let req = resp_req.build ().unwrap ();
|
|
|
|
|
2020-11-06 20:55:55 +00:00
|
|
|
debug! ("{:?}", req.headers ());
|
2020-11-06 18:47:04 +00:00
|
|
|
|
2020-11-02 02:07:46 +00:00
|
|
|
//println! ("Step 6");
|
2020-11-06 18:47:04 +00:00
|
|
|
match state.client.execute (req).await {
|
2020-11-06 20:55:55 +00:00
|
|
|
Ok (r) => {
|
|
|
|
let status = r.status ();
|
|
|
|
let text = r.text ().await.unwrap ();
|
|
|
|
debug! ("{:?} {:?}", status, text);
|
|
|
|
},
|
2020-11-06 23:43:52 +00:00
|
|
|
Err (e) => {
|
|
|
|
if e.is_request () {
|
|
|
|
warn! ("Error while POSTing response. Client probably hung up.");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
error! ("Err: {:?}", e);
|
|
|
|
}
|
|
|
|
},
|
2020-11-02 02:07:46 +00:00
|
|
|
}
|
2020-11-06 18:47:04 +00:00
|
|
|
|
2020-11-02 02:07:46 +00:00
|
|
|
});
|
2020-10-30 23:02:57 +00:00
|
|
|
}
|
2020-11-08 03:16:13 +00:00
|
|
|
|
|
|
|
Ok (())
|
2020-10-30 23:02:57 +00:00
|
|
|
}
|
|
|
|
|
2020-11-02 03:34:50 +00:00
|
|
|
#[derive (Default, Deserialize)]
|
|
|
|
pub struct ConfigFile {
|
|
|
|
pub name: String,
|
|
|
|
pub api_key: String,
|
2020-11-02 14:41:22 +00:00
|
|
|
pub relay_url: String,
|
|
|
|
pub file_server_root: Option <PathBuf>,
|
|
|
|
}
|
|
|
|
|
2020-11-24 23:56:43 +00:00
|
|
|
impl ConfigFile {
|
|
|
|
pub fn tripcode (&self) -> String {
|
|
|
|
base64::encode (blake3::hash (self.api_key.as_bytes ()).as_bytes ())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 14:41:22 +00:00
|
|
|
#[derive (Default)]
|
|
|
|
pub struct Config {
|
|
|
|
pub relay_url: String,
|
|
|
|
pub file_server_root: Option <PathBuf>,
|
2020-11-02 03:34:50 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 03:44:32 +00:00
|
|
|
pub async fn run_server (
|
2020-11-06 03:35:55 +00:00
|
|
|
config_file: ConfigFile,
|
2020-11-08 15:01:15 +00:00
|
|
|
shutdown_oneshot: oneshot::Receiver <()>,
|
2020-11-18 23:24:47 +00:00
|
|
|
hidden_path: Option <PathBuf>,
|
|
|
|
asset_root: Option <PathBuf>
|
2020-11-06 03:35:55 +00:00
|
|
|
)
|
2020-11-02 03:34:50 +00:00
|
|
|
-> Result <(), Box <dyn Error>>
|
|
|
|
{
|
2020-11-25 00:16:14 +00:00
|
|
|
let asset_root = asset_root.unwrap_or_else (PathBuf::new);
|
2020-11-18 23:24:47 +00:00
|
|
|
|
2020-11-02 03:34:50 +00:00
|
|
|
use std::convert::TryInto;
|
|
|
|
|
2020-11-26 23:41:32 +00:00
|
|
|
if password_is_bad (config_file.api_key.clone ()) {
|
2020-11-02 14:23:08 +00:00
|
|
|
panic! ("API key is too weak, server can't use it");
|
|
|
|
}
|
|
|
|
|
2020-11-10 00:44:21 +00:00
|
|
|
let server_info = file_server::ServerInfo {
|
|
|
|
server_name: config_file.name.clone (),
|
|
|
|
};
|
|
|
|
|
|
|
|
info! ("Server name is {}", config_file.name);
|
2020-11-24 23:56:43 +00:00
|
|
|
info! ("Tripcode is {}", config_file.tripcode ());
|
2020-11-02 03:34:50 +00:00
|
|
|
|
|
|
|
let mut headers = reqwest::header::HeaderMap::new ();
|
|
|
|
headers.insert ("X-ApiKey", config_file.api_key.try_into ().unwrap ());
|
|
|
|
|
2020-11-02 04:07:55 +00:00
|
|
|
let client = Client::builder ()
|
2020-11-02 03:34:50 +00:00
|
|
|
.default_headers (headers)
|
2020-11-06 23:43:52 +00:00
|
|
|
.timeout (Duration::from_secs (40))
|
2020-11-02 04:07:55 +00:00
|
|
|
.build ().unwrap ();
|
2020-11-18 23:24:47 +00:00
|
|
|
let handlebars = file_server::load_templates (&asset_root).expect ("Can't load Handlebars templates");
|
2020-11-02 04:07:55 +00:00
|
|
|
|
|
|
|
let state = Arc::new (ServerState {
|
2020-11-02 14:41:22 +00:00
|
|
|
config: Config {
|
|
|
|
relay_url: config_file.relay_url,
|
|
|
|
file_server_root: config_file.file_server_root,
|
|
|
|
},
|
2020-11-02 04:07:55 +00:00
|
|
|
handlebars,
|
2020-11-10 00:44:21 +00:00
|
|
|
server_info,
|
2020-11-02 04:07:55 +00:00
|
|
|
client,
|
2020-11-08 15:01:15 +00:00
|
|
|
hidden_path,
|
2020-11-02 04:07:55 +00:00
|
|
|
});
|
2020-10-31 02:31:03 +00:00
|
|
|
|
2020-10-30 23:02:57 +00:00
|
|
|
let mut backoff_delay = 0;
|
2020-11-06 05:03:33 +00:00
|
|
|
let mut shutdown_oneshot = shutdown_oneshot.fuse ();
|
2020-10-30 23:02:57 +00:00
|
|
|
|
|
|
|
loop {
|
2020-11-06 23:43:52 +00:00
|
|
|
// TODO: Extract loop body to function?
|
|
|
|
|
2020-10-30 23:02:57 +00:00
|
|
|
if backoff_delay > 0 {
|
2020-11-06 05:03:33 +00:00
|
|
|
let mut delay = delay_for (Duration::from_millis (backoff_delay)).fuse ();
|
|
|
|
|
|
|
|
if futures::select! (
|
|
|
|
_ = delay => false,
|
|
|
|
_ = shutdown_oneshot => true,
|
|
|
|
) {
|
|
|
|
info! ("Received graceful shutdown");
|
2020-11-06 03:35:55 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 20:55:55 +00:00
|
|
|
debug! ("http_listen");
|
2020-11-06 03:35:55 +00:00
|
|
|
|
2020-11-06 05:03:33 +00:00
|
|
|
let req_req = state.client.get (&format! ("{}/http_listen/{}", state.config.relay_url, config_file.name)).send ();
|
2020-11-02 03:34:50 +00:00
|
|
|
|
|
|
|
let err_backoff_delay = std::cmp::min (30_000, backoff_delay * 2 + 500);
|
2020-10-30 23:02:57 +00:00
|
|
|
|
2020-11-06 05:03:33 +00:00
|
|
|
let req_req = futures::select! {
|
|
|
|
r = req_req.fuse () => r,
|
|
|
|
_ = shutdown_oneshot => {
|
|
|
|
info! ("Received graceful shutdown");
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let req_resp = match req_req {
|
2020-10-30 23:02:57 +00:00
|
|
|
Err (e) => {
|
2020-11-06 23:43:52 +00:00
|
|
|
if e.is_timeout () {
|
|
|
|
error! ("Client-side timeout. Is an overly-aggressive firewall closing long-lived connections? Is the network flakey?");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
error! ("Err: {:?}", e);
|
|
|
|
if backoff_delay != err_backoff_delay {
|
|
|
|
error! ("Non-timeout issue, increasing backoff_delay");
|
|
|
|
backoff_delay = err_backoff_delay;
|
|
|
|
}
|
|
|
|
}
|
2020-10-30 23:02:57 +00:00
|
|
|
continue;
|
|
|
|
},
|
|
|
|
Ok (r) => {
|
|
|
|
r
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2020-11-06 23:43:52 +00:00
|
|
|
if req_resp.status () == StatusCode::NO_CONTENT {
|
|
|
|
debug! ("http_listen long poll timed out on the server, good.");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if req_resp.status () != StatusCode::OK {
|
2020-11-06 20:55:55 +00:00
|
|
|
error! ("{}", req_resp.status ());
|
|
|
|
let body = req_resp.bytes ().await.unwrap ();
|
|
|
|
let body = String::from_utf8 (body.to_vec ()).unwrap ();
|
|
|
|
error! ("{}", body);
|
2020-11-06 23:43:52 +00:00
|
|
|
if backoff_delay != err_backoff_delay {
|
|
|
|
error! ("Non-timeout issue, increasing backoff_delay");
|
|
|
|
backoff_delay = err_backoff_delay;
|
|
|
|
}
|
2020-11-02 03:34:50 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-11-06 20:55:55 +00:00
|
|
|
// Unpack the requests, spawn them into new tasks, then loop back
|
|
|
|
// around.
|
2020-10-30 23:02:57 +00:00
|
|
|
|
2020-11-08 03:16:13 +00:00
|
|
|
if handle_req_resp (&state, req_resp).await.is_err () {
|
|
|
|
backoff_delay = err_backoff_delay;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if backoff_delay != 0 {
|
|
|
|
debug! ("backoff_delay = 0");
|
|
|
|
backoff_delay = 0;
|
|
|
|
}
|
2020-10-30 23:02:57 +00:00
|
|
|
}
|
2020-11-06 03:35:55 +00:00
|
|
|
|
|
|
|
info! ("Exiting");
|
|
|
|
|
|
|
|
Ok (())
|
2020-10-30 23:02:57 +00:00
|
|
|
}
|
2020-11-24 23:56:43 +00:00
|
|
|
|
|
|
|
#[cfg (test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tripcode_algo () {
|
|
|
|
let config = ConfigFile {
|
|
|
|
name: "TestName".into (),
|
|
|
|
api_key: "PlaypenCausalPlatformCommodeImproveCatalyze".into (),
|
|
|
|
relay_url: "".into (),
|
|
|
|
file_server_root: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq! (config.tripcode (), "A9rPwZyY89Ag4TJjMoyYA2NeGOm99Je6rq1s0rg8PfY=".to_string ());
|
|
|
|
}
|
2020-11-26 23:41:32 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn check_bad_passwords () {
|
|
|
|
for pw in &[
|
|
|
|
"",
|
|
|
|
" ",
|
|
|
|
"user",
|
|
|
|
"password",
|
|
|
|
"pAsSwOrD",
|
|
|
|
"secret",
|
|
|
|
"123123",
|
|
|
|
] {
|
|
|
|
assert! (password_is_bad (pw.to_string ()));
|
|
|
|
}
|
|
|
|
|
|
|
|
use rand::prelude::*;
|
|
|
|
|
|
|
|
let mut entropy = [0u8; 32];
|
|
|
|
thread_rng ().fill_bytes (&mut entropy);
|
|
|
|
let good_password = base64::encode (entropy);
|
|
|
|
|
|
|
|
assert! (! password_is_bad (good_password));
|
|
|
|
}
|
2020-11-24 23:56:43 +00:00
|
|
|
}
|