use std::fmt;
use std::net::SocketAddr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PeerIdentity {
pub uid: Option<u32>,
pub gid: Option<u32>,
pub pid: Option<u32>,
pub sid: Option<String>,
pub remote_addr: Option<SocketAddr>,
pub transport: &'static str,
}
impl PeerIdentity {
pub fn from_tcp(remote: SocketAddr) -> Self {
Self {
uid: None,
gid: None,
pid: None,
sid: None,
remote_addr: Some(remote),
transport: "tcp",
}
}
}
impl fmt::Display for PeerIdentity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.transport {
"unix" => write!(
f,
"unix:{}:{}",
self.uid.map(|u| u as i64).unwrap_or(-1),
self.pid.map(|p| p as i64).unwrap_or(-1),
),
"pipe" => write!(
f,
"pipe:{}:{}",
self.sid.as_deref().unwrap_or("?"),
self.pid.map(|p| p as i64).unwrap_or(-1),
),
"tcp" => match self.remote_addr {
Some(addr) => write!(f, "tcp:{addr}"),
None => write!(f, "tcp:?"),
},
other => write!(f, "{other}:?"),
}
}
}
#[cfg(unix)]
pub mod unix {
#![allow(unsafe_code)]
use super::PeerIdentity;
use std::io;
use std::os::fd::AsRawFd;
use tokio::net::UnixStream;
pub fn from_stream(stream: &UnixStream) -> io::Result<PeerIdentity> {
let raw = stream.as_raw_fd();
let borrowed = || unsafe { std::os::fd::BorrowedFd::borrow_raw(raw) };
#[cfg(any(target_os = "android", target_os = "linux"))]
{
use nix::sys::socket::{getsockopt, sockopt::PeerCredentials};
let cred = getsockopt(&borrowed(), PeerCredentials)
.map_err(|e| io::Error::other(format!("SO_PEERCRED: {e}")))?;
Ok(PeerIdentity {
uid: Some(cred.uid()),
gid: Some(cred.gid()),
pid: Some(cred.pid() as u32),
sid: None,
remote_addr: None,
transport: "unix",
})
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
use nix::sys::socket::{
getsockopt,
sockopt::{LocalPeerCred, LocalPeerPid},
};
let cred = getsockopt(&borrowed(), LocalPeerCred)
.map_err(|e| io::Error::other(format!("LOCAL_PEERCRED: {e}")))?;
let pid = getsockopt(&borrowed(), LocalPeerPid)
.map_err(|e| io::Error::other(format!("LOCAL_PEERPID: {e}")))?;
let gid = cred.groups().first().copied().unwrap_or(0);
Ok(PeerIdentity {
uid: Some(cred.uid()),
gid: Some(gid),
pid: Some(pid as u32),
sid: None,
remote_addr: None,
transport: "unix",
})
}
#[cfg(not(any(
target_os = "android",
target_os = "linux",
target_os = "macos",
target_os = "ios",
)))]
{
let _ = borrowed;
Err(io::Error::other(
"SO_PEERCRED not supported on this platform",
))
}
}
}
#[cfg(windows)]
pub mod windows {
#![allow(unsafe_code)]
use super::PeerIdentity;
use std::io;
use std::os::windows::io::AsRawHandle;
use tokio::net::windows::named_pipe::NamedPipeServer;
use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE, HLOCAL, LocalFree};
use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW;
use windows_sys::Win32::Security::{GetTokenInformation, TOKEN_QUERY, TOKEN_USER, TokenUser};
use windows_sys::Win32::System::Pipes::GetNamedPipeClientProcessId;
use windows_sys::Win32::System::Threading::{
OpenProcess, OpenProcessToken, PROCESS_QUERY_LIMITED_INFORMATION,
};
pub fn from_stream(server: &NamedPipeServer) -> io::Result<PeerIdentity> {
let pipe_handle = server.as_raw_handle() as HANDLE;
let mut pid: u32 = 0;
let ok = unsafe { GetNamedPipeClientProcessId(pipe_handle, &mut pid) };
if ok == 0 {
return Err(io::Error::last_os_error());
}
let sid = sid_for_pid(pid).unwrap_or(None);
Ok(PeerIdentity {
uid: None,
gid: None,
pid: Some(pid),
sid,
remote_addr: None,
transport: "pipe",
})
}
fn sid_for_pid(pid: u32) -> io::Result<Option<String>> {
unsafe {
let process: HANDLE = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if process == 0 {
return Ok(None);
}
let mut token: HANDLE = 0;
let ok = OpenProcessToken(process, TOKEN_QUERY, &mut token);
if ok == 0 {
CloseHandle(process);
return Ok(None);
}
let mut needed: u32 = 0;
GetTokenInformation(token, TokenUser, std::ptr::null_mut(), 0, &mut needed);
if needed == 0 {
CloseHandle(token);
CloseHandle(process);
return Ok(None);
}
let mut buf = vec![0u8; needed as usize];
let ok = GetTokenInformation(
token,
TokenUser,
buf.as_mut_ptr().cast(),
needed,
&mut needed,
);
if ok == 0 {
CloseHandle(token);
CloseHandle(process);
return Ok(None);
}
let token_user = &*(buf.as_ptr() as *const TOKEN_USER);
let sid_ptr = token_user.User.Sid;
let mut sid_str_ptr: *mut u16 = std::ptr::null_mut();
let ok = ConvertSidToStringSidW(sid_ptr, &mut sid_str_ptr);
CloseHandle(token);
CloseHandle(process);
if ok == 0 || sid_str_ptr.is_null() {
return Ok(None);
}
let mut len = 0usize;
while *sid_str_ptr.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts(sid_str_ptr, len);
let sid_string = String::from_utf16_lossy(slice);
LocalFree(sid_str_ptr as HLOCAL);
Ok(Some(sid_string))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn tcp_identity_displays_remote_addr() {
let id = PeerIdentity::from_tcp(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 12345));
assert_eq!(id.transport, "tcp");
assert_eq!(format!("{id}"), "tcp:127.0.0.1:12345");
}
#[cfg(unix)]
#[tokio::test]
async fn unix_peer_credentials_self() {
use tempfile::tempdir;
use tokio::net::UnixListener;
let dir = tempdir().unwrap();
let path = dir.path().join("peer.sock");
let listener = UnixListener::bind(&path).unwrap();
let server = tokio::spawn(async move {
let (server_side, _) = listener.accept().await.unwrap();
unix::from_stream(&server_side).unwrap()
});
let _client = tokio::net::UnixStream::connect(&path).await.unwrap();
let id = server.await.unwrap();
assert_eq!(id.transport, "unix");
assert_eq!(id.pid, Some(std::process::id()));
}
}