# Sudoers

Parses /etc/sudoers

Author: Raphael Pinson rap.nosp@m.hink@gmai.nosp@m.l.com

Summary

Sudoers | Parses /etc/sudoers
Reference | This lens tries to keep as close as possible to `man sudoers` where possible.
License | This file is licensed under the LGPL v2+, like the rest of Augeas.
Lens Usage | Sample usage of this lens in augtool
Configuration files | This lens applies to /etc/sudoers.
USEFUL PRIMITIVES |
Generic primitives |
eol |
indent |
Separators |
sep_spc |
sep_cont |
sep_cont_opt |
sep_cont_opt_build |
sep_com |
sep_eq |
sep_col |
sep_dquote |
Negation expressions |
del_negate | Delete an even number of ‘!’
negate_node | Negation of boolean values for defaults.
negate_or_value | A del_negate, followed by either a negated key, or a key/value pair
Stores |
sto_to_com_cmnd | sto_to_com_cmnd does not begin or end with a space
sto_to_com | There could be a \ in the middle of a command
sto_to_com_host |
sto_to_com_user | Escaped spaces and NIS domains and allowed
to_com_chars |
to_com_dquot |
sto_to_com_dquot |
sto_to_com_col |
sto_to_eq |
sto_to_spc |
sto_to_spc_no_dquote |
sto_integer |
Comments and empty lines |
comment | Map comments in “#comment” nodes
comment_eol | Requires a space before the #
comment_or_eol | A comment_eol or eol
empty | Map empty lines
includedir |
ALIASES |
alias_field | Generic alias field to gather all Alias definitions
alias_list | List of alias_fields, separated by commas
alias_name | Name of an alias_entry_single
alias_entry_single | Single alias_entry, named using alias_name and listing alias_list
alias_entry | Alias entry, a list of comma-separated alias_entry_single fields
user_alias | User_Alias, see alias_field
runas_alias | Run_Alias, see alias_field
host_alias | Host_Alias, see alias_field
cmnd_alias | Cmnd_Alias, see alias_field
alias | Every kind of Alias entry, see user_alias, runas_alias, host_alias and cmnd_alias
DEFAULTS |
default_type | Type definition for defaults
parameter_flag | A flag parameter for defaults
parameter_integer | An integer parameter for defaults
parameter_string | A string parameter for defaults
parameter_lists | A single list parameter for defaults
parameter | A single parameter for defaults
parameter_list | A list of comma-separated parameters for defaults
defaults | A Defaults entry
USER SPECIFICATION |
runas_spec | A runas specification for spec, using alias_list for listing users and/or groups used to run a command
tag_spec | Tag specification for spec
cmnd_spec | Command specification for spec, with optional runas_spec and any amount of tag_specs
cmnd_spec_list | A list of comma-separated cmnd_specs
spec_list | Group of hosts with cmnd_spec_list
spec | A user specification, listing colon-separated spec_lists
LENS & FILTER |
lns | The sudoers lens, any amount of
filter |

### Reference

This lens tries to keep as close as possible to `man sudoers` where possible.

For example, recursive definitions such as

>     Cmnd_Spec_List ::= Cmnd_Spec |
>                        Cmnd_Spec ',' Cmnd_Spec_List

are replaced by

>     let cmnd_spec_list = cmnd_spec . ( sep_com . cmnd_spec )*

since Augeas cannot deal with recursive definitions. The definitions from `man sudoers` are put as commentaries for reference throughout the file. More information can be found in the manual.

### License

This file is licensed under the LGPL v2+, like the rest of Augeas.

### Lens Usage

Sample usage of this lens in augtool

- Set first Defaults to apply to the “LOCALNET” network alias

>     set /files/etc/sudoers/Defaults[1]/type "@LOCALNET"

- List all user specifications applying explicitly to the “admin” Unix group

>     match /files/etc/sudoers/spec/user "%admin"

- Remove the full 3rd user specification

>     rm /files/etc/sudoers/spec[3]

### Configuration files

This lens applies to /etc/sudoers. See filter.

### USEFUL PRIMITIVES

### Generic primitives

### eol

> let eol = Util.eol

### indent

> let indent = Util.indent

### Separators

### sep_spc

> let sep_spc = Sep.space

### sep_cont

> let sep_cont = Sep.cl_or_space

### sep_cont_opt

> let sep_cont_opt = Sep.cl_or_opt_space

### sep_cont_opt_build

> let sep_cont_opt_build (sep:string) = del (Rx.cl_or_opt_space . sep . Rx.cl_or_opt_space) (" " . sep . " ")

### sep_com

> let sep_com = sep_cont_opt_build ","

### sep_eq

> let sep_eq = sep_cont_opt_build "="

### sep_col

> let sep_col = sep_cont_opt_build ":"

### sep_dquote

> let sep_dquote = Util.del_str "\""

### Negation expressions

### del_negate

> let del_negate = del /(!!)*/ ""

Delete an even number of ‘!’ signs

### negate_node

> let negate_node = [ del "!" "!" . label "negate" ]

Negation of boolean values for defaults. Accept one optional ‘!’ and produce a ‘negate’ node if there is one.

### negate_or_value

> let negate_or_value (key:lens) (value:lens) = [ del_negate . (negate_node . key | key . value) ]

A del_negate, followed by either a negated key, or a key/value pair

### Stores

### sto_to_com_cmnd

> let sto_to_com_cmnd = del_negate . negate_node? . ( let alias = Rx.word - /(NO)?(PASSWD|EXEC|SETENV)/ in let non_alias = /[\/a-z]([^,:#()\n\\]|\\\\[=:,\\])*[^,=:#() \t\n\\]|[^,=:#() \t\n\\]/ in store (alias | non_alias))

sto_to_com_cmnd does not begin or end with a space

### sto_to_com

> let sto_to_com = store /([^,=:#() \t\n\\][^,=:#()\n]*[^,=:#() \t\n\\])|[^,=:#() \t\n\\]/

There could be a \ in the middle of a command

### sto_to_com_host

> let sto_to_com_host = store /[^,=:#() \t\n\\]+/

### sto_to_com_user

> let sto_to_com_user = let nis_re = /([A-Z]([-A-Z0-9]|(\\\\[ \t]))*+\\\\\\\\)/ in let user_re = /[%+@a-z]([-A-Za-z0-9._+]|(\\\\[ \t])|\\\\\\\\[A-Za-z0-9])*/ - /@include(dir)?/ in let alias_re = /[A-Z_]+/ in store ((nis_re? . user_re) | alias_re)

Escaped spaces and NIS domains and allowed

### to_com_chars

> let to_com_chars = /[^",=#() \t\n\\]+/ (* " relax emacs *)

### to_com_dquot

> let to_com_dquot = /"[^",=#()\n\\]+"/ (* " relax emacs *)

### sto_to_com_dquot

> let sto_to_com_dquot = store (to_com_chars|to_com_dquot)

### sto_to_com_col

> let sto_to_com_col = store to_com_chars

### sto_to_eq

> let sto_to_eq = store /[^,=:#() \t\n\\]+/

### sto_to_spc

> let sto_to_spc = store /[^", \t\n\\]+|"[^", \t\n\\]+"/

### sto_to_spc_no_dquote

> let sto_to_spc_no_dquote = store /[^",# \t\n\\]+/ (* " relax emacs *)

### sto_integer

> let sto_integer = store /-?[0-9]+/

### Comments and empty lines

### comment

> let comment = let sto_to_eol = store (/([^ \t\n].*[^ \t\n]|[^ \t\n])/ - /include(dir)?.*/) in [ label "#comment" . del /[ \t]*#[ \t]*/ "# " . sto_to_eol . eol ]

Map comments in “#comment” nodes

### comment_eol

> let comment_eol = Util.comment_generic /[ \t]+#[ \t]*/ " # "

Requires a space before the #

### comment_or_eol

> let comment_or_eol = comment_eol | (del /([ \t]+#\n|[ \t]*\n)/ "\n")

A comment_eol or eol

### empty

> let empty = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

Map empty lines

### includedir

> let includedir = [ key /(#|@)include(dir)?/ . Sep.space . store Rx.fspath . eol ]

### ALIASES

### alias_field

> let alias_field (kw:string) (sto:lens) = [ label kw . sto ]

Generic alias field to gather all Alias definitions

#### Definition

>     User_Alias ::= NAME '=' User_List
>     Runas_Alias ::= NAME '=' Runas_List
>     Host_Alias ::= NAME '=' Host_List
>     Cmnd_Alias ::= NAME '=' Cmnd_List

#### Parameters

kw:string | the label string
sto:lens | the store lens

### alias_list

> let alias_list (kw:string) (sto:lens) = Build.opt_list (alias_field kw sto) sep_com

List of alias_fields, separated by commas

### alias_name

> let alias_name = [ label "name" . store /[A-Z][A-Z0-9_]*/ ]

Name of an alias_entry_single

#### Definition

>     NAME ::= [A-Z]([A-Z][0-9]_)*

### alias_entry_single

> let alias_entry_single (field:string) (sto:lens) = [ label "alias" . alias_name . sep_eq . alias_list field sto ]

Single alias_entry, named using alias_name and listing alias_list

#### Definition

>     Alias_Type NAME = item1, item2, ...

#### Parameters

field:string | the field name, passed to alias_list
sto:lens | the store lens, passed to alias_list

### alias_entry

> let alias_entry (kw:string) (field:string) (sto:lens) = [ indent . key kw . sep_cont . alias_entry_single field sto . ( sep_col . alias_entry_single field sto )* . comment_or_eol ]

Alias entry, a list of comma-separated alias_entry_single fields

#### Definition

>     Alias_Type NAME = item1, item2, item3 : NAME = item4, item5

#### Parameters

kw:string | the alias keyword string
field:string | the field name, passed to alias_entry_single
sto:lens | the store lens, passed to alias_entry_single

### user_alias

> let user_alias = alias_entry "User_Alias" "user" sto_to_com

User_Alias, see alias_field

### runas_alias

> let runas_alias = alias_entry "Runas_Alias" "runas_user" sto_to_com

Run_Alias, see alias_field

### host_alias

> let host_alias = alias_entry "Host_Alias" "host" sto_to_com

Host_Alias, see alias_field

### cmnd_alias

> let cmnd_alias = alias_entry "Cmnd_Alias" "command" sto_to_com_cmnd

Cmnd_Alias, see alias_field

### alias

> let alias = user_alias | runas_alias | host_alias | cmnd_alias

Every kind of Alias entry, see user_alias, runas_alias, host_alias and cmnd_alias

#### Definition

>     Alias ::= 'User_Alias'  User_Alias (':' User_Alias)* |
>               'Runas_Alias' Runas_Alias (':' Runas_Alias)* |
>               'Host_Alias'  Host_Alias (':' Host_Alias)* |
>               'Cmnd_Alias'  Cmnd_Alias (':' Cmnd_Alias)*

### DEFAULTS

### default_type

> let default_type = let value = store /[@:!>][^ \t\n\\]+/ in [ label "type" . value ]

Type definition for defaults

#### Definition

>     Default_Type ::= 'Defaults' |
>                      'Defaults' '@' Host_List |
>                      'Defaults' ':' User_List |
>                      'Defaults' '!' Cmnd_List |
>                      'Defaults' '>' Runas_List

### parameter_flag

> let parameter_flag_kw = "always_set_home" | "authenticate" | "env_editor" | "env_reset" | "fqdn" | "ignore_dot" | "ignore_local_sudoers" | "insults" | "log_host" | "log_year" | "long_otp_prompt" | "mail_always" | "mail_badpass" | "mail_no_host" | "mail_no_perms" | "mail_no_user" | "noexec" | "path_info" | "passprompt_override" | "preserve_groups" | "requiretty" | "root_sudo" | "rootpw" | "runaspw" | "set_home" | "set_logname" | "setenv" | "shell_noargs" | "stay_setuid" | "targetpw" | "tty_tickets" | "visiblepw" | "closefrom_override" | "closefrom_override" | "compress_io" | "fast_glob" | "log_input" | "log_output" | "pwfeedback" | "umask_override" | "use_pty" | "match_group_by_gid" | "always_query_group_plugin"

A flag parameter for defaults

Flags are implicitly boolean and can be turned off via the ‘!’ operator. Some integer, string and list parameters may also be used in a boolean context to disable them.

### parameter_integer

> let parameter_integer_nobool_kw = "passwd_tries"

An integer parameter for defaults

### parameter_string

> let parameter_string_nobool_kw = "badpass_message" | "editor" | "mailsub" | "noexec_file" | "passprompt" | "runas_default" | "syslog_badpri" | "syslog_goodpri" | "timestampdir" | "timestampowner" | "secure_path"

A string parameter for defaults

An odd number of ‘!’ operators negate the value of the item; an even number just cancel each other out.

### parameter_lists

> let parameter_lists_kw = "env_check" | "env_delete" | "env_keep"

A single list parameter for defaults

All lists can be used in a boolean context The argument may be a double-quoted, space-separated list or a single value without double-quotes. The list can be replaced, added to, deleted from, or disabled by using the =, +=, -=, and ! operators respectively. An odd number of ‘!’ operators negate the value of the item; an even number just cancel each other out.

### parameter

> let parameter = parameter_flag | parameter_integer | parameter_string | parameter_lists

A single parameter for defaults

#### Definition

>     Parameter ::= Parameter '=' Value |
>                   Parameter '+=' Value |
>                   Parameter '-=' Value |
>                   '!'* Parameter

Parameters may be flags, integer values, strings, or lists.

### parameter_list

> let parameter_list = parameter . ( sep_com . parameter )*

A list of comma-separated parameters for defaults

#### Definition

>     Parameter_List ::= Parameter |
>                        Parameter ',' Parameter_List

### defaults

> let defaults = [ indent . key "Defaults" . default_type? . sep_cont . parameter_list . comment_or_eol ]

A Defaults entry

#### Definition

>     Default_Entry ::= Default_Type Parameter_List

### USER SPECIFICATION

### runas_spec

> let runas_spec_user = alias_list "runas_user" sto_to_com

A runas specification for spec, using alias_list for listing users and/or groups used to run a command

#### Definition

>     Runas_Spec ::= '(' Runas_List ')' |
>                    '(:' Runas_List ')' |
>                    '(' Runas_List ':' Runas_List ')'

### tag_spec

> let tag_spec = [ label "tag" . store /(NO)?(PASSWD|EXEC|SETENV)/ . sep_col ]

Tag specification for spec

#### Definition

>     Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' |
>                  'SETENV:' | 'NOSETENV:')

### cmnd_spec

> let cmnd_spec = [ label "command" . runas_spec? . tag_spec* . sto_to_com_cmnd ]

Command specification for spec, with optional runas_spec and any amount of tag_specs

#### Definition

>     Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd

### cmnd_spec_list

> let cmnd_spec_list = Build.opt_list cmnd_spec sep_com

A list of comma-separated cmnd_specs

#### Definition

>     Cmnd_Spec_List ::= Cmnd_Spec |
>                        Cmnd_Spec ',' Cmnd_Spec_List

### spec_list

> let spec_list = [ label "host_group" . alias_list "host" sto_to_com_host . sep_eq . cmnd_spec_list ]

Group of hosts with cmnd_spec_list

### spec

> let spec = [ label "spec" . indent . alias_list "user" sto_to_com_user . sep_cont . Build.opt_list spec_list sep_col . comment_or_eol ]

A user specification, listing colon-separated spec_lists

#### Definition

>     User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
>                   (':' Host_List '=' Cmnd_Spec_List)*

### LENS & FILTER

### lns

> let lns = ( empty | comment | includedir | alias | defaults | spec )*

The sudoers lens, any amount of

- empty lines
- comments
- includedirs
- aliases
- defaults
- specs

### filter

> let eol = Util.eol

> let indent = Util.indent

> let sep_spc = Sep.space

> let sep_cont = Sep.cl_or_space

> let sep_cont_opt = Sep.cl_or_opt_space

> let sep_cont_opt_build (sep:string) = del (Rx.cl_or_opt_space . sep . Rx.cl_or_opt_space) (" " . sep . " ")

> let sep_com = sep_cont_opt_build ","

> let sep_eq = sep_cont_opt_build "="

> let sep_col = sep_cont_opt_build ":"

> let sep_dquote = Util.del_str "\""

> let del_negate = del /(!!)*/ ""

Delete an even number of ‘!’

> let negate_node = [ del "!" "!" . label "negate" ]

Negation of boolean values for defaults.

> let defaults = [ indent . key "Defaults" . default_type? . sep_cont . parameter_list . comment_or_eol ]

A Defaults entry

> let negate_or_value (key:lens) (value:lens) = [ del_negate . (negate_node . key | key . value) ]

A del_negate, followed by either a negated key, or a key/value pair

> let sto_to_com_cmnd = del_negate . negate_node? . ( let alias = Rx.word - /(NO)?(PASSWD|EXEC|SETENV)/ in let non_alias = /[\/a-z]([^,:#()\n\\]|\\\\[=:,\\])*[^,=:#() \t\n\\]|[^,=:#() \t\n\\]/ in store (alias | non_alias))

sto_to_com_cmnd does not begin or end with a space

> let sto_to_com = store /([^,=:#() \t\n\\][^,=:#()\n]*[^,=:#() \t\n\\])|[^,=:#() \t\n\\]/

There could be a \ in the middle of a command

> let sto_to_com_host = store /[^,=:#() \t\n\\]+/

> let sto_to_com_user = let nis_re = /([A-Z]([-A-Z0-9]|(\\\\[ \t]))*+\\\\\\\\)/ in let user_re = /[%+@a-z]([-A-Za-z0-9._+]|(\\\\[ \t])|\\\\\\\\[A-Za-z0-9])*/ - /@include(dir)?/ in let alias_re = /[A-Z_]+/ in store ((nis_re? . user_re) | alias_re)

Escaped spaces and NIS domains and allowed

> let to_com_chars = /[^",=#() \t\n\\]+/ (* " relax emacs *)

> let to_com_dquot = /"[^",=#()\n\\]+"/ (* " relax emacs *)

> let sto_to_com_dquot = store (to_com_chars|to_com_dquot)

> let sto_to_com_col = store to_com_chars

> let sto_to_eq = store /[^,=:#() \t\n\\]+/

> let sto_to_spc = store /[^", \t\n\\]+|"[^", \t\n\\]+"/

> let sto_to_spc_no_dquote = store /[^",# \t\n\\]+/ (* " relax emacs *)

> let sto_integer = store /-?[0-9]+/

> let comment = let sto_to_eol = store (/([^ \t\n].*[^ \t\n]|[^ \t\n])/ - /include(dir)?.*/) in [ label "#comment" . del /[ \t]*#[ \t]*/ "# " . sto_to_eol . eol ]

Map comments in “#comment” nodes

> let comment_eol = Util.comment_generic /[ \t]+#[ \t]*/ " # "

Requires a space before the #

> let comment_or_eol = comment_eol | (del /([ \t]+#\n|[ \t]*\n)/ "\n")

A comment_eol or eol

> let empty = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

Map empty lines

> let includedir = [ key /(#|@)include(dir)?/ . Sep.space . store Rx.fspath . eol ]

> let alias_field (kw:string) (sto:lens) = [ label kw . sto ]

Generic alias field to gather all Alias definitions

> let alias_list (kw:string) (sto:lens) = Build.opt_list (alias_field kw sto) sep_com

List of alias_fields, separated by commas

> let alias_name = [ label "name" . store /[A-Z][A-Z0-9_]*/ ]

Name of an alias_entry_single

> let alias_entry_single (field:string) (sto:lens) = [ label "alias" . alias_name . sep_eq . alias_list field sto ]

Single alias_entry, named using alias_name and listing alias_list

> let alias_entry (kw:string) (field:string) (sto:lens) = [ indent . key kw . sep_cont . alias_entry_single field sto . ( sep_col . alias_entry_single field sto )* . comment_or_eol ]

Alias entry, a list of comma-separated alias_entry_single fields

> let user_alias = alias_entry "User_Alias" "user" sto_to_com

User_Alias, see alias_field

> let runas_alias = alias_entry "Runas_Alias" "runas_user" sto_to_com

Run_Alias, see alias_field

> let host_alias = alias_entry "Host_Alias" "host" sto_to_com

Host_Alias, see alias_field

> let cmnd_alias = alias_entry "Cmnd_Alias" "command" sto_to_com_cmnd

Cmnd_Alias, see alias_field

> let alias = user_alias | runas_alias | host_alias | cmnd_alias

Every kind of Alias entry, see user_alias, runas_alias, host_alias and cmnd_alias

> let default_type = let value = store /[@:!>][^ \t\n\\]+/ in [ label "type" . value ]

Type definition for defaults

> let parameter_flag_kw = "always_set_home" | "authenticate" | "env_editor" | "env_reset" | "fqdn" | "ignore_dot" | "ignore_local_sudoers" | "insults" | "log_host" | "log_year" | "long_otp_prompt" | "mail_always" | "mail_badpass" | "mail_no_host" | "mail_no_perms" | "mail_no_user" | "noexec" | "path_info" | "passprompt_override" | "preserve_groups" | "requiretty" | "root_sudo" | "rootpw" | "runaspw" | "set_home" | "set_logname" | "setenv" | "shell_noargs" | "stay_setuid" | "targetpw" | "tty_tickets" | "visiblepw" | "closefrom_override" | "closefrom_override" | "compress_io" | "fast_glob" | "log_input" | "log_output" | "pwfeedback" | "umask_override" | "use_pty" | "match_group_by_gid" | "always_query_group_plugin"

A flag parameter for defaults

> let parameter_integer_nobool_kw = "passwd_tries"

An integer parameter for defaults

> let parameter_string_nobool_kw = "badpass_message" | "editor" | "mailsub" | "noexec_file" | "passprompt" | "runas_default" | "syslog_badpri" | "syslog_goodpri" | "timestampdir" | "timestampowner" | "secure_path"

A string parameter for defaults

> let parameter_lists_kw = "env_check" | "env_delete" | "env_keep"

A single list parameter for defaults

> let parameter = parameter_flag | parameter_integer | parameter_string | parameter_lists

A single parameter for defaults

> let parameter_list = parameter . ( sep_com . parameter )*

A list of comma-separated parameters for defaults

> let runas_spec_user = alias_list "runas_user" sto_to_com

A runas specification for spec, using alias_list for listing users and/or groups used to run a command

> let spec = [ label "spec" . indent . alias_list "user" sto_to_com_user . sep_cont . Build.opt_list spec_list sep_col . comment_or_eol ]

A user specification, listing colon-separated spec_lists

> let tag_spec = [ label "tag" . store /(NO)?(PASSWD|EXEC|SETENV)/ . sep_col ]

Tag specification for spec

> let cmnd_spec = [ label "command" . runas_spec? . tag_spec* . sto_to_com_cmnd ]

Command specification for spec, with optional runas_spec and any amount of tag_specs

> let cmnd_spec_list = Build.opt_list cmnd_spec sep_com

A list of comma-separated cmnd_specs

> let spec_list = [ label "host_group" . alias_list "host" sto_to_com_host . sep_eq . cmnd_spec_list ]

Group of hosts with cmnd_spec_list

> let lns = ( empty | comment | includedir | alias | defaults | spec )*

The sudoers lens, any amount of
Close