diff --git a/heim-common/src/errors.rs b/heim-common/src/errors.rs index e4cc1af6..b7c725b4 100644 --- a/heim-common/src/errors.rs +++ b/heim-common/src/errors.rs @@ -358,7 +358,11 @@ impl From for Error { match e {} } } - +impl From for Error { + fn from(e: std::string::FromUtf16Error) -> Self { + Error::from(io::Error::new(io::ErrorKind::InvalidData, e)) + } +} #[cfg(unix)] impl From for Error { fn from(e: nix::Error) -> Self { diff --git a/heim-common/src/lib.rs b/heim-common/src/lib.rs index de5a6c51..3ea9a081 100644 --- a/heim-common/src/lib.rs +++ b/heim-common/src/lib.rs @@ -39,6 +39,15 @@ pub use self::errors::{Context, Error, Result}; /// Process identifier type. #[cfg(unix)] pub type Pid = libc::pid_t; +/// User identifier type +#[cfg(unix)] +pub type Uid = libc::uid_t; +/// Group identifier type +#[cfg(unix)] +pub type Gid = libc::gid_t; +/// Process umask +#[cfg(unix)] +pub type Umask = libc::mode_t; /// Process identifier type. // TODO: Is it a correct type for pid? diff --git a/heim-host/src/os/linux.rs b/heim-host/src/os/linux.rs index 01fb44c4..1243ad3a 100644 --- a/heim-host/src/os/linux.rs +++ b/heim-host/src/os/linux.rs @@ -1,8 +1,8 @@ //! Linux-specific extensions. -use std::net::IpAddr; +use std::{convert::TryFrom, net::IpAddr}; -use crate::Pid; +use heim_common::{Error, Pid, Result, Uid}; cfg_if::cfg_if! { // aarch64-unknown-linux-gnu has different type @@ -80,3 +80,12 @@ impl UserExt for crate::User { self.as_ref().session_id() } } + +#[cfg(target_os = "linux")] +impl TryFrom for crate::User { + type Error = Error; + fn try_from(uid: Uid) -> Result { + let user = crate::sys::User::try_from(uid)?; + Ok(crate::User::try_from(user)?) + } +} diff --git a/heim-host/src/os/macos.rs b/heim-host/src/os/macos.rs index 4f5e237a..52b3a6e6 100644 --- a/heim-host/src/os/macos.rs +++ b/heim-host/src/os/macos.rs @@ -1,6 +1,7 @@ //! macOS-specific extensions. -use crate::Pid; +use heim_common::{Error, Pid, Result, Uid}; +use std::convert::TryFrom; /// macOS-specific extensions for [User]. /// @@ -40,3 +41,12 @@ impl UserExt for crate::User { self.as_ref().hostname() } } + +#[cfg(target_os = "macos")] +impl TryFrom for crate::User { + type Error = Error; + fn try_from(uid: Uid) -> Result { + let user = crate::sys::User::try_from(uid)?; + Ok(crate::User::try_from(user)?) + } +} diff --git a/heim-host/src/os/windows.rs b/heim-host/src/os/windows.rs index 7486c533..389e57e8 100644 --- a/heim-host/src/os/windows.rs +++ b/heim-host/src/os/windows.rs @@ -2,10 +2,19 @@ use std::net::IpAddr; +#[cfg(target_os = "windows")] +use winapi::um::winnt::PSID; + +use heim_common::Result; + /// Extension for [User] struct. /// /// [User]: ../../struct.User.html -pub trait UserExt { +#[cfg(target_os = "windows")] +pub trait UserExt: Sized { + #[doc(hidden)] + fn try_from_sid(sid: PSID) -> Result; + /// Domain name that the user belongs to. fn domain(&self) -> &str; @@ -26,6 +35,10 @@ pub trait UserExt { #[cfg(target_os = "windows")] impl UserExt for crate::User { + fn try_from_sid(sid: PSID) -> Result { + crate::sys::User::try_from_sid(sid).map(crate::User::from) + } + fn domain(&self) -> &str { self.as_ref().domain() } diff --git a/heim-host/src/sys/linux/users/musl.rs b/heim-host/src/sys/linux/users/musl.rs index 9e001e3d..0be78f7a 100644 --- a/heim-host/src/sys/linux/users/musl.rs +++ b/heim-host/src/sys/linux/users/musl.rs @@ -1,7 +1,7 @@ -use std::net::IpAddr; +use std::{convert::TryFrom, net::IpAddr}; use heim_common::prelude::*; -use heim_common::Pid; +use heim_common::{Pid, Uid}; use crate::os::linux::SessionId; @@ -41,3 +41,10 @@ impl User { pub async fn users() -> Result>> { Ok(stream::empty()) } + +impl TryFrom for User { + type Error = Error; + fn try_from(_uid: Uid) -> Result { + unimplemented!("https://github.com/heim-rs/heim/issues/141") + } +} diff --git a/heim-host/src/sys/linux/users/other.rs b/heim-host/src/sys/linux/users/other.rs index 557a8f85..9a552953 100644 --- a/heim-host/src/sys/linux/users/other.rs +++ b/heim-host/src/sys/linux/users/other.rs @@ -1,8 +1,8 @@ use std::ffi::CStr; -use std::net::IpAddr; +use std::{convert::TryFrom, net::IpAddr}; use heim_common::prelude::*; -use heim_common::Pid; +use heim_common::{Pid, Uid}; use crate::os::linux::SessionId; use crate::sys::unix::{from_ut_addr_v6, get_users}; @@ -88,3 +88,36 @@ pub async fn users() -> Result>> { Ok(stream::iter(users).map(Ok)) } + +//TODO: Figureout how to get rest of data +impl From<*mut libc::passwd> for User { + fn from(entry: *mut libc::passwd) -> Self { + let username = unsafe { + CStr::from_ptr((*entry).pw_name) + .to_string_lossy() + .into_owned() + }; + + User { + username, + id: "".to_string(), + terminal: "".to_string(), + hostname: "".to_string(), + pid: 0, + session_id: 0, + addr: None, + } + } +} + +impl TryFrom for User { + type Error = Error; + fn try_from(uid: Uid) -> Result { + let passwd = unsafe { libc::getpwuid(uid) }; + if passwd.is_null() { + return Err(Error::last_os_error().with_ffi("getpwuid")); + } + let user = User::from(passwd); + Ok(user) + } +} diff --git a/heim-host/src/sys/macos/users.rs b/heim-host/src/sys/macos/users.rs index c820b3ac..e071c6d8 100644 --- a/heim-host/src/sys/macos/users.rs +++ b/heim-host/src/sys/macos/users.rs @@ -1,7 +1,7 @@ -use std::ffi::CStr; +use std::{convert::TryFrom, ffi::CStr}; use heim_common::prelude::*; -use heim_common::Pid; +use heim_common::{Pid, Uid}; use super::super::unix::get_users; @@ -69,6 +69,36 @@ impl From for User { } } +impl From<*mut libc::passwd> for User { + fn from(entry: *mut libc::passwd) -> Self { + let username = unsafe { + CStr::from_ptr((*entry).pw_name) + .to_string_lossy() + .into_owned() + }; + + User { + username, + id: "".to_string(), + terminal: "".to_string(), + hostname: "".to_string(), + pid: 0, + } + } +} + +impl TryFrom for User { + type Error = Error; + fn try_from(uid: Uid) -> Result { + let passwd = unsafe { libc::getpwuid(uid) }; + if passwd.is_null() { + return Err(Error::last_os_error().with_ffi("getpwuid")); + } + let user = User::from(passwd); + Ok(user) + } +} + pub async fn users() -> Result>> { let users = get_users::(); diff --git a/heim-host/src/sys/windows/users.rs b/heim-host/src/sys/windows/users.rs index f9ff545d..7c536013 100644 --- a/heim-host/src/sys/windows/users.rs +++ b/heim-host/src/sys/windows/users.rs @@ -2,6 +2,10 @@ use std::net::IpAddr; use super::wrappers::{Session, Sessions}; use heim_common::prelude::*; +use std::ptr; +use winapi::shared::minwindef::DWORD; +use winapi::um::winbase::LookupAccountSidW; +use winapi::um::winnt::{SidTypeUser, PSID, SID_NAME_USE, WCHAR}; #[derive(Debug)] pub struct User { @@ -26,6 +30,41 @@ impl User { address: session.address()?, })) } + pub fn try_from_sid(sid: PSID) -> Result { + // name and domain cannot be longer than 256 + let mut name_cch: DWORD = 256; + let mut name: Vec = Vec::with_capacity(name_cch as usize); + let mut domain_cch: DWORD = 256; + let mut domain: Vec = Vec::with_capacity(domain_cch as usize); + let mut account_type: SID_NAME_USE = 0; + + let result = unsafe { + LookupAccountSidW( + ptr::null(), + sid, + name.as_mut_ptr(), + &mut name_cch, + domain.as_mut_ptr(), + &mut domain_cch, + &mut account_type, + ) + }; + + if result == 0 || account_type != SidTypeUser { + return Err(Error::last_os_error().with_ffi("LookupAccountSidW")); + } + + unsafe { + name.set_len(name_cch as usize); + domain.set_len(domain_cch as usize); + } + + Ok(Self { + domain: String::from_utf16(domain.as_slice())?, + username: String::from_utf16(name.as_slice())?, + address: None, + }) + } pub fn domain(&self) -> &str { self.domain.as_str() diff --git a/heim-process/Cargo.toml b/heim-process/Cargo.toml index 534eb3d0..efced18d 100644 --- a/heim-process/Cargo.toml +++ b/heim-process/Cargo.toml @@ -49,10 +49,12 @@ features = [ "psapi", "processthreadsapi", "winerror", - "tlhelp32" + "tlhelp32", + "securitybaseapi" ] [target.'cfg(target_os = "macos")'.dependencies] +heim-host = { version = "0.1.0-beta.1", path = "../heim-host" } mach = "0.3.2" darwin-libproc = "0.2.0" diff --git a/heim-process/src/lib.rs b/heim-process/src/lib.rs index d718c7cf..561eaa9a 100644 --- a/heim-process/src/lib.rs +++ b/heim-process/src/lib.rs @@ -40,3 +40,12 @@ pub use heim_common::Pid; #[cfg(target_os = "linux")] pub use heim_net::IoCounters; + +#[cfg(target_os = "linux")] +pub use heim_common::Uid; + +#[cfg(target_os = "linux")] +pub use heim_common::Gid; + +#[cfg(target_os = "linux")] +pub use heim_common::Umask; diff --git a/heim-process/src/process/mod.rs b/heim-process/src/process/mod.rs index f5d5f37c..d5fc67e6 100644 --- a/heim-process/src/process/mod.rs +++ b/heim-process/src/process/mod.rs @@ -2,10 +2,10 @@ use std::fmt; use std::path::PathBuf; use std::time::Instant; +use crate::{sys, Pid, ProcessResult}; use heim_common::prelude::*; use heim_common::units::Time; - -use crate::{sys, Pid, ProcessResult}; +use heim_host::User; mod command; mod cpu_times; @@ -99,6 +99,11 @@ impl Process { self.as_ref().status().await } + /// Returns user who owns this process. + pub async fn user(&self) -> ProcessResult { + self.as_ref().user().await.map(Into::into) + } + /// Returns process environment. pub async fn environment(&self) -> ProcessResult { self.as_ref().environment().await.map(Into::into) diff --git a/heim-process/src/sys/linux/process/mod.rs b/heim-process/src/sys/linux/process/mod.rs index 0525364a..2f1ef0dd 100644 --- a/heim-process/src/sys/linux/process/mod.rs +++ b/heim-process/src/sys/linux/process/mod.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use heim_common::prelude::*; use heim_common::units::Time; +use heim_host::User; use heim_runtime as rt; use super::{pid_exists, pids}; @@ -13,10 +14,10 @@ use crate::os::unix::Signal; use crate::sys::common::UniqueId; use crate::sys::unix::{pid_kill, pid_priority, pid_setpriority, pid_wait}; use crate::{Pid, ProcessError, ProcessResult, Status}; - mod procfs; pub use self::procfs::{Command, CommandIter, CpuTime, Environment, IoCounters, Memory}; +use std::convert::TryFrom; #[derive(Debug)] pub struct Process { @@ -109,6 +110,11 @@ impl Process { Ok(state) } + pub async fn user(&self) -> ProcessResult { + let status = procfs::status(self.pid).await?; + Ok(User::try_from(status.uid.real)?) + } + pub async fn environment(&self) -> ProcessResult { procfs::environment(self.pid).await } diff --git a/heim-process/src/sys/linux/process/procfs/mod.rs b/heim-process/src/sys/linux/process/procfs/mod.rs index 47ac937b..0556a9e4 100644 --- a/heim-process/src/sys/linux/process/procfs/mod.rs +++ b/heim-process/src/sys/linux/process/procfs/mod.rs @@ -4,6 +4,7 @@ mod env; mod io; mod stat; mod statm; +mod status; pub use self::command::{command, Command, CommandIter}; pub use self::cpu_times::CpuTime; @@ -11,3 +12,4 @@ pub use self::env::{environment, Environment, IntoEnvironmentIter}; pub use self::io::{io, IoCounters}; pub use self::stat::{stat, Stat}; pub use self::statm::{stat_memory, Memory}; +pub use self::status::{status, Status}; diff --git a/heim-process/src/sys/linux/process/procfs/status.rs b/heim-process/src/sys/linux/process/procfs/status.rs new file mode 100644 index 00000000..5e5ae020 --- /dev/null +++ b/heim-process/src/sys/linux/process/procfs/status.rs @@ -0,0 +1,135 @@ +use crate::{Gid, Pid, ProcessResult, Status as State, Uid, Umask}; +use heim_common::prelude::*; +use heim_common::utils::iter::{ParseIterator, TryIterator}; +use heim_runtime as rt; +use std::str::FromStr; + +#[derive(Default)] +pub struct Uids { + pub real: Uid, + pub effective: Uid, + pub saved: Uid, + pub filesystem: Uid, +} + +#[derive(Default)] +pub struct Gids { + pub real: Gid, + pub effective: Gid, + pub saved: Gid, + pub filesystem: Gid, +} + +pub struct Status { + pub name: String, + pub umask: Umask, + pub state: State, + pub tgid: Gid, + pub ngid: Gid, + pub pid: Pid, + pub ppid: Pid, + pub tracer_pid: Pid, + pub uid: Uids, + pub gid: Gids, +} + +impl Default for Status { + fn default() -> Self { + Status { + name: String::default(), + umask: Umask::default(), + state: State::Running, + tgid: Gid::default(), + ngid: Gid::default(), + pid: Pid::default(), + ppid: Pid::default(), + tracer_pid: Pid::default(), + uid: Uids::default(), + gid: Gids::default(), + } + } +} + +impl FromStr for Status { + type Err = Error; + + fn from_str(s: &str) -> Result { + let mut status = Status::default(); + + let split_str = s.split('\n'); + for s in split_str { + let mut col = s.splitn(2, '\t'); + match col.try_next()?.trim_end_matches(':') { + "Name" => status.name = col.try_parse_next()?, + "Umask" => status.umask = col.try_parse_next()?, + "State" => status.state = col.try_parse_next()?, + "Tgid" => status.tgid = col.try_parse_next()?, + "Ngid" => status.ngid = col.try_parse_next()?, + "Pid" => status.pid = col.try_parse_next()?, + "PPid" => status.ppid = col.try_parse_next()?, + "TracerPid" => status.tracer_pid = col.try_parse_next()?, + "Uid" => { + let mut uids = col.try_next()?.split('\t'); + status.uid.real = uids.try_parse_next()?; + status.uid.effective = uids.try_parse_next()?; + status.uid.saved = uids.try_parse_next()?; + status.uid.filesystem = uids.try_parse_next()?; + } + "Gid" => { + let mut gids = col.try_next()?.split('\t'); + status.gid.real = gids.try_parse_next()?; + status.gid.effective = gids.try_parse_next()?; + status.gid.saved = gids.try_parse_next()?; + status.gid.filesystem = gids.try_parse_next()?; + } + _ => { + break; + } + } + } + Ok(status) + } +} + +pub async fn status(pid: Pid) -> ProcessResult { + rt::fs::read_into::<_, _, Error>(format!("/proc/{}/status", pid)) + .await + .map_err(Into::into) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn status_is_properly_parsed() { + let status_file = "Name:\tcat\n\ + Umask:\t0022\n\ + State:\tR (running)\n\ + Tgid:\t43888\n\ + Ngid:\t0\n\ + Pid:\t43888\n\ + PPid:\t43863\n\ + TracerPid:\t0\n\ + Uid:\t1000\t1001\t1002\t1003\n\ + Gid:\t1004\t1005\t1006\t1007\n\ + "; + + let status = Status::from_str(status_file).unwrap(); + assert_eq!(status.name, "cat"); + assert_eq!(status.umask, 0o22); + assert_eq!(status.state, State::Running); + assert_eq!(status.tgid, 43888); + assert_eq!(status.ngid, 0); + assert_eq!(status.pid, 43888); + assert_eq!(status.ppid, 43863); + assert_eq!(status.tracer_pid, 0); + assert_eq!(status.uid.real, 1000); + assert_eq!(status.uid.effective, 1001); + assert_eq!(status.uid.saved, 1002); + assert_eq!(status.uid.filesystem, 1003); + assert_eq!(status.gid.real, 1004); + assert_eq!(status.gid.effective, 1005); + assert_eq!(status.gid.saved, 1006); + assert_eq!(status.gid.filesystem, 1007); + } +} diff --git a/heim-process/src/sys/macos/process/mod.rs b/heim-process/src/sys/macos/process/mod.rs index 5e9e2018..611ef019 100644 --- a/heim-process/src/sys/macos/process/mod.rs +++ b/heim-process/src/sys/macos/process/mod.rs @@ -7,16 +7,16 @@ use std::path::PathBuf; use ::futures::future::BoxFuture; -use heim_common::prelude::*; -use heim_common::sys::IntoTime; -use heim_common::units::Time; - use super::{bindings, pids, utils::catch_zombie}; use crate::os::unix::Signal; use crate::sys::common::UniqueId; use crate::sys::unix::{pid_kill, pid_priority, pid_setpriority, pid_wait}; pub use crate::sys::unix::{Environment, EnvironmentIter, IntoEnvironmentIter}; use crate::{Pid, ProcessError, ProcessResult, Status}; +use heim_common::prelude::*; +use heim_common::sys::IntoTime; +use heim_common::{units::Time, Uid}; +use heim_host::User; mod command; mod cpu_times; @@ -124,6 +124,14 @@ impl Process { pid_setpriority(self.pid, value) } + pub async fn user(&self) -> ProcessResult { + let uid: Uid = match bindings::process(self.pid) { + Ok(kinfo_proc) => kinfo_proc.kp_eproc.e_pcred.p_ruid, + Err(e) => return Err(e), + }; + Ok(User::try_from(uid)?) + } + pub async fn is_running(&self) -> ProcessResult { let other = get(self.pid).await?; diff --git a/heim-process/src/sys/windows/bindings/handle/limited_info.rs b/heim-process/src/sys/windows/bindings/handle/limited_info.rs index a4ac924e..4ebb431c 100644 --- a/heim-process/src/sys/windows/bindings/handle/limited_info.rs +++ b/heim-process/src/sys/windows/bindings/handle/limited_info.rs @@ -1,6 +1,8 @@ //! Process handle variant for querying process information //! without requiring any additional privileges (expected to work for any user) +use super::super::token::Token; +use heim_host::User; use std::convert::TryFrom; use std::ffi::OsString; use std::io; @@ -8,7 +10,6 @@ use std::marker::PhantomData; use std::mem; use std::os::windows::ffi::OsStringExt; use std::path::PathBuf; - use winapi::ctypes::wchar_t; use winapi::shared::minwindef::{DWORD, FILETIME, MAX_PATH}; use winapi::um::{processthreadsapi, psapi, winbase, winnt}; @@ -167,4 +168,8 @@ impl ProcessHandle { Ok((creation, exit, kernel, user)) } } + + pub fn owner(&self) -> ProcessResult { + Token::open(&self.handle)?.user().map_err(Into::into) + } } diff --git a/heim-process/src/sys/windows/bindings/mod.rs b/heim-process/src/sys/windows/bindings/mod.rs index cfdbfde7..2de8f566 100644 --- a/heim-process/src/sys/windows/bindings/mod.rs +++ b/heim-process/src/sys/windows/bindings/mod.rs @@ -8,6 +8,7 @@ use heim_common::{Error, Result}; pub mod handle; pub mod processes; pub mod snapshot; +pub mod token; pub use self::handle::ProcessHandle; diff --git a/heim-process/src/sys/windows/bindings/token.rs b/heim-process/src/sys/windows/bindings/token.rs new file mode 100644 index 00000000..704ab497 --- /dev/null +++ b/heim-process/src/sys/windows/bindings/token.rs @@ -0,0 +1,52 @@ +use std::mem; +use std::ptr; +use winapi::shared::minwindef::{DWORD, LPVOID}; +use winapi::um::processthreadsapi::OpenProcessToken; +use winapi::um::securitybaseapi::GetTokenInformation; +use winapi::um::winnt::{TokenUser, HANDLE, TOKEN_QUERY, TOKEN_USER}; + +use heim_common::prelude::*; +use heim_common::sys::windows::Handle; +use heim_common::Result; +use heim_host::os::windows::UserExt; +use heim_host::User; + +pub struct Token(Handle); + +impl Token { + pub fn open(process_handle: &Handle) -> Result { + let mut token_handle: HANDLE = ptr::null_mut(); + + let result = unsafe { OpenProcessToken(**process_handle, TOKEN_QUERY, &mut token_handle) }; + + if result == 0 { + return Err(Error::last_os_error().with_ffi("OpenProcessToken")); + } + + Ok(Self(Handle::new(token_handle))) + } + + pub fn user(&self) -> Result { + let mut data = mem::MaybeUninit::::uninit(); + let mut length: DWORD = 0; + + let result = unsafe { + GetTokenInformation( + *self.0, + TokenUser, + data.as_mut_ptr() as LPVOID, + // data should always be 44 bytes + 44, + &mut length, + ) + }; + + if result == 0 { + return Err(Error::last_os_error().with_ffi("GetTokenInformation")); + } + + let token_user = unsafe { data.assume_init() }; + + User::try_from_sid(token_user.User.Sid) + } +} diff --git a/heim-process/src/sys/windows/process/mod.rs b/heim-process/src/sys/windows/process/mod.rs index 5d590b62..f403cab3 100644 --- a/heim-process/src/sys/windows/process/mod.rs +++ b/heim-process/src/sys/windows/process/mod.rs @@ -1,11 +1,11 @@ +use heim_common::prelude::*; +use heim_common::units::Time; +use heim_host::User; use std::cmp; use std::ffi::OsString; use std::hash; use std::os::windows::ffi::OsStringExt; use std::path::PathBuf; - -use heim_common::prelude::*; -use heim_common::units::Time; use winapi::um::processthreadsapi; use super::{bindings, pid_exists, pids}; @@ -157,6 +157,15 @@ impl Process { handle.set_priority(value).map_err(Into::into) } + pub async fn user(&self) -> ProcessResult { + if self.pid == 0 || self.pid == 4 { + Err(ProcessError::AccessDenied(self.pid)) + } else { + let handle = bindings::ProcessHandle::query_limited_info(self.pid)?; + + handle.owner() + } + } pub async fn is_running(&self) -> ProcessResult { let other = get(self.pid).await?; diff --git a/heim-process/tests/smoke.rs b/heim-process/tests/smoke.rs index d98cbfc0..0b2f7543 100644 --- a/heim-process/tests/smoke.rs +++ b/heim-process/tests/smoke.rs @@ -68,6 +68,7 @@ async fn smoke_processes() -> Result<()> { try_method!(process.memory()); try_method!(process.is_running()); try_method!(process.io_counters()); + try_method!(process.user()); #[cfg(unix)] {