From e7de536f184fceb71789d549c4f19127425aacbd Mon Sep 17 00:00:00 2001 From: hinto-janaiyo Date: Fri, 9 Dec 2022 21:00:33 -0500 Subject: [PATCH] windows: handle admin priviledge for xmrig Please read the [src/README.md]. I hate windows so much. --- Cargo.lock | 26 +++++++++-- Cargo.toml | 2 + build.rs | 21 +++++++-- src/README.md | 118 +++++++++++++++++++++++++++++++++++++++++------ src/constants.rs | 2 +- src/helper.rs | 36 ++++++++++++--- src/main.rs | 33 ++++++++++--- src/node.rs | 9 ++-- src/p2pool.rs | 2 +- src/sudo.rs | 15 ++++++ 10 files changed, 224 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3127dc..804bfa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ dependencies = [ "hyper", "hyper-tls", "image", + "is_elevated", "log", "num-format", "num_cpus", @@ -29,6 +30,7 @@ dependencies = [ "rfd", "serde", "serde_json", + "sudo", "tar", "tls-api", "tls-api-native-tls", @@ -2071,6 +2073,15 @@ dependencies = [ "libc", ] +[[package]] +name = "is_elevated" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5299060ff5db63e788015dcb9525ad9b84f4fd9717ed2cbdeba5018cbf42f9b5" +dependencies = [ + "winapi", +] + [[package]] name = "itertools" version = "0.10.5" @@ -3109,11 +3120,10 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "crossbeam-deque", "either", "rayon-core", ] @@ -3770,6 +3780,16 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "sudo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bd84d4c082e18e37fef52c0088e4407dabcef19d23a607fb4b5ee03b7d5b83" +dependencies = [ + "libc", + "log", +] + [[package]] name = "syn" version = "1.0.105" diff --git a/Cargo.toml b/Cargo.toml index 5456993..8abc93e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,10 +56,12 @@ zeroize = "1.5.7" [target.'cfg(unix)'.dependencies] tar = "0.4.38" flate2 = "1.0" +sudo = "0.6.0" # Windows dependencies [target.'cfg(windows)'.dependencies] zip = "0.6.3" +is_elevated = "0.1.2" # For Windows build (icon) [target.'cfg(windows)'.build-dependencies] diff --git a/build.rs b/build.rs index d7f1736..29b9b6a 100755 --- a/build.rs +++ b/build.rs @@ -3,8 +3,23 @@ // The icon in the taskbar and top of the App window gets // set in [src/main.rs, src/constants.rs] at runtime with // pre-compiled bytes using [include_bytes!()] on the images in [images/]. +#[cfg(windows)] fn main() -> std::io::Result<()> { - #[cfg(windows)] - winres::WindowsResource::new().set_icon("images/icons/icon.ico").compile()?; - Ok(()) + let mut res = winres::WindowsResource::new(); + res.set_icon("images/icons/icon.ico"); + res.set_manifest(r#" + + + + + + + + + + "#); + res.compile() } + +#[cfg(unix)] +fn main() {} diff --git a/src/README.md b/src/README.md index 39d2169..bd25a90 100644 --- a/src/README.md +++ b/src/README.md @@ -1,10 +1,18 @@ -# Gupax source +# Gupax source files. Development documentation is here. * [Structure](#Structure) * [Thread Model](#Thread-Model) * [Bootstrap](#Bootstrap) * [Disk](#Disk) * [Scale](#Scale) * [Naming Scheme](#naming-scheme) +* [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows) + - [The issue](#the-issue) + - [The requirements](#the-requirements) + - [CMD's RunAs](#cmds-runas) + - [PowerShell's Start-Process](#powershells-start-process) + - [Win32's ShellExecuteW](#win32s-shellexecutew) + - [Registry Edit](#registry-edit) + - [Windows vs Unix](#windows-vs-unix) ## Structure | File/Folder | Purpose | @@ -18,26 +26,19 @@ | node.rs | Community node ping code for the `P2Pool` simple tab | p2pool.rs | `P2Pool` tab | status.rs | `Status` tab +| sudo.rs | Code for handling `sudo` escalation for XMRig on Unix | update.rs | Update code for the `Gupax` tab | xmrig.rs | `XMRig` tab ## Thread Model ![thread_model.png](https://github.com/hinto-janaiyo/gupax/blob/main/images/thread_model.png) -Note: The process I/O model depends on if the `[Simple]` or `[Advanced]` version is used. +Process's (both Simple/Advanced) have: +- 1 OS thread for the watchdog (API fetching, watching signals, etc) +- 1 OS thread for a PTY-Child combo (combines STDOUT/STDERR for me, nice!) +- A PTY (pseudo terminal) whose underlying type is abstracted with the [`portable_pty`](https://docs.rs/portable-pty/) library -`[Simple]` has: - - 1 OS thread for the watchdog (API fetching, watching signals, etc) - - 1 OS thread (with 2 tokio tasks) for STDOUT/STDERR - - No pseudo terminal allocated - - No STDIN pipe - -`[Advanced]` has: - - 1 OS thread for the watchdog (API fetching, watching signals, relaying STDIN) - - 1 OS thread for a PTY-Child combo (combines STDOUT/STDERR for me, nice!) - - A PTY (pseudo terminal) whose underlying type is abstracted with the [`portable_pty`](https://docs.rs/portable-pty/) library - -The reason `[Advanced]` is non-async is because P2Pool requires a `TTY` to take STDIN. The PTY library used, [`portable_pty`](https://docs.rs/portable-pty/), doesn't implement async traits. There seem to be tokio PTY libraries, but they are Unix-specific. Having separate PTY code for Windows/Unix is also a big pain. Since the threads will be sleeping most of the time (the pipes are lazily read and buffered), it's fine. Ideally, any I/O should be a tokio task, though. +The reason why STDOUT/STDERR is non-async is because P2Pool requires a `TTY` to take STDIN. The PTY library used, [`portable_pty`](https://docs.rs/portable-pty/), doesn't implement async traits. There seem to be tokio PTY libraries, but they are Unix-specific. Having separate PTY code for Windows/Unix is also a big pain. Since the threads will be sleeping most of the time (the pipes are lazily read and buffered), it's fine. Ideally, any I/O should be a tokio task, though. ## Bootstrap This is how Gupax works internally when starting up: @@ -116,3 +117,92 @@ Exceptions (there are always exceptions...): - XMRig doesn't have a [v], so it is [xmrig-6.18.0-...] - XMRig separates the hash and signature - P2Pool hashes are in UPPERCASE + +## Why does Gupax need to be Admin? (on Windows) +**Simple TL;DR:** Because Windows. + +**Slightly more detailed TL;DR:** Rust does not have mature Win32 API wrapper libraries. Although Microsoft has an official ["Rust" library](https://github.com/microsoft/windows-rs), it is quite low-level and using it within Gupax would mean re-implementing a lot of Rust's STDLIB process module code. + +If you are confused because you use Gupax on macOS/Linux, this is a Windows-only issue. + +The following sections will go more into the technical issues I've encountered in trying to implement something that sounds pretty trivial: Starting a child process with elevated privilege, and getting a handle to it and its output. (it's a rant about windows). + +--- + +### The issue +`XMRig` needs to be run with administrative privileges to enable MSR mods and hugepages. There are other ways of achieving this through pretty manual and technical efforts (which also gets more complicated due to OS differences) but in the best interest of Gupax's users, I always want to implement things so that it's **easy for the user.** + +Users should not need to be familiar with MSRs to get max hashrate, this is something the program (me, Gupax!) should do for them. + +--- + +### The requirements +Process's in Gupax need the following criteria met: +- I (as the parent process, Gupax) *must* have a direct handle to the process so that I can send SIGNALs +- I *must* have a handle to the process's STDOUT+STDERR so that I can actually relay output to the user +- I *really should* but don't absolutely need a handle to STDIN so that I can send input from the user + +In the case of XMRig, **I absolutely must enable MSR's automatically for the user**, that's the whole point of XMRig, that's the point of an easy-to-use GUI. +Although I want XMRig with elevated rights, I don't want these side-effects: +- All of Gupax running as Admin +- P2Pool running as Admin + +Here are the "solutions" I've attempted: + +--- + +### CMD's RunAs +Window has a `runas` command, which allows for privilege escalation. Perfect! Spawn a shell and it's easy as running this: +``` +runas /user:Administrator xmrig.exe [...] +``` +...right? + +The `Administrator` in this context is a legacy account, not meant to be touched, not really the `Admin` we are looking for, but more importantly: the password is not set, and the entire account is disabled by default. This means you cannot actually `runas` as *that* `Administrator`. Technically, all it would take is for the user to enabled the account and set a password. But that is already asking for too much, remember: that's my job, to make this **easy and automatic**. So this is a no-go, next. + +--- + +### PowerShell's Start-Process +Window's `PowerShell` has a nice built-in called [`Start-Process`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.3). This allows PowerShell to start... processes. In particular, I was intrigued by the all-in-one flag: `-Verb RunAs`, which runs the provided process with elevated permissions after a **UAC prompt.** That sounds perfect... except if you click that link you'll see 2 sets of syntax. IF you are escalating privilege, Microsoft puts a lot more retrictions on what you can do with this built-in, in particular: +- You CANNOT redirect STDOUT/STDERR/STDIN +- You CANNOT run the process in the current shell (a new PowerShell window will always open!) + +I attempted some hacks like chaining non-admin PowerShell + admin PowerShell together, which made things overly complicated and meant I would be handling logic within these child PowerShell's which would be controlled via STDIN from Gupax code... Not very robust. I also tried just starting an admin PowerShell directly from Gupax, but that meant the user, upon clicking `[Start]` for XMRig, would see a UAC prompt to open PowerShell, which wasn't a good look. Eventually I gave up on PowerShell, next. + +--- + +### Win32's ShellExecuteW +This was the first option I came across, but I intentionally ignored it due to many reasons. Microsoft has official Windows API bindings in [Rust](https://github.com/microsoft/windows-rs). That library has a couple problems: +1. All (the entire library) code requires `unsafe` +2. It's extremely low-level + +The first one isn't actually as bad as it seems, this is Win32 so it's battle-tested. It's also extern C, so it makes sense it has to wrapped in `unsafe`. + +The second one is the real issue. [ShellExecuteW](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew) is a Win32 function that allows exactly what I need, starting a process with elevated privilege with the `runas` flag. It even shows the UAC to the user. But... that's it! No other functionality. The highly abstracted `Command` type in Rust's STDLIB actually uses [`CreateProcessW`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw), and due to type imcompatabilities, using `ShellExecuteW` on my own would mean re-implementing ALL the functionality Rust STDLIB gives, aka: handling STDOUT, STDERR, STDIN, sending SIGNALS, waiting on process, etc etc. I would be programming for "Windows", not "Rust". Okay... next. + +--- + +### Registry Edit +To start a process in Windows with elevated escalation you can right-click -> `Run as Administrator`, but you can also set a permanent flag deeper in the file's options. In reality this sets a Registry Key with the absolute path to that executable and a `RUNASADMIN` flag. This allows Windows to know which programs to run as an admin. There is a Rust library called [`WinReg`](https://github.com/gentoo90/winreg-rs) that provides functionality to read/write to the Registry. Editing the Registry is akin to editing someone's `.bashrc`, it's a sin! But... if it means **automatically applying the MSR mod** and **better UX**, then yes I will. The flow would have been: +- User starts XMRig +- Gupax notices XMRig is not admin +- Gupax tells user +- Gupax gives option to AUTOMATICALLY edit registry +- Gupax also gives the option to show how to do it manually + +This was the solution I would have gone with, but alas, the abstracted `Command` types I am using to start processes completely ignore this metadata. When Gupax starts XMRig, that `Run as Administrator` flag is completely ignored. Grrr... what options are left? + +--- + +### Windows vs Unix +Unix (macOS/Linux) has have a super nice, easy, friendly, not-completely-garbage userland program called: `sudo`. It is so extremely simple to use `sudo` as a sort of wrapper around XMRig since `sudo` isn't completely backwards and actually has valuable flags! No legacy `Administrator`, no UAC prompt, no shells within shells, no low-level system APIs, no messing with the user Registry. + +You get the user's password, you input it to `sudo` with `--stdin` and you execute XMRig with it. Simple, easy, nice. (Don't forget to zero the password memory, though). + +With no other option left on Windows, I unfortunately have to fallback to the worst solution: shipping Gupax's binary to have `Administrator` metadata, so that it will automatically prompt users for UAC. This means all child process spawned by Gupax will ALSO have admin rights. Windows having one of the most complicated spaghetti privilege systems is ironically what led me to use the most unsecure option. + +Depending on the privilege used, Gupax will error/panic: +- Windows: If not admin, warn the user about potential lower XMRig hashrate +- Unix: IF admin, panic! Don't allow anything. As it should be. + +If you're reading this and have a solution (that isn't using Win32) then please, please, teach me. diff --git a/src/constants.rs b/src/constants.rs index 0cd0e56..16f8016 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -82,7 +82,7 @@ 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."#; +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 privileges."#; // Password buttons pub const PASSWORD_TEXT: &str = "Enter sudo/admin password..."; pub const PASSWORD_LEAVE: &str = "Return to the previous screen"; diff --git a/src/helper.rs b/src/helper.rs index 3cffcdb..463ea8b 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -616,7 +616,9 @@ impl Helper { // the XMRig path is just an argument to sudo, so add it. // Before that though, add the ["--prompt"] flag and set it // to emptyness so that it doesn't show up in the output. + #[cfg(target_family = "unix")] // Of course, only on Unix. args.push(r#"--prompt="#.to_string()); + #[cfg(target_family = "unix")] args.push(path.display().to_string()); // [Simple] @@ -677,6 +679,24 @@ impl Helper { args } + // We actually spawn [sudo] on Unix, with XMRig being the argument. + #[cfg(target_family = "unix")] + fn create_xmrig_cmd_unix(args: Vec, path: PathBuf) -> portable_pty::CommandBuilder { + let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new("sudo"); + cmd.args(args); + cmd.cwd(path.as_path().parent().unwrap()); + cmd + } + + // Gupax should be admin on Windows, so just spawn XMRig normally. + #[cfg(target_os = "windows")] + fn create_xmrig_cmd_windows(args: Vec, path: PathBuf) -> portable_pty::CommandBuilder { + let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new(path.clone()); + cmd.args(args); + cmd.cwd(path.as_path().parent().unwrap()); + cmd + } + // The P2Pool watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works. #[tokio::main] async fn spawn_xmrig_watchdog(process: Arc>, gui_api: Arc>, pub_api: Arc>, priv_api: Arc>, args: Vec, mut path: std::path::PathBuf, sudo: Arc>) { @@ -689,13 +709,15 @@ impl Helper { pixel_height: 0, }).unwrap(); // 1b. Create command - let mut cmd = portable_pty::CommandBuilder::new("sudo"); - cmd.args(args); - cmd.cwd(path.as_path().parent().unwrap()); + #[cfg(target_os = "windows")] + let cmd = Self::create_xmrig_cmd_windows(args, path); + #[cfg(target_family = "unix")] + let cmd = Self::create_xmrig_cmd_unix(args, path); // 1c. Create child let child_pty = Arc::new(Mutex::new(pair.slave.spawn_command(cmd).unwrap())); // 1d. Wait a bit for [sudo]. + #[cfg(target_family = "unix")] thread::sleep(std::time::Duration::from_secs(3)); // 2. Set process state @@ -708,9 +730,11 @@ impl Helper { lock.stdin = Some(pair.master); // 3. Input [sudo] pass, wipe, then drop. - writeln!(lock.stdin.as_mut().unwrap(), "{}", sudo.lock().unwrap().pass); - SudoState::wipe(&sudo); - drop(sudo); + if cfg!(unix) { + writeln!(lock.stdin.as_mut().unwrap(), "{}", sudo.lock().unwrap().pass); + SudoState::wipe(&sudo); + drop(sudo); + } drop(lock); // 3. Spawn PTY read thread diff --git a/src/main.rs b/src/main.rs index a3881a7..5f29139 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,11 +62,11 @@ mod update; mod helper; use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*,helper::*}; -// Sudo (unix only) -#[cfg(target_family = "unix")] +// Sudo (dummy values for Windows) mod sudo; +use crate::sudo::*; #[cfg(target_family = "unix")] -use sudo::*; +extern crate sudo as sudo_check; //---------------------------------------------------------------------------------------------------- Struct + Impl // The state of the outer main [App]. @@ -122,8 +122,7 @@ pub struct App { p2pool_console: String, // The buffer between the p2pool console and the [Helper] xmrig_console: String, // The buffer between the xmrig console and the [Helper] // Sudo State - #[cfg(target_family = "unix")] - sudo: Arc>, + sudo: Arc>, // This is just a dummy struct on [Windows]. // State from [--flags] no_startup: bool, // Static stuff @@ -186,7 +185,6 @@ impl App { xmrig_img, p2pool_console: String::with_capacity(10), xmrig_console: String::with_capacity(10), - #[cfg(target_family = "unix")] sudo: Arc::new(Mutex::new(SudoState::new())), resizing: false, alpha: 0, @@ -343,6 +341,19 @@ impl App { Helper::spawn_helper(&app.helper); info!("Helper ... OK"); + // Check for privilege. Should be Admin on [Windows] and NOT root on Unix. + #[cfg(target_os = "windows")] + if !is_elevated::is_elevated() { + error!("Windows | Admin user not detected!"); + app.error_state.set(format!("Gupax was not launched as Administrator!\nBe warned, XMRig might have less hashrate!"), ErrorFerris::Sudo, ErrorButtons::Okay); + } + #[cfg(target_family = "unix")] + if sudo_check::check() != sudo_check::RunningAs::User { + let id = sudo_check::check(); + error!("Unix | Regular user not detected: [{:?}]", id); + app.error_state.set(format!("Gupax was launched as: [{:?}]\nPlease launch Gupax with regular user permissions.", id), ErrorFerris::Panic, ErrorButtons::Quit); + } + app } } @@ -588,7 +599,7 @@ fn init_auto(app: &mut App) { let auto_node = app.og.lock().unwrap().p2pool.auto_node; let simple = app.og.lock().unwrap().p2pool.simple; if auto_node && simple { - Ping::spawn_thread(&app.ping, &app.og) + Ping::spawn_thread(&app.ping) } else { info!("Skipping auto-ping..."); } @@ -1179,7 +1190,10 @@ impl eframe::App for App { } else if self.xmrig.lock().unwrap().is_alive() { if ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { self.sudo.lock().unwrap().signal = ProcessSignal::Restart; + #[cfg(target_family = "unix")] self.error_state.ask_sudo(&self.sudo); + #[cfg(target_os = "windows")] + Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo)); } if ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { Helper::stop_xmrig(&self.helper); @@ -1197,6 +1211,11 @@ impl eframe::App for App { ui.set_enabled(false); text = XMRIG_PATH_NOT_EXE.to_string(); } + #[cfg(target_os = "windows")] + if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { + Helper::start_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo)); + } + #[cfg(target_family = "unix")] if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { self.sudo.lock().unwrap().signal = ProcessSignal::Start; self.error_state.ask_sudo(&self.sudo); diff --git a/src/node.rs b/src/node.rs index 4cebfad..25642f5 100644 --- a/src/node.rs +++ b/src/node.rs @@ -212,12 +212,11 @@ impl Ping { //---------------------------------------------------------------------------------------------------- Main Ping function // Intermediate function for spawning thread - pub fn spawn_thread(ping: &Arc>, og: &Arc>) { + pub fn spawn_thread(ping: &Arc>) { let ping = Arc::clone(ping); - let og = Arc::clone(og); std::thread::spawn(move|| { info!("Spawning ping thread..."); - match Self::ping(ping.clone(), og) { + match Self::ping(ping.clone()) { Ok(_) => { info!("Ping ... OK"); ping.lock().unwrap().pinged = true; @@ -228,7 +227,7 @@ impl Ping { ping.lock().unwrap().pinged = false; ping.lock().unwrap().msg = err.to_string(); }, - }; + } ping.lock().unwrap().pinging = false; }); } @@ -251,7 +250,7 @@ impl Ping { // timeout = BLACK // default = GRAY #[tokio::main] - pub async fn ping(ping: Arc>, _og: Arc>) -> Result<(), anyhow::Error> { + pub async fn ping(ping: Arc>) -> Result<(), anyhow::Error> { // Timer let now = Instant::now(); diff --git a/src/p2pool.rs b/src/p2pool.rs index a1efacf..49a0655 100644 --- a/src/p2pool.rs +++ b/src/p2pool.rs @@ -167,7 +167,7 @@ impl P2pool { // [Ping Button] ui.set_enabled(!ping.lock().unwrap().pinging); if ui.add_sized([width, height], Button::new("Ping community nodes")).on_hover_text(P2POOL_PING).clicked() { - Ping::spawn_thread(ping, og); + Ping::spawn_thread(ping); }}); ui.vertical(|ui| { diff --git a/src/sudo.rs b/src/sudo.rs index 235a02c..aafa3bf 100644 --- a/src/sudo.rs +++ b/src/sudo.rs @@ -37,6 +37,7 @@ 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? @@ -46,8 +47,22 @@ pub struct SudoState { } 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,