use std::io;
pub enum RecvResult {
Authenticated {
peer_pid: u32,
data: [u8; 32],
},
WouldBlock,
ShortRead,
IoError(io::Error),
}
#[cfg(target_os = "linux")]
fn cmsg_align(len: usize) -> usize {
let align = core::mem::size_of::<usize>();
(len + align - 1) & !(align - 1)
}
#[cfg(target_os = "linux")]
fn cmsg_hdr_size() -> usize {
cmsg_align(core::mem::size_of::<plat::Cmsghdr>())
}
#[cfg(target_os = "linux")]
mod plat {
use core::ffi::c_void;
use core::mem;
#[repr(C)]
pub(super) struct Iovec {
pub iov_base: *mut c_void,
pub iov_len: usize,
}
#[repr(C)]
pub(super) struct Msghdr {
pub msg_name: *mut c_void,
pub msg_namelen: u32,
pub _pad1: u32,
pub msg_iov: *mut Iovec,
pub msg_iovlen: usize,
pub msg_control: *mut c_void,
pub msg_controllen: usize,
pub msg_flags: i32,
pub _pad2: i32,
}
#[repr(C)]
pub(super) struct Cmsghdr {
pub cmsg_len: usize,
pub cmsg_level: i32,
pub cmsg_type: i32,
}
#[repr(C)]
pub(super) struct Ucred {
pub pid: i32,
pub uid: u32,
pub gid: u32,
}
pub(super) const SOL_SOCKET: i32 = 1;
pub(super) const SO_PASSCRED: i32 = 16;
pub(super) const SCM_CREDENTIALS: i32 = 2;
extern "C" {
pub(super) fn setsockopt(
fd: i32,
level: i32,
optname: i32,
optval: *const c_void,
optlen: u32,
) -> i32;
pub(super) fn recvmsg(fd: i32, msg: *mut Msghdr, flags: i32) -> isize;
}
pub(super) const ANCILLARY_BUFFER_SIZE: usize = 64;
pub(super) fn peer_pid_after_recv(_fd: i32, mhdr: &Msghdr, anc_base: *const u8) -> Option<u32> {
let hdr = unsafe { super::cmsg_firsthdr(mhdr) };
unsafe { super::find_credential_pid(hdr, mhdr, anc_base) }
}
const _: () = assert!(mem::size_of::<Msghdr>() == 56);
}
#[cfg(target_os = "macos")]
mod plat {
use core::ffi::c_void;
use core::mem;
#[repr(C)]
pub(super) struct Iovec {
pub iov_base: *mut c_void,
pub iov_len: usize,
}
#[repr(C)]
pub(super) struct Msghdr {
pub msg_name: *mut c_void,
pub msg_namelen: u32,
pub _pad1: u32,
pub msg_iov: *mut Iovec,
pub msg_iovlen: i32,
pub _pad2: u32,
pub msg_control: *mut c_void,
pub msg_controllen: u32,
pub msg_flags: i32,
}
extern "C" {
pub(super) fn recvmsg(fd: i32, msg: *mut Msghdr, flags: i32) -> isize;
}
pub(super) const ANCILLARY_BUFFER_SIZE: usize = 16;
pub(super) fn peer_pid_after_recv(
_fd: i32,
_mhdr: &Msghdr,
_anc_base: *const u8,
) -> Option<u32> {
Some(0)
}
const _: () = assert!(mem::size_of::<Msghdr>() == 48);
}
pub fn enable_credential_passing(fd: i32) -> io::Result<()> {
#[cfg(target_os = "macos")]
{
let _ = fd;
Ok(())
}
#[cfg(target_os = "linux")]
{
let (level, optname) = (plat::SOL_SOCKET, plat::SO_PASSCRED);
let one: i32 = 1;
let ret = unsafe {
plat::setsockopt(
fd,
level,
optname,
core::ptr::addr_of!(one) as *const core::ffi::c_void,
core::mem::size_of::<i32>() as u32,
)
};
if ret != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
pub fn recv_authenticated(fd: i32) -> RecvResult {
let mut data = [0u8; 32];
#[repr(align(8))]
struct AncBuf([u8; plat::ANCILLARY_BUFFER_SIZE]);
let mut anc = AncBuf([0u8; plat::ANCILLARY_BUFFER_SIZE]);
let mut iov = plat::Iovec {
iov_base: data.as_mut_ptr() as *mut core::ffi::c_void,
iov_len: 32,
};
let mut mhdr = plat::Msghdr {
msg_name: core::ptr::null_mut(),
msg_namelen: 0,
msg_iov: &mut iov,
msg_iovlen: 1,
msg_control: anc.0.as_mut_ptr() as *mut core::ffi::c_void,
msg_controllen: plat::ANCILLARY_BUFFER_SIZE as _,
msg_flags: 0,
#[cfg(target_os = "linux")]
_pad1: 0,
#[cfg(target_os = "linux")]
_pad2: 0,
#[cfg(target_os = "macos")]
_pad1: 0,
#[cfg(target_os = "macos")]
_pad2: 0,
};
let n = loop {
let ret = unsafe { plat::recvmsg(fd, &mut mhdr, 0) };
if ret < 0 {
let err = io::Error::last_os_error();
match err.kind() {
io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => {
return RecvResult::WouldBlock;
}
io::ErrorKind::Interrupted => continue,
_ => return RecvResult::IoError(err),
}
}
break ret as isize;
};
if n as usize != 32 {
return RecvResult::ShortRead;
}
let peer_pid = match plat::peer_pid_after_recv(fd, &mhdr, anc.0.as_ptr()) {
Some(pid) => pid,
None => {
return RecvResult::IoError(io::Error::new(
io::ErrorKind::InvalidData,
"kernel did not attach SCM_CREDENTIALS ancillary data",
));
}
};
RecvResult::Authenticated { peer_pid, data }
}
#[cfg(target_os = "linux")]
unsafe fn cmsg_firsthdr(mhdr: &plat::Msghdr) -> Option<&plat::Cmsghdr> {
let control = mhdr.msg_control;
if control.is_null() {
return None;
}
if mhdr.msg_controllen < cmsg_hdr_size() {
return None;
}
unsafe { Some(&*(control as *const plat::Cmsghdr)) }
}
#[cfg(target_os = "linux")]
unsafe fn cmsg_nxthdr<'a>(
mhdr: &plat::Msghdr,
cmsg: &'a plat::Cmsghdr,
base: *const u8,
) -> Option<&'a plat::Cmsghdr> {
let cur = (cmsg as *const plat::Cmsghdr) as *const u8;
let offset = unsafe { cur.offset_from(base) } as usize;
let advance = cmsg_align(cmsg.cmsg_len);
let next_offset = offset + advance;
if next_offset + cmsg_hdr_size() > mhdr.msg_controllen {
return None;
}
let next = unsafe { &*(base.add(next_offset) as *const plat::Cmsghdr) };
let remaining = mhdr.msg_controllen - next_offset;
if next.cmsg_len > remaining {
return None;
}
Some(next)
}
#[cfg(target_os = "linux")]
unsafe fn cmsg_data(cmsg: &plat::Cmsghdr) -> *const u8 {
unsafe { (cmsg as *const plat::Cmsghdr as *const u8).add(cmsg_hdr_size()) }
}
#[cfg(target_os = "linux")]
unsafe fn find_credential_pid(
mut hdr: Option<&plat::Cmsghdr>,
mhdr: &plat::Msghdr,
base: *const u8,
) -> Option<u32> {
let target_level = plat::SOL_SOCKET;
let target_type = plat::SCM_CREDENTIALS;
let needed = cmsg_hdr_size() + core::mem::size_of::<plat::Ucred>();
while let Some(cmsg) = hdr {
if cmsg.cmsg_level == target_level && cmsg.cmsg_type == target_type {
if cmsg.cmsg_len < needed {
return None;
}
let data_ptr = unsafe { cmsg_data(cmsg) };
let ucred = unsafe { &*(data_ptr as *const plat::Ucred) };
return Some(ucred.pid as u32);
}
hdr = unsafe { cmsg_nxthdr(mhdr, cmsg, base) };
}
None
}