1
//! Code to inspect user db information on unix.
2

            
3
#[cfg(feature = "serde")]
4
mod serde_support;
5

            
6
use crate::Error;
7
use std::sync::LazyLock;
8
use std::{
9
    collections::HashMap,
10
    ffi::{OsStr, OsString},
11
    io,
12
    sync::Mutex,
13
};
14

            
15
use pwd_grp::{PwdGrp, PwdGrpProvider};
16

            
17
/// uids and gids, convenient type alias
18
type Id = u32;
19

            
20
/// Cache for the trusted uid/gid answers
21
#[derive(Default, Debug)]
22
struct TrustedUsersCache<U: PwdGrpProvider> {
23
    /// The passwd/group provider (possibly mocked)
24
    pwd_grp: U,
25
    /// Cached trusted uid determination
26
    trusted_uid: HashMap<TrustedUser, Option<Id>>,
27
    /// Cached trusted gid determination
28
    trusted_gid: HashMap<TrustedGroup, Option<Id>>,
29
}
30

            
31
/// Cached trusted id determinations
32
///
33
/// Caching here saves time - including passwd/group lookups, which can be slow enough
34
/// we don't want to do them often.
35
///
36
/// It isn't 100% correct since we don't track changes to the passwd/group databases.
37
/// That might not be OK everywhere, but it is OK in this application.
38
static CACHE: LazyLock<Mutex<TrustedUsersCache<PwdGrp>>> =
39
4893
    LazyLock::new(|| Mutex::new(TrustedUsersCache::default()));
40

            
41
/// Convert an [`io::Error `] representing a user/group handling failure into an [`Error`]
42
fn handle_pwd_error(e: io::Error) -> Error {
43
    Error::PasswdGroupIoError(e.into())
44
}
45

            
46
/// Obtain the gid of a group named after the current user
47
4893
fn get_self_named_gid_impl<U: PwdGrpProvider>(userdb: &U) -> io::Result<Option<u32>> {
48
4893
    let Some(username) = get_own_username(userdb)? else {
49
        return Ok(None);
50
    };
51

            
52
4893
    let Some(group) = userdb.getgrnam::<Vec<u8>>(username)? else {
53
        return Ok(None);
54
    };
55

            
56
    // TODO: Perhaps we should enforce a requirement that the group contains
57
    // _only_ the current users.  That's kinda tricky to do, though, without
58
    // walking the entire user db.
59

            
60
4893
    Ok(if cur_groups()?.contains(&group.gid) {
61
        Some(group.gid)
62
    } else {
63
4893
        None
64
    })
65
4893
}
66

            
67
/// Find our username, if possible.
68
///
69
/// By default, we look for the USER environment variable, and see whether we an
70
/// find a user db entry for that username with a UID that matches our own.
71
///
72
/// Failing that, we look for a user entry for our current UID.
73
4899
fn get_own_username<U: PwdGrpProvider>(userdb: &U) -> io::Result<Option<Vec<u8>>> {
74
    use std::os::unix::ffi::OsStringExt as _;
75

            
76
4899
    let my_uid = userdb.getuid();
77

            
78
4899
    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
4899
    }
86

            
87
4899
    if let Some(passwd) = userdb.getpwuid(my_uid)? {
88
        // This check should always pass, but let's be extra careful.
89
4897
        if passwd.uid == my_uid {
90
4897
            return Ok(Some(passwd.name));
91
        }
92
2
    }
93

            
94
2
    Ok(None)
95
4899
}
96

            
97
/// Return a vector of the group ID values for every group to which we belong.
98
4897
fn cur_groups() -> io::Result<Vec<u32>> {
99
4897
    PwdGrp.getgroups()
100
4897
}
101

            
102
/// A user that we can be configured to trust.
103
///
104
/// # Serde support
105
///
106
/// If this crate is build with the `serde1` feature enabled, you can serialize
107
/// and deserialize this type from any of the following:
108
///
109
///  * `false` and the string `":none"` correspond to `TrustedUser::None`.
110
///  * The string `":current"` and the map `{ special = ":current" }` correspond
111
///    to `TrustedUser::Current`.
112
///  * A numeric value (e.g., `413`) and the map `{ id = 413 }` correspond to
113
///    `TrustedUser::Id(413)`.
114
///  * A string not starting with `:` (e.g., "jane") and the map `{ name = "jane" }`
115
///    correspond to `TrustedUser::Name("jane".into())`.
116
///
117
/// ## Limitations
118
///
119
/// Non-UTF8 usernames cannot currently be represented in all serde formats.
120
/// Notably, toml doesn't support them.
121
#[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]
128
pub enum TrustedUser {
129
    /// We won't treat any user as trusted.
130
    None,
131
    /// Treat the current user as trusted.
132
    #[default]
133
    Current,
134
    /// Treat the user with a particular UID as trusted.
135
    Id(u32),
136
    /// Treat a user with a particular name as trusted.
137
    ///
138
    /// If there is no such user, we'll report an error.
139
    //
140
    // TODO change type of TrustedUser::Name.0 to Vec<u8> ? (also TrustedGroup)
141
    // This is a Unix-only module.  Arguably we shouldn't be using the OsString
142
    // type which is super-inconvenient and only really exists because on Windows
143
    // the environment, arguments, and filenames, are WTF-16.
144
    Name(OsString),
145
}
146

            
147
impl From<u32> for TrustedUser {
148
    fn from(val: u32) -> Self {
149
        TrustedUser::Id(val)
150
    }
151
}
152
impl From<OsString> for TrustedUser {
153
    fn from(val: OsString) -> Self {
154
        TrustedUser::Name(val)
155
    }
156
}
157
impl From<&OsStr> for TrustedUser {
158
    fn from(val: &OsStr) -> Self {
159
        val.to_owned().into()
160
    }
161
}
162
impl From<String> for TrustedUser {
163
    fn from(val: String) -> Self {
164
        OsString::from(val).into()
165
    }
166
}
167
impl From<&str> for TrustedUser {
168
    fn from(val: &str) -> Self {
169
        val.to_owned().into()
170
    }
171
}
172

            
173
impl TrustedUser {
174
    /// Try to convert this `User` into an optional UID.
175
31580
    pub(crate) fn get_uid(&self) -> Result<Option<u32>, Error> {
176
31580
        let mut cache = CACHE.lock().expect("poisoned lock");
177
31580
        if let Some(got) = cache.trusted_uid.get(self) {
178
26687
            return Ok(*got);
179
4893
        }
180
4893
        let calculated = self.get_uid_impl(&cache.pwd_grp)?;
181
4893
        cache.trusted_uid.insert(self.clone(), calculated);
182
4893
        Ok(calculated)
183
31580
    }
184
    /// As `get_uid`, but take a userdb.
185
4903
    fn get_uid_impl<U: PwdGrpProvider>(&self, userdb: &U) -> Result<Option<u32>, Error> {
186
        use std::os::unix::ffi::OsStrExt as _;
187

            
188
4903
        match self {
189
2
            TrustedUser::None => Ok(None),
190
4895
            TrustedUser::Current => Ok(Some(userdb.getuid())),
191
2
            TrustedUser::Id(id) => Ok(Some(*id)),
192
4
            TrustedUser::Name(name) => userdb
193
4
                .getpwnam(name.as_bytes())
194
4
                .map_err(handle_pwd_error)?
195
4
                .map(|u: pwd_grp::Passwd<Vec<u8>>| Some(u.uid))
196
4
                .ok_or_else(|| Error::NoSuchUser(name.to_string_lossy().into_owned())),
197
        }
198
4903
    }
199
}
200

            
201
/// A group that we can be configured to trust.
202
///
203
/// # Serde support
204
///
205
/// See the `serde support` section in [`TrustedUser`].  Additionally,
206
/// you can represent `TrustedGroup::SelfNamed` with the string `":username"`
207
/// or the map `{ special = ":username" }`.
208
#[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]
215
pub enum TrustedGroup {
216
    /// We won't treat any group as trusted
217
    None,
218
    /// We'll treat any group with same name as the current user as trusted.
219
    ///
220
    /// If there is no such group, we trust no group.
221
    ///
222
    /// (This is the default.)
223
    #[default]
224
    SelfNamed,
225
    /// We'll treat a specific group ID as trusted.
226
    Id(u32),
227
    /// We'll treat a group with a specific name as trusted.
228
    ///
229
    /// If there is no such group, we'll report an error.
230
    Name(OsString),
231
}
232

            
233
impl From<u32> for TrustedGroup {
234
4
    fn from(val: u32) -> Self {
235
4
        TrustedGroup::Id(val)
236
4
    }
237
}
238
impl From<OsString> for TrustedGroup {
239
    fn from(val: OsString) -> TrustedGroup {
240
        TrustedGroup::Name(val)
241
    }
242
}
243
impl From<&OsStr> for TrustedGroup {
244
    fn from(val: &OsStr) -> TrustedGroup {
245
        val.to_owned().into()
246
    }
247
}
248
impl From<String> for TrustedGroup {
249
    fn from(val: String) -> TrustedGroup {
250
        OsString::from(val).into()
251
    }
252
}
253
impl From<&str> for TrustedGroup {
254
    fn from(val: &str) -> TrustedGroup {
255
        val.to_owned().into()
256
    }
257
}
258

            
259
impl TrustedGroup {
260
    /// Try to convert this `Group` into an optional GID.
261
31580
    pub(crate) fn get_gid(&self) -> Result<Option<u32>, Error> {
262
31580
        let mut cache = CACHE.lock().expect("poisoned lock");
263
31580
        if let Some(got) = cache.trusted_gid.get(self) {
264
26681
            return Ok(*got);
265
4899
        }
266
4899
        let calculated = self.get_gid_impl(&cache.pwd_grp)?;
267
4899
        cache.trusted_gid.insert(self.clone(), calculated);
268
4899
        Ok(calculated)
269
31580
    }
270
    /// Like `get_gid`, but take a user db as an argument.
271
4907
    fn get_gid_impl<U: PwdGrpProvider>(&self, userdb: &U) -> Result<Option<u32>, Error> {
272
        use std::os::unix::ffi::OsStrExt as _;
273

            
274
4907
        match self {
275
4
            TrustedGroup::None => Ok(None),
276
4893
            TrustedGroup::SelfNamed => get_self_named_gid_impl(userdb).map_err(handle_pwd_error),
277
6
            TrustedGroup::Id(id) => Ok(Some(*id)),
278
4
            TrustedGroup::Name(name) => userdb
279
4
                .getgrnam(name.as_bytes())
280
4
                .map_err(handle_pwd_error)?
281
4
                .map(|g: pwd_grp::Group<Vec<u8>>| Some(g.gid))
282
4
                .ok_or_else(|| Error::NoSuchGroup(name.to_string_lossy().into_owned())),
283
        }
284
4907
    }
285
}
286

            
287
#[cfg(test)]
288
mod test {
289
    // @@ begin test lint list maintained by maint/add_warning @@
290
    #![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_time_subtraction)]
299
    #![allow(clippy::useless_vec)]
300
    #![allow(clippy::needless_pass_by_value)]
301
    #![allow(clippy::string_slice)] // See arti#2571
302
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
303
    use super::*;
304
    use pwd_grp::mock::MockPwdGrpProvider;
305
    type Id = u32;
306

            
307
    fn mock_users() -> MockPwdGrpProvider {
308
        let mock = MockPwdGrpProvider::new();
309
        mock.set_uids(413.into());
310
        mock
311
    }
312
    fn add_user(mock: &MockPwdGrpProvider, uid: Id, name: &str, gid: Id) {
313
        mock.add_to_passwds([pwd_grp::Passwd::<String> {
314
            name: name.into(),
315
            uid,
316
            gid,
317
            ..pwd_grp::Passwd::blank()
318
        }]);
319
    }
320
    fn add_group(mock: &MockPwdGrpProvider, gid: Id, name: &str) {
321
        mock.add_to_groups([pwd_grp::Group::<String> {
322
            name: name.into(),
323
            gid,
324
            ..pwd_grp::Group::blank()
325
        }]);
326
    }
327

            
328
    #[test]
329
    fn groups() {
330
        let groups = cur_groups().unwrap();
331
        let cur_gid = pwd_grp::getgid();
332
        if groups.is_empty() {
333
            // Some container/VM setups forget to put the (root) user into any
334
            // groups at all.
335
            return;
336
        }
337
        assert!(groups.contains(&cur_gid));
338
    }
339

            
340
    #[test]
341
    fn username_real() {
342
        // Here we'll do tests with our real username.  THere's not much we can
343
        // actually test there, but we'll try anyway.
344
        let cache = CACHE.lock().expect("poisoned lock");
345
        let uname = get_own_username(&cache.pwd_grp)
346
            .unwrap()
347
            .expect("Running on a misconfigured host");
348
        let user = PwdGrp.getpwnam::<Vec<u8>>(&uname).unwrap().unwrap();
349
        assert_eq!(user.name, uname);
350
        assert_eq!(user.uid, PwdGrp.getuid());
351
    }
352

            
353
    #[test]
354
    fn username_from_env() {
355
        let Ok(username_s) = std::env::var("USER")
356
        // If USER isn't set, can't test this without setting the environment,
357
        // and we don't do that in tests.
358
        // Likewise if USER is not UTF-8, we can't make mock usernames.
359
        else {
360
            return;
361
        };
362
        let username = username_s.as_bytes().to_vec();
363

            
364
        let other_name = format!("{}2", &username_s);
365

            
366
        // Case 1: Current user in environment exists, though there are some distractions.
367
        let db = mock_users();
368
        add_user(&db, 413, &username_s, 413);
369
        add_user(&db, 999, &other_name, 999);
370
        // I'd like to add another user with the same UID and a different name,
371
        // but MockUsers doesn't support that.
372
        let found = get_own_username(&db).unwrap();
373
        assert_eq!(found.as_ref(), Some(&username));
374

            
375
        // Case 2: Current user in environment exists, but has the wrong uid.
376
        let db = mock_users();
377
        add_user(&db, 999, &username_s, 999);
378
        add_user(&db, 413, &other_name, 413);
379
        let found = get_own_username(&db).unwrap();
380
        assert_eq!(found, Some(other_name.clone().into_bytes()));
381

            
382
        // Case 3: Current user in environment does not exist; no user can be found.
383
        let db = mock_users();
384
        add_user(&db, 999413, &other_name, 999);
385
        let found = get_own_username(&db).unwrap();
386
        assert!(found.is_none());
387
    }
388

            
389
    #[test]
390
    fn username_ignoring_env() {
391
        // Case 1: uid is found.
392
        let db = mock_users();
393
        add_user(&db, 413, "aranea", 413413);
394
        add_user(&db, 415, "notyouru!sername", 413413);
395
        let found = get_own_username(&db).unwrap();
396
        assert_eq!(found, Some(b"aranea".to_vec()));
397

            
398
        // Case 2: uid not found.
399
        let db = mock_users();
400
        add_user(&db, 999413, "notyourn!ame", 999);
401
        let found = get_own_username(&db).unwrap();
402
        assert!(found.is_none());
403
    }
404

            
405
    #[test]
406
    fn selfnamed() {
407
        // check the real groups we're in, since this isn't mockable.
408
        let cur_groups = cur_groups().unwrap();
409
        if cur_groups.is_empty() {
410
            // Can't actually proceed with the test unless we're in a group.
411
            return;
412
        }
413
        let not_our_gid = (1..65536)
414
            .find(|n| !cur_groups.contains(n))
415
            .expect("We are somehow in all groups 1..65535!");
416

            
417
        // Case 1: we find our username but no group with the same name.
418
        let db = mock_users();
419
        add_user(&db, 413, "aranea", 413413);
420
        add_group(&db, 413413, "serket");
421
        let found = get_self_named_gid_impl(&db).unwrap();
422
        assert!(found.is_none());
423

            
424
        // Case 2: we find our username and a group with the same name, but we
425
        // are not a member of that group.
426
        let db = mock_users();
427
        add_user(&db, 413, "aranea", 413413);
428
        add_group(&db, not_our_gid, "aranea");
429
        let found = get_self_named_gid_impl(&db).unwrap();
430
        assert!(found.is_none());
431

            
432
        // Case 3: we find our username and a group with the same name, AND we
433
        // are indeed a member of that group.
434
        let db = mock_users();
435
        add_user(&db, 413, "aranea", 413413);
436
        add_group(&db, cur_groups[0], "aranea");
437
        let found = get_self_named_gid_impl(&db).unwrap();
438
        assert_eq!(found, Some(cur_groups[0]));
439
    }
440

            
441
    #[test]
442
    fn lookup_id() {
443
        let db = mock_users();
444
        add_user(&db, 413, "aranea", 413413);
445
        add_group(&db, 33, "nepeta");
446

            
447
        assert_eq!(TrustedUser::None.get_uid_impl(&db).unwrap(), None);
448
        assert_eq!(TrustedUser::Current.get_uid_impl(&db).unwrap(), Some(413));
449
        assert_eq!(TrustedUser::Id(413).get_uid_impl(&db).unwrap(), Some(413));
450
        assert_eq!(
451
            TrustedUser::Name("aranea".into())
452
                .get_uid_impl(&db)
453
                .unwrap(),
454
            Some(413)
455
        );
456
        assert!(TrustedUser::Name("ac".into()).get_uid_impl(&db).is_err());
457

            
458
        assert_eq!(TrustedGroup::None.get_gid_impl(&db).unwrap(), None);
459
        assert_eq!(TrustedGroup::Id(33).get_gid_impl(&db).unwrap(), Some(33));
460
        assert_eq!(
461
            TrustedGroup::Name("nepeta".into())
462
                .get_gid_impl(&db)
463
                .unwrap(),
464
            Some(33)
465
        );
466
        assert!(TrustedGroup::Name("ac".into()).get_gid_impl(&db).is_err());
467
    }
468
}