#[cfg(unix)]
use std::os::fd::{AsFd, BorrowedFd};
use std::{
future::Future,
io::{self, IoSlice},
};
#[cfg(unix)]
use rustix::net::{
RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, SendAncillaryBuffer,
SendAncillaryMessage, recvmsg, sendmsg,
};
#[cfg(unix)]
use super::SEND_FLAGS;
use super::SocketOps;
#[cfg(unix)]
use crate::utils::FDS_MAX;
use crate::{
connection::socket::RecvmsgResult,
fdo::ConnectionCredentials,
runtime::{IoSource, Runtime},
};
#[derive(Debug)]
pub(crate) struct UnixOps;
impl SocketOps for UnixOps {
#[cfg(unix)]
fn recv(&self, source: &IoSource, buffer: &mut [u8]) -> RecvmsgResult {
fd_recvmsg(source.as_fd(), buffer)
}
#[cfg(windows)]
fn recv(&self, source: &IoSource, buffer: &mut [u8]) -> RecvmsgResult {
super::recv(source, buffer)
}
#[cfg(unix)]
fn send(
&self,
source: &IoSource,
buffers: &[IoSlice<'_>],
fds: &[BorrowedFd<'_>],
) -> io::Result<usize> {
fd_sendmsg(source.as_fd(), buffers, fds)
}
#[cfg(windows)]
fn send(&self, source: &IoSource, buffers: &[IoSlice<'_>]) -> io::Result<usize> {
super::send(source, buffers)
}
fn can_pass_unix_fd(&self) -> bool {
cfg!(unix)
}
fn peer_credentials(
&self,
source: &IoSource,
runtime: &Runtime,
) -> impl Future<Output = io::Result<ConnectionCredentials>> + Send {
peer_credentials(source, runtime)
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
fn sends_credentials_byte(&self) -> bool {
true
}
}
#[cfg(all(unix, any(feature = "default-rt", feature = "tokio")))]
pub(crate) fn unix_socket_address(
path: &std::path::Path,
) -> io::Result<std::os::unix::net::SocketAddr> {
use std::os::unix::net::SocketAddr;
#[cfg(any(target_os = "android", target_os = "linux"))]
{
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
use std::os::unix::ffi::OsStrExt;
if let Some(name) = path.as_os_str().as_bytes().strip_prefix(b"\0") {
return SocketAddr::from_abstract_name(name);
}
}
SocketAddr::from_pathname(path)
}
#[cfg(unix)]
async fn peer_credentials(
source: &IoSource,
runtime: &Runtime,
) -> io::Result<ConnectionCredentials> {
let (credentials, lookup) = socket_credentials(source.as_fd())?;
Ok(lookup.complete(credentials, runtime).await)
}
#[cfg(windows)]
async fn peer_credentials(
source: &IoSource,
runtime: &Runtime,
) -> io::Result<ConnectionCredentials> {
let source = source.clone();
runtime
.spawn_blocking(move || credentials_from_socket(&source))
.await
}
#[cfg(windows)]
fn credentials_from_socket(
socket: &impl std::os::windows::io::AsRawSocket,
) -> io::Result<ConnectionCredentials> {
use crate::win32::{ProcessToken, unix_stream_get_peer_pid};
let pid = unix_stream_get_peer_pid(socket)? as _;
let sid = ProcessToken::open(if pid != 0 { Some(pid as _) } else { None })
.and_then(|process_token| process_token.sid())?;
Ok(ConnectionCredentials::default()
.set_process_id(pid)
.set_windows_sid(sid))
}
#[cfg(unix)]
fn fd_recvmsg(fd: BorrowedFd<'_>, buffer: &mut [u8]) -> RecvmsgResult {
use std::{io::IoSliceMut, mem::MaybeUninit};
let mut iov = [IoSliceMut::new(buffer)];
let mut cmsg_buffer = [MaybeUninit::uninit(); rustix::cmsg_space!(ScmRights(FDS_MAX))];
let mut ancillary = RecvAncillaryBuffer::new(&mut cmsg_buffer);
let msg = recvmsg(fd, &mut iov, &mut ancillary, RecvFlags::empty())?;
if msg.bytes == 0 {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"failed to read from socket",
));
}
let mut fds = vec![];
for msg in ancillary.drain() {
match msg {
RecvAncillaryMessage::ScmRights(iter) => {
fds.extend(iter);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
RecvAncillaryMessage::ScmCredentials(_) => {
continue;
}
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"unexpected CMSG kind",
));
}
}
}
Ok((msg.bytes, fds))
}
#[cfg(unix)]
fn fd_sendmsg(
fd: BorrowedFd<'_>,
buffers: &[IoSlice<'_>],
fds: &[BorrowedFd<'_>],
) -> io::Result<usize> {
use std::mem::MaybeUninit;
let mut cmsg_buffer = [MaybeUninit::uninit(); rustix::cmsg_space!(ScmRights(FDS_MAX))];
let mut ancillary = SendAncillaryBuffer::new(&mut cmsg_buffer);
if !fds.is_empty() && !ancillary.push(SendAncillaryMessage::ScmRights(fds)) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"too many file descriptors",
));
}
let sent = sendmsg(fd, buffers, &mut ancillary, SEND_FLAGS)?;
if sent == 0 {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write to buffer",
));
}
Ok(sent)
}
#[cfg(unix)]
fn socket_credentials(fd: BorrowedFd<'_>) -> io::Result<(ConnectionCredentials, GroupLookup)> {
let mut creds = ConnectionCredentials::default();
#[cfg(any(target_os = "android", target_os = "linux"))]
let uid;
#[cfg(any(target_os = "android", target_os = "linux"))]
let gid;
#[cfg(any(target_os = "android", target_os = "linux"))]
{
use rustix::net::sockopt::socket_peercred;
let ucred = socket_peercred(fd)?;
uid = ucred.uid.as_raw();
gid = ucred.gid.as_raw();
let pid = ucred.pid.as_raw_nonzero().get() as u32;
creds = creds.set_unix_user_id(uid).set_process_id(pid);
#[cfg(target_os = "linux")]
{
use libc::{c_int, socklen_t};
use std::{
mem::{MaybeUninit, size_of},
os::fd::{AsRawFd, FromRawFd, OwnedFd},
};
let mut pidfd = MaybeUninit::<c_int>::zeroed();
let mut len = size_of::<c_int>() as 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() };
creds = creds.set_process_fd(unsafe { OwnedFd::from_raw_fd(pidfd).into() });
} else if ret < 0 {
let err = io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ENOPROTOOPT) {
return Err(err);
}
}
}
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd"
))]
{
use std::os::fd::AsRawFd;
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());
}
creds = creds.set_unix_user_id(uid);
}
Ok((
creds,
GroupLookup {
#[cfg(any(target_os = "android", target_os = "linux"))]
uid,
#[cfg(any(target_os = "android", target_os = "linux"))]
gid,
},
))
}
#[cfg(unix)]
#[derive(Debug)]
struct GroupLookup {
#[cfg(any(target_os = "android", target_os = "linux"))]
uid: libc::uid_t,
#[cfg(any(target_os = "android", target_os = "linux"))]
gid: libc::gid_t,
}
#[cfg(unix)]
impl GroupLookup {
#[cfg(any(target_os = "android", target_os = "linux"))]
async fn complete(
self,
mut credentials: ConnectionCredentials,
runtime: &Runtime,
) -> ConnectionCredentials {
for group in runtime.spawn_blocking(move || self.groups()).await {
credentials = credentials.add_unix_group_id(group);
}
credentials
}
#[cfg(not(any(target_os = "android", target_os = "linux")))]
async fn complete(
self,
credentials: ConnectionCredentials,
_runtime: &Runtime,
) -> ConnectionCredentials {
credentials
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn groups(self) -> Vec<u32> {
use crate::log::debug;
let Self { uid, gid } = self;
let mut passwd: libc::passwd = unsafe { std::mem::zeroed() };
let mut buf = vec![0u8; 16384];
let mut result: *mut libc::passwd = std::ptr::null_mut();
unsafe {
libc::getpwuid_r(
uid,
&mut passwd,
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
&mut result,
);
}
if result.is_null() {
return vec![];
}
let username = unsafe { std::ffi::CStr::from_ptr((*result).pw_name) };
let mut ngroups = 64i32;
let mut groups = vec![0u32; ngroups as usize];
let found = unsafe {
libc::getgrouplist(
username.as_ptr(),
gid,
groups.as_mut_ptr() as *mut libc::gid_t,
&mut ngroups,
)
};
if found < 0 {
debug!("Group lookup failed for user {:?}", username);
return vec![];
}
groups.truncate(ngroups as usize);
groups.sort();
groups
}
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub(super) fn send_credentials_byte(fd: std::os::fd::RawFd) -> io::Result<usize> {
use std::mem::MaybeUninit;
let mut iov = libc::iovec {
iov_base: c"".as_ptr() as *mut libc::c_void,
iov_len: 1,
};
let mut msg: libc::msghdr = unsafe { MaybeUninit::zeroed().assume_init() };
msg.msg_iov = &mut iov;
msg.msg_iovlen = 1;
let cmsg_space = unsafe { libc::CMSG_SPACE(0) as usize };
let mut cmsg_buf = vec![0u8; cmsg_space];
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut libc::c_void;
msg.msg_controllen = cmsg_space as _;
let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
if !cmsg.is_null() {
unsafe {
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_CREDS;
(*cmsg).cmsg_len = libc::CMSG_LEN(0) as _;
}
}
let ret = unsafe { libc::sendmsg(fd, &msg, 0) };
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret as usize)
}
}