main: add [zeroize] and implement sudo input/test screen for xmrig

This commit is contained in:
hinto-janaiyo 2022-12-07 18:02:08 -05:00
parent 31f23d9d58
commit f988e4224c
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
9 changed files with 169 additions and 9 deletions

1
Cargo.lock generated
View file

@ -37,6 +37,7 @@ dependencies = [
"tor-rtcompat",
"walkdir",
"winres",
"zeroize",
"zip",
]

View file

@ -50,6 +50,7 @@ tokio = { version = "1.21.2", features = ["rt", "time", "macros", "process"] }
toml = { version = "0.5.9", features = ["preserve_order"] }
tor-rtcompat = "0.7.0"
walkdir = "2.3.2"
zeroize = "1.5.7"
# Unix dependencies
[target.'cfg(unix)'.dependencies]

BIN
images/ferris/cute.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
images/ferris/gesture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
images/ferris/sudo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

View file

@ -67,6 +67,7 @@ pub const SPACE: f32 = 10.0;
pub const RED: egui::Color32 = egui::Color32::from_rgb(230, 50, 50);
pub const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 230, 100);
pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100);
pub const BRIGHT_YELLOW: egui::Color32 = egui::Color32::from_rgb(250, 250, 100);
pub const GRAY: egui::Color32 = egui::Color32::GRAY;
pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
pub const BLACK: egui::Color32 = egui::Color32::BLACK;
@ -78,6 +79,16 @@ pub const ZERO_SECONDS: std::time::Duration = std::time::Duration::from_secs(0);
pub const MILLI_900: std::time::Duration = std::time::Duration::from_millis(900);
pub const TOKIO_SECOND: tokio::time::Duration = std::time::Duration::from_secs(1);
// The explaination given to the user on why XMRig needs sudo.
pub const XMRIG_ADMIN_REASON: &str =
r#"The large hashrate difference between XMRig and other miners like Monero and P2Pool's built-in miners is mostly due to XMRig configuring CPU MSRs and setting up hugepages. Other miners like Monero or P2Pool's built-in miner do not do this. It can be done manually but it isn't recommended since XMRig does this for you automatically, but only if it has the proper admin priviledges."#;
// Password buttons
pub const PASSWORD_TEXT: &str = "Enter sudo/admin password here...";
pub const PASSWORD_LEAVE: &str = "Return to the previous screen";
pub const PASSWORD_ENTER: &str = "Attempt with the current password";
pub const PASSWORD_HIDE: &str = "Toggle hiding/showing the password";
// OS specific
#[cfg(target_os = "windows")]
pub const OS: &'static str = " Windows";

View file

@ -21,6 +21,7 @@ pub const FERRIS_HAPPY: &[u8] = include_bytes!("../images/ferris/happy.png");
pub const FERRIS_OOPS: &[u8] = include_bytes!("../images/ferris/oops.png");
pub const FERRIS_ERROR: &[u8] = include_bytes!("../images/ferris/error.png");
pub const FERRIS_PANIC: &[u8] = include_bytes!("../images/ferris/panic.png"); // This isnt technically ferris but its ok since its spooky
pub const FERRIS_SUDO: &[u8] = include_bytes!("../images/ferris/sudo.png");

View file

@ -24,11 +24,12 @@ use egui::{
TextStyle::*,
color::Color32,
FontFamily::Proportional,
TextStyle,
TextStyle,Spinner,
Layout,Align,
FontId,Label,RichText,Stroke,Vec2,Button,SelectableLabel,
Key,Modifiers,
Key,Modifiers,TextEdit,
CentralPanel,TopBottomPanel,
Hyperlink,
};
use egui_extras::RetainedImage;
use eframe::{egui,NativeOptions};
@ -61,6 +62,12 @@ mod update;
mod helper;
use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*,helper::*};
// Sudo (unix only)
#[cfg(target_family = "unix")]
mod sudo;
#[cfg(target_family = "unix")]
use sudo::*;
//---------------------------------------------------------------------------------------------------- Struct + Impl
// The state of the outer main [App].
// See the [State] struct in [state.rs] for the
@ -113,6 +120,9 @@ pub struct App {
xmrig_img: Arc<Mutex<ImgXmrig>>, // A one-time snapshot of what data XMRig started with
// Buffer State
p2pool_console: String, // The buffer between the p2pool console and the [Helper]
// Sudo State
#[cfg(target_family = "unix")]
sudo: Arc<Mutex<SudoState>>,
// State from [--flags]
no_startup: bool,
// Static stuff
@ -174,6 +184,8 @@ impl App {
p2pool_img,
xmrig_img,
p2pool_console: String::with_capacity(10),
#[cfg(target_family = "unix")]
sudo: Arc::new(Mutex::new(SudoState::new())),
resizing: false,
alpha: 0,
no_startup: false,
@ -366,6 +378,7 @@ pub enum ErrorButtons {
ResetNode,
Okay,
Quit,
Sudo,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -374,6 +387,7 @@ pub enum ErrorFerris {
Oops,
Error,
Panic,
Sudo,
}
pub struct ErrorState {
@ -412,6 +426,23 @@ impl ErrorState {
buttons,
};
}
// Just sets the current state to new, resetting it.
pub fn reset(&mut self) {
*self = Self::new();
}
// Instead of creating a whole new screen and system, this (ab)uses ErrorState
// to ask for the [sudo] when starting XMRig. Yes, yes I know, it's called "ErrorState"
// but rewriting the UI code and button stuff might be worse.
pub fn ask_sudo(&mut self) {
*self = Self {
error: true,
msg: String::new(),
ferris: ErrorFerris::Sudo,
buttons: ErrorButtons::Sudo,
}
}
}
//---------------------------------------------------------------------------------------------------- [Images] struct
@ -421,6 +452,7 @@ struct Images {
oops: RetainedImage,
error: RetainedImage,
panic: RetainedImage,
sudo: RetainedImage,
}
impl Images {
@ -431,6 +463,7 @@ impl Images {
oops: RetainedImage::from_image_bytes("oops.png", FERRIS_OOPS).unwrap(),
error: RetainedImage::from_image_bytes("error.png", FERRIS_ERROR).unwrap(),
panic: RetainedImage::from_image_bytes("panic.png", FERRIS_PANIC).unwrap(),
sudo: RetainedImage::from_image_bytes("panic.png", FERRIS_SUDO).unwrap(),
}
}
}
@ -804,6 +837,7 @@ impl eframe::App for App {
Oops => &self.img.oops,
Error => &self.img.error,
Panic => &self.img.panic,
ErrorFerris::Sudo => &self.img.sudo,
};
ferris.show_max_size(ui, Vec2::new(width, height));
@ -825,6 +859,14 @@ impl eframe::App for App {
ui.add_sized([width, height], Label::new(format!("--- Gupax has encountered an error! ---\n{}", &self.error_state.msg)));
ui.add_sized([width, height], Label::new("Reset the manual node list?"))
},
ErrorButtons::Sudo => {
let text = format!("Why does XMRig need admin priviledge?\n{}", XMRIG_ADMIN_REASON);
let height = height/4.0;
ui.add_sized([width, height], Label::new(format!("--- Gupax needs sudo/admin priviledge for XMRig! ---\n{}", &self.error_state.msg)));
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
ui.add_sized([width/2.0, height], Label::new(text));
ui.add_sized([width, height], Hyperlink::from_label_and_url("Click here for more info.", "https://xmrig.com/docs/miner/randomx-optimization-guide"))
},
_ => {
match self.error_state.ferris {
Panic => ui.add_sized([width, height], Label::new("--- Gupax has encountered an un-recoverable error! ---")),
@ -841,7 +883,7 @@ impl eframe::App for App {
match self.error_state.buttons {
YesNo => {
if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() { self.error_state = ErrorState::new(); }
if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() { self.error_state.reset() }
// If [Esc] was pressed, assume [No]
if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { exit(0); }
},
@ -871,7 +913,7 @@ impl eframe::App for App {
Err(e) => self.error_state.set(format!("State reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
};
}
if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state = ErrorState::new() }
if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state.reset() }
},
ResetNode => {
if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() {
@ -889,9 +931,38 @@ impl eframe::App for App {
Err(e) => self.error_state.set(format!("Node reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
};
}
if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state = ErrorState::new() }
if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state.reset() }
},
Okay => if esc || ui.add_sized([width, height], Button::new("Okay")).clicked() { self.error_state = ErrorState::new(); },
ErrorButtons::Sudo => {
let sudo_width = (width/10.0);
let height = ui.available_height()/4.0;
let mut sudo = self.sudo.lock().unwrap();
let hide = sudo.hide.clone();
ui.style_mut().override_text_style = Some(Monospace);
if sudo.testing {
ui.add_sized([width, height], Spinner::new().size(height));
ui.set_enabled(false);
} else {
ui.add_sized([width, height], Label::new(&sudo.msg));
}
ui.add_space(height);
let height = ui.available_height()/5.0;
// Password input box with a hider.
ui.horizontal(|ui| {
let response = ui.add_sized([sudo_width*8.0, height], TextEdit::hint_text(TextEdit::singleline(&mut sudo.pass).password(hide), PASSWORD_TEXT));
let box_width = (ui.available_width()/2.0)-5.0;
if (response.lost_focus() && ui.input().key_pressed(Key::Enter)) ||
ui.add_sized([box_width, height], Button::new("Enter")).on_hover_text(PASSWORD_ENTER).clicked() {
if !sudo.testing {
SudoState::test_sudo(Arc::clone(&self.sudo));
}
}
let color = if hide { BLACK } else { BRIGHT_YELLOW };
if ui.add_sized([box_width, height], Button::new(RichText::new("👁").color(color))).on_hover_text(PASSWORD_HIDE).clicked() { sudo.hide = !sudo.hide; }
});
if esc || ui.add_sized([width, height*4.0], Button::new("Leave")).clicked() { self.error_state.reset(); };
},
Okay => if esc || ui.add_sized([width, height], Button::new("Okay")).clicked() { self.error_state.reset(); },
Quit => if ui.add_sized([width, height], Button::new("Quit")).clicked() { exit(1); },
}
})});
@ -1079,10 +1150,10 @@ impl eframe::App for App {
let width = (ui.available_width()/3.0)-5.0;
if self.xmrig.lock().unwrap().is_alive() {
if ui.add_sized([width, height], Button::new("")).on_hover_text("Restart XMRig").clicked() {
self.xmrig.lock().unwrap().state = ProcessState::Middle;
self.error_state.ask_sudo();
}
if ui.add_sized([width, height], Button::new("")).on_hover_text("Stop XMRig").clicked() {
self.xmrig.lock().unwrap().state = ProcessState::Dead;
self.error_state.ask_sudo();
}
ui.add_enabled_ui(false, |ui| {
ui.add_sized([width, height], Button::new("")).on_hover_text("Start XMRig");
@ -1093,7 +1164,7 @@ impl eframe::App for App {
ui.add_sized([width, height], Button::new("")).on_hover_text("Stop XMRig");
});
if ui.add_sized([width, height], Button::new("")).on_hover_text("Start XMRig").clicked() {
// Helper::spawn_xmrig(&self.helper, &self.state.xmrig, self.state.gupax.absolute_xmrig_path.clone());
self.error_state.ask_sudo();
}
}
});

75
src/sudo.rs Normal file
View file

@ -0,0 +1,75 @@
// Gupax - GUI Uniting P2Pool And XMRig
//
// Copyright (c) 2022 hinto-janaiyo
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Handling of [sudo] for XMRig.
// [zeroize] is used to wipe the memory after use.
// Only gets imported in [main.rs] for Unix.
use zeroize::Zeroize;
use std::sync::{Arc,Mutex};
use std::thread;
use log::*;
#[derive(Debug,Clone)]
pub struct SudoState {
pub testing: bool, // Are we attempting a sudo test right now?
pub success: bool, // Was the sudo test a success?
pub hide: bool, // Are we hiding the password?
pub msg: String, // The message shown to the user if unsuccessful
pub pass: String, // The actual password wrapped in a [SecretVec]
}
impl SudoState {
pub fn new() -> Self {
Self {
testing: false,
success: false,
hide: true,
msg: "".to_string(),
pass: String::with_capacity(256),
}
}
// Swaps the pass with another 256-capacity String,
// zeroizes the old and drops it.
pub fn wipe(state: &Arc<Mutex<Self>>) {
info!("Sudo | Wiping password with zeros and dropping from memory...");
let mut new = String::with_capacity(256);
let mut state = state.lock().unwrap();
// new is now == old, and vice-versa.
std::mem::swap(&mut new, &mut state.pass);
// we're wiping & dropping the old pass here.
new.zeroize();
std::mem::drop(new);
info!("Sudo ... Password Wipe OK");
}
pub fn test_sudo(state: Arc<Mutex<Self>>) {
std::thread::spawn(move || {
state.lock().unwrap().testing = true;
info!("in test_sudo()");
std::thread::sleep(std::time::Duration::from_secs(3));
state.lock().unwrap().testing = false;
if state.lock().unwrap().pass == "secret" {
state.lock().unwrap().msg = "Correct!".to_string();
} else {
state.lock().unwrap().msg = "Incorrect password!".to_string();
}
Self::wipe(&state);
});
}
}