#![allow(
clippy::match_ref_pats, // looks more optimized with long array
clippy::needless_borrowed_reference,
)]
use std::os::unix::io::RawFd;
use std::{io, fmt};
use std::num::NonZeroU32;
use std::io::ErrorKind::*;
#[cfg(any(
target_os="linux", target_os="android",
target_os="freebsd", target_os="dragonfly", target_vendor="apple",
target_os="openbsd", target_os="netbsd"
))]
use std::mem;
#[cfg(any(target_os="illumos", target_os="solaris"))]
use std::ptr;
#[cfg(any(
target_os="linux", target_os="android",
target_os="freebsd", target_os="dragonfly", target_vendor="apple",
target_os="openbsd", target_os="netbsd"
))]
use libc::{getsockopt, c_void, socklen_t};
#[cfg(any(target_os="linux", target_os="android"))]
use libc::{pid_t, uid_t, gid_t, getpid, getuid, geteuid, getgid, getegid};
#[cfg(any(target_os="linux", target_os="android"))]
use libc::{ucred, SOL_SOCKET, SO_PEERCRED, SO_PEERSEC};
#[cfg(any(target_os="freebsd", target_os="dragonfly", target_vendor="apple"))]
use libc::{xucred, XUCRED_VERSION, LOCAL_PEERCRED};
#[cfg(target_vendor="apple")]
use libc::SOL_LOCAL; #[cfg(target_os="openbsd")]
use libc::{sockpeercred, SOL_SOCKET, SO_PEERCRED};
#[cfg(target_os="netbsd")]
use libc::{unpcbid, LOCAL_PEEREID};
#[cfg(any(target_os="illumos", target_os="solaris"))]
use libc::{getpeerucred, ucred_free, ucred_t};
#[cfg(any(target_os="illumos", target_os="solaris"))]
use libc::{ucred_geteuid, ucred_getegid, ucred_getpid, ucred_getgroups, uid_t, gid_t, pid_t};
#[derive(Clone,Copy, PartialEq,Eq, Debug)]
#[allow(unused)] pub enum SendCredentials {
Effective,
Real,
Custom{ pid: u32, uid: u32, gid: u32 }
}
#[cfg(any(target_os="linux", target_os="android"))]
impl SendCredentials {
pub fn into_raw(self) -> ucred {
let mut ucred: ucred = unsafe { mem::zeroed() };
let (pid, uid, gid) = match self {
SendCredentials::Effective => unsafe { (getpid(), geteuid(), getegid()) },
SendCredentials::Real => unsafe { (getpid(), getuid(), getgid()) },
SendCredentials::Custom{pid, uid, gid} => (pid as pid_t, uid as uid_t, gid as gid_t),
};
ucred.pid = pid;
ucred.uid = uid;
ucred.gid = gid;
return ucred;
}
}
#[cfg(any(target_os="linux", target_os="android"))]
pub fn selinux_context(fd: RawFd, buffer: &mut[u8]) -> Result<usize, io::Error> {
unsafe {
let ptr = buffer.as_mut_ptr() as *mut c_void;
let mut capacity = buffer.len().min(socklen_t::max_value() as usize) as socklen_t;
match getsockopt(fd, SOL_SOCKET, SO_PEERSEC, ptr, &mut capacity) {
-1 => Err(io::Error::last_os_error()),
_ => Ok(capacity as usize),
}
}
}
#[cfg(not(any(target_os="linux", target_os="android")))]
pub fn selinux_context(_fd: RawFd, _buffer: &mut[u8]) -> Result<usize, io::Error> {
Err(io::Error::new(Other, "not available"))
}
#[derive(Clone,Copy, PartialEq,Eq)]
pub enum ConnCredentials {
LinuxLike{ pid: NonZeroU32, euid: u32, egid: u32 },
MacOsLike{ euid: u32, number_of_groups: u8, groups: [u32; 16] },
}
impl ConnCredentials {
pub fn pid(&self) -> Option<NonZeroU32> {
match self {
&ConnCredentials::LinuxLike{ pid, .. } => Some(pid),
&ConnCredentials::MacOsLike{ .. } => None,
}
}
pub fn euid(&self) -> u32 {
match self {
&ConnCredentials::LinuxLike{ euid, .. } => euid,
&ConnCredentials::MacOsLike{ euid, .. } => euid,
}
}
pub fn egid(&self) -> Option<u32> {
match self {
&ConnCredentials::LinuxLike{ egid, .. } => Some(egid),
&ConnCredentials::MacOsLike{ number_of_groups: 1..=255, groups, .. } => Some(groups[0]),
&ConnCredentials::MacOsLike{ number_of_groups: 0, .. } => None,
}
}
pub fn groups(&self) -> &[u32] {
match self {
&ConnCredentials::LinuxLike{ .. } => &[],
&ConnCredentials::MacOsLike{ number_of_groups: n @ 0..=15, ref groups, .. } => {
&groups[..(n as usize)]
},
&ConnCredentials::MacOsLike{ number_of_groups: 16..=255, ref groups, .. } => groups,
}
}
}
impl fmt::Debug for ConnCredentials {
fn fmt(&self, fmtr: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let mut repr = fmtr.debug_struct("ConnCredentials");
match self {
&ConnCredentials::LinuxLike{ ref pid, ref euid, ref egid } => {
repr.field("pid", pid);
repr.field("euid", euid);
repr.field("egid", egid);
}
&ConnCredentials::MacOsLike{ ref euid, number_of_groups, ref groups } => {
repr.field("euid", euid);
let number_of_groups = (number_of_groups as usize).min(groups.len());
repr.field("groups", &&groups[..number_of_groups]);
}
}
repr.finish()
}
}
#[cfg(any(target_os="linux", target_os="android"))]
pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
let mut ucred: ucred = unsafe { mem::zeroed() };
unsafe {
let ptr = &mut ucred as *mut ucred as *mut c_void;
let mut size = mem::size_of::<ucred>() as socklen_t;
if getsockopt(conn, SOL_SOCKET, SO_PEERCRED, ptr, &mut size) == -1 {
Err(io::Error::last_os_error())
} else if let Some(pid) = NonZeroU32::new(ucred.pid as u32) {
Ok(ConnCredentials::LinuxLike{ pid, euid: ucred.uid as u32, egid: ucred.gid as u32 })
} else {
Err(io::Error::new(NotConnected, "socket is not a connection"))
}
}
}
#[cfg(any(target_os="freebsd", target_os="dragonfly", target_vendor="apple"))]
pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
let mut xucred: xucred = unsafe { mem::zeroed() };
xucred.cr_version = XUCRED_VERSION;
xucred.cr_ngroups = xucred.cr_groups.len() as _;
xucred.cr_uid = !0;
for group_slot in &mut xucred.cr_groups {
*group_slot = !0;
}
#[cfg(any(target_os="freebsd", target_os="dragonfly"))]
const PEERCRED_SOCKET_LEVEL: i32 = 0; #[cfg(target_vendor="apple")]
use SOL_LOCAL as PEERCRED_SOCKET_LEVEL;
unsafe {
let ptr = &mut xucred as *mut xucred as *mut c_void;
let mut size = mem::size_of::<xucred>() as socklen_t;
match getsockopt(conn, PEERCRED_SOCKET_LEVEL, LOCAL_PEERCRED, ptr, &mut size) {
-1 => Err(io::Error::last_os_error()),
_ if xucred.cr_version != XUCRED_VERSION => {
Err(io::Error::new(InvalidData, "unknown version of peer credentials"))
},
_ => {
let mut groups = [u32::max_value(); 16]; let filled_groups = xucred.cr_groups.iter().take(xucred.cr_ngroups as usize);
for (&src, dst) in filled_groups.zip(&mut groups) {
*dst = src.into();
}
Ok(ConnCredentials::MacOsLike {
euid: xucred.cr_uid.into(),
number_of_groups: xucred.cr_ngroups as u8,
groups: groups,
})
}
}
}
}
#[cfg(target_os="openbsd")]
pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
let mut sockpeercred: sockpeercred = unsafe { mem::zeroed() };
unsafe {
let ptr = &mut sockpeercred as *mut sockpeercred as *mut c_void;
let mut size = mem::size_of::<sockpeercred>() as socklen_t;
if getsockopt(conn, SOL_SOCKET, SO_PEERCRED, ptr, &mut size) == -1 {
Err(io::Error::last_os_error())
} else if let Some(pid) = NonZeroU32::new(sockpeercred.pid as u32) {
Ok(ConnCredentials::LinuxLike {
pid,
euid: sockpeercred.uid as u32,
egid: sockpeercred.gid as u32,
})
} else {
Err(io::Error::new(InvalidData, "the returned pid is zero"))
}
}
}
#[cfg(target_os="netbsd")]
pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
let mut unpcbid: unpcbid = unsafe { mem::zeroed() };
unsafe {
let ptr = &mut unpcbid as *mut unpcbid as *mut c_void;
let mut size = mem::size_of::<unpcbid>() as socklen_t;
if getsockopt(conn, 0, LOCAL_PEEREID, ptr, &mut size) == -1 {
Err(io::Error::last_os_error())
} else if let Some(pid) = NonZeroU32::new(unpcbid.unp_pid as u32) {
Ok(ConnCredentials::LinuxLike {
pid,
euid: unpcbid.unp_euid as u32,
egid: unpcbid.unp_egid as u32,
})
} else {
Err(io::Error::new(InvalidData, "the returned pid is zero"))
}
}
}
#[cfg(any(target_os="illumos", target_os="solaris"))]
pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
struct UcredAlloc(*mut ucred_t);
impl Drop for UcredAlloc {
fn drop(&mut self) {
unsafe {
if self.0 != ptr::null_mut() {
ucred_free(self.0);
}
}
}
}
unsafe {
let mut ucred = UcredAlloc(ptr::null_mut());
if getpeerucred(conn, &mut ucred.0) == -1 {
Err(io::Error::last_os_error())
} else if ucred.0 == ptr::null_mut() {
Err(io::Error::new(NotConnected, "socket is not a connection"))
} else {
let euid = ucred_geteuid(ucred.0 as *const _);
let egid = ucred_getegid(ucred.0 as *const _);
let pid = ucred_getpid(ucred.0 as *const _);
let mut groups_ptr: *const gid_t = ptr::null_mut();
let ngroups = ucred_getgroups(ucred.0 as *const _, &mut groups_ptr);
if euid != -1i32 as uid_t && egid != -1i32 as gid_t
&& pid != -1i32 as pid_t && pid != 0 {
Ok(ConnCredentials::LinuxLike {
pid: NonZeroU32::new(pid as u32).unwrap(), euid: euid as u32,
egid: egid as u32,
})
} else if euid != -1i32 as uid_t && ngroups > 0 && groups_ptr != ptr::null() {
let mut groups = [u32::max_value(); 16];
let number_of_groups = ngroups.min(16) as u8;
for i in 0..number_of_groups {
groups[i as usize] = *groups_ptr.offset(i as isize);
}
Ok(ConnCredentials::MacOsLike {
euid: euid as u32,
number_of_groups,
groups,
})
} else if euid != -1i32 as uid_t && egid != -1i32 as gid_t {
let mut groups = [u32::max_value(); 16];
groups[0] = egid as u32;
Ok(ConnCredentials::MacOsLike {
euid: euid as u32,
number_of_groups: 1,
groups,
})
} else {
Err(io::Error::new(Other, "Not enough information was available"))
}
}
}
}
#[cfg(not(any(
target_os="linux", target_os="android",
target_os="freebsd", target_os="dragonfly", target_vendor="apple",
target_os="openbsd", target_os="netbsd",
target_os="illumos", target_os="solaris",
)))]
pub fn peer_credentials(_: RawFd) -> Result<ConnCredentials, io::Error> {
Err(io::Error::new(Other, "Not available"))
}
#[cfg(any(target_os="linux", target_os="android"))]
pub type RawReceivedCredentials = libc::ucred;
#[derive(Clone,Copy, PartialEq,Eq,Hash, Debug)]
pub struct ReceivedCredentials {
#[cfg(any(target_os="linux", target_os="android", target_os="dragonfly"))]
pid: u32,
#[cfg(any(target_os="linux", target_os="android"))]
uid: u32,
#[cfg(any(target_os="linux", target_os="android"))]
gid: u32,
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))]
real_uid: u32,
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))]
effective_uid: u32,
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))]
real_gid: u32,
#[cfg(any(
target_os="freebsd", target_os="netbsd",
target_os="illumos", target_os="solaris", target_os="macos",
))]
effective_gid: u32,
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))]
groups: [u32; 5],
}
#[allow(unused)] impl ReceivedCredentials {
#[cfg(any(target_os="linux", target_os="android"))]
pub(crate) fn from_raw(creds: libc::ucred) -> Self {
ReceivedCredentials {
pid: creds.pid as u32,
uid: creds.uid as u32,
gid: creds.gid as u32,
}
}
pub fn pid(&self) -> Option<u32> {
#[cfg(any(target_os="linux", target_os="android", target_os="dragonfly"))] {
Some(self.pid)
}
#[cfg(not(any(target_os="linux", target_os="android", target_os="dragonfly")))] {
None
}
}
pub fn effective_or_sent_uid(&self) -> u32 {
#[cfg(any(target_os="linux", target_os="android"))] {
self.uid
}
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))] {
self.effective_uid
}
#[cfg(not(any(
target_os="linux", target_os="android",
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
)))] {
unreachable!("struct cannot be created on unsupported OSes")
}
}
pub fn real_or_sent_uid(&self) -> u32 {
#[cfg(any(target_os="linux", target_os="android"))] {
self.uid
}
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))] {
self.real_uid
}
#[cfg(not(any(
target_os="linux", target_os="android",
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
)))] {
unreachable!("struct cannot be created on unsupported OSes")
}
}
pub fn effective_or_sent_gid(&self) -> Option<u32> {
#[cfg(any(target_os="linux", target_os="android"))] {
Some(self.gid)
}
#[cfg(any(
target_os="freebsd", target_os="netbsd",
target_os="illumos", target_os="solaris", target_os="macos",
))] {
Some(self.effective_gid)
}
#[cfg(not(any(
target_os="linux", target_os="android",
target_os="freebsd", target_os="netbsd",
target_os="illumos", target_os="solaris", target_os="macos",
)))] {
None
}
}
pub fn real_or_sent_gid(&self) -> u32 {
#[cfg(any(target_os="linux", target_os="android"))] {
self.gid
}
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))] {
self.real_gid
}
#[cfg(not(any(
target_os="linux", target_os="android",
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
)))] {
unreachable!("struct cannot be created on unsupported OSes")
}
}
pub fn groups(&self) -> &[u32] {
#[cfg(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
))] {
&self.groups[..]
}
#[cfg(not(any(
target_os="freebsd", target_os="netbsd", target_os="dragonfly",
target_os="illumos", target_os="solaris", target_os="macos",
)))] {
&[]
}
}
}