gupaxx/src/sudo.rs

185 lines
5.3 KiB
Rust
Raw Normal View History

// Gupax - GUI Uniting P2Pool And XMRig
//
2023-02-26 16:45:58 +00:00
// Copyright (c) 2022-2023 hinto-janai
//
// 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::{
thread,
sync::{Arc,Mutex},
process::*,
io::Write,
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
path::PathBuf,
};
use crate::{
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
Helper,
disk::Xmrig,
ProcessSignal,
constants::*,
macros::*,
};
use log::*;
#[derive(Debug,Clone)]
pub struct SudoState {
pub windows: bool, // If this bool is set, this struct is just a dummy so I don't have to change my type signatures :)
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]
pub signal: ProcessSignal, // Main GUI will set this depending on if we want [Start] or [Restart]
}
2022-12-14 03:41:05 +00:00
impl Default for SudoState {
fn default() -> Self {
Self::new()
}
}
impl SudoState {
#[cfg(target_os = "windows")]
pub fn new() -> Self {
Self {
windows: true,
testing: false,
success: false,
hide: true,
msg: String::new(),
pass: String::new(),
signal: ProcessSignal::None,
}
}
#[cfg(target_family = "unix")]
pub fn new() -> Self {
Self {
windows: false,
testing: false,
success: false,
hide: true,
msg: "".to_string(),
pass: String::with_capacity(256),
signal: ProcessSignal::None,
}
}
// Resets the state.
pub fn reset(state: &Arc<Mutex<Self>>) {
2022-12-14 03:41:05 +00:00
Self::wipe(state);
let mut state = lock!(state);
state.testing = false;
state.success = false;
// state.signal = ProcessSignal::None;
}
// Swaps the pass with another 256-capacity String,
// zeroizes the old and drops it.
pub fn wipe(state: &Arc<Mutex<Self>>) {
let mut new = String::with_capacity(256);
// new is now == old, and vice-versa.
std::mem::swap(&mut new, &mut lock!(state).pass);
// we're wiping & dropping the old pass here.
new.zeroize();
std::mem::drop(new);
info!("Sudo | Password wipe with 0's ... OK");
}
// Spawns a thread and tests sudo with the provided password.
// Sudo takes the password through STDIN via [--stdin].
// Sets the appropriate state fields on success/failure.
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
pub fn test_sudo(state: Arc<Mutex<Self>>, helper: &Arc<Mutex<Helper>>, xmrig: &Xmrig, path: &PathBuf) {
let helper = Arc::clone(helper);
let xmrig = xmrig.clone();
let path = path.clone();
thread::spawn(move || {
// Set to testing
lock!(state).testing = true;
// Make sure sudo timestamp is reset
let reset = Command::new("sudo")
.arg("--reset-timestamp")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::piped())
.status();
match reset {
Ok(_) => info!("Sudo | Resetting timestamp ... OK"),
Err(e) => {
error!("Sudo | Couldn't reset timestamp: {}", e);
Self::wipe(&state);
lock!(state).msg = format!("Sudo error: {}", e);
lock!(state).testing = false;
return
},
}
// Spawn testing sudo
let mut sudo = Command::new("sudo")
.args(["--stdin", "--validate"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::piped())
.spawn()
.unwrap();
// Write pass to STDIN
let mut stdin = sudo.stdin.take().unwrap();
stdin.write_all(lock!(state).pass.as_bytes()).unwrap();
drop(stdin);
// Sudo re-prompts and will hang.
// To workaround this, try checking
// results for 5 seconds in a loop.
for i in 1..=5 {
match sudo.try_wait() {
Ok(Some(code)) => if code.success() {
info!("Sudo | Password ... OK!");
lock!(state).success = true;
break
},
Ok(None) => {
info!("Sudo | Waiting [{}/5]...", i);
std::thread::sleep(SECOND);
},
Err(e) => {
error!("Sudo | Couldn't reset timestamp: {}", e);
Self::wipe(&state);
lock!(state).msg = format!("Sudo error: {}", e);
lock!(state).testing = false;
return
},
}
}
if let Err(e) = sudo.kill() { warn!("Sudo | Kill error (it probably already exited): {}", e); }
if lock!(state).success {
match lock!(state).signal {
ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
ProcessSignal::Stop => crate::helper::Helper::stop_xmrig(&helper),
_ => crate::helper::Helper::start_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
}
} else {
lock!(state).msg = "Incorrect password! (or sudo timeout)".to_string();
Self::wipe(&state);
}
lock!(state).signal = ProcessSignal::None;
lock!(state).testing = false;
});
}
}