use std::future::Future;
use std::pin::Pin;
use rmux_ipc::PeerIdentity;
use rmux_os::identity::UserIdentity;
use rmux_proto::RmuxError;
use crate::handler::attach_support::ClientFlags;
use crate::handler::control_support::{current_control_queue_identity, ControlClientIdentity};
use crate::handler::{
DetachedRequesterAccess, DetachedRequesterAuthority, RequestHandler, RequesterOrigin,
};
use crate::server_access::{current_owner_uid, AccessMode, ServerAccessAdmission};
enum DetachedAdmissionLookup {
Absent,
Unambiguous(ServerAccessAdmission),
DeniedOrAmbiguous,
}
tokio::task_local! {
static AUTHENTICATED_CONNECTION_PEER: PeerIdentity;
}
pub(crate) fn with_authenticated_connection_peer<'body, T, F>(
peer: PeerIdentity,
future: F,
) -> impl Future<Output = T> + Send + 'body
where
F: Future<Output = T> + Send + 'body,
T: 'body,
{
let future: Pin<Box<dyn Future<Output = T> + Send + 'body>> = Box::pin(future);
AUTHENTICATED_CONNECTION_PEER.scope(peer, future)
}
fn current_connection_peer(requester_pid: u32) -> Option<PeerIdentity> {
AUTHENTICATED_CONNECTION_PEER
.try_with(|peer| (peer.pid == requester_pid).then(|| peer.clone()))
.ok()
.flatten()
}
impl RequestHandler {
pub(in crate::handler) async fn capture_requester_origin(
&self,
requester_pid: u32,
) -> RequesterOrigin {
RequesterOrigin::new(
requester_pid,
self.requester_detached_authority(requester_pid).await,
)
}
pub(in crate::handler) async fn requester_can_write(&self, requester_pid: u32) -> bool {
if let Some(identity) = current_control_queue_identity(requester_pid) {
return self.control_queue_can_write(identity).await;
}
match self.detached_admission_lookup(requester_pid) {
DetachedAdmissionLookup::Absent => {}
DetachedAdmissionLookup::Unambiguous(admission) => {
return self
.server_access
.lock()
.expect("server access mutex must not be poisoned")
.revalidate_detached_admission(&admission)
.is_some_and(AccessMode::can_write);
}
DetachedAdmissionLookup::DeniedOrAmbiguous => return false,
}
{
let active_attach = self.active_attach.lock().await;
if let Some(active) = active_attach.by_pid.get(&requester_pid) {
return active.can_write && !active.flags.contains(ClientFlags::READONLY);
}
}
let active_control = self.active_control.lock().await;
if let Some(active) = active_control.by_pid.get(&requester_pid) {
return active.can_write;
}
drop(active_control);
requester_pid == std::process::id()
}
pub(in crate::handler) async fn requester_detached_authority(
&self,
requester_pid: u32,
) -> DetachedRequesterAuthority {
if let Some(identity) = current_control_queue_identity(requester_pid) {
return self
.control_queue_access(identity)
.await
.and_then(|(user, can_write)| {
self.admission_for_identity_with_write_cap(&user, can_write)
})
.map_or(
DetachedRequesterAuthority::Denied,
DetachedRequesterAuthority::Admission,
);
}
match self.detached_admission_lookup(requester_pid) {
DetachedAdmissionLookup::Unambiguous(admission) => {
return DetachedRequesterAuthority::Admission(admission);
}
DetachedAdmissionLookup::DeniedOrAmbiguous => {
return DetachedRequesterAuthority::Denied;
}
DetachedAdmissionLookup::Absent => {}
}
let attach_access = {
let active_attach = self.active_attach.lock().await;
active_attach.by_pid.get(&requester_pid).map(|active| {
(
active.user.clone(),
active.can_write && !active.flags.contains(ClientFlags::READONLY),
)
})
};
if let Some((user, can_write)) = attach_access {
return self.authority_for_identity(&user, can_write);
}
let control_access = {
let active_control = self.active_control.lock().await;
active_control
.by_pid
.get(&requester_pid)
.map(|active| (active.user.clone(), active.can_write))
};
if let Some((user, can_write)) = control_access {
return self.authority_for_identity(&user, can_write);
}
if requester_pid == std::process::id() {
return DetachedRequesterAuthority::Admission(
self.server_access
.lock()
.expect("server access mutex must not be poisoned")
.owner_admission(),
);
}
DetachedRequesterAuthority::Denied
}
pub(in crate::handler) fn authenticated_requester_peer(
&self,
requester_pid: u32,
) -> Option<PeerIdentity> {
if let Some(peer) = current_connection_peer(requester_pid) {
return Some(peer);
}
self.active_detached_requester_access
.lock()
.expect("active detached requester access mutex must not be poisoned")
.get(&requester_pid)?
.unambiguous_peer()
.cloned()
}
pub(in crate::handler) fn attaching_client_identity(
&self,
requester_pid: u32,
) -> Result<(u32, UserIdentity), RmuxError> {
if let Some(peer) = self.authenticated_requester_peer(requester_pid) {
return Ok((peer.uid, peer.user));
}
if self.requester_peer_scopes_conflict(requester_pid) {
return Err(RmuxError::Server(
"attaching client identity is ambiguous for this requester".to_owned(),
));
}
Ok((current_owner_uid(), self.server_owner_identity()))
}
fn requester_peer_scopes_conflict(&self, requester_pid: u32) -> bool {
self.active_detached_requester_access
.lock()
.expect("active detached requester access mutex must not be poisoned")
.get(&requester_pid)
.is_some_and(DetachedRequesterAccess::has_conflicting_peers)
}
fn detached_admission_lookup(&self, requester_pid: u32) -> DetachedAdmissionLookup {
let detached_access = self
.active_detached_requester_access
.lock()
.expect("active detached requester access mutex must not be poisoned");
let Some(active) = detached_access.get(&requester_pid) else {
return DetachedAdmissionLookup::Absent;
};
active.unambiguous_admission().cloned().map_or(
DetachedAdmissionLookup::DeniedOrAmbiguous,
DetachedAdmissionLookup::Unambiguous,
)
}
fn authority_for_identity(
&self,
identity: &UserIdentity,
can_write: bool,
) -> DetachedRequesterAuthority {
self.admission_for_identity_with_write_cap(identity, can_write)
.map_or(
DetachedRequesterAuthority::Denied,
DetachedRequesterAuthority::Admission,
)
}
fn admission_for_identity_with_write_cap(
&self,
identity: &UserIdentity,
can_write: bool,
) -> Option<ServerAccessAdmission> {
self.server_access
.lock()
.ok()?
.admission_for_identity_with_write_cap(identity, can_write)
}
async fn control_queue_access(
&self,
identity: ControlClientIdentity,
) -> Option<(UserIdentity, bool)> {
let state = self.state.lock().await;
let active_control = self.active_control.lock().await;
Self::validate_control_queue_identity_locked(
&state,
&active_control,
identity.requester_pid(),
identity.control_id(),
)
.ok()?;
active_control
.by_pid
.get(&identity.requester_pid())
.map(|active| (active.user.clone(), active.can_write))
}
}
#[cfg(test)]
mod tests {
use std::future::{pending, ready};
use rmux_os::identity::UserIdentity;
use super::{current_connection_peer, with_authenticated_connection_peer, PeerIdentity};
fn test_peer(pid: u32, uid: u32) -> PeerIdentity {
PeerIdentity {
pid,
uid,
user: UserIdentity::Uid(uid),
}
}
#[test]
fn the_peer_scope_costs_the_same_whatever_body_it_guards() {
const PADDING: usize = 64 * 1024;
let small = ready(());
let large = async {
let padding = [0_u8; PADDING];
pending::<()>().await;
std::hint::black_box(padding);
};
assert!(
std::mem::size_of_val(&large) >= PADDING,
"the large probe body must really carry its padding across the await"
);
let scoped_small = with_authenticated_connection_peer(test_peer(1, 1), small);
let scoped_large = with_authenticated_connection_peer(test_peer(1, 1), large);
assert_eq!(
std::mem::size_of_val(&scoped_small),
std::mem::size_of_val(&scoped_large),
"the peer scope must hold its connection body behind a pointer"
);
}
#[tokio::test]
async fn every_poll_of_a_scoped_body_reads_the_connection_peer() {
let peer = test_peer(std::process::id().wrapping_add(18_211), 18_211);
let pid = peer.pid;
let read = with_authenticated_connection_peer(peer.clone(), async move {
let first = current_connection_peer(pid);
tokio::task::yield_now().await;
[first, current_connection_peer(pid)]
})
.await;
assert_eq!(read, [Some(peer.clone()), Some(peer)]);
}
}