@@ -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
5052impl 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) ]
72104pub 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 {
258292pub 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