use std::{
cmp::Ordering,
ffi::CStr,
os::fd::{AsRawFd, BorrowedFd},
};
use libc::{c_char, c_int, gid_t, pid_t, projid_t, ucred_t, uid_t, zoneid_t};
extern "C" {
fn door_ucred(info: *mut *mut ucred_t) -> c_int;
fn getzoneid() -> zoneid_t;
}
const P_MYID: pid_t = -1;
const GLOBAL_ZONEID: zoneid_t = 0;
pub struct UCred {
uc: *mut libc::ucred_t,
}
impl UCred {
pub fn from_self() -> std::io::Result<UCred> {
Self::from_pid(P_MYID)
}
#[doc(hidden)]
#[deprecated(note = "use from_self() instead")]
pub fn for_self() -> std::io::Result<UCred> {
Self::from_self()
}
pub fn from_pid(pid: libc::pid_t) -> std::io::Result<UCred> {
let uc = unsafe { libc::ucred_get(pid) };
if uc.is_null() {
Err(std::io::Error::last_os_error())
} else {
Ok(UCred { uc })
}
}
#[doc(hidden)]
#[deprecated(note = "use from_pid() instead")]
pub fn for_pid(pid: libc::pid_t) -> std::io::Result<UCred> {
Self::from_pid(pid)
}
pub fn from_socket(fd: BorrowedFd) -> std::io::Result<UCred> {
let mut uc: *mut libc::ucred_t = std::ptr::null_mut();
let r = unsafe { libc::getpeerucred(fd.as_raw_fd(), &mut uc) };
if r == 0 {
assert!(!uc.is_null());
Ok(UCred { uc })
} else {
Err(std::io::Error::last_os_error())
}
}
#[doc(hidden)]
#[deprecated(note = "use from_socket() instead")]
pub fn for_socket(fd: BorrowedFd) -> std::io::Result<UCred> {
Self::from_socket(fd)
}
pub fn from_door_call() -> std::io::Result<UCred> {
let mut uc: *mut libc::ucred_t = std::ptr::null_mut();
let r = unsafe { door_ucred(&mut uc) };
if r == 0 {
assert!(!uc.is_null());
Ok(UCred { uc })
} else {
Err(std::io::Error::last_os_error())
}
}
#[doc(hidden)]
#[deprecated(note = "use from_door_call() instead")]
pub fn for_door_call() -> std::io::Result<UCred> {
Self::from_door_call()
}
fn common_get_s<T: PartialEq<i32>>(
&self,
func: unsafe extern "C" fn(*const ucred_t) -> T,
) -> Option<T> {
let id = unsafe { func(self.uc) };
if id == -1 {
None
} else {
Some(id)
}
}
fn common_get_u<T: PartialEq<u32>>(
&self,
func: unsafe extern "C" fn(*const ucred_t) -> T,
) -> Option<T> {
let id = unsafe { func(self.uc) };
if id == u32::MAX {
None
} else {
Some(id)
}
}
pub fn pid(&self) -> Option<pid_t> {
self.common_get_s(libc::ucred_getpid)
}
pub fn is_same_process(&self) -> bool {
self.pid()
.map(|otherpid| std::process::id() == otherpid.try_into().unwrap())
.unwrap_or(false)
}
pub fn project(&self) -> Option<projid_t> {
self.common_get_s(libc::ucred_getprojid)
}
pub fn zoneid(&self) -> Option<zoneid_t> {
self.common_get_s(libc::ucred_getzoneid)
}
pub fn is_same_zone(&self) -> std::io::Result<bool> {
let zid = unsafe { getzoneid() };
if zid < 0 {
return Err(std::io::Error::last_os_error());
}
Ok(self.zoneid().map(|otherzid| otherzid == zid).unwrap_or(false))
}
pub fn is_global_zone(&self) -> bool {
self.zoneid().map(|otherzid| otherzid == GLOBAL_ZONEID).unwrap_or(false)
}
pub fn euid(&self) -> Option<uid_t> {
self.common_get_u(libc::ucred_geteuid)
}
pub fn euser(&self) -> std::io::Result<Option<User>> {
self.euid().map(uid_to_user).transpose().map(Option::flatten)
}
pub fn ruid(&self) -> Option<uid_t> {
self.common_get_u(libc::ucred_getruid)
}
pub fn ruser(&self) -> std::io::Result<Option<User>> {
self.ruid().map(uid_to_user).transpose().map(Option::flatten)
}
pub fn suid(&self) -> Option<uid_t> {
self.common_get_u(libc::ucred_getsuid)
}
pub fn suser(&self) -> std::io::Result<Option<User>> {
self.suid().map(uid_to_user).transpose().map(Option::flatten)
}
pub fn egid(&self) -> Option<gid_t> {
self.common_get_u(libc::ucred_getegid)
}
pub fn egroup(&self) -> std::io::Result<Option<Group>> {
self.egid().map(gid_to_group).transpose().map(Option::flatten)
}
pub fn rgid(&self) -> Option<gid_t> {
self.common_get_u(libc::ucred_getrgid)
}
pub fn rgroup(&self) -> std::io::Result<Option<Group>> {
self.rgid().map(gid_to_group).transpose().map(Option::flatten)
}
pub fn sgid(&self) -> Option<gid_t> {
self.common_get_u(libc::ucred_getsgid)
}
pub fn sgroup(&self) -> std::io::Result<Option<Group>> {
self.sgid().map(gid_to_group).transpose().map(Option::flatten)
}
pub fn groups(&self) -> Option<&[gid_t]> {
let mut groups: *const gid_t = std::ptr::null();
let ngroups = unsafe { libc::ucred_getgroups(self.uc, &mut groups) };
match ngroups.cmp(&0) {
Ordering::Less => None,
Ordering::Equal => Some(&[]),
Ordering::Greater => {
assert!(!groups.is_null());
Some(unsafe {
std::slice::from_raw_parts(
groups,
ngroups.try_into().unwrap(),
)
})
}
}
}
}
impl Drop for UCred {
fn drop(&mut self) {
assert!(!self.uc.is_null());
unsafe { libc::ucred_free(self.uc) };
}
}
#[derive(Debug)]
pub struct User {
pub uid: uid_t,
pub gid: gid_t,
pub name: String,
pub gecos: String,
pub dir: String,
pub shell: String,
}
pub fn uid_to_user(uid: uid_t) -> std::io::Result<Option<User>> {
unsafe { *libc::___errno() = 0 };
let pw = unsafe { libc::getpwuid(uid) };
if pw.is_null() {
let e = unsafe { *libc::___errno() };
if e == 0 {
Ok(None)
} else {
Err(std::io::Error::from_raw_os_error(e))
}
} else {
let pw = unsafe { &*pw };
assert_eq!(pw.pw_uid, uid);
Ok(Some(User {
uid: pw.pw_uid,
gid: pw.pw_gid,
name: cstr_to_string(pw.pw_name)?,
gecos: cstr_to_string(pw.pw_gecos)?,
dir: cstr_to_string(pw.pw_dir)?,
shell: cstr_to_string(pw.pw_shell)?,
}))
}
}
#[derive(Debug)]
pub struct Group {
pub gid: gid_t,
pub name: String,
}
pub fn gid_to_group(gid: gid_t) -> std::io::Result<Option<Group>> {
unsafe { *libc::___errno() = 0 };
let gr = unsafe { libc::getgrgid(gid) };
if gr.is_null() {
let e = unsafe { *libc::___errno() };
if e == 0 {
Ok(None)
} else {
Err(std::io::Error::from_raw_os_error(e))
}
} else {
let gr = unsafe { &*gr };
assert_eq!(gr.gr_gid, gid);
Ok(Some(Group { gid: gr.gr_gid, name: cstr_to_string(gr.gr_name)? }))
}
}
fn cstr_to_string(c: *const c_char) -> std::io::Result<String> {
if c.is_null() {
Ok("".into())
} else {
let c = unsafe { CStr::from_ptr(c) };
c.to_str().map(str::to_string).map_err(|e| {
std::io::Error::new(std::io::ErrorKind::InvalidData, e)
})
}
}