@@ -67,6 +67,8 @@ pub use policy::{AuthenticatingUser, Authentication, Authorization, DirChange, R
6767
6868pub use self :: entry:: Entry ;
6969
70+ type MatchedCommand < ' a > = ( Option < & ' a RunAs > , ( Tag , & ' a Spec < Command > ) ) ;
71+
7072/// This function takes a file argument for a sudoers file and processes it.
7173impl Sudoers {
7274 pub fn open ( path : impl AsRef < Path > ) -> Result < ( Sudoers , Vec < Error > ) , io:: Error > {
@@ -147,12 +149,11 @@ impl Sudoers {
147149 ///
148150 /// the outer iterator are the `User_Spec`s; the inner iterator are the `Cmnd_Spec`s of
149151 /// 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 > > (
151153 & ' 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 > > > {
156157 let Self { rules, aliases, .. } = self ;
157158 let user_aliases = get_aliases ( & aliases. user , & match_user ( invoking_user) ) ;
158159 let host_aliases = get_aliases ( & aliases. host , & match_token ( hostname) ) ;
@@ -170,23 +171,14 @@ impl Sudoers {
170171 } )
171172 }
172173
173- /// returns `User_Spec`s that match `invoking_user` and `hostname` in a print-able format
174174 pub fn matching_entries < ' a , User : UnixUser + PartialEq < User > > (
175175 & ' 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 > > {
181179 let user_specs = self . matching_user_specs ( invoking_user, hostname) ;
182180
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 ) )
190182 }
191183
192184 pub ( crate ) fn solve_editor_path ( & self ) -> Option < PathBuf > {
@@ -214,51 +206,42 @@ impl Sudoers {
214206
215207fn group_cmd_specs_per_runas < ' a > (
216208 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 ;
226213 let mut collected_specs = vec ! [ ] ;
227214
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) {
230226 if !collected_specs. is_empty ( ) {
231227 entries. push ( Entry :: new (
232- runas . take ( ) . unwrap_or ( & EMPTY_RUNAS ) ,
228+ last_runas ,
233229 mem:: take ( & mut collected_specs) ,
230+ cmnd_aliases,
234231 ) ) ;
235232 }
236233
237- runas = Some ( new_runas ) ;
234+ last_runas = runas ;
238235 }
239236
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) ) ;
257238 }
258239
259240 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 ) ) ;
261242 }
243+
244+ entries. into_iter ( )
262245}
263246
264247fn read_sudoers < R : io:: Read > ( mut reader : R ) -> io:: Result < Vec < basic_parser:: Parsed < Sudo > > > {
@@ -290,10 +273,18 @@ pub(super) struct AliasTable {
290273}
291274
292275/// 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+ }
294283
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+ }
297288}
298289
299290/// 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>(
315306 let runas_user_aliases = get_aliases ( & aliases. runas , & match_user ( request. user ) ) ;
316307 let runas_group_aliases = get_aliases ( & aliases. runas , & match_group_alias ( request. group ) ) ;
317308
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
320309 let matching_user_specs = sudoers. matching_user_specs ( am_user, on_host) . flatten ( ) ;
321310
322311 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
490479 }
491480}
492481
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-
497482/// Find all the aliases that a object is a member of; this requires [sanitize_alias_table] to have run first;
498483/// I.e. this function should not be "pub".
499484fn get_aliases < Predicate , T > ( table : & VecOrd < Def < T > > , pred : & Predicate ) -> FoundAliases
@@ -504,7 +489,7 @@ where
504489 let all = Qualified :: Allow ( Meta :: All ) ;
505490
506491 let mut set = HashMap :: new ( ) ;
507- for Def ( id, list) in elems ( table) {
492+ for Def ( id, list) in table. iter ( ) {
508493 if find_item ( list, & pred, & set) . is_some ( ) {
509494 set. insert ( id. clone ( ) , true ) ;
510495 } else if find_item ( once ( & all) . chain ( list) , & pred, & set) . is_none ( ) {
0 commit comments