Skip to content

Commit d1893c9

Browse files
authored
Merge pull request #1137 from cgwalters/sysusers-fix
sysusers: Handle named group references
2 parents 00860d7 + 2b32297 commit d1893c9

File tree

1 file changed

+85
-12
lines changed

1 file changed

+85
-12
lines changed

sysusers/src/lib.rs

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ mod nameservice;
66

77
use std::collections::{BTreeMap, BTreeSet};
88
use std::io::{BufRead, BufReader};
9+
use std::num::ParseIntError;
910
use std::path::PathBuf;
11+
use std::str::FromStr;
1012

1113
use camino::Utf8Path;
1214
use cap_std_ext::dirext::{CapStdExtDirExt, CapStdExtDirExtUtf8};
@@ -36,6 +38,34 @@ pub enum Error {
3638
/// The type of Result.
3739
pub type Result<T> = std::result::Result<T, Error>;
3840

41+
/// In sysusers, a user can refer to a group via name or number
42+
#[derive(Debug, PartialEq, Eq)]
43+
pub enum GroupReference {
44+
/// A numeric reference
45+
Numeric(u32),
46+
/// A named reference
47+
Name(String),
48+
}
49+
50+
impl From<u32> for GroupReference {
51+
fn from(value: u32) -> Self {
52+
Self::Numeric(value)
53+
}
54+
}
55+
56+
impl FromStr for GroupReference {
57+
type Err = ParseIntError;
58+
59+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
60+
let r = if s.chars().all(|c| matches!(c, '0'..='9')) {
61+
Self::Numeric(u32::from_str(s)?)
62+
} else {
63+
Self::Name(s.to_owned())
64+
};
65+
Ok(r)
66+
}
67+
}
68+
3969
/// A parsed sysusers.d entry
4070
#[derive(Debug, PartialEq, Eq)]
4171
#[allow(missing_docs)]
@@ -44,7 +74,7 @@ pub enum SysusersEntry {
4474
User {
4575
name: String,
4676
uid: Option<u32>,
47-
pgid: Option<u32>,
77+
pgid: Option<GroupReference>,
4878
gecos: String,
4979
home: Option<String>,
5080
shell: Option<String>,
@@ -112,7 +142,11 @@ impl SysusersEntry {
112142
.or_else(|| id.map(|id| (id, id)))
113143
.map(|(uid, gid)| (Some(uid), Some(gid)))
114144
.unwrap_or((None, None));
115-
let uid = uid.map(|id| id.parse()).transpose().map_err(|_| err())?;
145+
let uid = uid
146+
.filter(|&v| v != "-")
147+
.map(|id| id.parse())
148+
.transpose()
149+
.map_err(|_| err())?;
116150
let pgid = pgid.map(|id| id.parse()).transpose().map_err(|_| err())?;
117151
let (gecos, s) = Self::next_token_owned(s).ok_or_else(err.clone())?;
118152
let (home, s) = Self::next_optional_token_owned(s).unwrap_or_default();
@@ -181,9 +215,13 @@ pub fn read_sysusers(rootfs: &Dir) -> Result<Vec<SysusersEntry>> {
181215
found_users.insert(name.clone());
182216
found_groups.insert(name.clone());
183217
// Users implicitly create a group with the same name
218+
let pgid = pgid.as_ref().and_then(|g| match g {
219+
GroupReference::Numeric(n) => Some(*n),
220+
GroupReference::Name(_) => None,
221+
});
184222
result.push(SysusersEntry::Group {
185223
name: name.clone(),
186-
id: pgid.clone(),
224+
id: pgid,
187225
});
188226
result.push(e);
189227
}
@@ -222,7 +260,7 @@ pub fn analyze(rootfs: &Dir) -> Result<SysusersAnalysis> {
222260
#[allow(dead_code)]
223261
uid: Option<u32>,
224262
#[allow(dead_code)]
225-
pgid: Option<u32>,
263+
pgid: Option<GroupReference>,
226264
}
227265

228266
struct SysgroupData {
@@ -351,18 +389,27 @@ mod tests {
351389
g nobody 65534
352390
"##};
353391

392+
/// Non-default sysusers found in the wild
393+
const OTHER_SYSUSERS_REF: &str = indoc! { r#"
394+
u qemu 107:qemu "qemu user" - -
395+
u vboxadd -:1 - /var/run/vboxadd -
396+
"#};
397+
398+
fn parse_all(s: &str) -> impl Iterator<Item = SysusersEntry> + use<'_> {
399+
s.lines()
400+
.filter(|line| !(line.is_empty() || line.starts_with('#')))
401+
.map(|line| SysusersEntry::parse(line).unwrap().unwrap())
402+
}
403+
354404
#[test]
355405
fn test_sysusers_parse() -> Result<()> {
356-
let mut entries = SYSUSERS_REF
357-
.lines()
358-
.filter(|line| !(line.is_empty() || line.starts_with('#')))
359-
.map(|line| SysusersEntry::parse(line).unwrap().unwrap());
406+
let mut entries = parse_all(SYSUSERS_REF);
360407
assert_eq!(
361408
entries.next().unwrap(),
362409
SysusersEntry::User {
363410
name: "root".into(),
364411
uid: Some(0),
365-
pgid: Some(0),
412+
pgid: Some(0.into()),
366413
gecos: "Super User".into(),
367414
home: Some("/root".into()),
368415
shell: Some("/bin/bash".into())
@@ -373,7 +420,7 @@ mod tests {
373420
SysusersEntry::User {
374421
name: "root".into(),
375422
uid: Some(0),
376-
pgid: Some(0),
423+
pgid: Some(0.into()),
377424
gecos: "Super User".into(),
378425
home: Some("/root".into()),
379426
shell: None
@@ -384,7 +431,7 @@ mod tests {
384431
SysusersEntry::User {
385432
name: "bin".into(),
386433
uid: Some(1),
387-
pgid: Some(1),
434+
pgid: Some(1.into()),
388435
gecos: "bin".into(),
389436
home: Some("/bin".into()),
390437
shell: None
@@ -396,13 +443,39 @@ mod tests {
396443
SysusersEntry::User {
397444
name: "adm".into(),
398445
uid: Some(3),
399-
pgid: Some(4),
446+
pgid: Some(4.into()),
400447
gecos: "adm".into(),
401448
home: Some("/var/adm".into()),
402449
shell: None
403450
}
404451
);
405452
assert_eq!(entries.count(), 9);
453+
454+
let mut entries = parse_all(OTHER_SYSUSERS_REF);
455+
assert_eq!(
456+
entries.next().unwrap(),
457+
SysusersEntry::User {
458+
name: "qemu".into(),
459+
uid: Some(107),
460+
pgid: Some(GroupReference::Name("qemu".into())),
461+
gecos: "qemu user".into(),
462+
home: None,
463+
shell: None
464+
}
465+
);
466+
assert_eq!(
467+
entries.next().unwrap(),
468+
SysusersEntry::User {
469+
name: "vboxadd".into(),
470+
uid: None,
471+
pgid: Some(1.into()),
472+
gecos: "-".into(),
473+
home: Some("/var/run/vboxadd".into()),
474+
shell: None
475+
}
476+
);
477+
assert_eq!(entries.count(), 0);
478+
406479
Ok(())
407480
}
408481

0 commit comments

Comments
 (0)