@@ -45,6 +45,8 @@ pub enum GroupReference {
45
45
Numeric ( u32 ) ,
46
46
/// A named reference
47
47
Name ( String ) ,
48
+ /// A file path
49
+ Path ( String ) ,
48
50
}
49
51
50
52
impl From < u32 > for GroupReference {
@@ -57,7 +59,9 @@ impl FromStr for GroupReference {
57
59
type Err = ParseIntError ;
58
60
59
61
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' ) ) {
61
65
Self :: Numeric ( u32:: from_str ( s) ?)
62
66
} else {
63
67
Self :: Name ( s. to_owned ( ) )
@@ -66,21 +70,49 @@ impl FromStr for GroupReference {
66
70
}
67
71
}
68
72
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
+
69
101
/// A parsed sysusers.d entry
70
102
#[ derive( Debug , PartialEq , Eq ) ]
71
103
#[ allow( missing_docs) ]
72
104
pub enum SysusersEntry {
73
105
/// Defines a user
74
106
User {
75
107
name : String ,
76
- uid : Option < u32 > ,
108
+ uid : Option < IdSource > ,
77
109
pgid : Option < GroupReference > ,
78
110
gecos : String ,
79
111
home : Option < String > ,
80
112
shell : Option < String > ,
81
113
} ,
82
114
/// Defines a group
83
- Group { name : String , id : Option < u32 > } ,
115
+ Group { name : String , id : Option < IdSource > } ,
84
116
/// Defines a range of uids
85
117
Range { start : u32 , end : u32 } ,
86
118
}
@@ -134,9 +166,9 @@ impl SysusersEntry {
134
166
let err = || Error :: ParseFailure ( s. to_owned ( ) ) ;
135
167
let ( ftype, s) = Self :: next_token ( s) . ok_or_else ( err. clone ( ) ) ?;
136
168
let r = match ftype {
137
- "u" => {
169
+ "u" | "u!" => {
138
170
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 ( ) ;
140
172
let ( uid, pgid) = id
141
173
. and_then ( |v| v. split_once ( ':' ) )
142
174
. or_else ( || id. map ( |id| ( id, id) ) )
@@ -148,7 +180,8 @@ impl SysusersEntry {
148
180
. transpose ( )
149
181
. map_err ( |_| err ( ) ) ?;
150
182
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 ( ) ;
152
185
let ( home, s) = Self :: next_optional_token_owned ( s) . unwrap_or_default ( ) ;
153
186
let ( shell, _) = Self :: next_optional_token_owned ( s) . unwrap_or_default ( ) ;
154
187
SysusersEntry :: User {
@@ -162,7 +195,7 @@ impl SysusersEntry {
162
195
}
163
196
"g" => {
164
197
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 ( ) ;
166
199
let id = id. map ( |id| id. parse ( ) ) . transpose ( ) . map_err ( |_| err ( ) ) ?;
167
200
SysusersEntry :: Group { name, id }
168
201
}
@@ -216,7 +249,8 @@ pub fn read_sysusers(rootfs: &Dir) -> Result<Vec<SysusersEntry>> {
216
249
found_groups. insert ( name. clone ( ) ) ;
217
250
// Users implicitly create a group with the same name
218
251
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 ( ) ) ) ,
220
254
GroupReference :: Name ( _) => None ,
221
255
} ) ;
222
256
result. push ( SysusersEntry :: Group {
@@ -258,14 +292,14 @@ impl SysusersAnalysis {
258
292
pub fn analyze ( rootfs : & Dir ) -> Result < SysusersAnalysis > {
259
293
struct SysuserData {
260
294
#[ allow( dead_code) ]
261
- uid : Option < u32 > ,
295
+ uid : Option < IdSource > ,
262
296
#[ allow( dead_code) ]
263
297
pgid : Option < GroupReference > ,
264
298
}
265
299
266
300
struct SysgroupData {
267
301
#[ allow( dead_code) ]
268
- id : Option < u32 > ,
302
+ id : Option < IdSource > ,
269
303
}
270
304
271
305
let Some ( passwd) = nameservice:: passwd:: load_etc_passwd ( rootfs)
@@ -353,6 +387,8 @@ mod tests {
353
387
u games 12:100 "games" /usr/games -
354
388
u ftp 14:50 "FTP User" /var/ftp -
355
389
u nobody 65534:65534 "Kernel Overflow User" - -
390
+ # Newer systemd uses locked references
391
+ u! systemd-coredump - "systemd Core Dumper"
356
392
"## } ;
357
393
358
394
const SYSGROUPS_REF : & str = indoc:: indoc! { r##"
@@ -395,6 +431,22 @@ mod tests {
395
431
u vboxadd -:1 - /var/run/vboxadd -
396
432
"# } ;
397
433
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
+
398
450
fn parse_all ( s : & str ) -> impl Iterator < Item = SysusersEntry > + use < ' _ > {
399
451
s. lines ( )
400
452
. filter ( |line| !( line. is_empty ( ) || line. starts_with ( '#' ) ) )
@@ -408,7 +460,7 @@ mod tests {
408
460
entries. next( ) . unwrap( ) ,
409
461
SysusersEntry :: User {
410
462
name: "root" . into( ) ,
411
- uid: Some ( 0 ) ,
463
+ uid: Some ( 0 . into ( ) ) ,
412
464
pgid: Some ( 0 . into( ) ) ,
413
465
gecos: "Super User" . into( ) ,
414
466
home: Some ( "/root" . into( ) ) ,
@@ -419,7 +471,7 @@ mod tests {
419
471
entries. next( ) . unwrap( ) ,
420
472
SysusersEntry :: User {
421
473
name: "root" . into( ) ,
422
- uid: Some ( 0 ) ,
474
+ uid: Some ( 0 . into ( ) ) ,
423
475
pgid: Some ( 0 . into( ) ) ,
424
476
gecos: "Super User" . into( ) ,
425
477
home: Some ( "/root" . into( ) ) ,
@@ -430,7 +482,7 @@ mod tests {
430
482
entries. next( ) . unwrap( ) ,
431
483
SysusersEntry :: User {
432
484
name: "bin" . into( ) ,
433
- uid: Some ( 1 ) ,
485
+ uid: Some ( 1 . into ( ) ) ,
434
486
pgid: Some ( 1 . into( ) ) ,
435
487
gecos: "bin" . into( ) ,
436
488
home: Some ( "/bin" . into( ) ) ,
@@ -442,21 +494,21 @@ mod tests {
442
494
entries. next( ) . unwrap( ) ,
443
495
SysusersEntry :: User {
444
496
name: "adm" . into( ) ,
445
- uid: Some ( 3 ) ,
497
+ uid: Some ( 3 . into ( ) ) ,
446
498
pgid: Some ( 4 . into( ) ) ,
447
499
gecos: "adm" . into( ) ,
448
500
home: Some ( "/var/adm" . into( ) ) ,
449
501
shell: None
450
502
}
451
503
) ;
452
- assert_eq ! ( entries. count( ) , 9 ) ;
504
+ assert_eq ! ( entries. count( ) , 10 ) ;
453
505
454
506
let mut entries = parse_all ( OTHER_SYSUSERS_REF ) ;
455
507
assert_eq ! (
456
508
entries. next( ) . unwrap( ) ,
457
509
SysusersEntry :: User {
458
510
name: "qemu" . into( ) ,
459
- uid: Some ( 107 ) ,
511
+ uid: Some ( 107 . into ( ) ) ,
460
512
pgid: Some ( GroupReference :: Name ( "qemu" . into( ) ) ) ,
461
513
gecos: "qemu user" . into( ) ,
462
514
home: None ,
@@ -476,6 +528,68 @@ mod tests {
476
528
) ;
477
529
assert_eq ! ( entries. count( ) , 0 ) ;
478
530
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
+
479
593
Ok ( ( ) )
480
594
}
481
595
@@ -489,14 +603,14 @@ mod tests {
489
603
entries. next( ) . unwrap( ) ,
490
604
SysusersEntry :: Group {
491
605
name: "root" . into( ) ,
492
- id: Some ( 0 ) ,
606
+ id: Some ( 0 . into ( ) ) ,
493
607
}
494
608
) ;
495
609
assert_eq ! (
496
610
entries. next( ) . unwrap( ) ,
497
611
SysusersEntry :: Group {
498
612
name: "bin" . into( ) ,
499
- id: Some ( 1 ) ,
613
+ id: Some ( 1 . into ( ) ) ,
500
614
}
501
615
) ;
502
616
assert_eq ! ( entries. count( ) , 28 ) ;
0 commit comments