1use std::fmt;
5
6use std::io;
7use std::os::unix::net::UnixStream;
8use std::os::unix::prelude::*;
9
10#[cfg(target_os = "freebsd")]
11mod xucred_cr {
12 #[derive(Copy, Clone)]
13 pub union XucredCr {
14 pub cr_pid: libc::pid_t,
15 _cr_unused_1: *const libc::c_void,
16 }
17
18 impl Eq for XucredCr {}
19
20 impl std::hash::Hash for XucredCr {
21 #[inline]
22 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
23 state.write_u64(unsafe { self.cr_pid } as u64)
24 }
25 }
26
27 impl PartialEq<Self> for XucredCr {
28 #[inline]
29 fn eq(&self, other: &Self) -> bool {
30 unsafe { self.cr_pid == other.cr_pid }
31 }
32 }
33}
34
35#[derive(Clone, Eq, Hash, PartialEq)]
37#[repr(C)]
38pub struct Xucred {
39 cr_version: libc::c_uint,
40 cr_uid: libc::uid_t,
41 cr_ngroups: libc::c_short,
42 cr_groups: [libc::gid_t; crate::constants::XU_NGROUPS],
43 #[cfg(target_os = "freebsd")]
44 _cr: xucred_cr::XucredCr,
45 #[cfg(target_os = "dragonfly")]
46 _cr_unused1: *const libc::c_void,
47}
48
49impl Xucred {
50 #[inline]
52 pub fn uid(&self) -> libc::uid_t {
53 self.cr_uid
54 }
55
56 #[inline]
58 pub fn gid(&self) -> libc::gid_t {
59 self.cr_groups[0]
60 }
61
62 #[inline]
66 pub fn groups(&self) -> &[libc::gid_t] {
67 &self.cr_groups[..self.cr_ngroups as usize]
68 }
69
70 #[cfg(target_os = "freebsd")]
74 #[inline]
75 pub fn pid(&self) -> Option<libc::pid_t> {
76 match unsafe { self._cr.cr_pid } {
77 0 => None,
78 pid => Some(pid),
79 }
80 }
81}
82
83impl fmt::Debug for Xucred {
84 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85 let mut s = f.debug_struct("Xucred");
86
87 s.field("uid", &self.uid())
88 .field("gid", &self.gid())
89 .field("groups", &self.groups());
90
91 #[cfg(target_os = "freebsd")]
92 s.field("pid", &self.pid());
93
94 s.finish()
95 }
96}
97
98pub(crate) unsafe fn get_xucred_raw(sockfd: RawFd) -> io::Result<Xucred> {
99 let mut xucred: Xucred = std::mem::zeroed();
100 xucred.cr_version = libc::XUCRED_VERSION;
101
102 let len = crate::util::getsockopt_raw(
103 sockfd,
104 0,
105 libc::LOCAL_PEERCRED,
106 std::slice::from_mut(&mut xucred),
107 )?;
108
109 if len != std::mem::size_of::<Xucred>()
117 || xucred.cr_version != libc::XUCRED_VERSION
118 || xucred.cr_ngroups < 1
119 || xucred.cr_ngroups as usize > crate::constants::XU_NGROUPS
120 {
121 return Err(io::Error::from_raw_os_error(libc::EINVAL));
122 }
123
124 Ok(xucred)
125}
126
127#[inline]
129pub fn get_xucred(sock: &UnixStream) -> io::Result<Xucred> {
130 unsafe { get_xucred_raw(sock.as_raw_fd()) }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 use std::os::unix::net::UnixDatagram;
138
139 fn getgroups() -> Vec<libc::gid_t> {
140 let mut ngroups = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
141 assert!(ngroups >= 0, "{:?}", io::Error::last_os_error());
142
143 let mut groups = Vec::new();
144 groups.resize(ngroups as usize, 0);
145
146 ngroups = unsafe { libc::getgroups(groups.len() as libc::c_int, groups.as_mut_ptr()) };
147 assert!(ngroups >= 0, "{:?}", io::Error::last_os_error());
148
149 groups.truncate(ngroups as usize);
150 groups
151 }
152
153 #[cfg(target_os = "freebsd")]
154 fn get_expected_pid() -> Option<libc::pid_t> {
155 if crate::util::has_cr_pid() {
156 Some(unsafe { libc::getpid() })
157 } else {
158 None
159 }
160 }
161
162 #[test]
163 fn test_get_xucred() {
164 let (a, b) = UnixStream::pair().unwrap();
165
166 let mut groups = getgroups();
167 groups.sort_unstable();
168
169 let acred = get_xucred(&a).unwrap();
170 assert_eq!(acred.uid(), unsafe { libc::geteuid() });
171 assert_eq!(acred.gid(), unsafe { libc::getegid() });
172
173 let mut agroups = Vec::from(acred.groups());
174 agroups.sort_unstable();
175 assert_eq!(agroups, groups);
176
177 #[cfg(target_os = "freebsd")]
178 assert_eq!(acred.pid(), get_expected_pid());
179
180 let bcred = get_xucred(&b).unwrap();
181 assert_eq!(bcred.uid(), unsafe { libc::geteuid() });
182 assert_eq!(bcred.gid(), unsafe { libc::getegid() });
183
184 let mut bgroups = Vec::from(bcred.groups());
185 bgroups.sort_unstable();
186 assert_eq!(bgroups, groups);
187
188 #[cfg(target_os = "freebsd")]
189 assert_eq!(bcred.pid(), get_expected_pid());
190 }
191
192 fn same_hash<T: std::hash::Hash>(a: &T, b: &T) -> bool {
193 use std::hash::{BuildHasher, Hasher};
194
195 let s = std::collections::hash_map::RandomState::new();
196
197 let mut hasher_a = s.build_hasher();
198 a.hash(&mut hasher_a);
199
200 let mut hasher_b = s.build_hasher();
201 b.hash(&mut hasher_b);
202
203 hasher_a.finish() == hasher_b.finish()
204 }
205
206 #[test]
207 fn test_xucred() {
208 let (a, b) = UnixStream::pair().unwrap();
209
210 let acred = get_xucred(&a).unwrap();
211 let bcred = get_xucred(&b).unwrap();
212
213 assert_eq!(acred, bcred);
214 assert!(same_hash(&acred, &bcred));
215
216 assert_eq!(acred, acred.clone());
217 assert!(same_hash(&acred, &acred.clone()));
218
219 let zcred: Xucred = unsafe { std::mem::zeroed() };
220
221 assert_eq!(zcred, zcred.clone());
222 assert!(same_hash(&zcred, &zcred.clone()));
223
224 assert_ne!(acred, zcred);
225 assert!(!same_hash(&acred, &zcred));
226
227 #[cfg(target_os = "freebsd")]
229 assert_eq!(zcred.pid(), None);
230
231 let mut test_cred: Xucred = unsafe { std::mem::zeroed() };
232
233 #[cfg(target_os = "freebsd")]
234 {
235 assert_eq!(
236 format!("{:?}", test_cred),
237 "Xucred { uid: 0, gid: 0, groups: [], pid: None }"
238 );
239
240 test_cred.cr_uid = 1000;
241 test_cred.cr_ngroups = 2;
242 test_cred.cr_groups[0] = 1001;
243 test_cred.cr_groups[1] = 10;
244 assert_eq!(
245 format!("{:?}", test_cred),
246 "Xucred { uid: 1000, gid: 1001, groups: [1001, 10], pid: None }"
247 );
248
249 test_cred._cr.cr_pid = 1494;
250 assert_eq!(
251 format!("{:?}", test_cred),
252 "Xucred { uid: 1000, gid: 1001, groups: [1001, 10], pid: Some(1494) }"
253 );
254 }
255
256 #[cfg(not(target_os = "freebsd"))]
257 {
258 assert_eq!(
259 format!("{:?}", test_cred),
260 "Xucred { uid: 0, gid: 0, groups: [] }"
261 );
262
263 test_cred.cr_uid = 1000;
264 test_cred.cr_ngroups = 2;
265 test_cred.cr_groups[0] = 1001;
266 test_cred.cr_groups[1] = 10;
267 assert_eq!(
268 format!("{:?}", test_cred),
269 "Xucred { uid: 1000, gid: 1001, groups: [1001, 10] }"
270 );
271 }
272 }
273
274 #[test]
275 fn test_get_xucred_error() {
276 let dir = tempfile::tempdir().unwrap();
277
278 let sock = UnixDatagram::bind(dir.path().join("sock")).unwrap();
279
280 assert_eq!(
281 get_xucred(unsafe { &UnixStream::from_raw_fd(sock.as_raw_fd()) })
282 .unwrap_err()
283 .raw_os_error(),
284 Some(libc::EINVAL),
285 );
286 }
287}