@@ -67,6 +67,8 @@ pub use policy::{AuthenticatingUser, Authentication, Authorization, DirChange, R
67
67
68
68
pub use self :: entry:: Entry ;
69
69
70
+ type MatchedCommand < ' a > = ( Option < & ' a RunAs > , ( Tag , & ' a Spec < Command > ) ) ;
71
+
70
72
/// This function takes a file argument for a sudoers file and processes it.
71
73
impl Sudoers {
72
74
pub fn open ( path : impl AsRef < Path > ) -> Result < ( Sudoers , Vec < Error > ) , io:: Error > {
@@ -147,12 +149,11 @@ impl Sudoers {
147
149
///
148
150
/// the outer iterator are the `User_Spec`s; the inner iterator are the `Cmnd_Spec`s of
149
151
/// said `User_Spec`s
150
- fn matching_user_specs < ' a : ' b + ' c , ' b : ' c , ' c , User : UnixUser + PartialEq < User > > (
152
+ fn matching_user_specs < ' a , User : UnixUser + PartialEq < User > > (
151
153
& ' a self ,
152
- invoking_user : & ' b User ,
153
- hostname : & ' c system:: Hostname ,
154
- ) -> impl Iterator < Item = impl Iterator < Item = ( Option < & ' a RunAs > , ( Tag , & ' a Spec < Command > ) ) > + ' b >
155
- + ' c {
154
+ invoking_user : & ' a User ,
155
+ hostname : & ' a system:: Hostname ,
156
+ ) -> impl Iterator < Item = impl Iterator < Item = MatchedCommand < ' a > > > {
156
157
let Self { rules, aliases, .. } = self ;
157
158
let user_aliases = get_aliases ( & aliases. user , & match_user ( invoking_user) ) ;
158
159
let host_aliases = get_aliases ( & aliases. host , & match_token ( hostname) ) ;
@@ -170,23 +171,14 @@ impl Sudoers {
170
171
} )
171
172
}
172
173
173
- /// returns `User_Spec`s that match `invoking_user` and `hostname` in a print-able format
174
174
pub fn matching_entries < ' a , User : UnixUser + PartialEq < User > > (
175
175
& ' a self ,
176
- invoking_user : & User ,
177
- hostname : & system:: Hostname ,
178
- ) -> Vec < Entry < ' a > > {
179
- // NOTE this method MUST NOT perform any filtering that `Self::check` does not do to
180
- // ensure `sudo $command` and `sudo --list` use the same permission checking logic
176
+ invoking_user : & ' a User ,
177
+ hostname : & ' a system:: Hostname ,
178
+ ) -> impl Iterator < Item = Entry < ' a > > {
181
179
let user_specs = self . matching_user_specs ( invoking_user, hostname) ;
182
180
183
- let cmnd_aliases = unfold_alias_table ( & self . aliases . cmnd ) ;
184
- let mut entries = vec ! [ ] ;
185
- for cmd_specs in user_specs {
186
- group_cmd_specs_per_runas ( cmd_specs, & mut entries, & cmnd_aliases) ;
187
- }
188
-
189
- entries
181
+ user_specs. flat_map ( |cmd_specs| group_cmd_specs_per_runas ( cmd_specs, & self . aliases . cmnd . 1 ) )
190
182
}
191
183
192
184
pub ( crate ) fn solve_editor_path ( & self ) -> Option < PathBuf > {
@@ -214,51 +206,42 @@ impl Sudoers {
214
206
215
207
fn group_cmd_specs_per_runas < ' a > (
216
208
cmnd_specs : impl Iterator < Item = ( Option < & ' a RunAs > , ( Tag , & ' a Spec < Command > ) ) > ,
217
- entries : & mut Vec < Entry < ' a > > ,
218
- cmnd_aliases : & HashMap < & String , & ' a Vec < Spec < Command > > > ,
219
- ) {
220
- static EMPTY_RUNAS : RunAs = RunAs {
221
- users : Vec :: new ( ) ,
222
- groups : Vec :: new ( ) ,
223
- } ;
224
-
225
- let mut runas = None ;
209
+ cmnd_aliases : & ' a [ Def < Command > ] ,
210
+ ) -> impl Iterator < Item = Entry < ' a > > {
211
+ let mut entries = vec ! [ ] ;
212
+ let mut last_runas = None ;
226
213
let mut collected_specs = vec ! [ ] ;
227
214
228
- for ( new_runas, ( tag, spec) ) in cmnd_specs {
229
- if let Some ( new_runas) = new_runas {
215
+ // `distribute_tags` will have given every spec a reference to the "runas specification"
216
+ // that applies to it. The output of sudo --list splits the CmndSpec list based on that:
217
+ // every line only has a single "runas" specifier. So we need to combine them for that.
218
+ //
219
+ // But sudo --list also outputs lines that are from different lines in the sudoers file on
220
+ // different lines in the output of sudo --list, so we cannot compare "by value". Luckily,
221
+ // once a RunAs is parsed, it will have a unique identifier in the form of its address.
222
+ let origin = |runas : Option < & RunAs > | runas. map ( |r| r as * const _ ) ;
223
+
224
+ for ( runas, ( tag, spec) ) in cmnd_specs {
225
+ if origin ( runas) != origin ( last_runas) {
230
226
if !collected_specs. is_empty ( ) {
231
227
entries. push ( Entry :: new (
232
- runas . take ( ) . unwrap_or ( & EMPTY_RUNAS ) ,
228
+ last_runas ,
233
229
mem:: take ( & mut collected_specs) ,
230
+ cmnd_aliases,
234
231
) ) ;
235
232
}
236
233
237
- runas = Some ( new_runas ) ;
234
+ last_runas = runas ;
238
235
}
239
236
240
- let ( negate, meta) = match spec {
241
- Qualified :: Allow ( meta) => ( false , meta) ,
242
- Qualified :: Forbid ( meta) => ( true , meta) ,
243
- } ;
244
-
245
- if let Meta :: Alias ( alias_name) = meta {
246
- if let Some ( specs) = cmnd_aliases. get ( alias_name) {
247
- // expand Cmnd_Alias
248
- for spec in specs. iter ( ) {
249
- let new_spec = if negate { spec. negate ( ) } else { spec. as_ref ( ) } ;
250
-
251
- collected_specs. push ( ( tag. clone ( ) , new_spec) )
252
- }
253
- }
254
- } else {
255
- collected_specs. push ( ( tag, spec. as_ref ( ) ) ) ;
256
- }
237
+ collected_specs. push ( ( tag, spec) ) ;
257
238
}
258
239
259
240
if !collected_specs. is_empty ( ) {
260
- entries. push ( Entry :: new ( runas . unwrap_or ( & EMPTY_RUNAS ) , collected_specs) ) ;
241
+ entries. push ( Entry :: new ( last_runas , collected_specs, cmnd_aliases ) ) ;
261
242
}
243
+
244
+ entries. into_iter ( )
262
245
}
263
246
264
247
fn read_sudoers < R : io:: Read > ( mut reader : R ) -> io:: Result < Vec < basic_parser:: Parsed < Sudo > > > {
@@ -290,10 +273,18 @@ pub(super) struct AliasTable {
290
273
}
291
274
292
275
/// A vector with a list defining the order in which it needs to be processed
293
- type VecOrd < T > = ( Vec < usize > , Vec < T > ) ;
276
+ struct VecOrd < T > ( Vec < usize > , Vec < T > ) ;
277
+
278
+ impl < T > Default for VecOrd < T > {
279
+ fn default ( ) -> Self {
280
+ VecOrd ( Vec :: default ( ) , Vec :: default ( ) )
281
+ }
282
+ }
294
283
295
- fn elems < T > ( vec : & VecOrd < T > ) -> impl Iterator < Item = & T > {
296
- vec. 0 . iter ( ) . map ( |& i| & vec. 1 [ i] )
284
+ impl < T > VecOrd < T > {
285
+ fn iter ( & self ) -> impl Iterator < Item = & T > {
286
+ self . 0 . iter ( ) . map ( |& i| & self . 1 [ i] )
287
+ }
297
288
}
298
289
299
290
/// Check if the user `am_user` is allowed to run `cmdline` on machine `on_host` as the requested
@@ -315,8 +306,6 @@ fn check_permission<User: UnixUser + PartialEq<User>, Group: UnixGroup>(
315
306
let runas_user_aliases = get_aliases ( & aliases. runas , & match_user ( request. user ) ) ;
316
307
let runas_group_aliases = get_aliases ( & aliases. runas , & match_group_alias ( request. group ) ) ;
317
308
318
- // NOTE to ensure `sudo $command` and `sudo --list` behave the same, both this function and
319
- // `Sudoers::matching_entries` must call this `matching_user_specs` method
320
309
let matching_user_specs = sudoers. matching_user_specs ( am_user, on_host) . flatten ( ) ;
321
310
322
311
let allowed_commands = matching_user_specs. filter_map ( |( runas, cmdspec) | {
@@ -490,10 +479,6 @@ fn match_command<'a>((cmd, args): (&'a Path, &'a [String])) -> (impl Fn(&Command
490
479
}
491
480
}
492
481
493
- fn unfold_alias_table < T > ( table : & VecOrd < Def < T > > ) -> HashMap < & String , & Vec < Qualified < Meta < T > > > > {
494
- elems ( table) . map ( |Def ( id, list) | ( id, list) ) . collect ( )
495
- }
496
-
497
482
/// Find all the aliases that a object is a member of; this requires [sanitize_alias_table] to have run first;
498
483
/// I.e. this function should not be "pub".
499
484
fn get_aliases < Predicate , T > ( table : & VecOrd < Def < T > > , pred : & Predicate ) -> FoundAliases
@@ -504,7 +489,7 @@ where
504
489
let all = Qualified :: Allow ( Meta :: All ) ;
505
490
506
491
let mut set = HashMap :: new ( ) ;
507
- for Def ( id, list) in elems ( table) {
492
+ for Def ( id, list) in table. iter ( ) {
508
493
if find_item ( list, & pred, & set) . is_some ( ) {
509
494
set. insert ( id. clone ( ) , true ) ;
510
495
} else if find_item ( once ( & all) . chain ( list) , & pred, & set) . is_none ( ) {
0 commit comments