1#[cfg(feature = "serde")]
4mod serde_support;
5
6use crate::Error;
7use once_cell::sync::Lazy;
8use std::{
9 collections::HashMap,
10 ffi::{OsStr, OsString},
11 io,
12 sync::Mutex,
13};
14
15use pwd_grp::{PwdGrp, PwdGrpProvider};
16
17type Id = u32;
19
20#[derive(Default, Debug)]
22struct TrustedUsersCache<U: PwdGrpProvider> {
23 pwd_grp: U,
25 trusted_uid: HashMap<TrustedUser, Option<Id>>,
27 trusted_gid: HashMap<TrustedGroup, Option<Id>>,
29}
30
31static CACHE: Lazy<Mutex<TrustedUsersCache<PwdGrp>>> =
39 Lazy::new(|| Mutex::new(TrustedUsersCache::default()));
40
41fn handle_pwd_error(e: io::Error) -> Error {
43 Error::PasswdGroupIoError(e.into())
44}
45
46fn get_self_named_gid_impl<U: PwdGrpProvider>(userdb: &U) -> io::Result<Option<u32>> {
48 let Some(username) = get_own_username(userdb)? else {
49 return Ok(None);
50 };
51
52 let Some(group) = userdb.getgrnam::<Vec<u8>>(username)? else {
53 return Ok(None);
54 };
55
56 Ok(if cur_groups()?.contains(&group.gid) {
61 Some(group.gid)
62 } else {
63 None
64 })
65}
66
67fn get_own_username<U: PwdGrpProvider>(userdb: &U) -> io::Result<Option<Vec<u8>>> {
74 use std::os::unix::ffi::OsStringExt as _;
75
76 let my_uid = userdb.getuid();
77
78 if let Some(username) = std::env::var_os("USER") {
79 let username = username.into_vec();
80 if let Some(passwd) = userdb.getpwnam::<Vec<u8>>(&username)? {
81 if passwd.uid == my_uid {
82 return Ok(Some(username));
83 }
84 }
85 }
86
87 if let Some(passwd) = userdb.getpwuid(my_uid)? {
88 if passwd.uid == my_uid {
90 return Ok(Some(passwd.name));
91 }
92 }
93
94 Ok(None)
95}
96
97fn cur_groups() -> io::Result<Vec<u32>> {
99 PwdGrp.getgroups()
100}
101
102#[derive(Clone, Default, Debug, Eq, PartialEq, Hash)]
122#[cfg_attr(
123 feature = "serde",
124 derive(serde::Serialize, serde::Deserialize),
125 serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
126)]
127#[non_exhaustive]
128pub enum TrustedUser {
129 None,
131 #[default]
133 Current,
134 Id(u32),
136 Name(OsString),
145}
146
147impl From<u32> for TrustedUser {
148 fn from(val: u32) -> Self {
149 TrustedUser::Id(val)
150 }
151}
152impl From<OsString> for TrustedUser {
153 fn from(val: OsString) -> Self {
154 TrustedUser::Name(val)
155 }
156}
157impl From<&OsStr> for TrustedUser {
158 fn from(val: &OsStr) -> Self {
159 val.to_owned().into()
160 }
161}
162impl From<String> for TrustedUser {
163 fn from(val: String) -> Self {
164 OsString::from(val).into()
165 }
166}
167impl From<&str> for TrustedUser {
168 fn from(val: &str) -> Self {
169 val.to_owned().into()
170 }
171}
172
173impl TrustedUser {
174 pub(crate) fn get_uid(&self) -> Result<Option<u32>, Error> {
176 let mut cache = CACHE.lock().expect("poisoned lock");
177 if let Some(got) = cache.trusted_uid.get(self) {
178 return Ok(*got);
179 }
180 let calculated = self.get_uid_impl(&cache.pwd_grp)?;
181 cache.trusted_uid.insert(self.clone(), calculated);
182 Ok(calculated)
183 }
184 fn get_uid_impl<U: PwdGrpProvider>(&self, userdb: &U) -> Result<Option<u32>, Error> {
186 use std::os::unix::ffi::OsStrExt as _;
187
188 match self {
189 TrustedUser::None => Ok(None),
190 TrustedUser::Current => Ok(Some(userdb.getuid())),
191 TrustedUser::Id(id) => Ok(Some(*id)),
192 TrustedUser::Name(name) => userdb
193 .getpwnam(name.as_bytes())
194 .map_err(handle_pwd_error)?
195 .map(|u: pwd_grp::Passwd<Vec<u8>>| Some(u.uid))
196 .ok_or_else(|| Error::NoSuchUser(name.to_string_lossy().into_owned())),
197 }
198 }
199}
200
201#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
209#[cfg_attr(
210 feature = "serde",
211 derive(serde::Serialize, serde::Deserialize),
212 serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
213)]
214#[non_exhaustive]
215pub enum TrustedGroup {
216 None,
218 #[default]
224 SelfNamed,
225 Id(u32),
227 Name(OsString),
231}
232
233impl From<u32> for TrustedGroup {
234 fn from(val: u32) -> Self {
235 TrustedGroup::Id(val)
236 }
237}
238impl From<OsString> for TrustedGroup {
239 fn from(val: OsString) -> TrustedGroup {
240 TrustedGroup::Name(val)
241 }
242}
243impl From<&OsStr> for TrustedGroup {
244 fn from(val: &OsStr) -> TrustedGroup {
245 val.to_owned().into()
246 }
247}
248impl From<String> for TrustedGroup {
249 fn from(val: String) -> TrustedGroup {
250 OsString::from(val).into()
251 }
252}
253impl From<&str> for TrustedGroup {
254 fn from(val: &str) -> TrustedGroup {
255 val.to_owned().into()
256 }
257}
258
259impl TrustedGroup {
260 pub(crate) fn get_gid(&self) -> Result<Option<u32>, Error> {
262 let mut cache = CACHE.lock().expect("poisoned lock");
263 if let Some(got) = cache.trusted_gid.get(self) {
264 return Ok(*got);
265 }
266 let calculated = self.get_gid_impl(&cache.pwd_grp)?;
267 cache.trusted_gid.insert(self.clone(), calculated);
268 Ok(calculated)
269 }
270 fn get_gid_impl<U: PwdGrpProvider>(&self, userdb: &U) -> Result<Option<u32>, Error> {
272 use std::os::unix::ffi::OsStrExt as _;
273
274 match self {
275 TrustedGroup::None => Ok(None),
276 TrustedGroup::SelfNamed => get_self_named_gid_impl(userdb).map_err(handle_pwd_error),
277 TrustedGroup::Id(id) => Ok(Some(*id)),
278 TrustedGroup::Name(name) => userdb
279 .getgrnam(name.as_bytes())
280 .map_err(handle_pwd_error)?
281 .map(|g: pwd_grp::Group<Vec<u8>>| Some(g.gid))
282 .ok_or_else(|| Error::NoSuchGroup(name.to_string_lossy().into_owned())),
283 }
284 }
285}
286
287#[cfg(test)]
288mod test {
289 #![allow(clippy::bool_assert_comparison)]
291 #![allow(clippy::clone_on_copy)]
292 #![allow(clippy::dbg_macro)]
293 #![allow(clippy::mixed_attributes_style)]
294 #![allow(clippy::print_stderr)]
295 #![allow(clippy::print_stdout)]
296 #![allow(clippy::single_char_pattern)]
297 #![allow(clippy::unwrap_used)]
298 #![allow(clippy::unchecked_duration_subtraction)]
299 #![allow(clippy::useless_vec)]
300 #![allow(clippy::needless_pass_by_value)]
301 use super::*;
303 use pwd_grp::mock::MockPwdGrpProvider;
304 type Id = u32;
305
306 fn mock_users() -> MockPwdGrpProvider {
307 let mock = MockPwdGrpProvider::new();
308 mock.set_uids(413.into());
309 mock
310 }
311 fn add_user(mock: &MockPwdGrpProvider, uid: Id, name: &str, gid: Id) {
312 mock.add_to_passwds([pwd_grp::Passwd::<String> {
313 name: name.into(),
314 uid,
315 gid,
316 ..pwd_grp::Passwd::blank()
317 }]);
318 }
319 fn add_group(mock: &MockPwdGrpProvider, gid: Id, name: &str) {
320 mock.add_to_groups([pwd_grp::Group::<String> {
321 name: name.into(),
322 gid,
323 ..pwd_grp::Group::blank()
324 }]);
325 }
326
327 #[test]
328 fn groups() {
329 let groups = cur_groups().unwrap();
330 let cur_gid = pwd_grp::getgid();
331 if groups.is_empty() {
332 return;
335 }
336 assert!(groups.contains(&cur_gid));
337 }
338
339 #[test]
340 fn username_real() {
341 let cache = CACHE.lock().expect("poisoned lock");
344 let uname = get_own_username(&cache.pwd_grp)
345 .unwrap()
346 .expect("Running on a misconfigured host");
347 let user = PwdGrp.getpwnam::<Vec<u8>>(&uname).unwrap().unwrap();
348 assert_eq!(user.name, uname);
349 assert_eq!(user.uid, PwdGrp.getuid());
350 }
351
352 #[test]
353 fn username_from_env() {
354 let Ok(username_s) = std::env::var("USER")
355 else {
359 return;
360 };
361 let username = username_s.as_bytes().to_vec();
362
363 let other_name = format!("{}2", &username_s);
364
365 let db = mock_users();
367 add_user(&db, 413, &username_s, 413);
368 add_user(&db, 999, &other_name, 999);
369 let found = get_own_username(&db).unwrap();
372 assert_eq!(found.as_ref(), Some(&username));
373
374 let db = mock_users();
376 add_user(&db, 999, &username_s, 999);
377 add_user(&db, 413, &other_name, 413);
378 let found = get_own_username(&db).unwrap();
379 assert_eq!(found, Some(other_name.clone().into_bytes()));
380
381 let db = mock_users();
383 add_user(&db, 999413, &other_name, 999);
384 let found = get_own_username(&db).unwrap();
385 assert!(found.is_none());
386 }
387
388 #[test]
389 fn username_ignoring_env() {
390 let db = mock_users();
392 add_user(&db, 413, "aranea", 413413);
393 add_user(&db, 415, "notyouru!sername", 413413);
394 let found = get_own_username(&db).unwrap();
395 assert_eq!(found, Some(b"aranea".to_vec()));
396
397 let db = mock_users();
399 add_user(&db, 999413, "notyourn!ame", 999);
400 let found = get_own_username(&db).unwrap();
401 assert!(found.is_none());
402 }
403
404 #[test]
405 fn selfnamed() {
406 let cur_groups = cur_groups().unwrap();
408 if cur_groups.is_empty() {
409 return;
411 }
412 let not_our_gid = (1..65536)
413 .find(|n| !cur_groups.contains(n))
414 .expect("We are somehow in all groups 1..65535!");
415
416 let db = mock_users();
418 add_user(&db, 413, "aranea", 413413);
419 add_group(&db, 413413, "serket");
420 let found = get_self_named_gid_impl(&db).unwrap();
421 assert!(found.is_none());
422
423 let db = mock_users();
426 add_user(&db, 413, "aranea", 413413);
427 add_group(&db, not_our_gid, "aranea");
428 let found = get_self_named_gid_impl(&db).unwrap();
429 assert!(found.is_none());
430
431 let db = mock_users();
434 add_user(&db, 413, "aranea", 413413);
435 add_group(&db, cur_groups[0], "aranea");
436 let found = get_self_named_gid_impl(&db).unwrap();
437 assert_eq!(found, Some(cur_groups[0]));
438 }
439
440 #[test]
441 fn lookup_id() {
442 let db = mock_users();
443 add_user(&db, 413, "aranea", 413413);
444 add_group(&db, 33, "nepeta");
445
446 assert_eq!(TrustedUser::None.get_uid_impl(&db).unwrap(), None);
447 assert_eq!(TrustedUser::Current.get_uid_impl(&db).unwrap(), Some(413));
448 assert_eq!(TrustedUser::Id(413).get_uid_impl(&db).unwrap(), Some(413));
449 assert_eq!(
450 TrustedUser::Name("aranea".into())
451 .get_uid_impl(&db)
452 .unwrap(),
453 Some(413)
454 );
455 assert!(TrustedUser::Name("ac".into()).get_uid_impl(&db).is_err());
456
457 assert_eq!(TrustedGroup::None.get_gid_impl(&db).unwrap(), None);
458 assert_eq!(TrustedGroup::Id(33).get_gid_impl(&db).unwrap(), Some(33));
459 assert_eq!(
460 TrustedGroup::Name("nepeta".into())
461 .get_gid_impl(&db)
462 .unwrap(),
463 Some(33)
464 );
465 assert!(TrustedGroup::Name("ac".into()).get_gid_impl(&db).is_err());
466 }
467}