diff --git a/Cargo.toml b/Cargo.toml index bd3fb7c..2470460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "binhost" -version = "0.3.2" +version = "0.3.3" edition = "2021" license = "MIT" repository = "https://github.com/ivabus/binhost" description = "HTTP server to easily serve files" [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } rocket = "0.5.0" serde = { version = "1.0", features = ["derive"] } sha2 = { version = "0.10" } -ed25519-compact = { version = "2.0.6", default-features = false, features = [ +ed25519-compact = { version = "2.1.1", default-features = false, features = [ "random", "self-verify", "std", ] } +once_cell = "1.19" diff --git a/src/main.rs b/src/main.rs index 5c359b5..fd8de32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +#![forbid(unsafe_code)] + #[macro_use] extern crate rocket; @@ -8,10 +10,12 @@ use std::time::Instant; use clap::Parser; use ed25519_compact::{KeyPair, Noise}; +use once_cell::sync::Lazy; use rocket::figment::Figment; use rocket::fs::{FileServer, NamedFile}; use rocket::http::Status; use rocket::response::content::RawText; +use rocket::tokio::sync::RwLock; use sha2::digest::FixedOutput; use sha2::Digest; @@ -19,17 +23,62 @@ use structs::*; mod structs; -static mut BINS: Option<(HashMap, Instant)> = None; -static mut MANIFEST: Option> = None; -static mut KEYPAIR: Option = None; +static BINS: Lazy, Instant)>> = + Lazy::new(|| RwLock::new((get_bins(&Args::parse()), Instant::now()))); +static KEYPAIR: Lazy = Lazy::new(|| { + println!("Generating keypair"); + let kp = KeyPair::generate(); + + println!( + "Keypair generated. Public key: {}", + kp.pk.iter().map(|x| format!("{:x}", x)).collect::>().join("") + ); + kp +}); +static MANIFEST: Lazy> = Lazy::new(|| { + let args = Args::parse(); + + println!("Generating manifest"); + let mut manifest: Vec = Vec::new(); + let mut bin_pub_key: Vec = KEYPAIR.pk.to_vec(); + manifest.append(&mut bin_pub_key); + let mut runners = 0; + + for element in std::fs::read_dir(args.runners_dir).unwrap() { + let en = element.unwrap(); + if en.path().is_file() { + let mut hasher = sha2::Sha256::new(); + hasher.update(std::fs::read(en.path()).unwrap().as_slice()); + let mut contents = Vec::from( + format!( + "{:x} {}\n", + hasher.finalize_fixed(), + en.path().file_name().unwrap().to_str().unwrap() + ) + .as_bytes(), + ); + runners += 1; + manifest.append(&mut contents); + } + } + let mut hasher = sha2::Sha256::new(); + hasher.update(&manifest); + println!( + "Manifest generated with {} runners and SHA256: {:x}", + runners, + hasher.finalize_fixed() + ); + manifest +}); static WEB_SH: &str = include_str!("../web.sh"); static HASH_CALCULATION_SH: &str = ""; -fn reload_bins(bins: (&mut HashMap, &mut Instant), args: &Args) { - if (Instant::now() - *bins.1).as_secs() > args.refresh { - *bins.0 = get_bins(args); - *bins.1 = Instant::now(); +async fn reload_bins(args: &Args) { + let (bins, time) = &mut *BINS.write().await; + if (Instant::now() - *time).as_secs() > args.refresh { + *bins = get_bins(args); + *time = Instant::now(); } } @@ -71,79 +120,64 @@ fn format_platform_list(bin: &Bin) -> String { async fn index() -> RawText { let args = Args::parse(); let mut ret = String::new(); - unsafe { - if let Some(manifest) = &MANIFEST { - let mut hasher = sha2::Sha256::new(); - hasher.update(manifest); - ret.push_str(&format!("Manifest hashsum: {:x}\n", hasher.finalize_fixed())); - } + + let mut hasher = sha2::Sha256::new(); + hasher.update(&*MANIFEST); + ret.push_str(&format!("Manifest hashsum: {:x}\n", hasher.finalize_fixed())); + + reload_bins(&args).await; + let (bins, _) = &*BINS.read().await; + + if bins.is_empty() { + return RawText(String::from("No binaries found")); } - unsafe { - if let Some((bins, time)) = &mut BINS { - reload_bins((bins, time), &args); - if bins.is_empty() { - return RawText(String::from("No binaries found")); - } - for (name, bin) in bins { - ret.push_str(&format!( - "- {} (platforms: {:?})\n", - name, - bin.platforms - .iter() - .map(|plat| format!("{}-{}", plat.system, plat.arch)) - .collect::>() - )) - } - } + for (name, bin) in bins { + ret.push_str(&format!( + "- {} (platforms: {:?})\n", + name, + bin.platforms + .iter() + .map(|plat| format!("{}-{}", plat.system, plat.arch)) + .collect::>() + )) } + RawText(ret) } #[get("/runner/manifest")] -async fn get_manifest() -> Vec { - unsafe { - match &MANIFEST { - Some(man) => man.clone(), - _ => unreachable!(), - } - } +async fn get_manifest<'a>() -> Vec { + let manifest = &*MANIFEST; + manifest.clone() } #[get("/")] async fn get_script(bin: &str) -> ScriptResponse { let args = Args::parse(); - unsafe { - if let Some((bins, time)) = &mut BINS { - reload_bins((bins, time), &args); - return match bins.get(bin) { - None => ScriptResponse::Status(Status::NotFound), - Some(bin) => { - let mut script = String::from(WEB_SH); - script = script - .replace("{{HASH_CALCULATION}}", HASH_CALCULATION_SH) - .replace("{{NAME}}", &bin.name) - .replace("{{PLATFORM_LIST}}", &format_platform_list(bin)) - .replace("{{EXTERNAL_ADDRESS}}", &args.url); - ScriptResponse::Text(RawText(script)) - } - }; + reload_bins(&args).await; + let (bins, _) = &*BINS.read().await; + match bins.get(bin) { + None => ScriptResponse::Status(Status::NotFound), + Some(bin) => { + let mut script = String::from(WEB_SH); + script = script + .replace("{{HASH_CALCULATION}}", HASH_CALCULATION_SH) + .replace("{{NAME}}", &bin.name) + .replace("{{PLATFORM_LIST}}", &format_platform_list(bin)) + .replace("{{EXTERNAL_ADDRESS}}", &args.url); + ScriptResponse::Text(RawText(script)) } } - ScriptResponse::Status(Status::NotFound) } #[get("//platforms")] async fn get_platforms(bin: &str) -> ScriptResponse { let args = Args::parse(); - unsafe { - if let Some((bins, time)) = &mut BINS { - reload_bins((bins, time), &args); - return match bins.get(bin) { - None => ScriptResponse::Status(Status::NotFound), - Some(bin) => ScriptResponse::Text(RawText(format_platform_list(bin))), - }; - } + reload_bins(&args).await; + let (bins, _) = &*BINS.read().await; + match bins.get(bin) { + None => ScriptResponse::Status(Status::NotFound), + Some(bin) => ScriptResponse::Text(RawText(format_platform_list(bin))), } - ScriptResponse::Status(Status::NotFound) } #[get("/bin///")] @@ -178,12 +212,7 @@ async fn get_binary_sign(bin: &str, platform: &str, arch: &str) -> SignResponse Ok(f) => f, Err(_) => return SignResponse::Status(Status::BadRequest), }; - let keypair = unsafe { - match &KEYPAIR { - Some(pair) => pair, - _ => unreachable!(), - } - }; + let keypair = &*KEYPAIR; SignResponse::Bin(keypair.sk.sign(file.as_slice(), Some(Noise::generate())).as_slice().to_vec()) } #[launch] @@ -194,47 +223,7 @@ async fn rocket() -> _ { std::process::exit(1); } - unsafe { - BINS = Some((get_bins(&args), Instant::now())); - println!("Generating keypair"); - let kp = KeyPair::generate(); - KEYPAIR = Some(kp.clone()); - println!( - "Keypair generated. Public key: {}", - &kp.pk.iter().map(|x| format!("{:x}", x)).collect::>().join("") - ); - println!("Generating manifest"); - let mut manifest: Vec = Vec::new(); - let mut bin_pub_key: Vec = kp.pk.to_vec(); - manifest.append(&mut bin_pub_key); - let mut runners = 0; - - for element in std::fs::read_dir(args.runners_dir).unwrap() { - let en = element.unwrap(); - if en.path().is_file() { - let mut hasher = sha2::Sha256::new(); - hasher.update(std::fs::read(en.path()).unwrap().as_slice()); - let mut contents = Vec::from( - format!( - "{:x} {}\n", - hasher.finalize_fixed(), - en.path().file_name().unwrap().to_str().unwrap() - ) - .as_bytes(), - ); - runners += 1; - manifest.append(&mut contents); - } - } - let mut hasher = sha2::Sha256::new(); - hasher.update(&manifest); - println!( - "Manifest generated with {} runners and SHA256: {:x}", - runners, - hasher.finalize_fixed() - ); - MANIFEST = Some(manifest) - }; + let _ = &*BINS.read().await; let figment = Figment::from(rocket::Config::default()) .merge(("ident", "Binhost"))