use core::mem::MaybeUninit;
use std::{
io,
os::fd::{AsFd, BorrowedFd, OwnedFd},
};
use crate::connection::Credentials;
#[doc(hidden)]
pub fn recvmsg(fd: impl AsFd, buf: &mut [u8]) -> io::Result<(usize, alloc::vec::Vec<OwnedFd>)> {
use rustix::net::{RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, recvmsg};
use std::io::IoSliceMut;
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();
for m in control.drain() {
if let RecvAncillaryMessage::ScmRights(rights) = m {
fds.extend(rights);
}
}
(msg.bytes, fds)
})
.map_err(io::Error::from)
}
#[doc(hidden)]
pub fn sendmsg(fd: impl AsFd, buf: &[u8], fds: &[BorrowedFd<'_>]) -> io::Result<usize> {
use rustix::net::{SendAncillaryBuffer, SendAncillaryMessage, SendFlags, sendmsg};
use std::io::IoSlice;
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",
));
}
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 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,
)
};
let err = io::Error::last_os_error();
if ret == -1 && err.raw_os_error() != Some(libc::ERANGE) {
return Err(err);
}
if nr_supp_gids as usize <= supp_gids.capacity() {
supp_gids.shrink_to(nr_supp_gids as usize);
break;
}
supp_gids.reserve(nr_supp_gids as usize - supp_gids.capacity());
}
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 {
let pidfd = unsafe { pidfd.assume_init() };
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);
}
rustix::process::pidfd_open(pid, rustix::process::PidfdFlags::empty())?
}
};
#[cfg(target_os = "android")]
let creds = Credentials::new(uid, primary_gid, pid);
#[cfg(target_os = "linux")]
let creds = Credentials::new(uid, primary_gid, supplementary_gids, pid, 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(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;