#![allow(unused)]
use std::{
ffi::{c_int, CStr, CString},
fs::OpenOptions,
io,
mem::MaybeUninit,
os::fd::AsRawFd,
path::PathBuf,
str::FromStr,
};
use crate::cutils::*;
pub use audit::secure_open;
use interface::{DeviceId, GroupId, ProcessId, UserId};
pub use libc::PATH_MAX;
use time::SystemTime;
mod audit;
pub mod interface;
pub mod file;
pub mod time;
pub mod timestamp;
pub mod signal;
pub mod poll;
pub mod term;
pub mod wait;
#[cfg(target_os = "linux")]
pub fn fork() -> io::Result<ProcessId> {
cerr(unsafe { libc::fork() })
}
#[cfg(not(target_os = "linux"))]
pub unsafe fn fork() -> io::Result<ProcessId> {
cerr(unsafe { libc::fork() })
}
pub fn setsid() -> io::Result<ProcessId> {
cerr(unsafe { libc::setsid() })
}
pub fn hostname() -> String {
const MAX_HOST_NAME_SIZE_ACCORDING_TO_SUSV2: libc::c_long = 255;
let max_hostname_size =
sysconf(libc::_SC_HOST_NAME_MAX).unwrap_or(MAX_HOST_NAME_SIZE_ACCORDING_TO_SUSV2) as usize;
let buffer_size = max_hostname_size + 1 ;
let mut buf = vec![0; buffer_size];
match cerr(unsafe { libc::gethostname(buf.as_mut_ptr(), buffer_size) }) {
Ok(_) => unsafe { string_from_ptr(buf.as_ptr()) },
Err(_) => {
panic!("Unexpected error while retrieving hostname, this should not happen");
}
}
}
pub fn syslog(priority: libc::c_int, facility: libc::c_int, message: &str) {
let msg = CString::new(message).unwrap();
unsafe {
libc::syslog(priority | facility, msg.as_ptr());
}
}
pub fn set_target_user(
cmd: &mut std::process::Command,
mut target_user: User,
target_group: Group,
) {
use std::os::unix::process::CommandExt;
if !target_user.groups.contains(&target_group.gid) {
target_user.groups.push(target_group.gid);
}
unsafe {
cmd.pre_exec(move || {
cerr(libc::setgroups(
target_user.groups.len(),
target_user.groups.as_ptr(),
))?;
cerr(libc::setgid(target_group.gid))?;
cerr(libc::setuid(target_user.uid))?;
Ok(())
});
}
}
pub fn kill(pid: ProcessId, signal: c_int) -> io::Result<()> {
cerr(unsafe { libc::kill(pid, signal) }).map(|_| ())
}
pub fn killpg(pgid: ProcessId, signal: c_int) -> io::Result<()> {
cerr(unsafe { libc::killpg(pgid, signal) }).map(|_| ())
}
pub fn getpgid(pid: ProcessId) -> io::Result<ProcessId> {
cerr(unsafe { libc::getpgid(pid) })
}
pub fn setpgid(pid: ProcessId, pgid: ProcessId) -> io::Result<()> {
cerr(unsafe { libc::setpgid(pid, pgid) }).map(|_| ())
}
pub fn chdir<S: AsRef<CStr>>(path: &S) -> io::Result<()> {
cerr(unsafe { libc::chdir(path.as_ref().as_ptr()) }).map(|_| ())
}
pub fn chown<S: AsRef<CStr>>(
path: &S,
uid: impl Into<Option<UserId>>,
gid: impl Into<Option<GroupId>>,
) -> io::Result<()> {
let path = path.as_ref().as_ptr();
let uid = uid.into().unwrap_or(UserId::MAX);
let gid = gid.into().unwrap_or(GroupId::MAX);
cerr(unsafe { libc::chown(path, uid, gid) }).map(|_| ())
}
#[derive(Debug, Clone, PartialEq)]
pub struct User {
pub uid: UserId,
pub gid: GroupId,
pub name: String,
pub gecos: String,
pub home: PathBuf,
pub shell: PathBuf,
pub passwd: String,
pub groups: Vec<GroupId>,
}
impl User {
unsafe fn from_libc(pwd: &libc::passwd) -> User {
let mut buf_len: libc::c_int = 32;
let mut groups_buffer: Vec<libc::gid_t>;
while {
groups_buffer = vec![0; buf_len as usize];
let result = unsafe {
libc::getgrouplist(
pwd.pw_name,
pwd.pw_gid,
groups_buffer.as_mut_ptr(),
&mut buf_len,
)
};
result == -1
} {
if buf_len >= 65536 {
panic!("user has too many groups (> 65536), this should not happen");
}
buf_len *= 2;
}
groups_buffer.resize_with(buf_len as usize, || {
panic!("invalid groups count returned from getgrouplist, this should not happen")
});
User {
uid: pwd.pw_uid,
gid: pwd.pw_gid,
name: string_from_ptr(pwd.pw_name),
gecos: string_from_ptr(pwd.pw_gecos),
home: os_string_from_ptr(pwd.pw_dir).into(),
shell: os_string_from_ptr(pwd.pw_shell).into(),
passwd: string_from_ptr(pwd.pw_passwd),
groups: groups_buffer,
}
}
pub fn from_uid(uid: UserId) -> std::io::Result<Option<User>> {
let max_pw_size = sysconf(libc::_SC_GETPW_R_SIZE_MAX).unwrap_or(16_384);
let mut buf = vec![0; max_pw_size as usize];
let mut pwd = MaybeUninit::uninit();
let mut pwd_ptr = std::ptr::null_mut();
cerr(unsafe {
libc::getpwuid_r(
uid,
pwd.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut pwd_ptr,
)
})?;
if pwd_ptr.is_null() {
Ok(None)
} else {
let pwd = unsafe { pwd.assume_init() };
Ok(Some(unsafe { Self::from_libc(&pwd) }))
}
}
pub fn effective_uid() -> UserId {
unsafe { libc::geteuid() }
}
pub fn effective() -> std::io::Result<Option<User>> {
Self::from_uid(Self::effective_uid())
}
pub fn real_uid() -> UserId {
unsafe { libc::getuid() }
}
pub fn real() -> std::io::Result<Option<User>> {
Self::from_uid(Self::real_uid())
}
pub fn from_name(name: &str) -> std::io::Result<Option<User>> {
let max_pw_size = sysconf(libc::_SC_GETPW_R_SIZE_MAX).unwrap_or(16_384);
let mut buf = vec![0; max_pw_size as usize];
let mut pwd = MaybeUninit::uninit();
let mut pwd_ptr = std::ptr::null_mut();
let name_c = CString::new(name).expect("String contained null bytes");
cerr(unsafe {
libc::getpwnam_r(
name_c.as_ptr(),
pwd.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut pwd_ptr,
)
})?;
if pwd_ptr.is_null() {
Ok(None)
} else {
let pwd = unsafe { pwd.assume_init() };
Ok(Some(unsafe { Self::from_libc(&pwd) }))
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Group {
pub gid: GroupId,
pub name: String,
pub passwd: String,
pub members: Vec<String>,
}
impl Group {
unsafe fn from_libc(grp: &libc::group) -> Group {
let mut mem_count = 0;
while !(*grp.gr_mem.offset(mem_count)).is_null() {
mem_count += 1;
}
let mut members = Vec::with_capacity(mem_count as usize);
let mem_slice = std::slice::from_raw_parts(grp.gr_mem, mem_count as usize);
for mem in mem_slice {
members.push(string_from_ptr(*mem));
}
Group {
gid: grp.gr_gid,
name: string_from_ptr(grp.gr_name),
passwd: string_from_ptr(grp.gr_passwd),
members,
}
}
pub fn effective_gid() -> GroupId {
unsafe { libc::getegid() }
}
pub fn effective() -> std::io::Result<Option<Group>> {
Self::from_gid(Self::effective_gid())
}
pub fn real_gid() -> UserId {
unsafe { libc::getgid() }
}
pub fn real() -> std::io::Result<Option<Group>> {
Self::from_gid(Self::real_gid())
}
pub fn from_gid(gid: GroupId) -> std::io::Result<Option<Group>> {
let max_gr_size = sysconf(libc::_SC_GETGR_R_SIZE_MAX).unwrap_or(16_384);
let mut buf = vec![0; max_gr_size as usize];
let mut grp = MaybeUninit::uninit();
let mut grp_ptr = std::ptr::null_mut();
cerr(unsafe {
libc::getgrgid_r(
gid,
grp.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut grp_ptr,
)
})?;
if grp_ptr.is_null() {
Ok(None)
} else {
let grp = unsafe { grp.assume_init() };
Ok(Some(unsafe { Group::from_libc(&grp) }))
}
}
pub fn from_name(name: &str) -> std::io::Result<Option<Group>> {
let max_gr_size = sysconf(libc::_SC_GETGR_R_SIZE_MAX).unwrap_or(16_384);
let mut buf = vec![0; max_gr_size as usize];
let mut grp = MaybeUninit::uninit();
let mut grp_ptr = std::ptr::null_mut();
let name_c = CString::new(name).expect("String contained null bytes");
cerr(unsafe {
libc::getgrnam_r(
name_c.as_ptr(),
grp.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut grp_ptr,
)
})?;
if grp_ptr.is_null() {
Ok(None)
} else {
let grp = unsafe { grp.assume_init() };
Ok(Some(unsafe { Group::from_libc(&grp) }))
}
}
}
pub enum WithProcess {
Current,
Other(ProcessId),
}
impl WithProcess {
fn to_proc_string(&self) -> String {
match self {
WithProcess::Current => "self".into(),
WithProcess::Other(pid) => pid.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct Process {
pub pid: ProcessId,
pub parent_pid: Option<ProcessId>,
pub group_id: ProcessId,
pub session_id: ProcessId,
pub term_foreground_group_id: Option<ProcessId>,
pub name: PathBuf,
}
impl Default for Process {
fn default() -> Self {
Self::new()
}
}
impl Process {
pub fn new() -> Process {
Process {
pid: Self::process_id(),
parent_pid: Self::parent_id(),
group_id: Self::group_id(),
session_id: Self::session_id(),
term_foreground_group_id: Self::term_foreground_group_id(),
name: Self::process_name().unwrap_or_else(|| PathBuf::from("sudo")),
}
}
pub fn process_name() -> Option<PathBuf> {
std::env::args().next().map(PathBuf::from)
}
pub fn process_id() -> ProcessId {
unsafe { libc::getpid() }
}
pub fn parent_id() -> Option<ProcessId> {
let pid = unsafe { libc::getppid() };
if pid == 0 {
None
} else {
Some(pid)
}
}
pub fn group_id() -> ProcessId {
unsafe { libc::getpgid(0) }
}
pub fn session_id() -> ProcessId {
unsafe { libc::getsid(0) }
}
pub fn term_foreground_group_id() -> Option<ProcessId> {
match OpenOptions::new().read(true).write(true).open("/dev/tty") {
Ok(f) => {
let res = unsafe { libc::tcgetpgrp(f.as_raw_fd()) };
if res == -1 {
None
} else {
Some(res)
}
}
Err(_) => None,
}
}
pub fn tty_device_id(pid: WithProcess) -> std::io::Result<Option<DeviceId>> {
let data: i32 = read_proc_stat(pid, 6)?;
if data == 0 {
Ok(None)
} else {
Ok(Some(data as u32 as DeviceId))
}
}
pub fn starting_time(pid: WithProcess) -> io::Result<SystemTime> {
let process_start: u64 = read_proc_stat(pid, 21)?;
let ticks_per_second = crate::cutils::sysconf(libc::_SC_CLK_TCK).ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"Could not retrieve system config variable for ticks per second",
)
})? as u64;
Ok(SystemTime::new(
(process_start / ticks_per_second) as i64,
((process_start % ticks_per_second) * (1_000_000_000 / ticks_per_second)) as i64,
))
}
}
fn read_proc_stat<T: FromStr>(pid: WithProcess, field_idx: isize) -> io::Result<T> {
let pidref = pid.to_proc_string();
let path = PathBuf::from_iter(&["/proc", &pidref, "stat"]);
let proc_stat = std::fs::read(path)?;
let skip_past_second_arg = proc_stat.iter().rposition(|b| *b == b')').ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Could not find position of 'comm' field in process stat",
)
})?;
let mut stat = &proc_stat[skip_past_second_arg..];
let mut curr_field = 1;
while curr_field < field_idx && !stat.is_empty() {
if stat[0] == b' ' {
curr_field += 1;
}
stat = &stat[1..];
}
if stat.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Stat file was not of the expected format",
));
}
let mut idx = 0;
while stat[idx] != b' ' && idx < stat.len() {
idx += 1;
}
let field = &stat[0..idx];
let fielddata = std::str::from_utf8(field).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Could not interpret byte slice as string",
)
})?;
fielddata.parse().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Could not interpret string as number",
)
})
}
#[cfg(test)]
mod tests {
use std::{
io::{Read, Write},
os::unix::net::UnixStream,
};
use libc::SIGKILL;
use super::{fork, setpgid, Group, User, WithProcess};
#[test]
fn test_get_user_and_group_by_id() {
let fixed_users = &[(0, "root"), (1, "daemon")];
for &(id, name) in fixed_users {
let root = User::from_uid(id).unwrap().unwrap();
assert_eq!(root.uid, id as libc::uid_t);
assert_eq!(root.name, name);
}
for &(id, name) in fixed_users {
let root = Group::from_gid(id).unwrap().unwrap();
assert_eq!(root.gid, id as libc::gid_t);
assert_eq!(root.name, name);
}
}
#[test]
fn miri_test_group_impl() {
use super::Group;
use std::ffi::CString;
fn test(name: &str, passwd: &str, gid: libc::gid_t, mem: &[&str]) {
assert_eq!(
{
let c_mem: Vec<CString> =
mem.iter().map(|&s| CString::new(s).unwrap()).collect();
let c_name = CString::new(name).unwrap();
let c_passwd = CString::new(passwd).unwrap();
unsafe {
Group::from_libc(&libc::group {
gr_name: c_name.as_ptr() as *mut _,
gr_passwd: c_passwd.as_ptr() as *mut _,
gr_gid: gid,
gr_mem: c_mem
.iter()
.map(|cs| cs.as_ptr() as *mut _)
.chain(std::iter::once(std::ptr::null_mut()))
.collect::<Vec<*mut libc::c_char>>()
.as_mut_ptr(),
})
}
},
Group {
name: name.to_string(),
passwd: passwd.to_string(),
gid,
members: mem.iter().map(|s| s.to_string()).collect(),
}
)
}
test("dr. bill", "fidelio", 1999, &["eyes", "wide", "shut"]);
test("eris", "fnord", 5, &[]);
test("abc", "password123", 42, &[""]);
}
#[test]
fn get_process_tty_device() {
assert!(super::Process::tty_device_id(WithProcess::Current).is_ok());
}
#[test]
fn get_process_start_time() {
let time = super::Process::starting_time(WithProcess::Current).unwrap();
let now = super::SystemTime::now().unwrap();
assert!(time > now - super::time::Duration::minutes(24 * 60));
assert!(time < now);
}
#[test]
fn pgid_test() {
use super::{getpgid, setpgid};
assert_eq!(
getpgid(std::process::id() as i32).unwrap(),
getpgid(0).unwrap()
);
match super::fork().unwrap() {
0 => {
std::thread::sleep(std::time::Duration::from_secs(1))
}
child_pid => {
assert_eq!(getpgid(child_pid).unwrap(), getpgid(0).unwrap(),);
setpgid(child_pid, child_pid).unwrap();
assert_eq!(getpgid(child_pid).unwrap(), child_pid);
}
}
}
#[test]
fn kill_test() {
let mut child = std::process::Command::new("/bin/sleep")
.arg("1")
.spawn()
.unwrap();
super::kill(child.id() as i32, SIGKILL).unwrap();
assert!(!child.wait().unwrap().success());
}
#[test]
fn killpg_test() {
let (mut rx, mut tx) = UnixStream::pair().unwrap();
let pid1 = fork().unwrap();
if pid1 == 0 {
std::thread::sleep(std::time::Duration::from_secs(1));
tx.write_all(&[42]).unwrap();
}
let pid2 = fork().unwrap();
if pid2 == 0 {
std::thread::sleep(std::time::Duration::from_secs(1));
tx.write_all(&[42]).unwrap();
}
drop(tx);
let pgid = pid1;
setpgid(pid1, pgid).unwrap();
setpgid(pid2, pgid).unwrap();
super::killpg(pgid, SIGKILL).unwrap();
assert_eq!(
rx.read_exact(&mut [0; 2]).unwrap_err().kind(),
std::io::ErrorKind::UnexpectedEof
);
}
}