#![allow(
clippy::match_ref_pats, // looks more optimized with long array
clippy::needless_borrowed_reference,
)]
use std::os::fd::{AsFd, AsRawFd};
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: AsFd>(fd: FD, buffer: &mut[u8]) -> Result<usize, io::Error>
{
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;
let res =
unsafe
{
getsockopt(fd.as_fd().as_raw_fd(), SOL_SOCKET, SO_PEERSEC, ptr, &mut capacity)
};
return
match res
{
-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: AsFd>(_fd: FD, _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>
{
return
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]
{
return
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]);
}
}
return repr.finish();
}
}
#[cfg(any(target_os="linux", target_os="android"))]
pub
fn peer_credentials<FD: AsFd>(conn: FD) -> Result<ConnCredentials, io::Error>
{
let mut ucred: ucred = unsafe { mem::zeroed() };
let ptr = &mut ucred as *mut ucred as *mut c_void;
let mut size = mem::size_of::<ucred>() as socklen_t;
let res =
unsafe
{
getsockopt(conn.as_fd().as_raw_fd(), SOL_SOCKET, SO_PEERCRED, ptr, &mut size)
};
return
if res == -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<FD: AsFd>(conn: FD) -> 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.as_fd().as_raw_fd(), 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();
}
return 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<FD: AsFd>(conn: FD) -> 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.as_fd().as_raw_fd(), SOL_SOCKET, SO_PEERCRED, ptr, &mut size) == -1
{
return Err(io::Error::last_os_error());
}
else if let Some(pid) = NonZeroU32::new(sockpeercred.pid as u32)
{
return
Ok(
ConnCredentials::LinuxLike
{
pid,
euid: sockpeercred.uid as u32,
egid: sockpeercred.gid as u32,
}
);
}
else
{
return Err(io::Error::new(InvalidData, "the returned pid is zero"));
}
}
}
#[cfg(target_os="netbsd")]
pub fn peer_credentials<FD: AsFd>(conn: FD) -> 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.as_fd().as_raw_fd(), 0, LOCAL_PEEREID, ptr, &mut size) == -1
{
return Err(io::Error::last_os_error());
}
else if let Some(pid) = NonZeroU32::new(unpcbid.unp_pid as u32)
{
return Ok(
ConnCredentials::LinuxLike
{
pid,
euid: unpcbid.unp_euid as u32,
egid: unpcbid.unp_egid as u32,
}
);
}
else
{
return Err(io::Error::new(InvalidData, "the returned pid is zero"));
}
}
}
#[cfg(any(target_os="illumos", target_os="solaris"))]
pub
fn peer_credentials<FD: AsFd>(conn: FD) -> 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
{
return Err(io::Error::last_os_error());
}
else if ucred.0 == ptr::null_mut()
{
return 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
{
return 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, group) in groups[0..number_of_groups].iter_mut().enumerate()
{
*group = *groups_ptr.offset(i as isize);
}
return 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;
return Ok(
ConnCredentials::MacOsLike
{
euid: euid as u32,
number_of_groups: 1,
groups,
}
);
}
else
{
return 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<FD: AsFd>(_conn: FD) -> 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",
)))]
{
&[]
}
}
}