Repository: rust-lang/socket2
Pull request #506 (2024-04-15 19:46:50)

Add `Socket::passcred`/`set_passcred` for working with `SO_PASSCRED`.
This PR adds the ability to get and set the value of `SO_PASSCRED` on sockets.

Theoretically, FreeBSD [does support](https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234722) the equivalent behavior, but does so via differently named constants/values, so I've opted to constrain this implementation to Linux only for now.


Patch:
diff --git a/src/lib.rs b/src/lib.rs
index 4f6bd78..89dce26 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -719,6 +719,15 @@ impl<'addr, 'bufs, 'control> MsgHdrMut<'addr, 'bufs, 'control> {
     pub fn flags(&self) -> RecvFlags {
         sys::msghdr_flags(&self.inner)
     }
+
+    /// Gets the length of the control buffer.
+    ///
+    /// Can be used to determine how much, if any, of the control buffer was filled by `recvmsg`.
+    ///
+    /// Corresponds to `msg_controllen` on Unix and `Control.len` on Windows.
+    pub fn control_len(&self) -> usize {
+        sys::msghdr_control_len(&self.inner)
+    }
 }
 
 #[cfg(not(target_os = "redox"))]
diff --git a/src/socket.rs b/src/socket.rs
index 3291707..21bcfcb 100644
--- a/src/socket.rs
+++ b/src/socket.rs
@@ -965,6 +965,37 @@ impl Socket {
         }
     }
 
+    /// Get value for the `SO_PASSCRED` option on this socket.
+    ///
+    /// For more information about this option, see [`set_passcred`].
+    ///
+    /// [`set_passcred`]: Socket::set_passcred
+    #[cfg(all(unix, target_os = "linux"))]
+    #[cfg_attr(docsrs, doc(cfg(all(unix, target_os = "linux"))))]
+    pub fn passcred(&self) -> io::Result<bool> {
+        unsafe {
+            getsockopt::<c_int>(self.as_raw(), sys::SOL_SOCKET, sys::SO_PASSCRED)
+                .map(|passcred| passcred != 0)
+        }
+    }
+
+    /// Set value for the `SO_PASSCRED` option on this socket.
+    ///
+    /// If this option is enabled, enables the receiving of the `SCM_CREDENTIALS`
+    /// control messages.
+    #[cfg(all(unix, target_os = "linux"))]
+    #[cfg_attr(docsrs, doc(cfg(all(unix, target_os = "linux"))))]
+    pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
+        unsafe {
+            setsockopt(
+                self.as_raw(),
+                sys::SOL_SOCKET,
+                sys::SO_PASSCRED,
+                passcred as c_int,
+            )
+        }
+    }
+
     /// Get value for the `SO_RCVBUF` option on this socket.
     ///
     /// For more information about this option, see [`set_recv_buffer_size`].
diff --git a/src/sys/unix.rs b/src/sys/unix.rs
index 562cd60..51ef4a5 100644
--- a/src/sys/unix.rs
+++ b/src/sys/unix.rs
@@ -172,6 +172,8 @@ pub(crate) use libc::SO_LINGER;
     target_os = "watchos",
 ))]
 pub(crate) use libc::SO_LINGER_SEC as SO_LINGER;
+#[cfg(target_os = "linux")]
+pub(crate) use libc::SO_PASSCRED;
 pub(crate) use libc::{
     ip_mreq as IpMreq, linger, IPPROTO_IP, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF,
     IPV6_MULTICAST_LOOP, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP,
@@ -738,6 +740,11 @@ pub(crate) fn msghdr_flags(msg: &msghdr) -> RecvFlags {
     RecvFlags(msg.msg_flags)
 }
 
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn msghdr_control_len(msg: &msghdr) -> usize {
+    msg.msg_controllen as _
+}
+
 /// Unix only API.
 impl SockAddr {
     /// Constructs a `SockAddr` with the family `AF_VSOCK` and the provided CID/port.
diff --git a/src/sys/windows.rs b/src/sys/windows.rs
index 4c5d987..11f2b7b 100644
--- a/src/sys/windows.rs
+++ b/src/sys/windows.rs
@@ -215,6 +215,10 @@ pub(crate) fn msghdr_flags(msg: &msghdr) -> RecvFlags {
     RecvFlags(msg.dwFlags as c_int)
 }
 
+pub(crate) fn msghdr_control_len(msg: &msghdr) -> usize {
+    msg.Control.len as _
+}
+
 fn init() {
     static INIT: Once = Once::new();
 


Test patch:
diff --git a/tests/socket.rs b/tests/socket.rs
index 1c83321..89b79f5 100644
--- a/tests/socket.rs
+++ b/tests/socket.rs
@@ -1680,3 +1680,19 @@ fn cookie() {
         Err(err) => panic!("Could not get socket cookie a second time, err: {err}"),
     }
 }
+
+#[cfg(all(unix, target_os = "linux"))]
+#[test]
+fn set_passcred() {
+    let socket = Socket::new(Domain::UNIX, Type::DGRAM, None).unwrap();
+    assert!(!socket.passcred().unwrap());
+
+    socket.set_passcred(true).unwrap();
+    assert!(socket.passcred().unwrap());
+
+    let socket = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap();
+    assert!(!socket.passcred().unwrap());
+
+    socket.set_passcred(true).unwrap();
+    assert!(socket.passcred().unwrap());
+}