use std::{ffi::CStr, os::fd::AsFd};
use memchr::arch::all::{is_equal, is_prefix};
use nix::{
errno::Errno,
sys::stat::SFlag,
unistd::{Gid, Uid},
};
use crate::{
caps::{self, CapSet, Capabilities},
compat::{fstatx, getgroups, STATX_GID, STATX_UID},
};
pub fn capable_xattr<Fd: AsFd>(fd: Fd, name: &CStr, write: bool) -> Result<(), Errno> {
let name = name.to_bytes();
if write && is_prefix(name, b"system.posix_acl_") {
capable_or_inode_owner(fd)
} else if is_prefix(name, b"trusted.") && !capable(Capabilities::CAP_SYS_ADMIN) {
Err(if write { Errno::EPERM } else { Errno::ENODATA })
} else if write && is_equal(name, b"security.capability") {
if capable(Capabilities::CAP_SETFCAP) {
Ok(())
} else {
Err(Errno::EPERM)
}
} else if write && is_prefix(name, b"security.") && !capable(Capabilities::CAP_MAC_ADMIN) {
Err(Errno::EPERM)
} else {
Ok(())
}
}
pub fn capable_mknod(ftyp: SFlag, dev: libc::dev_t) -> Result<(), Errno> {
match ftyp {
SFlag::S_IFCHR if dev == 0 => {} SFlag::S_IFBLK | SFlag::S_IFCHR if capable(Capabilities::CAP_MKNOD) => {}
SFlag::S_IFBLK | SFlag::S_IFCHR => return Err(Errno::EPERM),
_ => {}
}
Ok(())
}
pub fn capable_uidgid<Fd: AsFd>(
fd: Fd,
owner: Option<Uid>,
group: Option<Gid>,
) -> Result<(), Errno> {
if owner.is_none() && group.is_none() {
return Ok(());
}
if capable(Capabilities::CAP_CHOWN) {
return Ok(());
}
let mut flags = STATX_UID;
if group.is_some() {
flags |= STATX_GID;
}
let stx = fstatx(fd, flags)?;
let file_uid = Uid::from_raw(stx.stx_uid);
let is_owner = file_uid == Uid::effective();
if let Some(owner) = owner {
if !is_owner || owner != file_uid {
return Err(Errno::EPERM);
}
}
if let Some(group) = group {
let file_gid = Gid::from_raw(stx.stx_gid);
if !is_owner || (!has_gid(group)? && group != file_gid) {
return Err(Errno::EPERM);
}
}
Ok(())
}
pub fn capable_or_inode_owner<Fd: AsFd>(fd: Fd) -> Result<(), Errno> {
let uid = fstatx(fd, STATX_UID)
.map(|stx| stx.stx_uid)
.map(Uid::from_raw)?;
if uid == Uid::effective() {
return Ok(());
}
if capable(Capabilities::CAP_FOWNER) {
return Ok(());
}
Err(Errno::EPERM)
}
pub fn capable_or_inode_group<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
let gid = fstatx(fd, STATX_GID)
.map(|stx| stx.stx_gid)
.map(Gid::from_raw)?;
if has_gid(gid)? {
Ok(true)
} else {
Ok(capable(Capabilities::CAP_FSETID))
}
}
#[must_use]
pub fn capable(caps: Capabilities) -> bool {
caps::read(None, CapSet::Effective)
.map(|caps_eff| caps_eff.contains(caps))
.unwrap_or(false)
}
#[must_use]
pub fn capable_any(caps: Capabilities) -> bool {
caps::read(None, CapSet::Effective)
.map(|caps_eff| caps_eff.intersects(caps))
.unwrap_or(false)
}
fn has_gid(gid: Gid) -> Result<bool, Errno> {
if gid == Gid::effective() {
Ok(true)
} else {
Ok(getgroups()?.contains(&gid))
}
}