mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-13 17:35:37 +00:00
231 lines
6.8 KiB
Rust
231 lines
6.8 KiB
Rust
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Luis Liu. All rights reserved.
|
|
* Licensed under the MIT License. See License in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
// Thanks to https://github.com/jorangreef/sudo-prompt/blob/master/index.js
|
|
// MIT License
|
|
//
|
|
// Copyright (c) 2015 Joran Dirk Greef
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// ...
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
use super::Command;
|
|
use anyhow::Result;
|
|
use std::env;
|
|
use std::fs::File;
|
|
use std::io::Read as _;
|
|
use std::path::PathBuf;
|
|
use std::process::{ExitStatus, Output};
|
|
|
|
use std::ffi::{CString, OsString};
|
|
use std::io;
|
|
use std::mem;
|
|
use std::os::unix::ffi::OsStrExt;
|
|
use std::os::unix::io::FromRawFd;
|
|
use std::os::unix::process::ExitStatusExt;
|
|
use std::path::Path;
|
|
use std::ptr;
|
|
|
|
use libc::{EINTR, SHUT_WR, fileno, wait};
|
|
use security_framework_sys::authorization::{
|
|
AuthorizationCreate, AuthorizationExecuteWithPrivileges, AuthorizationFree, AuthorizationRef,
|
|
errAuthorizationSuccess, kAuthorizationFlagDefaults, kAuthorizationFlagDestroyRights,
|
|
};
|
|
|
|
const ENV_PATH: &str = "PATH";
|
|
|
|
fn get_exe_path<P: AsRef<Path>>(exe_name: P) -> Option<PathBuf> {
|
|
let exe_name = exe_name.as_ref();
|
|
if exe_name.has_root() {
|
|
return Some(exe_name.into());
|
|
}
|
|
|
|
if let Ok(abs_path) = exe_name.canonicalize() {
|
|
if abs_path.is_file() {
|
|
return Some(abs_path);
|
|
}
|
|
}
|
|
|
|
env::var_os(ENV_PATH).and_then(|paths| {
|
|
env::split_paths(&paths)
|
|
.filter_map(|dir| {
|
|
let full_path = dir.join(exe_name);
|
|
if full_path.is_file() {
|
|
Some(full_path)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.next()
|
|
})
|
|
}
|
|
|
|
macro_rules! make_cstring {
|
|
($s:expr) => {
|
|
match CString::new($s.as_bytes()) {
|
|
Ok(s) => s,
|
|
Err(_) => {
|
|
return Err(io::Error::new(io::ErrorKind::Other, "null byte in string"));
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
unsafe fn gui_runas(prog: *const i8, argv: *const *const i8) -> io::Result<ExitStatus> {
|
|
let mut authref: AuthorizationRef = ptr::null_mut();
|
|
let mut pipe: *mut libc::FILE = ptr::null_mut();
|
|
|
|
if AuthorizationCreate(
|
|
ptr::null(),
|
|
ptr::null(),
|
|
kAuthorizationFlagDefaults,
|
|
&mut authref,
|
|
) != errAuthorizationSuccess
|
|
{
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
if AuthorizationExecuteWithPrivileges(
|
|
authref,
|
|
prog,
|
|
kAuthorizationFlagDefaults,
|
|
argv as *const *mut _,
|
|
&mut pipe,
|
|
) != errAuthorizationSuccess
|
|
{
|
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
|
|
let fd = fileno(pipe);
|
|
if fd == -1 {
|
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
|
|
// We never send input to the elevated GUI. Close the parent write half so
|
|
// the child sees EOF on stdin instead of waiting forever.
|
|
if libc::shutdown(fd, SHUT_WR) == -1 {
|
|
let err = io::Error::last_os_error();
|
|
libc::fclose(pipe);
|
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
|
return Err(err);
|
|
}
|
|
|
|
// AuthorizationExecuteWithPrivileges wires the tool's stdin/stdout to a
|
|
// bidirectional pipe. Drain stdout so the child can't block on a full pipe.
|
|
let read_fd = libc::dup(fd);
|
|
if read_fd == -1 {
|
|
let err = io::Error::last_os_error();
|
|
libc::fclose(pipe);
|
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
|
return Err(err);
|
|
}
|
|
let mut pipe_file = unsafe { File::from_raw_fd(read_fd) };
|
|
let mut sink = [0_u8; 8192];
|
|
loop {
|
|
match pipe_file.read(&mut sink) {
|
|
Ok(0) => break,
|
|
Ok(_) => {}
|
|
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
|
|
Err(err) => {
|
|
libc::fclose(pipe);
|
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
|
return Err(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut status = 0;
|
|
loop {
|
|
let r = wait(&mut status);
|
|
if r == -1 && io::Error::last_os_error().raw_os_error() == Some(EINTR) {
|
|
continue;
|
|
} else if r == -1 {
|
|
let err = io::Error::last_os_error();
|
|
libc::fclose(pipe);
|
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
|
return Err(err);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
libc::fclose(pipe);
|
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
|
Ok(ExitStatus::from_raw(status))
|
|
}
|
|
|
|
fn runas_root_gui(cmd: &Command) -> io::Result<ExitStatus> {
|
|
let exe: OsString = match get_exe_path(&cmd.cmd.get_program()) {
|
|
Some(exe) => exe.into(),
|
|
None => unsafe {
|
|
return Ok(mem::transmute(!0));
|
|
},
|
|
};
|
|
let prog = make_cstring!(exe);
|
|
let mut args = vec![];
|
|
for arg in cmd.cmd.get_args() {
|
|
args.push(make_cstring!(arg))
|
|
}
|
|
let mut argv: Vec<_> = args.iter().map(|x| x.as_ptr()).collect();
|
|
argv.push(ptr::null());
|
|
|
|
unsafe { gui_runas(prog.as_ptr(), argv.as_ptr()) }
|
|
}
|
|
|
|
/// The implementation of state check and elevated executing varies on each platform
|
|
impl Command {
|
|
/// Check the state the current program running
|
|
///
|
|
/// Return `true` if the program is running as root, otherwise false
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// use elevated_command::Command;
|
|
///
|
|
/// fn main() {
|
|
/// let is_elevated = Command::is_elevated();
|
|
///
|
|
/// }
|
|
/// ```
|
|
pub fn is_elevated() -> bool {
|
|
let uid = unsafe { libc::getuid() };
|
|
let euid = unsafe { libc::geteuid() };
|
|
|
|
match (uid, euid) {
|
|
(0, 0) => true,
|
|
(_, 0) => true,
|
|
(_, _) => false,
|
|
}
|
|
}
|
|
|
|
/// Prompting the user with a graphical OS dialog for the root password,
|
|
/// excuting the command with escalated privileges, and return the output
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// use elevated_command::Command;
|
|
/// use std::process::Command as StdCommand;
|
|
///
|
|
/// fn main() {
|
|
/// let mut cmd = StdCommand::new("path to the application");
|
|
/// let elevated_cmd = Command::new(cmd);
|
|
/// let output = elevated_cmd.output().unwrap();
|
|
/// }
|
|
/// ```
|
|
pub fn output(&self) -> Result<Output> {
|
|
let status = runas_root_gui(self)?;
|
|
Ok(Output {
|
|
status,
|
|
stdout: Vec::new(),
|
|
stderr: Vec::new(),
|
|
})
|
|
}
|
|
}
|