diff --git a/Cargo.lock b/Cargo.lock index c4c8fd7..b377333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,17 +363,6 @@ dependencies = [ "syn", ] -[[package]] -name = "actix-web-middleware-redirect-https" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce1464786203c29120f6d1e4dcdc9c3975506e70b89c3a56c5389f265256dc6" -dependencies = [ - "actix-service", - "actix-web", - "futures", -] - [[package]] name = "actix_derive" version = "0.5.0" @@ -409,6 +398,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +dependencies = [ + "backtrace", +] + [[package]] name = "ascii" version = "0.9.3" @@ -623,26 +621,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.0.10" +version = "4.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" dependencies = [ "atty", "bitflags", "clap_derive", - "indexmap", - "lazy_static", - "os_str_bytes", + "clap_lex", + "once_cell", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.0.6" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -651,6 +647,15 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_lex" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "combine" version = "3.8.1" @@ -1292,7 +1297,7 @@ dependencies = [ "actix-ratelimit", "actix-web", "actix-web-actors", - "actix-web-middleware-redirect-https", + "anyhow", "base64 0.13.0", "clap", "env_logger", @@ -1456,9 +1461,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -1498,9 +1503,6 @@ name = "os_str_bytes" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] [[package]] name = "parking_lot" @@ -1635,11 +1637,11 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2144,12 +2146,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" - [[package]] name = "thiserror" version = "1.0.30" @@ -2407,6 +2403,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + [[package]] name = "unicode-normalization" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index c54022b..17a4c8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,10 @@ actix-web-actors = "3" actix-broker = "0.3" actix-files = "0.5" actix-ratelimit = "0.3" -actix-web-middleware-redirect-https = "3.0.1" env_logger = "0.9" openssl = "0.10" -clap = { version = "3.0", features = ["derive"] } +clap = { version = "4", features = ["derive"] } lazy_static = "1.4" serde = "1.0" @@ -36,3 +35,4 @@ base64 = "0.13" log = "0.4.17" rustls = "0.18.0" rustls-pemfile = "1.0.2" +anyhow = { version = "1.0.71", features = ["backtrace"] } diff --git a/etc/config.json.example b/etc/config.json.example index 1af44b6..357ad21 100644 --- a/etc/config.json.example +++ b/etc/config.json.example @@ -1,7 +1,10 @@ { + "bind_address": "Address to start server. (For example: localhost:8000)", + "static_dir_path": "Directory to static html files to serve", + "logger_pattern": "https://docs.rs/actix-web/latest/actix_web/middleware/struct.Logger.html (For example: %t [%a] %s %{User-Agent}i %r)", "salt": "PUT SOME TEXT FOR SALT", "tenor_key": "GET API KEY FROM https://tenor.com AND PASTE HERE", - "ssl_cert": "", - "ssl_key": "", - "logger_pattern": "%t [%a] %s %{User-Agent}i %r" + "allow_ssl": false, + "ssl_cert": "Path to ssl certificate", + "ssl_key": "Path to ssl key" } diff --git a/src/config.rs b/src/config.rs index a40c0d0..35b6765 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,170 +15,45 @@ along with Lupt. If not, see */ -use clap::{ErrorKind as ClapErrorKind, IntoApp, Parser}; -// use clap::{App, Arg}; -use serde::{Deserialize, Serialize}; -use std::{ - fs::File, - io::{BufReader, ErrorKind as IOErrorKind}, - path::PathBuf, -}; +use clap::Parser; +use serde::Deserialize; +use std::{fs::File, io::BufReader, path::PathBuf}; #[derive(Parser)] #[clap(author, version, about, long_about = None)] -pub(crate) struct Config { - /// Path of directory with index.html - #[clap(short, long, parse(from_os_str), value_name = "FILE")] - pub(crate) static_path: PathBuf, - - /// Address to bind for server - #[clap(short, long, value_name = "ADDRESS")] - pub(crate) bind_address: String, - - /// Port to bind for http server - #[clap(short, long, value_name = "PORT")] - pub(crate) port: String, - - /// Port to bind for https (ssl) server - #[clap(short = 'o', long, value_name = "PORT")] - pub(crate) port_ssl: Option, - +pub(crate) struct Args { /// Path to config file - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[clap(short, long, value_name = "FILE")] pub(crate) config_file: PathBuf, } -#[derive(Serialize, Deserialize)] +#[derive(Deserialize)] pub(crate) struct ConfigFile { + pub(crate) bind_address: String, + + pub(crate) static_dir_path: PathBuf, + pub(crate) logger_pattern: String, + pub(crate) salt: String, pub(crate) tenor_key: Option, + + pub(crate) allow_ssl: Option, pub(crate) ssl_cert: Option, pub(crate) ssl_key: Option, - pub(crate) logger_pattern: String, } -const HELP_CONFIG_FILE: &'static str = "Config File is corrupt. - -Config file must have following fields - - salt: Salt for hashing - - tenor_key: Key of tenor gif api - - ssl_cert: Path to certificate of ssl - - ssl_key: Path to private key of ssl - - logger_pattern: Pattern to make log according to Actix Logger"; - -pub(crate) fn generate() -> (Config, ConfigFile) { - let config: Config = Config::parse(); - let config_file = File::open(&config.config_file); - if let Err(e) = config_file { - let mut app = Config::into_app(); - match e.kind() { - IOErrorKind::NotFound => app - .error(ClapErrorKind::InvalidValue, "Error: Config file is missing") - .exit(), - _ => app - .error( - ClapErrorKind::InvalidValue, - format!("Error(Config File): {}", e.to_string()), - ) - .exit(), - } - } - - let reader = BufReader::new(config_file.unwrap()); - let json: ConfigFile = match serde_json::from_reader(reader) { - Ok(read) => read, - Err(e) => { - let mut app = Config::into_app(); - app.error( - ClapErrorKind::InvalidValue, - format!( - "Error(Config File): {}\n\n{}", - e.to_string(), - HELP_CONFIG_FILE - ), - ) - .exit(); - } - }; - - (config, json) +pub(crate) fn generate() -> ConfigFile { + let args: Args = Args::parse(); + let config_file = File::open(&args.config_file) + .map_err(|e| anyhow!(e)) + .expect("Failed to open config file!"); + let reader = BufReader::new(config_file); + let json: ConfigFile = serde_json::from_reader(reader) + .map_err(|e| anyhow!(e)) + .expect("Failed to open config file!"); + json } -// impl Config { -// pub fn new() -> Self { -// let matches = App::new("Lupt (लुप्त)") -// .version(env!("CARGO_PKG_VERSION")) -// .author(env!("CARGO_PKG_AUTHORS")) -// .about(env!("CARGO_PKG_DESCRIPTION")) -// .arg( -// Arg::with_name("bind_address") -// .short("a") -// .long("bind_address") -// .value_name("ADDRESS") -// .help("Address to bind for server") -// .required(true) -// .takes_value(true), -// ) -// .arg( -// Arg::with_name("port") -// .short("p") -// .long("port") -// .value_name("PORT") -// .help("Port to bind for server") -// .required(true) -// .takes_value(true), -// ) -// .arg( -// Arg::with_name("port_x") -// .short("x") -// .long("port_x") -// .value_name("PORT") -// .help("Port to bind for http if ssl is enabled to redirect to https") -// .required(false) -// .takes_value(true), -// ) -// .arg( -// Arg::with_name("static_path") -// .short("s") -// .long("static_path") -// .value_name("DIR") -// .help("Path of directory with index.html") -// .required(true) -// .takes_value(true), -// ) -// .arg( -// Arg::with_name("config") -// .short("c") -// .long("config") -// .value_name("FILE") -// .help("Path to config file") -// .required(true) -// .takes_value(true), -// ) -// .get_matches(); - -// let conf = matches.value_of("config").unwrap().to_owned(); -// let conf = std::fs::read_to_string(conf).expect("Failed to read config"); - -// let config = serde_json::from_str::(&conf).expect( -// r" -// Config File is corrupt. - -// Config file must have following fields -// - salt: Salt for hashing -// - tenor_key: Key of tenor gif api -// - ssl_cert: Path to certificate of ssl -// - ssl_key: Path to private key of ssl -// - logger_pattern: Pattern to make log according to Actix Logger -// ", -// ); - -// Config { -// static_path: matches.value_of("static_path").unwrap().to_owned(), -// bind_address: matches.value_of("bind_address").unwrap().to_owned(), -// port: matches.value_of("port").unwrap().to_owned(), -// port_x: matches.value_of("port_x").unwrap_or("").to_owned(), -// config, -// } -// } -// } +lazy_static! { + pub(crate) static ref CONFIG: ConfigFile = generate(); +} diff --git a/src/main.rs b/src/main.rs index 4005875..09c9a68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,9 @@ #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate anyhow; + use actix_files as fs; use actix_ratelimit::{MemoryStore, MemoryStoreActor, RateLimiter}; use actix_web::{ @@ -35,10 +38,9 @@ use actix_web::{ web, App, Error, HttpRequest, HttpResponse, HttpServer, }; use actix_web_actors::ws; -use log::error; -// use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslConnector, SslFiletype, SslMethod}; +use config::CONFIG; use rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig}; -use std::{fs::File, sync::RwLock}; +use std::fs::File; use ws_sansad::WsSansad; mod broker_messages; @@ -48,95 +50,73 @@ mod errors; mod validator; mod ws_sansad; -lazy_static! { - pub static ref SALT: RwLock = RwLock::new("".to_owned()); - pub static ref TENOR_API_KEY: RwLock> = RwLock::new(None); -} - #[actix_web::main] async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "info"); env_logger::init(); - let (config, config_file) = config::generate(); - error!("Hello"); - *SALT.write().unwrap() = config_file.salt; - if let Some(key) = config_file.tenor_key { - *TENOR_API_KEY.write().unwrap() = Some(key); - } + lazy_static::initialize(&CONFIG); - let rustls_server_config = if config_file.ssl_key.is_some() && config_file.ssl_cert.is_some() { - gen_rustls_server_config(config_file.ssl_key.unwrap(), config_file.ssl_cert.unwrap()) - } else { - None - }; - - let logger_pattern = config_file.logger_pattern; - let static_path = config.static_path; - - let server = HttpServer::new(move || { + let main_server = HttpServer::new(move || { let mut app = App::new() .wrap( RateLimiter::new(MemoryStoreActor::from(MemoryStore::new().clone()).start()) .with_interval(std::time::Duration::from_secs(60)) .with_max_requests(200), ) - .wrap(Logger::new(&logger_pattern)) + .wrap(Logger::new(&CONFIG.logger_pattern)) .service(web::resource("/ws/").route(web::get().to(ws_index))); - if TENOR_API_KEY.read().unwrap().is_some() { - app = app - .service(web::resource("/gif/{pos}/").route(web::get().to(gif))) - .service(web::resource("/gif/{pos}/{query}").route(web::get().to(gif))); + if let Some(key) = &CONFIG.tenor_key { + if key.len() > 0 { + app = app + .service(web::resource("/gif/{pos}/").route(web::get().to(gif))) + .service(web::resource("/gif/{pos}/{query}").route(web::get().to(gif))); + } } - app = app.service(fs::Files::new("/", &static_path).index_file("index.html")); + app = app.service(fs::Files::new("/", &CONFIG.static_dir_path).index_file("index.html")); app }); - if rustls_server_config.is_some() && config.port_ssl.is_some() { - let port = config.port.clone(); - let port_ssl = config.port_ssl.clone().unwrap(); - let redirect_server = HttpServer::new(move || { - App::new() - .wrap( - RateLimiter::new(MemoryStoreActor::from(MemoryStore::new().clone()).start()) - .with_interval(std::time::Duration::from_secs(60)) - .with_max_requests(100), - ) - .wrap( - actix_web_middleware_redirect_https::RedirectHTTPS::with_replacements(&[( - port.clone(), - port_ssl.clone(), - )]), - ) - .route( - "/", - web::get().to(|| { - HttpResponse::Ok() - .content_type("text/plain") - .body("Always HTTPS on non-default ports!") - }), - ) - }) - .bind(format!("{}:{}", config.bind_address, config.port))? - .run(); - let sc = rustls_server_config.unwrap(); - let server = server + let main_server = if CONFIG.allow_ssl.unwrap_or(false) { + main_server .bind_rustls( - format!("{}:{}", config.bind_address, config.port_ssl.unwrap()), - sc, + &CONFIG.bind_address, + gen_rustls_server_config( + CONFIG.ssl_key.clone().unwrap(), + CONFIG.ssl_cert.clone().unwrap(), + ), )? - .run(); - - tokio::try_join!(redirect_server, server)?; - } else { - server - .bind(format!("{}:{}", config.bind_address, config.port))? .run() - .await?; - } - Ok(()) + } else { + main_server.bind(&CONFIG.bind_address)?.run() + }; + + main_server.await +} + +fn gen_rustls_server_config(key: String, cert: String) -> ServerConfig { + let mut br = std::io::BufReader::new(File::open(cert).unwrap()); + let certs = rustls_pemfile::certs(&mut br) + .unwrap() + .iter() + .map(|a| Certificate(a.to_owned())) + .collect::>(); + + let mut br = std::io::BufReader::new(File::open(key).unwrap()); + let private_key = rustls_pemfile::ec_private_keys(&mut br).unwrap_or( + rustls_pemfile::rsa_private_keys(&mut br) + .unwrap_or(rustls_pemfile::pkcs8_private_keys(&mut br).unwrap()), + ); + + let private_key = private_key.get(0).unwrap(); + + let private_key = PrivateKey(private_key.to_owned()); + + let mut config = ServerConfig::new(NoClientAuth::new()); + config.set_single_cert(certs, private_key).unwrap(); + config } async fn ws_index(req: HttpRequest, stream: web::Payload) -> Result { @@ -154,23 +134,19 @@ async fn gif(req: HttpRequest) -> Result { .connector(Connector::new().finish()) .finish(); - let tenor_key = TENOR_API_KEY.read().unwrap(); - let key = match &*tenor_key { - Some(a) => a.as_str(), - None => panic!("No api key!"), - }; + let tenor_key = CONFIG.tenor_key.clone().unwrap(); let url = if name != "" { format!( "https://tenor.googleapis.com/v2/search?q={}&key={}&limit=20&media_filter=tinygif&pos={}", name.replace(" ", "+"), - key, + tenor_key, pos ) } else { format!( "https://tenor.googleapis.com/v2/featured?key={}&limit=20&media_filter=tinygif&pos={}", - key, pos + tenor_key, pos ) }; @@ -186,30 +162,3 @@ async fn gif(req: HttpRequest) -> Result { .content_type("application/json") .body(response)) } - -fn gen_rustls_server_config(key: String, cert: String) -> Option { - if key != "" && cert != "" { - let mut br = std::io::BufReader::new(File::open(cert).unwrap()); - let certs = rustls_pemfile::certs(&mut br) - .unwrap() - .iter() - .map(|a| Certificate(a.to_owned())) - .collect::>(); - - let mut br = std::io::BufReader::new(File::open(key).unwrap()); - let private_key = rustls_pemfile::ec_private_keys(&mut br).unwrap_or( - rustls_pemfile::rsa_private_keys(&mut br) - .unwrap_or(rustls_pemfile::pkcs8_private_keys(&mut br).unwrap()), - ); - - let private_key = private_key.get(0).unwrap(); - - let private_key = PrivateKey(private_key.to_owned()); - - let mut config = ServerConfig::new(NoClientAuth::new()); - config.set_single_cert(certs, private_key).unwrap(); - Some(config) - } else { - None - } -} diff --git a/src/ws_sansad/users.rs b/src/ws_sansad/users.rs index a3f1d3c..a9b2386 100644 --- a/src/ws_sansad/users.rs +++ b/src/ws_sansad/users.rs @@ -16,8 +16,8 @@ */ use super::*; +use crate::config::CONFIG; use sha2::{Digest, Sha224}; - impl WsSansad { /// Request to join to kaksh pub async fn join_kaksh(&mut self, val: Value) { @@ -55,7 +55,7 @@ impl WsSansad { return; } let mut hasher = Sha224::new(); - hasher.update(format!("{}{}", kunjika, crate::SALT.read().unwrap()).as_bytes()); + hasher.update(format!("{}{}", kunjika, CONFIG.salt).as_bytes()); let kunjika = base64::encode(hasher.finalize())[..8].to_owned(); // Name @@ -160,7 +160,7 @@ impl WsSansad { return; } let mut hasher = Sha224::new(); - hasher.update(format!("{}{}", kunjika, crate::SALT.read().unwrap()).as_bytes()); + hasher.update(format!("{}{}", kunjika, &CONFIG.salt).as_bytes()); let kunjika = base64::encode(hasher.finalize())[..8].to_owned(); // Name