1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
//! Code to inspect user db information on unix.

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

use crate::Error;
use once_cell::sync::Lazy;
use std::{
    ffi::{OsStr, OsString},
    sync::Mutex,
};

/// Cached values of user db entries we've looked up.
///
/// Caching here saves time, AND makes our code testable.
///
/// Though this type has interior mutability, it isn't Sync, so we need to add a mutex.
static CACHE: Lazy<Mutex<users::UsersCache>> = Lazy::new(|| Mutex::new(users::UsersCache::new()));

/// Like get_self_named_gid(), but use a provided user database.
fn get_self_named_gid_impl<U: users::Groups + users::Users>(userdb: &U) -> Option<u32> {
    let username = get_own_username(userdb)?;

    let group = userdb.get_group_by_name(username.as_os_str())?;

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

    if cur_groups().contains(&group.gid()) {
        Some(group.gid())
    } else {
        None
    }
}

/// Find our username, if possible.
///
/// By default, we look for the USER environment variable, and see whether we an
/// find a user db entry for that username with a UID that matches our own.
///
/// Failing that, we look for a user entry for our current UID.
fn get_own_username<U: users::Users>(userdb: &U) -> Option<OsString> {
    let my_uid = userdb.get_current_uid();

    if let Some(username) = std::env::var_os("USER") {
        if let Some(passwd) = userdb.get_user_by_name(username.as_os_str()) {
            if passwd.uid() == my_uid {
                return Some(username);
            }
        }
    }

    if let Some(passwd) = userdb.get_user_by_uid(my_uid) {
        // This check should always pass, but let's be extra careful.
        if passwd.uid() == my_uid {
            return Some(passwd.name().to_owned());
        }
    }

    None
}

/// Return a vector of the group ID values for every group to which we belong.
///
/// (We don't use `users::group_access_list()` here, since that function calls
/// `getgrnam_r` on every group we belong to, when in fact we don't care what
/// the groups are named.)
fn cur_groups() -> Vec<u32> {
    let n_groups = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
    if n_groups <= 0 {
        return Vec::new();
    }
    let mut buf: Vec<users::gid_t> = vec![0; n_groups as usize];
    let n_groups2 = unsafe { libc::getgroups(buf.len() as i32, buf.as_mut_ptr()) };
    if n_groups2 <= 0 {
        return Vec::new();
    }
    if n_groups2 < n_groups {
        buf.resize(n_groups2 as usize, 0);
    }
    // It's not guaranteed that our current GID is necessarily one of our
    // current groups.  So, we add it.
    let cur_gid = users::get_current_gid();
    if !buf.contains(&cur_gid) {
        buf.push(cur_gid);
    }
    buf
}

/// A user that we can be configured to trust.
///
/// # Serde support
///
/// If this crate is build with the `serde1` feature enabled, you can serialize
/// and deserialize this type from any of the following:
///
///  * `false` and the string `":none"` correspond to `TrustedUser::None`.
///  * The string `":current"` and the map `{ special = ":current" }` correspond
///    to `TrustedUser::Current`.
///  * A numeric value (e.g., `413`) and the map `{ id = 413 }` correspond to
///    `TrustedUser::Id(413)`.
///  * A string not starting with `:` (e.g., "jane") and the map `{ name = "jane" }`
///    correspond to `TrustedUser::Name("jane".into())`.
///
/// ## Limitations
///
/// Non-UTF8 usernames cannot currently be represented in all serde formats.
/// Notably, toml doesn't support them.
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
#[educe(Default)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
)]
#[non_exhaustive]
pub enum TrustedUser {
    /// We won't treat any user as trusted.
    None,
    /// Treat the current user as trusted.
    #[educe(Default)]
    Current,
    /// Treat the user with a particular UID as trusted.
    Id(u32),
    /// Treat a user with a particular name as trusted.
    ///
    /// If there is no such user, we'll report an error.
    Name(OsString),
}

impl From<u32> for TrustedUser {
    fn from(val: u32) -> Self {
        TrustedUser::Id(val)
    }
}
impl From<OsString> for TrustedUser {
    fn from(val: OsString) -> Self {
        TrustedUser::Name(val)
    }
}
impl From<&OsStr> for TrustedUser {
    fn from(val: &OsStr) -> Self {
        val.to_owned().into()
    }
}
impl From<String> for TrustedUser {
    fn from(val: String) -> Self {
        OsString::from(val).into()
    }
}
impl From<&str> for TrustedUser {
    fn from(val: &str) -> Self {
        val.to_owned().into()
    }
}

impl TrustedUser {
    /// Try to convert this `User` into an optional UID.
    pub(crate) fn get_uid(&self) -> Result<Option<u32>, Error> {
        let userdb = CACHE.lock().expect("poisoned lock");
        self.get_uid_impl(&*userdb)
    }
    /// As `get_uid`, but take a userdb.
    fn get_uid_impl<U: users::Users>(&self, userdb: &U) -> Result<Option<u32>, Error> {
        match self {
            TrustedUser::None => Ok(None),
            TrustedUser::Current => Ok(Some(userdb.get_current_uid())),
            TrustedUser::Id(id) => Ok(Some(*id)),
            TrustedUser::Name(name) => userdb
                .get_user_by_name(&name)
                .map(|u| Some(u.uid()))
                .ok_or_else(|| Error::NoSuchUser(name.to_string_lossy().into_owned())),
        }
    }
}

/// A group that we can be configured to trust.
///
/// # Serde support
///
/// See the `serde support` section in [`TrustedUser`].  Additionally,
/// you can represent `TrustedGroup::SelfNamed` with the string `":username"`
/// or the map `{ special = ":username" }`.
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
#[educe(Default)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
)]
#[non_exhaustive]
pub enum TrustedGroup {
    /// We won't treat any group as trusted
    None,
    /// We'll treat any group with same name as the current user as trusted.
    ///
    /// If there is no such group, we trust no group.
    ///
    /// (This is the default.)
    #[educe(Default)]
    SelfNamed,
    /// We'll treat a specific group ID as trusted.
    Id(u32),
    /// We'll treat a group with a specific name as trusted.
    ///
    /// If there is no such group, we'll report an error.
    Name(OsString),
}

impl From<u32> for TrustedGroup {
    fn from(val: u32) -> Self {
        TrustedGroup::Id(val)
    }
}
impl From<OsString> for TrustedGroup {
    fn from(val: OsString) -> TrustedGroup {
        TrustedGroup::Name(val)
    }
}
impl From<&OsStr> for TrustedGroup {
    fn from(val: &OsStr) -> TrustedGroup {
        val.to_owned().into()
    }
}
impl From<String> for TrustedGroup {
    fn from(val: String) -> TrustedGroup {
        OsString::from(val).into()
    }
}
impl From<&str> for TrustedGroup {
    fn from(val: &str) -> TrustedGroup {
        val.to_owned().into()
    }
}

impl TrustedGroup {
    /// Try to convert this `Group` into an optional GID.
    pub(crate) fn get_gid(&self) -> Result<Option<u32>, Error> {
        let userdb = CACHE.lock().expect("poisoned lock");
        self.get_gid_impl(&*userdb)
    }
    /// Like `get_gid`, but take a user db as an argument.
    fn get_gid_impl<U: users::Users + users::Groups>(
        &self,
        userdb: &U,
    ) -> Result<Option<u32>, Error> {
        match self {
            TrustedGroup::None => Ok(None),
            TrustedGroup::SelfNamed => Ok(get_self_named_gid_impl(userdb)),
            TrustedGroup::Id(id) => Ok(Some(*id)),
            TrustedGroup::Name(name) => userdb
                .get_group_by_name(&name)
                .map(|u| Some(u.gid()))
                .ok_or_else(|| Error::NoSuchGroup(name.to_string_lossy().into_owned())),
        }
    }
}

#[cfg(test)]
mod test {
    #![allow(clippy::unwrap_used)]
    use super::*;
    use users::mock::{Group, MockUsers, User};

    #[test]
    fn groups() {
        let groups = cur_groups();
        let cur_gid = users::get_current_gid();
        if groups.is_empty() {
            // Some container/VM setups forget to put the (root) user into any
            // groups at all.
            return;
        }
        assert!(groups.contains(&cur_gid));
    }

    #[test]
    fn username_real() {
        // Here we'll do tests with our real username.  THere's not much we can
        // actually test there, but we'll try anyway.
        let cache = CACHE.lock().expect("poisoned lock");
        let uname = get_own_username(&*cache).expect("Running on a misconfigured host");
        let user = users::get_user_by_name(uname.as_os_str()).unwrap();
        assert_eq!(user.name(), uname);
        assert_eq!(user.uid(), users::get_current_uid());
    }

    #[test]
    fn username_from_env() {
        let username = if let Some(username) = std::env::var_os("USER") {
            username
        } else {
            // Can't test this without setting the environment, and we don't do that in tests.
            return;
        };
        let username_s = if let Some(u) = username.to_str() {
            u
        } else {
            // Can't mock usernames that aren't utf8.
            return;
        };

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

        // Case 1: Current user in environment exists, though there are some distractions.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(413, username_s, 413));
        db.add_user(User::new(999, &other_name, 999));
        // I'd like to add another user with the same UID and a different name,
        // but MockUsers doesn't support that.
        let found = get_own_username(&db);
        assert_eq!(found.as_ref(), Some(&username));

        // Case 2: Current user in environment exists, but has the wrong uid.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(999, username_s, 999));
        db.add_user(User::new(413, &other_name, 413));
        let found = get_own_username(&db);
        assert_eq!(found, Some(OsString::from(other_name.clone())));

        // Case 3: Current user in environment does not exist; no user can be found.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(999413, &other_name, 999));
        let found = get_own_username(&db);
        assert!(found.is_none());
    }

    #[test]
    fn username_ignoring_env() {
        // Case 1: uid is found.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(413, "aranea", 413413));
        db.add_user(User::new(415, "notyouru!sername", 413413));
        let found = get_own_username(&db);
        assert_eq!(found, Some(OsString::from("aranea")));

        // Case 2: uid not found.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(999413, "notyourn!ame", 999));
        let found = get_own_username(&db);
        assert!(found.is_none());
    }

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

        // Case 1: we find our username but no group with the same name.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(413, "aranea", 413413));
        db.add_group(Group::new(413413, "serket"));
        let found = get_self_named_gid_impl(&db);
        assert!(found.is_none());

        // Case 2: we find our username and a group with the same name, but we
        // are not a member of that group.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(413, "aranea", 413413));
        db.add_group(Group::new(not_our_gid, "aranea"));
        let found = get_self_named_gid_impl(&db);
        assert!(found.is_none());

        // Case 3: we find our username and a group with the same name, AND we
        // are indeed a member of that group.
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(413, "aranea", 413413));
        db.add_group(Group::new(cur_groups[0], "aranea"));
        let found = get_self_named_gid_impl(&db);
        assert_eq!(found, Some(cur_groups[0]));
    }

    #[test]
    fn lookup_id() {
        let mut db = MockUsers::with_current_uid(413);
        db.add_user(User::new(413, "aranea", 413413));
        db.add_group(Group::new(33, "nepeta"));

        assert_eq!(TrustedUser::None.get_uid_impl(&db).unwrap(), None);
        assert_eq!(TrustedUser::Current.get_uid_impl(&db).unwrap(), Some(413));
        assert_eq!(TrustedUser::Id(413).get_uid_impl(&db).unwrap(), Some(413));
        assert_eq!(
            TrustedUser::Name("aranea".into())
                .get_uid_impl(&db)
                .unwrap(),
            Some(413)
        );
        assert!(TrustedUser::Name("ac".into()).get_uid_impl(&db).is_err());

        assert_eq!(TrustedGroup::None.get_gid_impl(&db).unwrap(), None);
        assert_eq!(TrustedGroup::Id(33).get_gid_impl(&db).unwrap(), Some(33));
        assert_eq!(
            TrustedGroup::Name("nepeta".into())
                .get_gid_impl(&db)
                .unwrap(),
            Some(33)
        );
        assert!(TrustedGroup::Name("ac".into()).get_gid_impl(&db).is_err());
    }
}