5 Author: Raphael Pinson <raphink@gmail.com>
8 This lens tries to keep as close as possible to `man sudoers` where possible.
10 For example, recursive definitions such as
12 > Cmnd_Spec_List ::= Cmnd_Spec |
13 > Cmnd_Spec ',' Cmnd_Spec_List
17 > let cmnd_spec_list = cmnd_spec . ( sep_com . cmnd_spec )*
19 since Augeas cannot deal with recursive definitions.
20 The definitions from `man sudoers` are put as commentaries for reference
21 throughout the file. More information can be found in the manual.
24 This file is licensed under the LGPL v2+, like the rest of Augeas.
28 Sample usage of this lens in augtool
30 * Set first Defaults to apply to the "LOCALNET" network alias
31 > set /files/etc/sudoers/Defaults[1]/type "@LOCALNET"
32 * List all user specifications applying explicitly to the "admin" Unix group
33 > match /files/etc/sudoers/spec/user "%admin"
34 * Remove the full 3rd user specification
35 > rm /files/etc/sudoers/spec[3]
37 About: Configuration files
38 This lens applies to /etc/sudoers. See <filter>.
46 (************************************************************************
47 * Group: USEFUL PRIMITIVES
48 *************************************************************************)
50 (* Group: Generic primitives *)
54 (* Variable: indent *)
55 let indent = Util.indent
58 (* Group: Separators *)
60 (* Variable: sep_spc *)
61 let sep_spc = Sep.space
63 (* Variable: sep_cont *)
64 let sep_cont = Sep.cl_or_space
66 (* Variable: sep_cont_opt *)
67 let sep_cont_opt = Sep.cl_or_opt_space
69 (* Variable: sep_cont_opt_build *)
70 let sep_cont_opt_build (sep:string) =
71 del (Rx.cl_or_opt_space . sep . Rx.cl_or_opt_space) (" " . sep . " ")
73 (* Variable: sep_com *)
74 let sep_com = sep_cont_opt_build ","
76 (* Variable: sep_eq *)
77 let sep_eq = sep_cont_opt_build "="
79 (* Variable: sep_col *)
80 let sep_col = sep_cont_opt_build ":"
82 (* Variable: sep_dquote *)
83 let sep_dquote = Util.del_str "\""
85 (* Group: Negation expressions *)
87 (************************************************************************
89 * Delete an even number of '!' signs
90 *************************************************************************)
91 let del_negate = del /(!!)*/ ""
93 (************************************************************************
95 * Negation of boolean values for <defaults>. Accept one optional '!'
96 * and produce a 'negate' node if there is one.
97 *************************************************************************)
98 let negate_node = [ del "!" "!" . label "negate" ]
100 (************************************************************************
101 * View: negate_or_value
102 * A <del_negate>, followed by either a negated key, or a key/value pair
103 *************************************************************************)
104 let negate_or_value (key:lens) (value:lens) =
105 [ del_negate . (negate_node . key | key . value) ]
109 (* Variable: sto_to_com_cmnd
110 sto_to_com_cmnd does not begin or end with a space *)
112 let sto_to_com_cmnd = del_negate . negate_node? . (
113 let alias = Rx.word - /(NO)?(PASSWD|EXEC|SETENV)/
114 in let non_alias = /[\/a-z]([^,:#()\n\\]|\\\\[=:,\\])*[^,=:#() \t\n\\]|[^,=:#() \t\n\\]/
115 in store (alias | non_alias))
117 (* Variable: sto_to_com
119 There could be a \ in the middle of a command *)
120 let sto_to_com = store /([^,=:#() \t\n\\][^,=:#()\n]*[^,=:#() \t\n\\])|[^,=:#() \t\n\\]/
122 (* Variable: sto_to_com_host *)
123 let sto_to_com_host = store /[^,=:#() \t\n\\]+/
126 (* Variable: sto_to_com_user
127 Escaped spaces and NIS domains and allowed*)
128 let sto_to_com_user =
129 let nis_re = /([A-Z]([-A-Z0-9]|(\\\\[ \t]))*+\\\\\\\\)/
130 in let user_re = /[%+@a-z]([-A-Za-z0-9._+]|(\\\\[ \t])|\\\\\\\\[A-Za-z0-9])*/ - /@include(dir)?/
131 in let alias_re = /[A-Z_]+/
132 in store ((nis_re? . user_re) | alias_re)
134 (* Variable: to_com_chars *)
135 let to_com_chars = /[^",=#() \t\n\\]+/ (* " relax emacs *)
137 (* Variable: to_com_dquot *)
138 let to_com_dquot = /"[^",=#()\n\\]+"/ (* " relax emacs *)
140 (* Variable: sto_to_com_dquot *)
141 let sto_to_com_dquot = store (to_com_chars|to_com_dquot)
143 (* Variable: sto_to_com_col *)
144 let sto_to_com_col = store to_com_chars
146 (* Variable: sto_to_eq *)
147 let sto_to_eq = store /[^,=:#() \t\n\\]+/
149 (* Variable: sto_to_spc *)
150 let sto_to_spc = store /[^", \t\n\\]+|"[^", \t\n\\]+"/
152 (* Variable: sto_to_spc_no_dquote *)
153 let sto_to_spc_no_dquote = store /[^",# \t\n\\]+/ (* " relax emacs *)
155 (* Variable: sto_integer *)
156 let sto_integer = store /-?[0-9]+/
159 (* Group: Comments and empty lines *)
162 Map comments in "#comment" nodes *)
164 let sto_to_eol = store (/([^ \t\n].*[^ \t\n]|[^ \t\n])/ - /include(dir)?.*/) in
165 [ label "#comment" . del /[ \t]*#[ \t]*/ "# " . sto_to_eol . eol ]
168 Requires a space before the # *)
169 let comment_eol = Util.comment_generic /[ \t]+#[ \t]*/ " # "
171 (* View: comment_or_eol
172 A <comment_eol> or <eol> *)
173 let comment_or_eol = comment_eol | (del /([ \t]+#\n|[ \t]*\n)/ "\n")
177 let empty = [ del /[ \t]*#?[ \t]*\n/ "\n" ]
179 (* View: includedir *)
181 [ key /(#|@)include(dir)?/ . Sep.space . store Rx.fspath . eol ]
184 (************************************************************************
186 *************************************************************************)
188 (************************************************************************
190 * Generic alias field to gather all Alias definitions
193 * > User_Alias ::= NAME '=' User_List
194 * > Runas_Alias ::= NAME '=' Runas_List
195 * > Host_Alias ::= NAME '=' Host_List
196 * > Cmnd_Alias ::= NAME '=' Cmnd_List
199 * kw:string - the label string
200 * sto:lens - the store lens
201 *************************************************************************)
202 let alias_field (kw:string) (sto:lens) = [ label kw . sto ]
205 List of <alias_fields>, separated by commas *)
206 let alias_list (kw:string) (sto:lens) =
207 Build.opt_list (alias_field kw sto) sep_com
209 (************************************************************************
211 * Name of an <alias_entry_single>
214 * > NAME ::= [A-Z]([A-Z][0-9]_)*
215 *************************************************************************)
217 = [ label "name" . store /[A-Z][A-Z0-9_]*/ ]
219 (************************************************************************
220 * View: alias_entry_single
221 * Single <alias_entry>, named using <alias_name> and listing <alias_list>
224 * > Alias_Type NAME = item1, item2, ...
227 * field:string - the field name, passed to <alias_list>
228 * sto:lens - the store lens, passed to <alias_list>
229 *************************************************************************)
230 let alias_entry_single (field:string) (sto:lens)
231 = [ label "alias" . alias_name . sep_eq . alias_list field sto ]
233 (************************************************************************
235 * Alias entry, a list of comma-separated <alias_entry_single> fields
238 * > Alias_Type NAME = item1, item2, item3 : NAME = item4, item5
241 * kw:string - the alias keyword string
242 * field:string - the field name, passed to <alias_entry_single>
243 * sto:lens - the store lens, passed to <alias_entry_single>
244 *************************************************************************)
245 let alias_entry (kw:string) (field:string) (sto:lens)
246 = [ indent . key kw . sep_cont . alias_entry_single field sto
247 . ( sep_col . alias_entry_single field sto )* . comment_or_eol ]
249 (* TODO: go further in user definitions *)
251 User_Alias, see <alias_field> *)
252 let user_alias = alias_entry "User_Alias" "user" sto_to_com
254 Run_Alias, see <alias_field> *)
255 let runas_alias = alias_entry "Runas_Alias" "runas_user" sto_to_com
257 Host_Alias, see <alias_field> *)
258 let host_alias = alias_entry "Host_Alias" "host" sto_to_com
260 Cmnd_Alias, see <alias_field> *)
261 let cmnd_alias = alias_entry "Cmnd_Alias" "command" sto_to_com_cmnd
264 (************************************************************************
266 * Every kind of Alias entry,
267 * see <user_alias>, <runas_alias>, <host_alias> and <cmnd_alias>
270 * > Alias ::= 'User_Alias' User_Alias (':' User_Alias)* |
271 * > 'Runas_Alias' Runas_Alias (':' Runas_Alias)* |
272 * > 'Host_Alias' Host_Alias (':' Host_Alias)* |
273 * > 'Cmnd_Alias' Cmnd_Alias (':' Cmnd_Alias)*
274 *************************************************************************)
275 let alias = user_alias | runas_alias | host_alias | cmnd_alias
277 (************************************************************************
279 *************************************************************************)
282 (************************************************************************
284 * Type definition for <defaults>
287 * > Default_Type ::= 'Defaults' |
288 * > 'Defaults' '@' Host_List |
289 * > 'Defaults' ':' User_List |
290 * > 'Defaults' '!' Cmnd_List |
291 * > 'Defaults' '>' Runas_List
292 *************************************************************************)
294 let value = store /[@:!>][^ \t\n\\]+/ in
295 [ label "type" . value ]
297 (************************************************************************
298 * View: parameter_flag
299 * A flag parameter for <defaults>
301 * Flags are implicitly boolean and can be turned off via the '!' operator.
302 * Some integer, string and list parameters may also be used in a boolean
303 * context to disable them.
304 *************************************************************************)
305 let parameter_flag_kw = "always_set_home" | "authenticate" | "env_editor"
306 | "env_reset" | "fqdn" | "ignore_dot"
307 | "ignore_local_sudoers" | "insults" | "log_host"
308 | "log_year" | "long_otp_prompt" | "mail_always"
309 | "mail_badpass" | "mail_no_host" | "mail_no_perms"
310 | "mail_no_user" | "noexec" | "path_info"
311 | "passprompt_override" | "preserve_groups"
312 | "requiretty" | "root_sudo" | "rootpw" | "runaspw"
313 | "set_home" | "set_logname" | "setenv"
314 | "shell_noargs" | "stay_setuid" | "targetpw"
315 | "tty_tickets" | "visiblepw" | "closefrom_override"
316 | "closefrom_override" | "compress_io" | "fast_glob"
317 | "log_input" | "log_output" | "pwfeedback"
318 | "umask_override" | "use_pty" | "match_group_by_gid"
319 | "always_query_group_plugin"
321 let parameter_flag = [ del_negate . negate_node?
322 . key parameter_flag_kw ]
324 (************************************************************************
325 * View: parameter_integer
326 * An integer parameter for <defaults>
327 *************************************************************************)
328 let parameter_integer_nobool_kw = "passwd_tries"
330 let parameter_integer_nobool = [ key parameter_integer_nobool_kw . sep_eq
331 . del /"?/ "" . sto_integer
335 let parameter_integer_bool_kw = "loglinelen" | "passwd_timeout"
336 | "timestamp_timeout" | "umask"
338 let parameter_integer_bool =
340 (key parameter_integer_bool_kw)
341 (sep_eq . del /"?/ "" . sto_integer . del /"?/ "")
343 let parameter_integer = parameter_integer_nobool
344 | parameter_integer_bool
346 (************************************************************************
347 * View: parameter_string
348 * A string parameter for <defaults>
350 * An odd number of '!' operators negate the value of the item;
351 * an even number just cancel each other out.
352 *************************************************************************)
353 let parameter_string_nobool_kw = "badpass_message" | "editor" | "mailsub"
354 | "noexec_file" | "passprompt" | "runas_default"
355 | "syslog_badpri" | "syslog_goodpri"
356 | "timestampdir" | "timestampowner" | "secure_path"
358 let parameter_string_nobool = [ key parameter_string_nobool_kw . sep_eq
361 let parameter_string_bool_kw = "exempt_group" | "lecture" | "lecture_file"
362 | "listpw" | "logfile" | "mailerflags"
363 | "mailerpath" | "mailto" | "mailfrom"
364 | "syslog" | "verifypw"
366 let parameter_string_bool =
368 (key parameter_string_bool_kw)
369 (sep_eq . sto_to_com_dquot)
371 let parameter_string = parameter_string_nobool
372 | parameter_string_bool
374 (************************************************************************
375 * View: parameter_lists
376 * A single list parameter for <defaults>
378 * All lists can be used in a boolean context
379 * The argument may be a double-quoted, space-separated list or a single
380 * value without double-quotes.
381 * The list can be replaced, added to, deleted from, or disabled
382 * by using the =, +=, -=, and ! operators respectively.
383 * An odd number of '!' operators negate the value of the item;
384 * an even number just cancel each other out.
385 *************************************************************************)
386 let parameter_lists_kw = "env_check" | "env_delete" | "env_keep"
387 let parameter_lists_value = [ label "var" . sto_to_spc_no_dquote ]
388 let parameter_lists_value_dquote = [ label "var"
389 . del /"?/ "" . sto_to_spc_no_dquote
392 let parameter_lists_values = parameter_lists_value_dquote
393 | ( sep_dquote . parameter_lists_value
394 . ( sep_cont . parameter_lists_value )+
397 let parameter_lists_sep = sep_cont_opt
398 . ( [ del "+" "+" . label "append" ]
399 | [ del "-" "-" . label "remove" ] )?
400 . del "=" "=" . sep_cont_opt
402 let parameter_lists =
404 (key parameter_lists_kw)
405 (parameter_lists_sep . parameter_lists_values)
407 (************************************************************************
409 * A single parameter for <defaults>
412 * > Parameter ::= Parameter '=' Value |
413 * > Parameter '+=' Value |
414 * > Parameter '-=' Value |
417 * Parameters may be flags, integer values, strings, or lists.
419 *************************************************************************)
420 let parameter = parameter_flag | parameter_integer
421 | parameter_string | parameter_lists
423 (************************************************************************
424 * View: parameter_list
425 * A list of comma-separated <parameters> for <defaults>
428 * > Parameter_List ::= Parameter |
429 * > Parameter ',' Parameter_List
430 *************************************************************************)
431 let parameter_list = parameter . ( sep_com . parameter )*
433 (************************************************************************
438 * > Default_Entry ::= Default_Type Parameter_List
439 *************************************************************************)
440 let defaults = [ indent . key "Defaults" . default_type? . sep_cont
441 . parameter_list . comment_or_eol ]
445 (************************************************************************
446 * Group: USER SPECIFICATION
447 *************************************************************************)
449 (************************************************************************
451 * A runas specification for <spec>, using <alias_list> for listing
452 * users and/or groups used to run a command
455 * > Runas_Spec ::= '(' Runas_List ')' |
456 * > '(:' Runas_List ')' |
457 * > '(' Runas_List ':' Runas_List ')'
458 *************************************************************************)
459 let runas_spec_user = alias_list "runas_user" sto_to_com
460 let runas_spec_group = Util.del_str ":" . indent
461 . alias_list "runas_group" sto_to_com
463 let runas_spec_usergroup = runas_spec_user . indent . runas_spec_group
465 let runas_spec = Util.del_str "("
468 | runas_spec_usergroup )
469 . Util.del_str ")" . sep_cont_opt
471 (************************************************************************
473 * Tag specification for <spec>
476 * > Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' |
477 * > 'SETENV:' | 'NOSETENV:')
478 *************************************************************************)
480 [ label "tag" . store /(NO)?(PASSWD|EXEC|SETENV)/ . sep_col ]
482 (************************************************************************
484 * Command specification for <spec>,
485 * with optional <runas_spec> and any amount of <tag_specs>
488 * > Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd
489 *************************************************************************)
491 [ label "command" . runas_spec? . tag_spec* . sto_to_com_cmnd ]
493 (************************************************************************
494 * View: cmnd_spec_list
495 * A list of comma-separated <cmnd_specs>
498 * > Cmnd_Spec_List ::= Cmnd_Spec |
499 * > Cmnd_Spec ',' Cmnd_Spec_List
500 *************************************************************************)
501 let cmnd_spec_list = Build.opt_list cmnd_spec sep_com
504 (************************************************************************
506 * Group of hosts with <cmnd_spec_list>
507 *************************************************************************)
508 let spec_list = [ label "host_group" . alias_list "host" sto_to_com_host
509 . sep_eq . cmnd_spec_list ]
511 (************************************************************************
513 * A user specification, listing colon-separated <spec_lists>
516 * > User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
517 * > (':' Host_List '=' Cmnd_Spec_List)*
518 *************************************************************************)
519 let spec = [ label "spec" . indent
520 . alias_list "user" sto_to_com_user . sep_cont
521 . Build.opt_list spec_list sep_col
525 (************************************************************************
526 * Group: LENS & FILTER
527 *************************************************************************)
530 The sudoers lens, any amount of
538 let lns = ( empty | comment | includedir | alias | defaults | spec )*
541 let filter = (incl "/etc/sudoers")
542 . (incl "/usr/local/etc/sudoers")
543 . (incl "/etc/sudoers.d/*")
544 . (incl "/usr/local/etc/sudoers.d/*")
545 . (incl "/opt/csw/etc/sudoers")
546 . (incl "/etc/opt/csw/sudoers")
549 let xfm = transform lns filter