diff --git a/.gitignore b/.gitignore index 975aeaf..ac1f268 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ notes.txt *.zip */monero-x86_64-linux-gnu-v0.18.2.2/** */i2p-zero-linux.v1.20/** +*.jar diff --git a/nevmes-core/src/i2p.rs b/nevmes-core/src/i2p.rs index ee6bfd3..7c1346c 100644 --- a/nevmes-core/src/i2p.rs +++ b/nevmes-core/src/i2p.rs @@ -1,6 +1,5 @@ use std::{fs, env, process::Command}; use log::{debug, error, info, warn}; -use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::{args, utils}; use clap::Parser; @@ -88,7 +87,15 @@ pub async fn start() { .expect("i2p-zero failed to start"); debug!("{:?}", output.stdout); find_tunnels().await; - { tokio::spawn(async { check_connection(true).await }); } + { + tokio::spawn(async move { + let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000); + loop { + tick.recv().unwrap(); + check_connection().await; + } + }); + } } fn create_tunnel() { @@ -133,26 +140,9 @@ pub fn get_destination() -> String { utils::empty_string() } -pub async fn get_proxy_status() -> HttpProxyStatus { - check_connection(false).await -} - -/// Ping i2pd tunnels local server for status -async fn check_connection(bg: bool) -> HttpProxyStatus { +pub async fn check_connection() -> HttpProxyStatus { let client: reqwest::Client = reqwest::Client::new(); let host: &str = "http://localhost:7657/tunnels"; - if bg { - let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000); - loop { - tick.recv().unwrap(); - return process_connection_info(client, host).await; - } - } else { - return process_connection_info(client, host).await; - } -} - -async fn process_connection_info(client: Client, host: &str) -> HttpProxyStatus { match client.get(host).send().await { Ok(response) => { // do some parsing here to check the status diff --git a/nevmes-core/src/lib.rs b/nevmes-core/src/lib.rs index 8f9ae3a..932ebd7 100644 --- a/nevmes-core/src/lib.rs +++ b/nevmes-core/src/lib.rs @@ -17,7 +17,11 @@ pub const NEVMES_JWT_SECRET_KEY: &str = "NEVMES_JWT_SECRET_KEY"; /// The latest monero release download pub const MONERO_RELEASE_VERSION: &str = "monero-linux-x64-v0.18.2.2.tar.bz2"; +pub const MONERO_RELEASE_HASH: &str = "186800de18f67cca8475ce392168aabeb5709a8f8058b0f7919d7c693786d56b"; /// The latest i2p-zero release version pub const I2P_ZERO_RELEASE_VERSION: &str = "v1.20"; - +pub const I2P_ZERO_RELEASH_HASH: &str = "7e7216b281624ec464b55217284017576d109eaba7b35f7e4994ae2a78634de7"; +/// The latest i2pd release version +pub const I2P_RELEASE_VERSION: &str = "2.2.1"; +pub const I2P_RELEASE_HASH: &str = "c9879b8f69ea13c758672c2fa083dc2e0abb289e0fc9a55af98f9f1795f82659"; // DO NOT EDIT BELOW THIS LINE diff --git a/nevmes-core/src/utils.rs b/nevmes-core/src/utils.rs index ab87780..a78be55 100644 --- a/nevmes-core/src/utils.rs +++ b/nevmes-core/src/utils.rs @@ -5,6 +5,10 @@ use crate::{args, db, i2p, message, models, monero, gpg, utils, reqres }; use log::{info, debug, error, warn}; use std::time::Duration; +/// Enum for selecting hash validation +#[derive(PartialEq)] +enum ExternalSoftware { I2P, I2PZero, XMR } + /// Handles the state for the installation manager popup pub struct Installations { pub xmr: bool, @@ -416,9 +420,32 @@ pub fn stage_cleanup(f: String) { /// software on their own. Note that software pull is over /// /// clearnet. TODO(c2m): trusted download locations over i2p. -pub async fn install_software(installations: Installations) { +pub async fn install_software(installations: Installations) -> bool { + let mut valid_i2p_hash = true; + let mut valid_i2p_zero_hash = true; + let mut valid_xmr_hash = true; if installations.i2p { info!("installing i2p"); + let i2p_version = crate::I2P_RELEASE_VERSION; + let i2p_jar = format!("i2pinstall_{}.jar", i2p_version); + let link = format!("https://download.i2p2.no/releases/{}/{}", i2p_version, i2p_jar); + let curl = std::process::Command::new("curl") + .args(["-O#", &link]) + .status(); + match curl { + Ok(curl_output) => { + debug!("{:?}", curl_output); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + let jar_output = std::process::Command::new("java") + .args(["-jar", &i2p_jar]) + .spawn() + .expect("i2p gui installation failed"); + debug!("{:?}", jar_output.stdout); + }, + _=> error!("i2p download failed") + } + valid_i2p_hash = validate_installation_hash(ExternalSoftware::I2P, &i2p_jar); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; } if installations.i2p_zero { info!("installing i2p-zero"); @@ -434,13 +461,15 @@ pub async fn install_software(installations: Installations) { debug!("{:?}", curl_output); tokio::time::sleep(std::time::Duration::from_secs(1)).await; let unzip_output = std::process::Command::new("unzip") - .arg(i2p_zero_zip) + .arg(&i2p_zero_zip) .spawn() .expect("i2p unzip failed"); debug!("{:?}", unzip_output.stdout); }, _=> error!("i2p-zero download failed") } + valid_i2p_zero_hash = validate_installation_hash(ExternalSoftware::I2PZero, &i2p_zero_zip); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; } if installations.xmr { info!("installing monero"); @@ -460,5 +489,29 @@ pub async fn install_software(installations: Installations) { }, _=> error!("monero download failed") } - } + valid_xmr_hash = validate_installation_hash(ExternalSoftware::XMR, &String::from(crate::MONERO_RELEASE_VERSION)); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + valid_i2p_hash && valid_i2p_zero_hash && valid_xmr_hash +} + +/// Linux specific hash validation using the command `sha256sum` +fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool { + debug!("validating hash"); + let expected_hash = if sw == ExternalSoftware::I2P { + String::from(crate::I2P_RELEASE_HASH) + } else if sw == ExternalSoftware::I2PZero { + String::from(crate::I2P_ZERO_RELEASH_HASH) + } else { + String::from(crate::MONERO_RELEASE_HASH) + }; + let sha_output = std::process::Command::new("sha256sum") + .arg(filename).output().expect("hash validation failed"); + let str_sha = String::from_utf8(sha_output.stdout).unwrap(); + let split1 = str_sha.split(" "); + let mut v: Vec = split1.map(|s| String::from(s)).collect(); + let actual_hash = v.remove(0); + debug!("actual hash: {}", actual_hash); + debug!("expected hash: {}", expected_hash); + actual_hash == expected_hash } diff --git a/nevmes-gui/src/apps/home.rs b/nevmes-gui/src/apps/home.rs index a576e36..a455355 100644 --- a/nevmes-gui/src/apps/home.rs +++ b/nevmes-gui/src/apps/home.rs @@ -6,12 +6,11 @@ use nevmes_core::*; use std::sync::mpsc::{Receiver, Sender}; use std::time::Duration; -use crate::{BLOCK_TIME_IN_SECS_EST, BYTES_IN_GB, START_CORE_TIMEOUT_SECS}; - pub struct HomeApp { connections: utils::Connections, core_timeout_tx: Sender, core_timeout_rx: Receiver, + has_install_failed: bool, installations: utils::Installations, installation_tx: Sender, installation_rx: Receiver, @@ -50,6 +49,7 @@ pub struct HomeApp { impl Default for HomeApp { fn default() -> Self { let connections = Default::default(); + let has_install_failed = false; let installations = Default::default(); let is_core_running = false; let is_editing_connections = false; @@ -80,6 +80,7 @@ impl Default for HomeApp { connections, core_timeout_rx, core_timeout_tx, + has_install_failed, installations, installation_rx, installation_tx, @@ -139,6 +140,7 @@ impl eframe::App for HomeApp { } if let Ok(install) = self.installation_rx.try_recv() { self.is_installing = !install; + if !install && self.is_loading { self.has_install_failed = true } self.is_loading = false; } if let Ok(timeout) = self.core_timeout_rx.try_recv() { @@ -150,6 +152,21 @@ impl eframe::App for HomeApp { } } + // Installation Error window + //----------------------------------------------------------------------------------- + let mut has_install_failed = self.has_install_failed; + egui::Window::new("Error") + .open(&mut has_install_failed) + .vscroll(false) + .show(&ctx, |ui| { + ui.heading("Installation Failure"); + if ui.button("Exit").clicked() { + self.has_install_failed = false; + self.is_installing = false; + self.is_loading = false; + } + }); + // Connection Manager window //----------------------------------------------------------------------------------- let mut is_editing_connections = self.is_editing_connections; @@ -217,12 +234,12 @@ impl eframe::App for HomeApp { .open(&mut is_installing) .vscroll(true) .show(&ctx, |ui| { - // let mut wants_i2p = self.installations.i2p; + let mut wants_i2p = self.installations.i2p; let mut wants_i2p_zero = self.installations.i2p_zero; let mut wants_xmr = self.installations.xmr; - // if ui.checkbox(&mut wants_i2p, "i2p").changed() { - // self.installations.i2p = !self.installations.i2p; - // } + if ui.checkbox(&mut wants_i2p, "i2p").changed() { + self.installations.i2p = !self.installations.i2p; + } if ui.checkbox(&mut wants_i2p_zero, "i2p-zero").changed() { self.installations.i2p_zero = !self.installations.i2p_zero; } @@ -231,9 +248,11 @@ impl eframe::App for HomeApp { } let install = &self.installations; if install.i2p || install.i2p_zero || install.xmr { - if ui.button("Install").clicked() { - self.is_loading = true; - install_software_req(self.installation_tx.clone(), ctx.clone(), &self.installations); + if !self.is_loading { + if ui.button("Install").clicked() { + self.is_loading = true; + install_software_req(self.installation_tx.clone(), ctx.clone(), &self.installations); + } } } if ui.button("Exit").clicked() { @@ -280,10 +299,10 @@ impl eframe::App for HomeApp { let address = &self.s_xmr_address.result.address; let unlocked_balance = self.s_xmr_balance.result.unlocked_balance; let locked_balance = self.s_xmr_balance.result.balance - unlocked_balance; - let unlock_time = self.s_xmr_balance.result.blocks_to_unlock * BLOCK_TIME_IN_SECS_EST; + let unlock_time = self.s_xmr_balance.result.blocks_to_unlock * crate::BLOCK_TIME_IN_SECS_EST; let xmrd_info: &reqres::XmrDaemonGetInfoResult = &self.s_xmrd_get_info.result; - let free_space = xmrd_info.free_space / BYTES_IN_GB; - let db_size = xmrd_info.database_size / BYTES_IN_GB; + let free_space = xmrd_info.free_space / crate::BYTES_IN_GB; + let db_size = xmrd_info.database_size / crate::BYTES_IN_GB; ui.label(format!("- rpc version: {}\n- address: {}\n- balance: {} piconero(s)\n- locked balance: {} piconero(s)\n- unlock time (secs): {}\n- daemon info\n\t- net type: {}\n\t- current hash: {}\n\t- height: {}\n\t- synced: {}\n\t- blockchain size : ~{} GB\n\t- free space : ~{} GB\n\t- version: {}\n", self.s_xmr_rpc_ver.result.version, address, unlocked_balance, locked_balance, unlock_time, xmrd_info.nettype, xmrd_info.top_block_hash, xmrd_info.height, xmrd_info.synchronized, @@ -352,7 +371,7 @@ fn send_balance_req(tx: Sender, ctx: egui::Contex fn send_i2p_status_req(tx: Sender, ctx: egui::Context) { tokio::spawn(async move { - let status = i2p::get_proxy_status().await; + let status = i2p::check_connection().await; let _ = tx.send(status); ctx.request_repaint(); }); @@ -373,7 +392,7 @@ fn send_reset_refresh(tx: Sender, ctx: egui::Context, init: bool) { fn start_core_timeout (tx: Sender, ctx: egui::Context) { tokio::spawn(async move { - tokio::time::sleep(std::time::Duration::from_secs(START_CORE_TIMEOUT_SECS)).await; + tokio::time::sleep(std::time::Duration::from_secs(crate::START_CORE_TIMEOUT_SECS)).await; log::error!("start nevmes-core timeout"); let _ = tx.send(true); ctx.request_repaint(); @@ -388,8 +407,8 @@ fn install_software_req xmr: installations.xmr, }; tokio::spawn(async move { - utils::install_software(req_install).await; - let _ = tx.send(true); + let did_install = utils::install_software(req_install).await; + let _ = tx.send(did_install); ctx.request_repaint(); }); } diff --git a/src/controller.rs b/src/controller.rs index 8797f0e..bb9c905 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -24,7 +24,7 @@ pub async fn get_version(_jwp: proof::PaymentProof) -> Custom Custom> { - Custom(Status::Ok, Json(i2p::get_proxy_status().await)) + Custom(Status::Ok, Json(i2p::check_connection().await)) } /// Share your contact information