@@ -4,7 +4,7 @@ use std::{
44 path:: Path ,
55} ;
66
7- use anyhow:: { Context , Result } ;
7+ use anyhow:: { Context , Result , bail } ;
88use serde:: Deserialize ;
99
1010/// # User
@@ -89,6 +89,12 @@ pub struct Group {
8989#[ cfg_attr( feature = "jsonschema" , derive( schemars:: JsonSchema ) ) ]
9090#[ serde( rename_all = "camelCase" ) ]
9191pub struct Config {
92+ /// Range for dynamically allocated normal UIDs (login.defs `UID_MIN`/`UID_MAX`).
93+ #[ serde( default ) ]
94+ pub normal_uid_range : IdRange ,
95+ /// Range for dynamically allocated normal GIDs (login.defs `GID_MIN`/`GID_MAX`).
96+ #[ serde( default ) ]
97+ pub normal_gid_range : IdRange ,
9298 /// Users to manage.
9399 #[ serde( default ) ]
94100 pub users : Vec < User > ,
@@ -97,6 +103,41 @@ pub struct Config {
97103 pub groups : Vec < Group > ,
98104}
99105
106+ /// The lowest ID considered "normal" (i.e. not a system ID).
107+ pub const NORMAL_ID_MIN : u32 = 1000 ;
108+
109+ /// Inclusive range from which normal IDs are dynamically allocated.
110+ #[ derive( Deserialize , Debug , Clone , Copy ) ]
111+ #[ cfg_attr( feature = "jsonschema" , derive( schemars:: JsonSchema ) ) ]
112+ pub struct IdRange {
113+ pub min : u32 ,
114+ pub max : u32 ,
115+ }
116+
117+ impl Default for IdRange {
118+ fn default ( ) -> Self {
119+ Self {
120+ min : NORMAL_ID_MIN ,
121+ max : 29999 ,
122+ }
123+ }
124+ }
125+
126+ impl IdRange {
127+ pub fn validate ( self ) -> Result < ( ) > {
128+ if self . min > self . max {
129+ bail ! ( "Invalid ID range: min ({}) > max ({})" , self . min, self . max) ;
130+ }
131+ if self . min < NORMAL_ID_MIN {
132+ bail ! (
133+ "Invalid ID range: min ({}) must be at least {NORMAL_ID_MIN}" ,
134+ self . min
135+ ) ;
136+ }
137+ Ok ( ( ) )
138+ }
139+ }
140+
100141/// Range of subordiate IDs to create.
101142#[ derive( Deserialize , Debug , Clone , Copy , PartialEq , Eq ) ]
102143#[ cfg_attr( feature = "jsonschema" , derive( schemars:: JsonSchema ) ) ]
@@ -111,7 +152,16 @@ impl Config {
111152 pub fn from_file ( path : impl AsRef < Path > ) -> Result < Self > {
112153 let contents = fs:: read ( & path)
113154 . with_context ( || format ! ( "Failed to read {}" , path. as_ref( ) . display( ) ) ) ?;
114- serde_json:: from_slice ( & contents) . context ( "Failed to parse config" )
155+ let config: Self = serde_json:: from_slice ( & contents) . context ( "Failed to parse config" ) ?;
156+ config
157+ . normal_uid_range
158+ . validate ( )
159+ . context ( "normalUidRange" ) ?;
160+ config
161+ . normal_gid_range
162+ . validate ( )
163+ . context ( "normalGidRange" ) ?;
164+ Ok ( config)
115165 }
116166
117167 #[ must_use]
@@ -132,6 +182,8 @@ mod tests {
132182 #[ test]
133183 fn config ( ) -> Result < ( ) > {
134184 let value = serde_json:: json!( {
185+ "normalUidRange" : { "min" : 30000 , "max" : 39999 } ,
186+ "normalGidRange" : { "min" : 40000 , "max" : 49999 } ,
135187 "users" : [
136188 {
137189 "isNormal" : true ,
@@ -170,4 +222,25 @@ mod tests {
170222 serde_json:: from_value :: < Config > ( value) ?;
171223 Ok ( ( ) )
172224 }
225+
226+ #[ test]
227+ fn validate_range ( ) {
228+ assert ! ( IdRange :: default ( ) . validate( ) . is_ok( ) ) ;
229+ assert ! (
230+ IdRange {
231+ min: 999 ,
232+ max: 2000
233+ }
234+ . validate( )
235+ . is_err( )
236+ ) ;
237+ assert ! (
238+ IdRange {
239+ min: 2000 ,
240+ max: 1999
241+ }
242+ . validate( )
243+ . is_err( )
244+ ) ;
245+ }
173246}
0 commit comments