From 0d3bc089d53a126716b4ed8f6b629c5b8c76964a Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 6 Aug 2021 20:15:24 -0600 Subject: [PATCH] Add support for LOCAL_PEER_CRED On FreeBSD and its derivatives, this socket option gets the credentials of the connected peer. --- CHANGELOG.md | 2 ++ src/sys/socket/mod.rs | 32 ++++++++++++++++++++++++++ src/sys/socket/sockopt.rs | 48 +++++++++++++++++++++++---------------- test/sys/test_sockopt.rs | 41 +++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdf796ce3a..5031d02c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] - ReleaseDate ### Added +- Added the `LocalPeerCred` sockopt. + (#[1482](https://github.com/nix-rust/nix/pull/1482)) - Added `TimeSpec::from_duration` and `TimeSpec::from_timespec` (#[1465](https://github.com/nix-rust/nix/pull/1465)) - Added `IPV6_V6ONLY` sockopt. diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 0f54ef0cea..733bd6608b 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -358,6 +358,38 @@ cfg_if! { } } +cfg_if!{ + if #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" + ))] { + /// Return type of [`LocalPeerCred`](crate::sys::socket::sockopt::LocalPeerCred) + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct XuCred(libc::xucred); + + impl XuCred { + /// Structure layout version + pub fn version(&self) -> u32 { + self.0.cr_version + } + + /// Effective user ID + pub fn uid(&self) -> libc::uid_t { + self.0.cr_uid + } + + /// Returns a list of group identifiers (the first one being the + /// effective GID) + pub fn groups(&self) -> &[libc::gid_t] { + &self.0.cr_groups + } + } + } +} + /// Request for multicast socket operations /// /// This is a wrapper type around `ip_mreq`. diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 69fe479aa2..82df7f87d1 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -30,7 +30,7 @@ const TCP_CA_NAME_MAX: usize = 16; /// # Arguments /// /// * `$name:ident`: name of the type you want to implement `SetSockOpt` for. -/// * `$level:path` : socket layer, or a `protocol level`: could be *raw sockets* +/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* /// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), /// and more. Please refer to your system manual for more options. Will be passed as the second /// argument (`level`) to the `setsockopt` call. @@ -41,7 +41,7 @@ const TCP_CA_NAME_MAX: usize = 16; /// * Type that implements the `Set` trait for the type from the previous item (like `SetBool` for /// `bool`, `SetUsize` for `usize`, etc.). macro_rules! setsockopt_impl { - ($name:ident, $level:path, $flag:path, $ty:ty, $setter:ty) => { + ($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => { impl SetSockOpt for $name { type Val = $ty; @@ -82,7 +82,7 @@ macro_rules! setsockopt_impl { /// * Type that implements the `Get` trait for the type from the previous item (`GetBool` for /// `bool`, `GetUsize` for `usize`, etc.). macro_rules! getsockopt_impl { - ($name:ident, $level:path, $flag:path, $ty:ty, $getter:ty) => { + ($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => { impl GetSockOpt for $name { type Val = $ty; @@ -117,7 +117,7 @@ macro_rules! getsockopt_impl { /// * `GetOnly`, `SetOnly` or `Both`: whether you want to implement only getter, only setter or /// both of them. /// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for. -/// * `$level:path` : socket layer, or a `protocol level`: could be *raw sockets* +/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* /// (`lic::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), /// and more. Please refer to your system manual for more options. Will be passed as the second /// argument (`level`) to the `getsockopt`/`setsockopt` call. @@ -128,43 +128,43 @@ macro_rules! getsockopt_impl { /// * `$getter:ty`: `Get` implementation; optional; only for `GetOnly` and `Both`. /// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`. macro_rules! sockopt_impl { - (GetOnly, $name:ident, $level:path, $flag:path, bool) => { + (GetOnly, $name:ident, $level:expr, $flag:path, bool) => { sockopt_impl!(GetOnly, $name, $level, $flag, bool, GetBool); }; - (GetOnly, $name:ident, $level:path, $flag:path, u8) => { + (GetOnly, $name:ident, $level:expr, $flag:path, u8) => { sockopt_impl!(GetOnly, $name, $level, $flag, u8, GetU8); }; - (GetOnly, $name:ident, $level:path, $flag:path, usize) => { + (GetOnly, $name:ident, $level:expr, $flag:path, usize) => { sockopt_impl!(GetOnly, $name, $level, $flag, usize, GetUsize); }; - (SetOnly, $name:ident, $level:path, $flag:path, bool) => { + (SetOnly, $name:ident, $level:expr, $flag:path, bool) => { sockopt_impl!(SetOnly, $name, $level, $flag, bool, SetBool); }; - (SetOnly, $name:ident, $level:path, $flag:path, u8) => { + (SetOnly, $name:ident, $level:expr, $flag:path, u8) => { sockopt_impl!(SetOnly, $name, $level, $flag, u8, SetU8); }; - (SetOnly, $name:ident, $level:path, $flag:path, usize) => { + (SetOnly, $name:ident, $level:expr, $flag:path, usize) => { sockopt_impl!(SetOnly, $name, $level, $flag, usize, SetUsize); }; - (Both, $name:ident, $level:path, $flag:path, bool) => { + (Both, $name:ident, $level:expr, $flag:path, bool) => { sockopt_impl!(Both, $name, $level, $flag, bool, GetBool, SetBool); }; - (Both, $name:ident, $level:path, $flag:path, u8) => { + (Both, $name:ident, $level:expr, $flag:path, u8) => { sockopt_impl!(Both, $name, $level, $flag, u8, GetU8, SetU8); }; - (Both, $name:ident, $level:path, $flag:path, usize) => { + (Both, $name:ident, $level:expr, $flag:path, usize) => { sockopt_impl!(Both, $name, $level, $flag, usize, GetUsize, SetUsize); }; - (Both, $name:ident, $level:path, $flag:path, OsString<$array:ty>) => { + (Both, $name:ident, $level:expr, $flag:path, OsString<$array:ty>) => { sockopt_impl!(Both, $name, $level, $flag, OsString, GetOsString<$array>, SetOsString); }; @@ -172,29 +172,29 @@ macro_rules! sockopt_impl { * Matchers with generic getter types must be placed at the end, so * they'll only match _after_ specialized matchers fail */ - (GetOnly, $name:ident, $level:path, $flag:path, $ty:ty) => { + (GetOnly, $name:ident, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!(GetOnly, $name, $level, $flag, $ty, GetStruct<$ty>); }; - (GetOnly, $name:ident, $level:path, $flag:path, $ty:ty, $getter:ty) => { + (GetOnly, $name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct $name; getsockopt_impl!($name, $level, $flag, $ty, $getter); }; - (SetOnly, $name:ident, $level:path, $flag:path, $ty:ty) => { + (SetOnly, $name:ident, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!(SetOnly, $name, $level, $flag, $ty, SetStruct<$ty>); }; - (SetOnly, $name:ident, $level:path, $flag:path, $ty:ty, $setter:ty) => { + (SetOnly, $name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct $name; setsockopt_impl!($name, $level, $flag, $ty, $setter); }; - (Both, $name:ident, $level:path, $flag:path, $ty:ty, $getter:ty, $setter:ty) => { + (Both, $name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty, $setter:ty) => { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct $name; @@ -202,7 +202,7 @@ macro_rules! sockopt_impl { getsockopt_impl!($name, $level, $flag, $ty, $getter); }; - (Both, $name:ident, $level:path, $flag:path, $ty:ty) => { + (Both, $name:ident, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!(Both, $name, $level, $flag, $ty, GetStruct<$ty>, SetStruct<$ty>); }; } @@ -246,6 +246,14 @@ sockopt_impl!(Both, Broadcast, libc::SOL_SOCKET, libc::SO_BROADCAST, bool); sockopt_impl!(Both, OobInline, libc::SOL_SOCKET, libc::SO_OOBINLINE, bool); sockopt_impl!(GetOnly, SocketError, libc::SOL_SOCKET, libc::SO_ERROR, i32); sockopt_impl!(Both, KeepAlive, libc::SOL_SOCKET, libc::SO_KEEPALIVE, bool); +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" +))] +// Get the credentials of the peer process of a connected unix domain socket. +sockopt_impl!(GetOnly, LocalPeerCred, 0, libc::LOCAL_PEERCRED, super::XuCred); #[cfg(any(target_os = "android", target_os = "linux"))] sockopt_impl!(GetOnly, PeerCredentials, libc::SOL_SOCKET, libc::SO_PEERCRED, super::UnixCredentials); #[cfg(any(target_os = "ios", diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index e0ed0f7c41..4d2d7e515d 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -3,6 +3,47 @@ use nix::sys::socket::{socket, sockopt, getsockopt, setsockopt, AddressFamily, S #[cfg(any(target_os = "android", target_os = "linux"))] use crate::*; +// NB: FreeBSD supports LOCAL_PEERCRED for SOCK_SEQPACKET, but OSX does not. +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", +))] +#[test] +pub fn test_local_peercred_seqpacket() { + use nix::{ + unistd::{Gid, Uid}, + sys::socket::socketpair + }; + + let (fd1, _fd2) = socketpair(AddressFamily::Unix, SockType::SeqPacket, None, + SockFlag::empty()).unwrap(); + let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap(); + assert_eq!(xucred.version(), 0); + assert_eq!(Uid::from_raw(xucred.uid()), Uid::current()); + assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current()); +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" +))] +#[test] +pub fn test_local_peercred_stream() { + use nix::{ + unistd::{Gid, Uid}, + sys::socket::socketpair + }; + + let (fd1, _fd2) = socketpair(AddressFamily::Unix, SockType::Stream, None, + SockFlag::empty()).unwrap(); + let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap(); + assert_eq!(xucred.version(), 0); + assert_eq!(Uid::from_raw(xucred.uid()), Uid::current()); + assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current()); +} + #[cfg(target_os = "linux")] #[test] fn is_so_mark_functional() {