Skip to content

Commit 2445e8d

Browse files
authored
Merge pull request #1332 from cgwalters/sysusers-fix
sysusers: Various fixes
2 parents 5aa6d80 + 5961d29 commit 2445e8d

File tree

1 file changed

+132
-18
lines changed

1 file changed

+132
-18
lines changed

sysusers/src/lib.rs

Lines changed: 132 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub enum GroupReference {
4545
Numeric(u32),
4646
/// A named reference
4747
Name(String),
48+
/// A file path
49+
Path(String),
4850
}
4951

5052
impl From<u32> for GroupReference {
@@ -57,7 +59,9 @@ impl FromStr for GroupReference {
5759
type Err = ParseIntError;
5860

5961
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
60-
let r = if s.chars().all(|c| matches!(c, '0'..='9')) {
62+
let r = if s.starts_with('/') {
63+
Self::Path(s.to_owned())
64+
} else if s.chars().all(|c| matches!(c, '0'..='9')) {
6165
Self::Numeric(u32::from_str(s)?)
6266
} else {
6367
Self::Name(s.to_owned())
@@ -66,21 +70,49 @@ impl FromStr for GroupReference {
6670
}
6771
}
6872

73+
/// In sysusers a uid can be defined statically or via a file path
74+
#[derive(Debug, PartialEq, Eq)]
75+
pub enum IdSource {
76+
/// A numeric uid
77+
Numeric(u32),
78+
/// The uid is defined by the owner of this path
79+
Path(String),
80+
}
81+
82+
impl FromStr for IdSource {
83+
type Err = ParseIntError;
84+
85+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
86+
let r = if s.starts_with('/') {
87+
Self::Path(s.to_owned())
88+
} else {
89+
Self::Numeric(u32::from_str(s)?)
90+
};
91+
Ok(r)
92+
}
93+
}
94+
95+
impl From<u32> for IdSource {
96+
fn from(value: u32) -> Self {
97+
Self::Numeric(value)
98+
}
99+
}
100+
69101
/// A parsed sysusers.d entry
70102
#[derive(Debug, PartialEq, Eq)]
71103
#[allow(missing_docs)]
72104
pub enum SysusersEntry {
73105
/// Defines a user
74106
User {
75107
name: String,
76-
uid: Option<u32>,
108+
uid: Option<IdSource>,
77109
pgid: Option<GroupReference>,
78110
gecos: String,
79111
home: Option<String>,
80112
shell: Option<String>,
81113
},
82114
/// Defines a group
83-
Group { name: String, id: Option<u32> },
115+
Group { name: String, id: Option<IdSource> },
84116
/// Defines a range of uids
85117
Range { start: u32, end: u32 },
86118
}
@@ -134,9 +166,9 @@ impl SysusersEntry {
134166
let err = || Error::ParseFailure(s.to_owned());
135167
let (ftype, s) = Self::next_token(s).ok_or_else(err.clone())?;
136168
let r = match ftype {
137-
"u" => {
169+
"u" | "u!" => {
138170
let (name, s) = Self::next_token_owned(s).ok_or_else(err.clone())?;
139-
let (id, s) = Self::next_optional_token(s).ok_or_else(err.clone())?;
171+
let (id, s) = Self::next_optional_token(s).unwrap_or_default();
140172
let (uid, pgid) = id
141173
.and_then(|v| v.split_once(':'))
142174
.or_else(|| id.map(|id| (id, id)))
@@ -148,7 +180,8 @@ impl SysusersEntry {
148180
.transpose()
149181
.map_err(|_| err())?;
150182
let pgid = pgid.map(|id| id.parse()).transpose().map_err(|_| err())?;
151-
let (gecos, s) = Self::next_token_owned(s).ok_or_else(err.clone())?;
183+
let (gecos, s) = Self::next_token(s).unwrap_or_default();
184+
let gecos = gecos.to_owned();
152185
let (home, s) = Self::next_optional_token_owned(s).unwrap_or_default();
153186
let (shell, _) = Self::next_optional_token_owned(s).unwrap_or_default();
154187
SysusersEntry::User {
@@ -162,7 +195,7 @@ impl SysusersEntry {
162195
}
163196
"g" => {
164197
let (name, s) = Self::next_token_owned(s).ok_or_else(err.clone())?;
165-
let (id, _) = Self::next_optional_token(s).ok_or_else(err.clone())?;
198+
let (id, _) = Self::next_optional_token(s).unwrap_or_default();
166199
let id = id.map(|id| id.parse()).transpose().map_err(|_| err())?;
167200
SysusersEntry::Group { name, id }
168201
}
@@ -216,7 +249,8 @@ pub fn read_sysusers(rootfs: &Dir) -> Result<Vec<SysusersEntry>> {
216249
found_groups.insert(name.clone());
217250
// Users implicitly create a group with the same name
218251
let pgid = pgid.as_ref().and_then(|g| match g {
219-
GroupReference::Numeric(n) => Some(*n),
252+
GroupReference::Numeric(n) => Some(IdSource::Numeric(*n)),
253+
GroupReference::Path(p) => Some(IdSource::Path(p.clone())),
220254
GroupReference::Name(_) => None,
221255
});
222256
result.push(SysusersEntry::Group {
@@ -258,14 +292,14 @@ impl SysusersAnalysis {
258292
pub fn analyze(rootfs: &Dir) -> Result<SysusersAnalysis> {
259293
struct SysuserData {
260294
#[allow(dead_code)]
261-
uid: Option<u32>,
295+
uid: Option<IdSource>,
262296
#[allow(dead_code)]
263297
pgid: Option<GroupReference>,
264298
}
265299

266300
struct SysgroupData {
267301
#[allow(dead_code)]
268-
id: Option<u32>,
302+
id: Option<IdSource>,
269303
}
270304

271305
let Some(passwd) = nameservice::passwd::load_etc_passwd(rootfs)
@@ -353,6 +387,8 @@ mod tests {
353387
u games 12:100 "games" /usr/games -
354388
u ftp 14:50 "FTP User" /var/ftp -
355389
u nobody 65534:65534 "Kernel Overflow User" - -
390+
# Newer systemd uses locked references
391+
u! systemd-coredump - "systemd Core Dumper"
356392
"##};
357393

358394
const SYSGROUPS_REF: &str = indoc::indoc! { r##"
@@ -395,6 +431,22 @@ mod tests {
395431
u vboxadd -:1 - /var/run/vboxadd -
396432
"#};
397433

434+
/// Taken from man sysusers.d
435+
const OTHER_SYSUSERS_EXAMPLES: &str = indoc! { r#"
436+
u user_name /file/owned/by/user "User Description" /home/dir /path/to/shell
437+
g group_name /file/owned/by/group
438+
# Note no GECOS field
439+
u otheruser -
440+
# And finally, no numeric specification at all
441+
u justusername
442+
g justgroupname
443+
"#};
444+
445+
const OTHER_SYSUSERS_UNHANDLED: &str = indoc! { r#"
446+
m user_name group_name
447+
r - 42-43
448+
"#};
449+
398450
fn parse_all(s: &str) -> impl Iterator<Item = SysusersEntry> + use<'_> {
399451
s.lines()
400452
.filter(|line| !(line.is_empty() || line.starts_with('#')))
@@ -408,7 +460,7 @@ mod tests {
408460
entries.next().unwrap(),
409461
SysusersEntry::User {
410462
name: "root".into(),
411-
uid: Some(0),
463+
uid: Some(0.into()),
412464
pgid: Some(0.into()),
413465
gecos: "Super User".into(),
414466
home: Some("/root".into()),
@@ -419,7 +471,7 @@ mod tests {
419471
entries.next().unwrap(),
420472
SysusersEntry::User {
421473
name: "root".into(),
422-
uid: Some(0),
474+
uid: Some(0.into()),
423475
pgid: Some(0.into()),
424476
gecos: "Super User".into(),
425477
home: Some("/root".into()),
@@ -430,7 +482,7 @@ mod tests {
430482
entries.next().unwrap(),
431483
SysusersEntry::User {
432484
name: "bin".into(),
433-
uid: Some(1),
485+
uid: Some(1.into()),
434486
pgid: Some(1.into()),
435487
gecos: "bin".into(),
436488
home: Some("/bin".into()),
@@ -442,21 +494,21 @@ mod tests {
442494
entries.next().unwrap(),
443495
SysusersEntry::User {
444496
name: "adm".into(),
445-
uid: Some(3),
497+
uid: Some(3.into()),
446498
pgid: Some(4.into()),
447499
gecos: "adm".into(),
448500
home: Some("/var/adm".into()),
449501
shell: None
450502
}
451503
);
452-
assert_eq!(entries.count(), 9);
504+
assert_eq!(entries.count(), 10);
453505

454506
let mut entries = parse_all(OTHER_SYSUSERS_REF);
455507
assert_eq!(
456508
entries.next().unwrap(),
457509
SysusersEntry::User {
458510
name: "qemu".into(),
459-
uid: Some(107),
511+
uid: Some(107.into()),
460512
pgid: Some(GroupReference::Name("qemu".into())),
461513
gecos: "qemu user".into(),
462514
home: None,
@@ -476,6 +528,68 @@ mod tests {
476528
);
477529
assert_eq!(entries.count(), 0);
478530

531+
let mut entries = parse_all(OTHER_SYSUSERS_EXAMPLES);
532+
assert_eq!(
533+
entries.next().unwrap(),
534+
SysusersEntry::User {
535+
name: "user_name".into(),
536+
uid: Some(IdSource::Path("/file/owned/by/user".into())),
537+
pgid: Some(GroupReference::Path("/file/owned/by/user".into())),
538+
gecos: "User Description".into(),
539+
home: Some("/home/dir".into()),
540+
shell: Some("/path/to/shell".into())
541+
}
542+
);
543+
assert_eq!(
544+
entries.next().unwrap(),
545+
SysusersEntry::Group {
546+
name: "group_name".into(),
547+
id: Some(IdSource::Path("/file/owned/by/group".into()))
548+
}
549+
);
550+
assert_eq!(
551+
entries.next().unwrap(),
552+
SysusersEntry::User {
553+
name: "otheruser".into(),
554+
uid: None,
555+
pgid: None,
556+
gecos: "".into(),
557+
home: None,
558+
shell: None
559+
}
560+
);
561+
assert_eq!(
562+
entries.next().unwrap(),
563+
SysusersEntry::User {
564+
name: "justusername".into(),
565+
uid: None,
566+
pgid: None,
567+
gecos: "".into(),
568+
home: None,
569+
shell: None
570+
}
571+
);
572+
assert_eq!(
573+
entries.next().unwrap(),
574+
SysusersEntry::Group {
575+
name: "justgroupname".into(),
576+
id: None
577+
}
578+
);
579+
assert_eq!(entries.count(), 0);
580+
581+
let n = OTHER_SYSUSERS_UNHANDLED
582+
.lines()
583+
.filter(|line| !(line.is_empty() || line.starts_with('#')))
584+
.try_fold(Vec::new(), |mut acc, line| {
585+
if let Some(v) = SysusersEntry::parse(line)? {
586+
acc.push(v);
587+
}
588+
anyhow::Ok(acc)
589+
})?;
590+
assert_eq!(n.len(), 1);
591+
assert_eq!(n[0], SysusersEntry::Range { start: 42, end: 43 });
592+
479593
Ok(())
480594
}
481595

@@ -489,14 +603,14 @@ mod tests {
489603
entries.next().unwrap(),
490604
SysusersEntry::Group {
491605
name: "root".into(),
492-
id: Some(0),
606+
id: Some(0.into()),
493607
}
494608
);
495609
assert_eq!(
496610
entries.next().unwrap(),
497611
SysusersEntry::Group {
498612
name: "bin".into(),
499-
id: Some(1),
613+
id: Some(1.into()),
500614
}
501615
);
502616
assert_eq!(entries.count(), 28);

0 commit comments

Comments
 (0)