use core::mem::MaybeUninit;
#[cfg(target_os = "linux")]
use std::os::fd::OwnedFd;
use std::{
io,
os::fd::{AsFd, BorrowedFd},
};
use crate::connection::{Credentials, PassedCredentials, socket::ReadResult};
#[doc(hidden)]
pub fn recvmsg(fd: impl AsFd, buf: &mut [u8]) -> io::Result<ReadResult> {
use rustix::net::{RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, recvmsg};
use std::io::IoSliceMut;
#[cfg(target_os = "linux")]
let mut cmsg_buf =
[MaybeUninit::<u8>::uninit(); rustix::cmsg_space!(ScmRights(MAX_FDS), ScmCredentials(1))];
#[cfg(not(target_os = "linux"))]
let mut cmsg_buf = [MaybeUninit::<u8>::uninit(); rustix::cmsg_space!(ScmRights(MAX_FDS))];
let mut control = RecvAncillaryBuffer::new(&mut cmsg_buf);
let mut iov = [IoSliceMut::new(buf)];
recvmsg(fd.as_fd(), &mut iov, &mut control, RecvFlags::empty())
.map(|msg| {
let mut fds = alloc::vec::Vec::new();
#[cfg(target_os = "linux")]
let mut creds = None;
for m in control.drain() {
match m {
RecvAncillaryMessage::ScmRights(rights) => fds.extend(rights),
#[cfg(target_os = "linux")]
RecvAncillaryMessage::ScmCredentials(ucreds) => {
creds = Some(PassedCredentials::new(ucreds.uid, ucreds.gid, ucreds.pid));
}
_ => (),
}
}
let result = ReadResult::new(msg.bytes);
#[cfg(feature = "std")]
let result = result.set_fds(fds);
#[cfg(all(feature = "std", target_os = "linux"))]
let result = result.set_credentials(creds);
result
})
.map_err(io::Error::from)
}
#[cfg(target_os = "linux")]
#[doc(hidden)]
pub fn enable_passcred(fd: impl AsFd) -> io::Result<()> {
rustix::net::sockopt::set_socket_passcred(fd.as_fd(), true).map_err(io::Error::from)
}
#[doc(hidden)]
pub fn sendmsg(
fd: impl AsFd,
buf: &[u8],
fds: &[BorrowedFd<'_>],
#[cfg(target_os = "linux")] creds: Option<&crate::connection::PassedCredentials>,
) -> io::Result<usize> {
use rustix::net::{SendAncillaryBuffer, SendAncillaryMessage, SendFlags, sendmsg};
use std::io::IoSlice;
#[cfg(target_os = "linux")]
let mut cmsg_buf =
[MaybeUninit::<u8>::uninit(); rustix::cmsg_space!(ScmRights(MAX_FDS), ScmCredentials(1))];
#[cfg(not(target_os = "linux"))]
let mut cmsg_buf = [MaybeUninit::<u8>::uninit(); rustix::cmsg_space!(ScmRights(MAX_FDS))];
let mut control = SendAncillaryBuffer::new(&mut cmsg_buf);
if !fds.is_empty() && !control.push(SendAncillaryMessage::ScmRights(fds)) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"too many file descriptors to send",
));
}
#[cfg(target_os = "linux")]
if let Some(creds) = creds {
let ucred = rustix::net::UCred {
pid: creds.process_id(),
uid: creds.unix_user_id(),
gid: creds.unix_primary_group_id(),
};
if !control.push(SendAncillaryMessage::ScmCredentials(ucred)) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"failed to push credentials",
));
}
}
let iov = [IoSlice::new(buf)];
sendmsg(fd.as_fd(), &iov, &mut control, SendFlags::empty()).map_err(io::Error::from)
}
pub(crate) fn get_peer_credentials(fd: impl AsFd) -> io::Result<Credentials> {
use std::os::fd::AsRawFd;
let fd = fd.as_fd();
#[cfg(any(target_os = "android", target_os = "linux"))]
{
use std::os::fd::FromRawFd;
let ucred = rustix::net::sockopt::socket_peercred(fd)?;
let uid = ucred.uid;
let pid = ucred.pid;
let primary_gid = ucred.gid;
#[cfg(target_os = "linux")]
let supplementary_gids = {
use rustix::fs::Gid;
let mut nr_supp_gids = INITIAL_NUMBER_SUPPLEMENTARY_GROUPS;
let mut nr_supp_gids_in_bytes = nr_supp_gids * (size_of::<Gid>() as u32);
let mut supp_gids: Vec<Gid> = Vec::with_capacity(nr_supp_gids as usize);
loop {
let ret = unsafe {
libc::getsockopt(
fd.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERGROUPS,
supp_gids.as_mut_ptr().cast(),
&mut nr_supp_gids_in_bytes,
)
};
let err = io::Error::last_os_error();
if ret == -1 && err.raw_os_error() != Some(libc::ERANGE) {
return Err(err);
}
nr_supp_gids = nr_supp_gids_in_bytes / size_of::<Gid>() as u32;
if nr_supp_gids as usize <= supp_gids.capacity() {
supp_gids.shrink_to(nr_supp_gids as usize);
unsafe { supp_gids.set_len(nr_supp_gids as usize) };
break;
}
supp_gids.reserve(nr_supp_gids as usize - supp_gids.capacity());
nr_supp_gids_in_bytes = (supp_gids.capacity() as u32) * (size_of::<Gid>() as u32);
}
supp_gids
};
#[cfg(target_os = "linux")]
let process_fd = {
use core::mem::{MaybeUninit, size_of};
let mut pidfd = MaybeUninit::<libc::c_int>::zeroed();
let mut len = size_of::<libc::c_int>() as libc::socklen_t;
let ret = unsafe {
libc::getsockopt(
fd.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERPIDFD,
pidfd.as_mut_ptr().cast(),
&mut len,
)
};
if ret == 0 {
assert_eq!(
len as usize,
size_of::<libc::c_int>(),
"unexpected getsockopt size"
);
let pidfd = unsafe { pidfd.assume_init() };
Some(unsafe { OwnedFd::from_raw_fd(pidfd) })
} else {
let err = io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ENOPROTOOPT) {
return Err(err);
}
None
}
};
#[cfg(target_os = "android")]
let creds = Credentials::new(PassedCredentials::new(uid, primary_gid, pid));
#[cfg(target_os = "linux")]
let creds = Credentials::new(
PassedCredentials::new(uid, primary_gid, pid),
supplementary_gids,
process_fd,
);
Ok(creds)
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd"
))]
{
let mut uid: libc::uid_t = 0;
let mut gid: libc::gid_t = 0;
let ret = unsafe { libc::getpeereid(fd.as_raw_fd(), &mut uid, &mut gid) };
if ret != 0 {
return Err(io::Error::last_os_error());
}
let uid = rustix::process::Uid::from_raw(uid);
let gid = rustix::process::Gid::from_raw(gid);
#[cfg(any(target_os = "macos", target_os = "ios"))]
let pid = {
let mut pid: libc::pid_t = 0;
let mut len = core::mem::size_of::<libc::pid_t>() as libc::socklen_t;
let ret = unsafe {
libc::getsockopt(
fd.as_raw_fd(),
libc::SOL_LOCAL,
libc::LOCAL_PEERPID,
(&raw mut pid).cast(),
&mut len,
)
};
if ret != 0 {
return Err(io::Error::last_os_error());
}
rustix::process::Pid::from_raw(pid)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid peer PID"))?
};
#[cfg(target_os = "openbsd")]
let pid = {
#[repr(C)]
struct sockpeercred {
uid: libc::uid_t,
gid: libc::gid_t,
pid: libc::pid_t,
}
let mut creds = core::mem::MaybeUninit::<sockpeercred>::zeroed();
let mut len = core::mem::size_of::<sockpeercred>() as libc::socklen_t;
let ret = unsafe {
libc::getsockopt(
fd.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERCRED,
creds.as_mut_ptr().cast(),
&mut len,
)
};
if ret != 0 {
return Err(io::Error::last_os_error());
}
let creds = unsafe { creds.assume_init() };
rustix::process::Pid::from_raw(creds.pid)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid peer PID"))?
};
#[cfg(target_os = "netbsd")]
let pid = {
#[repr(C)]
struct unpcbid {
unp_pid: libc::pid_t,
unp_euid: libc::uid_t,
unp_egid: libc::gid_t,
}
const LOCAL_PEEREID: libc::c_int = 3;
let mut creds = core::mem::MaybeUninit::<unpcbid>::zeroed();
let mut len = core::mem::size_of::<unpcbid>() as libc::socklen_t;
let ret = unsafe {
libc::getsockopt(
fd.as_raw_fd(),
0, LOCAL_PEEREID,
creds.as_mut_ptr().cast(),
&mut len,
)
};
if ret != 0 {
return Err(io::Error::last_os_error());
}
let creds = unsafe { creds.assume_init() };
rustix::process::Pid::from_raw(creds.unp_pid)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid peer PID"))?
};
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
let pid = rustix::process::Pid::from_raw(0).unwrap();
Ok(Credentials::new(PassedCredentials::new(uid, gid, pid)))
}
#[cfg(not(any(
target_os = "android",
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd"
)))]
{
Err(io::Error::new(
io::ErrorKind::Unsupported,
"peer credentials not supported on this platform",
))
}
}
const MAX_FDS: usize = 1024;
#[cfg(target_os = "linux")]
const INITIAL_NUMBER_SUPPLEMENTARY_GROUPS: libc::socklen_t = 128 as libc::socklen_t;