Shellvars: use Util.eol for eol

Shellvars: use Util.eol for eol

(* Generic lens for shell-script config files like the ones found *)
(* in /etc/sysconfig                                              *)
module Shellvars =

  let eol = Util.eol

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/
  let eq = Util.del_str "="
  let comment = [ del /(#.*)?[ \t]*\n/ "# \n" ]

  let char  = /[^() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/

  (* Array values of the form '(val1 val2 val3)'. We do not handle empty *)
  (* arrays here because of typechecking headaches. Instead, they are    *)
  (* treated as a simple value                                           *)
  let array =
    let array_value = store (char+ | dquot) in
    del "(" "(" . counter "values" .
      [ seq "values" . array_value ] .
      [ del /[ \t]+/ " " . seq "values" . array_value ] *
      . del ")" ")"

  (* Treat an empty list () as a value '()'; that's not quite correct *)
  (* but fairly close.                                                *)
  let simple_value =
    let empty_array = /\([ \t]*\)/ in
   store (char* | dquot | squot | empty_array)

  let kv = [ key key_re . eq . (simple_value | array) . eol ]

  let source =
    [
      del /\.|source/ "." . label ".source" .
      Util.del_ws_spc . store /[^= \t\n]+/ . eol
    ]

  let lns = (comment | source | kv) *

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

(* Generic lens for shell-script config files like the ones found *)
(* in /etc/sysconfig                                              *)
module Shellvars =

  let eol = del /[ \t]*\n/ "\n"

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/
  let eq = Util.del_str "="
  let comment = [ del /(#.*)?[ \t]*\n/ "# \n" ]

  let char  = /[^() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/

  (* Array values of the form '(val1 val2 val3)'. We do not handle empty *)
  (* arrays here because of typechecking headaches. Instead, they are    *)
  (* treated as a simple value                                           *)
  let array =
    let array_value = store (char+ | dquot) in
    del "(" "(" . counter "values" .
      [ seq "values" . array_value ] .
      [ del /[ \t]+/ " " . seq "values" . array_value ] *
      . del ")" ")"

  (* Treat an empty list () as a value '()'; that's not quite correct *)
  (* but fairly close.                                                *)
  let simple_value =
    let empty_array = /\([ \t]*\)/ in
   store (char* | dquot | squot | empty_array)

  let kv = [ key key_re . eq . (simple_value | array) . eol ]

  let source =
    [
      del /\.|source/ "." . label ".source" .
      Util.del_ws_spc . store /[^= \t\n]+/ . eol
    ]

  let lns = (comment | source | kv) *

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


Add automount, netmasks and sendmailvars keywords for databases in nsswitch.aug

(*
Module: Nsswitch
  Parses /etc/nsswitch.conf

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
  This lens tries to keep as close as possible to `man nsswitch.conf` where possible.

About: Licence
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage

About: Configuration files
  This lens applies to /etc/nsswitch.conf. See <filter>.
*)

module Nsswitch =
autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* View: comment *)
let comment = Util.comment

(* View: empty *)
let empty = Util.empty

(* View: sep_colon
    The separator for database entries *)
let sep_colon = del /:[ \t]*/ ": "

(* View: service
    The service specification like `files', `db', or `nis' *)
let service = [ label "service" . store Rx.word ]

(* View: reaction
    The reaction on lookup result like `[NOTFOUND=return]'
    TODO: Use case-insensitive regexps when ticket #147 is fixed.
*)
let reaction =
  let status_kw = /[Ss][Uu][Cc][Cc][Ee][Ss][Ss]/
                | /[Nn][Oo][Tt][Ff][Oo][Uu][Nn][Dd]/
                | /[Uu][Nn][Aa][Vv][Aa][Ii][Ll]/
                | /[Tt][Rr][Yy][Aa][Gg][Aa][Ii][Nn]/
    in let action_kw = /[Rr][Ee][Tt][Uu][Rr][Nn]/
                     | /[Cc][Oo][Nn][Tt][Ii][Nn][Uu][Ee]/
      in let negate = [ Util.del_str "!" . label "negate" ]
        in let reaction_entry = [ label "status" . negate?
                                . store status_kw
                                . Util.del_str "="
                                . [ label "action" . store action_kw ] ]
          in Util.del_str "["
             . [ label "reaction"
               . (Build.opt_list reaction_entry Sep.space) ]
             . Util.del_str "]"

(* View: database *)
let database =
  let database_kw = "aliases"
                  | "automount"
                  | "bootparams"
                  | "ethers"
                  | "group"
                  | "hosts"
                  | "netgroup"
                  | "netmasks"
                  | "networks"
                  | "passwd"
                  | "protocols"
                  | "publickey"
                  | "rpc"
                  | "sendmailvars"
                  | "services"
                  | "shadow"
    in [ label "database" . store database_kw
               . sep_colon
               . (Build.opt_list
                    (service|reaction)
                    Sep.space)
               . Util.eol ]

(* View: lns *)
let lns = ( empty | comment | database )*

(* Variable: filter *)
let filter = (incl "/etc/nsswitch.conf")
    . Util.stdexcl

let xfm = transform lns filter

(*
Module: Nsswitch
  Parses /etc/nsswitch.conf

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
  This lens tries to keep as close as possible to `man nsswitch.conf` where possible.

About: Licence
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage

About: Configuration files
  This lens applies to /etc/nsswitch.conf. See <filter>.
*)

module Nsswitch =
autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* View: comment *)
let comment = Util.comment

(* View: empty *)
let empty = Util.empty

(* View: sep_colon
    The separator for database entries *)
let sep_colon = del /:[ \t]*/ ": "

(* View: service
    The service specification like `files', `db', or `nis' *)
let service = [ label "service" . store Rx.word ]

(* View: reaction
    The reaction on lookup result like `[NOTFOUND=return]'
    TODO: Use case-insensitive regexps when ticket #147 is fixed.
*)
let reaction =
  let status_kw = /[Ss][Uu][Cc][Cc][Ee][Ss][Ss]/
                | /[Nn][Oo][Tt][Ff][Oo][Uu][Nn][Dd]/
                | /[Uu][Nn][Aa][Vv][Aa][Ii][Ll]/
                | /[Tt][Rr][Yy][Aa][Gg][Aa][Ii][Nn]/
    in let action_kw = /[Rr][Ee][Tt][Uu][Rr][Nn]/
                     | /[Cc][Oo][Nn][Tt][Ii][Nn][Uu][Ee]/
      in let negate = [ Util.del_str "!" . label "negate" ]
        in let reaction_entry = [ label "status" . negate?
                                . store status_kw
                                . Util.del_str "="
                                . [ label "action" . store action_kw ] ]
          in Util.del_str "["
             . [ label "reaction"
               . (Build.opt_list reaction_entry Sep.space) ]
             . Util.del_str "]"

(* View: database *)
let database =
  let database_kw = "aliases"
                  | "ethers"
                  | "group"
                  | "hosts"
                  | "netgroup"
                  | "networks"
                  | "passwd"
                  | "protocols"
                  | "publickey"
                  | "rpc"
                  | "services"
                  | "shadow"
                  | "bootparams"
    in [ label "database" . store database_kw
               . sep_colon
               . (Build.opt_list
                    (service|reaction)
                    Sep.space)
               . Util.eol ]

(* View: lns *)
let lns = ( empty | comment | database )*

(* Variable: filter *)
let filter = (incl "/etc/nsswitch.conf")
    . Util.stdexcl

let xfm = transform lns filter


Debctrl (multi_line_entry): fix bad regexp

The lens 'del /^ / " "' is not valid, since we do not allow '^' as a meta
character. Changing that to 'del / / " "' exposed a typecheck problem in
lns: both multi_line_entry and eol match blank lines. Fixed this by
requiring that a multi_line_entry has to have at least one non-blank
character.

(*
Module: debctrl
  Parses ./debian/control

Author:
        Dominique Dumont domi.dumont@free.fr or dominique.dumont@hp.com

About: Reference
  http://augeas.net/page/Create_a_lens_from_bottom_to_top
  http://www.debian.org/doc/debian-policy/ch-controlfields.html

About: License
  This file is licensed under the LGPL v2+.

About: Lens Usage
  Since control file is not a system configuration file, you will have
  to use augtool -r option to point to 'debian' directory.

  Run augtool:
  $ augtool -r debian

  Sample usage of this lens in augtool:

    * Get the value stored in control file
      > print /files/control
      ...

  Saving your file:

      > save

*)

module Debctrl =
  autoload xfm

let eol = Util.eol
let del_ws_spc = del /[\t ]*/ " "
let hardeol = del /\n/ "\n"
let del_opt_ws = del /[\t ]*/ ""
let colon = del /:[ \t]*/ ": "

let simple_entry (k:regexp) =
   let value =  store /[^ \t][^\n]+/ in
   [ key k . colon . value . hardeol ]

let cont_line = del /\n[ \t]+/ "\n "
let comma     = del  /,[ \t]*/  ", "

let sep_comma_with_nl = del /[ \t\n]*,[ \t\n]*/ ",\n "
 (*= del_opt_ws . cont_line* . comma . cont_line**)

let email =  store ( /([A-Za-z]+ )+<[^\n>]+>/ |  /[^\n,\t<> ]+/ )

let multi_line_array_entry (k:regexp) (v:lens) =
    [ key k . colon . [ counter "array" . seq "array" .  v ] .
      [ seq "array" . sep_comma_with_nl . v ]* . hardeol ]

(* dependency stuff *)

let version_depends =
    [ label "version"
     . [   del / *\( */ " ( " . label "relation" . store /[<>=]+/ ]
     . [   del_ws_spc . label "number" . store /[a-zA-Z0-9_\.\-]+/
         . del / *\)/ " )" ]
    ]

let arch_depends =
    [ label "arch"
    . [  del / *\[ */ " [ " . label "prefix" . store /!?/ ]
    . [ label "name" . store /[a-zA-Z0-9_\.\-]+/ . del / *\]/ " ]" ] ]

let package_depends
  =  [ key ( /[a-zA-Z0-9_\-]+/ | /\$\{[a-zA-Z0-9:]+\}/ )
        . ( version_depends | arch_depends ) * ]

let dependency = [ label "or" . package_depends ]
               . [ label "or" . del / *\| */ " | "
                   . package_depends ] *

let dependency_list (field:regexp) =
    [ key field . colon . [ label "and" .  dependency ]
      . [ label "and" . sep_comma_with_nl . dependency ]*
      . eol ]

(* source package *)
let uploaders  =
    multi_line_array_entry /Uploaders/ email

let simple_src_keyword = "Source" | "Section" | "Priority"
    | "Standards\-Version" | "Homepage" | /Vcs\-Svn/ | /Vcs\-Browser/
    | "Maintainer"
let depend_src_keywords = /Build\-Depends/ | /Build\-Depends\-Indep/

let src_entries = (   simple_entry simple_src_keyword
                    | uploaders
                    | dependency_list depend_src_keywords ) *

(* package paragraph *)

let simple_bin_keyword = "Package" | "Architecture" |  "Section"
    | "Priority" | "Essential" | "Homepage"

let simple_bin_entry = simple_entry simple_bin_keyword

let multi_line_entry (k:string) =
     let line = /.*[^ \t\n].*/ in
      [ label k .  del / / " " .  store line . hardeol ] *

let description
  = [ key "Description" . colon
     . [ label "summary" . store /[a-zA-Z][^\n]+/ . hardeol ]
     . multi_line_entry "text" ]

(* binary package *)
let simple_bin_keywords = "Package" | "Architecture"
let depend_bin_keywords = "Depends" | "Recommends" | "Suggests"

let bin_entries = ( simple_entry simple_bin_keywords
                  | dependency_list depend_bin_keywords
                  ) + . description

(* The whole stuff *)
let lns =  [ label "srcpkg" .  src_entries  ]
        .  [ label "binpkg" . hardeol+ . bin_entries ]+
        . eol*

(* lens must be used with AUG_ROOT set to debian package source directory *)
let xfm = transform lns (incl "/control")

(*
Module: debctrl
  Parses ./debian/control

Author:
        Dominique Dumont domi.dumont@free.fr or dominique.dumont@hp.com

About: Reference
  http://augeas.net/page/Create_a_lens_from_bottom_to_top
  http://www.debian.org/doc/debian-policy/ch-controlfields.html

About: License
  This file is licensed under the LGPL v2+.

About: Lens Usage
  Since control file is not a system configuration file, you will have
  to use augtool -r option to point to 'debian' directory.

  Run augtool:
  $ augtool -r debian

  Sample usage of this lens in augtool:

    * Get the value stored in control file
      > print /files/control
      ...

  Saving your file:

      > save

*)

module Debctrl =
  autoload xfm

let eol = Util.eol
let del_ws_spc = del /[\t ]*/ " "
let hardeol = del /\n/ "\n"
let del_opt_ws = del /[\t ]*/ ""
let colon = del /:[ \t]*/ ": "

let simple_entry (k:regexp) =
   let value =  store /[^ \t][^\n]+/ in
   [ key k . colon . value . hardeol ]

let cont_line = del /\n[ \t]+/ "\n "
let comma     = del  /,[ \t]*/  ", "

let sep_comma_with_nl = del /[ \t\n]*,[ \t\n]*/ ",\n "
 (*= del_opt_ws . cont_line* . comma . cont_line**)

let email =  store ( /([A-Za-z]+ )+<[^\n>]+>/ |  /[^\n,\t<> ]+/ )

let multi_line_array_entry (k:regexp) (v:lens) =
    [ key k . colon . [ counter "array" . seq "array" .  v ] .
      [ seq "array" . sep_comma_with_nl . v ]* . hardeol ]

(* dependency stuff *)

let version_depends =
    [ label "version"
     . [   del / *\( */ " ( " . label "relation" . store /[<>=]+/ ]
     . [   del_ws_spc . label "number" . store /[a-zA-Z0-9_\.\-]+/
         . del / *\)/ " )" ]
    ]

let arch_depends =
    [ label "arch"
    . [  del / *\[ */ " [ " . label "prefix" . store /!?/ ]
    . [ label "name" . store /[a-zA-Z0-9_\.\-]+/ . del / *\]/ " ]" ] ]

let package_depends
  =  [ key ( /[a-zA-Z0-9_\-]+/ | /\$\{[a-zA-Z0-9:]+\}/ )
        . ( version_depends | arch_depends ) * ]

let dependency = [ label "or" . package_depends ]
               . [ label "or" . del / *\| */ " | "
                   . package_depends ] *

let dependency_list (field:regexp) =
    [ key field . colon . [ label "and" .  dependency ]
      . [ label "and" . sep_comma_with_nl . dependency ]*
      . eol ]

(* source package *)
let uploaders  =
    multi_line_array_entry /Uploaders/ email

let simple_src_keyword = "Source" | "Section" | "Priority"
    | "Standards\-Version" | "Homepage" | /Vcs\-Svn/ | /Vcs\-Browser/
    | "Maintainer"
let depend_src_keywords = /Build\-Depends/ | /Build\-Depends\-Indep/

let src_entries = (   simple_entry simple_src_keyword
                    | uploaders
                    | dependency_list depend_src_keywords ) *

(* package paragraph *)

let simple_bin_keyword = "Package" | "Architecture" |  "Section"
    | "Priority" | "Essential" | "Homepage"

let simple_bin_entry = simple_entry simple_bin_keyword

let multi_line_entry (k:string) =
     let line = /[^\n]+/ in
      [ label k .  del /^ / " " .  store line . hardeol ] *

let description
  = [ key "Description" . colon
     . [ label "summary" . store /[a-zA-Z][^\n]+/ . hardeol ]
     . multi_line_entry "text" ]

(* binary package *)
let simple_bin_keywords = "Package" | "Architecture"
let depend_bin_keywords = "Depends" | "Recommends" | "Suggests"

let bin_entries = ( simple_entry simple_bin_keywords
                  | dependency_list depend_bin_keywords
                  ) + . description

(* The whole stuff *)
let lns =  [ label "srcpkg" .  src_entries  ]
        .  [ label "binpkg" . hardeol+ . bin_entries ]+
        . eol*

(* lens must be used with AUG_ROOT set to debian package source directory *)
let xfm = transform lns (incl "/control")


/etc/sudoers.d is now supported (at least in Debian and Ubuntu)

(*
Module: Sudoers
  Parses /etc/sudoers

Author: Raphael Pinson <raphink@gmail.com>

About: 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.

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: 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 explicitely to the "admin" Unix group
      > match /files/etc/sudoers/spec/user "%admin"
    * Remove the full 3rd user specification
      > rm /files/etc/sudoers/spec[3]

About: Configuration files
  This lens applies to /etc/sudoers. See <filter>.
*)

module Sudoers =
  autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* Group: Generic primitives *)
(* Variable: eol *)
let eol       = del /[ \t]*\n/ "\n"

(* Variable: indent *)
let indent    = del /[ \t]*/ ""

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc  = del /[ \t]+/ " "

(* Variable: sep_cont *)
let sep_cont = del /([ \t]+|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_cont_opt *)
let sep_cont_opt = del /([ \t]*|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_com *)
let sep_com  = sep_cont_opt . Util.del_str "," . sep_cont_opt

(* Variable: sep_eq *)
let sep_eq   = sep_cont_opt . Util.del_str "=" . sep_cont_opt

(* Variable: sep_col *)
let sep_col  = sep_cont_opt . Util.del_str ":" . sep_cont_opt

(* Variable: sep_dquote *)
let sep_dquote   = Util.del_str "\""

(* Group: Stores *)

(* Variable: sto_to_com_cmnd
sto_to_com_cmnd does not begin or end with a space *)
let sto_to_com_cmnd = store /([^,=:#() \t\n\\\\]([^,=:#()\n\\\\]|\\\\[=:,\\\\])*[^,=:#() \t\n\\\\])|[^,=:#() \t\n\\\\]/

(* Variable: sto_to_com

There could be a \ in the middle of a command *)
let sto_to_com      = store /([^,=:#() \t\n\\\\][^,=:#()\n]*[^,=:#() \t\n\\\\])|[^,=:#() \t\n\\\\]/

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

(* Variable: sto_to_com_user
Escaped spaces are allowed *)
let sto_to_com_user = store ( /([^,=:#() \t\n]([^,=:#() \t\n]|(\\\\[ \t]))*[^,=:#() \t\n])|[^,=:#() \t\n]/
                              - /(User|Runas|Host|Cmnd)_Alias|Defaults.*/ )

(* Variable: sto_to_com_col *)
let sto_to_com_col      = store /[^",=#() \t\n\\\\]+/ (* " relax emacs *)

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

(* Variable: sto_to_spc *)
let sto_to_spc = store /[^", \t\n\\\\]+|"[^", \t\n\\\\]+"/

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

(* Variable: sto_integer *)
let sto_integer = store /[0-9]+/

(* Group: Comments and empty lines *)

(* View: comment
Map comments in "#comment" nodes *)
let comment =
  let sto_to_eol = store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ in
  [ label "#comment" . del /[ \t]*#[ \t]*/ "# " . sto_to_eol . eol ]

(* View: empty
Map empty lines *)
let empty   = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

(************************************************************************
 * Group:                                    ALIASES
 *************************************************************************)

(************************************************************************
 * View: alias_field
 *   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
 *************************************************************************)
let alias_field (kw:string) (sto:lens) = [ label kw . sto ]

(* View: alias_list
     List of <alias_fields>, separated by commas *)
let alias_list  (kw:string) (sto:lens) =
  alias_field kw sto . ( sep_com . alias_field kw sto )*

(************************************************************************
 * View: alias_name
 *   Name of an <alias_entry_single>
 *
 *   Definition:
 *     > NAME ::= [A-Z]([A-Z][0-9]_)*
 *************************************************************************)
let alias_name
    = [ label "name" . store /[A-Z][A-Z0-9_]*/ ]

(************************************************************************
 * View: alias_entry_single
 *   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>
 *************************************************************************)
let alias_entry_single (field:string) (sto:lens)
    = [ label "alias" . alias_name . sep_eq . alias_list field sto ]

(************************************************************************
 * View: alias_entry
 *   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>
 *************************************************************************)
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 )* . eol ]

(* TODO: go further in user definitions *)
(* View: user_alias
     User_Alias, see <alias_field> *)
let user_alias  = alias_entry "User_Alias" "user" sto_to_com
(* View: runas_alias
     Run_Alias, see <alias_field> *)
let runas_alias = alias_entry "Runas_Alias" "runas_user" sto_to_com
(* View: host_alias
     Host_Alias, see <alias_field> *)
let host_alias  = alias_entry "Host_Alias" "host" sto_to_com
(* View: cmnd_alias
     Cmnd_Alias, see <alias_field> *)
let cmnd_alias  = alias_entry "Cmnd_Alias" "command" sto_to_com_cmnd

(************************************************************************
 * View: 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)*
 *************************************************************************)
let alias = user_alias | runas_alias | host_alias | cmnd_alias
(************************************************************************
 * Group:                          DEFAULTS
 *************************************************************************)

(************************************************************************
 * View: default_type
 *   Type definition for <defaults>
 *
 *   Definition:
 *     > Default_Type ::= 'Defaults' |
 *     >                  'Defaults' '@' Host_List |
 *     >                  'Defaults' ':' User_List |
 *     >                  'Defaults' '>' Runas_List
 *************************************************************************)
let default_type     =
  let value = store /[@:>][^ \t\n\\\\]+/ in
  [ label "type" . value ]

(************************************************************************
 * View: del_negate
 *   Delete an even number of '!' signs
 *************************************************************************)
let del_negate = del /(!!)*/ ""

(************************************************************************
 * View: negate_node
 *   Negation of boolean values for <defaults>. Accept one optional '!'
 *   and produce a 'negate' node if there is one.
 *************************************************************************)
let negate_node = [ del "!" "!" . label "negate" ]

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

(************************************************************************
 * View: parameter_flag
 *   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.
 *************************************************************************)
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"

let parameter_flag       = [ del_negate . negate_node?
                               . key parameter_flag_kw ]

(************************************************************************
 * View: parameter_integer
 *   An integer parameter for <defaults>
 *************************************************************************)
let parameter_integer_nobool_kw = "passwd_tries"

let parameter_integer_nobool    = [ key parameter_integer_nobool_kw . sep_eq
                                      . del /"?/ "" . sto_integer
                                      . del /"?/ "" ]

let parameter_integer_bool_kw   = "loglinelen" | "passwd_timeout"
                                | "timestamp_timeout" | "umask"

let parameter_integer_bool      =
  negate_or_value
    (key parameter_integer_bool_kw)
    (sep_eq . del /"?/ "" . sto_integer . del /"?/ "")

let parameter_integer           = parameter_integer_nobool
                                | parameter_integer_bool

(************************************************************************
 * View: parameter_string
 *   A string parameter for <defaults>
 *
 *   An odd number of '!' operators negate the value of the item;
 *      an even number just cancel each other out.
 *************************************************************************)
let parameter_string_nobool_kw = "badpass_message" | "editor" | "mailsub"
                               | "noexec_file" | "passprompt" | "runas_default"
                               | "syslog_badpri" | "syslog_goodpri"
                               | "timestampdir" | "timestampowner" | "secure_path"

let parameter_string_nobool    = [ key parameter_string_nobool_kw . sep_eq
                                     . del /"?/ "" . sto_to_com_col
                                     . del /"?/ "" ]

let parameter_string_bool_kw   = "exempt_group" | "lecture" | "lecture_file"
                               | "listpw" | "logfile" | "mailerflags"
                               | "mailerpath" | "mailto" | "exempt_group"
                               | "syslog" | "verifypw" | "logfile"
                               | "mailerflags" | "mailerpath" | "mailto"
                               | "syslog" | "verifypw"

let parameter_string_bool      =
  negate_or_value
    (key parameter_string_bool_kw)
    (sep_eq . sto_to_com_col)

let parameter_string           = parameter_string_nobool
                               | parameter_string_bool

(************************************************************************
 * View: parameter_lists
 *   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.
 *************************************************************************)
let parameter_lists_kw           = "env_check" | "env_delete" | "env_keep"
let parameter_lists_value        = [ label "var" . sto_to_spc_no_dquote ]
let parameter_lists_value_dquote = [ label "var"
                                     . del /"?/ "" . sto_to_spc_no_dquote
                                     . del /"?/ "" ]

let parameter_lists_values = parameter_lists_value_dquote
                           | ( sep_dquote . parameter_lists_value
                               . ( sep_cont . parameter_lists_value )+
                               . sep_dquote )

let parameter_lists_sep    = sep_cont_opt
                             . ( [ del "+" "+" . label "append" ]
                               | [ del "-" "-" . label "remove" ] )?
                             . del "=" "=" . sep_cont_opt

let parameter_lists        =
  negate_or_value
    (key parameter_lists_kw)
    (parameter_lists_sep . parameter_lists_values)

(************************************************************************
 * View: parameter
 *   A single parameter for <defaults>
 *
 *   Definition:
 *     > Parameter ::= Parameter '=' Value |
 *     >               Parameter '+=' Value |
 *     >               Parameter '-=' Value |
 *     >               '!'* Parameter
 *
 *     Parameters may be flags, integer values, strings, or lists.
 *
 *************************************************************************)
let parameter        = parameter_flag | parameter_integer
                     | parameter_string | parameter_lists

(************************************************************************
 * View: paramater_list
 *   A list of comma-separated <parameters> for <defaults>
 *
 *   Definition:
 *     > Parameter_List ::= Parameter |
 *     >                    Parameter ',' Parameter_List
 *************************************************************************)
let parameter_list   = parameter . ( sep_com . parameter )*

(************************************************************************
 * View: defaults
 *   A Defaults entry
 *
 *   Definition:
 *     > Default_Entry ::= Default_Type Parameter_List
 *************************************************************************)
let defaults = [ indent . key "Defaults" . default_type? . sep_cont
                   . parameter_list . eol ]

(************************************************************************
 * Group:                     USER SPECIFICATION
 *************************************************************************)

(************************************************************************
 * View: runas_spec
 *   A runas specification for <spec>, using <alias_list>
 *
 *   Definition:
 *     > Runas_Spec ::= '(' Runas_List ')'
 *************************************************************************)
let runas_spec = Util.del_str "(" . alias_list "runas_user" sto_to_com
    . Util.del_str ")" . sep_cont_opt

(************************************************************************
 * View: tag_spec
 *   Tag specification for <spec>
 *
 *   Definition:
 *     > Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' |
 *     >              'SETENV:' | 'NOSETENV:')
 *************************************************************************)
let tag_spec   =
  [ label "tag" . store /(NO)?(PASSWD|EXEC|SETENV)/ . sep_col ]

(************************************************************************
 * View: cmnd_spec
 *   Command specification for <spec>,
 *     with optional <runas_spec> and any amount of <tag_specs>
 *
 *   Definition:
 *     > Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd
 *************************************************************************)
let cmnd_spec  =
  [ label "command" .  runas_spec? . tag_spec* . sto_to_com_cmnd ]

(************************************************************************
 * View: cmnd_spec_list
 *   A list of comma-separated <cmnd_specs>
 *
 *   Definition:
 *     > Cmnd_Spec_List ::= Cmnd_Spec |
 *     >                    Cmnd_Spec ',' Cmnd_Spec_List
 *************************************************************************)
let cmnd_spec_list = cmnd_spec . ( sep_com . cmnd_spec )*

(************************************************************************
 * View: spec_list
 *   Group of hosts with <cmnd_spec_list>
 *************************************************************************)
let spec_list = [ label "host_group" . alias_list "host" sto_to_com_host
                    . sep_eq . cmnd_spec_list ]

(************************************************************************
 * View: spec
 *   A user specification, listing colon-separated <spec_lists>
 *
 *   Definition:
 *     > User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
 *     >               (':' Host_List '=' Cmnd_Spec_List)*
 *************************************************************************)
let spec = [ label "spec" . indent
               . alias_list "user" sto_to_com_user . sep_cont
               . spec_list
               . ( sep_col . spec_list )* . eol ]

(************************************************************************
 * Group:                        LENS & FILTER
 *************************************************************************)

(* View: lns
     The sudoers lens, any amount of
       * <empty> lines
       * <comments>
       * <aliases>
       * <defaults>
       * <specs>
*)
let lns = ( empty | comment | alias | defaults | spec  )*

(* Variable: filter *)
let filter = (incl "/etc/sudoers")
    . (incl "/etc/sudoers.d/*")
    . Util.stdexcl

let xfm = transform lns filter

(*
Module: Sudoers
  Parses /etc/sudoers

Author: Raphael Pinson <raphink@gmail.com>

About: 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.

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: 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 explicitely to the "admin" Unix group
      > match /files/etc/sudoers/spec/user "%admin"
    * Remove the full 3rd user specification
      > rm /files/etc/sudoers/spec[3]

About: Configuration files
  This lens applies to /etc/sudoers. See <filter>.
*)

module Sudoers =
  autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* Group: Generic primitives *)
(* Variable: eol *)
let eol       = del /[ \t]*\n/ "\n"

(* Variable: indent *)
let indent    = del /[ \t]*/ ""

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc  = del /[ \t]+/ " "

(* Variable: sep_cont *)
let sep_cont = del /([ \t]+|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_cont_opt *)
let sep_cont_opt = del /([ \t]*|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_com *)
let sep_com  = sep_cont_opt . Util.del_str "," . sep_cont_opt

(* Variable: sep_eq *)
let sep_eq   = sep_cont_opt . Util.del_str "=" . sep_cont_opt

(* Variable: sep_col *)
let sep_col  = sep_cont_opt . Util.del_str ":" . sep_cont_opt

(* Variable: sep_dquote *)
let sep_dquote   = Util.del_str "\""

(* Group: Stores *)

(* Variable: sto_to_com_cmnd
sto_to_com_cmnd does not begin or end with a space *)
let sto_to_com_cmnd = store /([^,=:#() \t\n\\\\]([^,=:#()\n\\\\]|\\\\[=:,\\\\])*[^,=:#() \t\n\\\\])|[^,=:#() \t\n\\\\]/

(* Variable: sto_to_com

There could be a \ in the middle of a command *)
let sto_to_com      = store /([^,=:#() \t\n\\\\][^,=:#()\n]*[^,=:#() \t\n\\\\])|[^,=:#() \t\n\\\\]/

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

(* Variable: sto_to_com_user
Escaped spaces are allowed *)
let sto_to_com_user = store ( /([^,=:#() \t\n]([^,=:#() \t\n]|(\\\\[ \t]))*[^,=:#() \t\n])|[^,=:#() \t\n]/
                              - /(User|Runas|Host|Cmnd)_Alias|Defaults.*/ )

(* Variable: sto_to_com_col *)
let sto_to_com_col      = store /[^",=#() \t\n\\\\]+/ (* " relax emacs *)

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

(* Variable: sto_to_spc *)
let sto_to_spc = store /[^", \t\n\\\\]+|"[^", \t\n\\\\]+"/

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

(* Variable: sto_integer *)
let sto_integer = store /[0-9]+/

(* Group: Comments and empty lines *)

(* View: comment
Map comments in "#comment" nodes *)
let comment =
  let sto_to_eol = store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ in
  [ label "#comment" . del /[ \t]*#[ \t]*/ "# " . sto_to_eol . eol ]

(* View: empty
Map empty lines *)
let empty   = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

(************************************************************************
 * Group:                                    ALIASES
 *************************************************************************)

(************************************************************************
 * View: alias_field
 *   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
 *************************************************************************)
let alias_field (kw:string) (sto:lens) = [ label kw . sto ]

(* View: alias_list
     List of <alias_fields>, separated by commas *)
let alias_list  (kw:string) (sto:lens) =
  alias_field kw sto . ( sep_com . alias_field kw sto )*

(************************************************************************
 * View: alias_name
 *   Name of an <alias_entry_single>
 *
 *   Definition:
 *     > NAME ::= [A-Z]([A-Z][0-9]_)*
 *************************************************************************)
let alias_name
    = [ label "name" . store /[A-Z][A-Z0-9_]*/ ]

(************************************************************************
 * View: alias_entry_single
 *   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>
 *************************************************************************)
let alias_entry_single (field:string) (sto:lens)
    = [ label "alias" . alias_name . sep_eq . alias_list field sto ]

(************************************************************************
 * View: alias_entry
 *   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>
 *************************************************************************)
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 )* . eol ]

(* TODO: go further in user definitions *)
(* View: user_alias
     User_Alias, see <alias_field> *)
let user_alias  = alias_entry "User_Alias" "user" sto_to_com
(* View: runas_alias
     Run_Alias, see <alias_field> *)
let runas_alias = alias_entry "Runas_Alias" "runas_user" sto_to_com
(* View: host_alias
     Host_Alias, see <alias_field> *)
let host_alias  = alias_entry "Host_Alias" "host" sto_to_com
(* View: cmnd_alias
     Cmnd_Alias, see <alias_field> *)
let cmnd_alias  = alias_entry "Cmnd_Alias" "command" sto_to_com_cmnd

(************************************************************************
 * View: 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)*
 *************************************************************************)
let alias = user_alias | runas_alias | host_alias | cmnd_alias
(************************************************************************
 * Group:                          DEFAULTS
 *************************************************************************)

(************************************************************************
 * View: default_type
 *   Type definition for <defaults>
 *
 *   Definition:
 *     > Default_Type ::= 'Defaults' |
 *     >                  'Defaults' '@' Host_List |
 *     >                  'Defaults' ':' User_List |
 *     >                  'Defaults' '>' Runas_List
 *************************************************************************)
let default_type     =
  let value = store /[@:>][^ \t\n\\\\]+/ in
  [ label "type" . value ]

(************************************************************************
 * View: del_negate
 *   Delete an even number of '!' signs
 *************************************************************************)
let del_negate = del /(!!)*/ ""

(************************************************************************
 * View: negate_node
 *   Negation of boolean values for <defaults>. Accept one optional '!'
 *   and produce a 'negate' node if there is one.
 *************************************************************************)
let negate_node = [ del "!" "!" . label "negate" ]

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

(************************************************************************
 * View: parameter_flag
 *   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.
 *************************************************************************)
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"

let parameter_flag       = [ del_negate . negate_node?
                               . key parameter_flag_kw ]

(************************************************************************
 * View: parameter_integer
 *   An integer parameter for <defaults>
 *************************************************************************)
let parameter_integer_nobool_kw = "passwd_tries"

let parameter_integer_nobool    = [ key parameter_integer_nobool_kw . sep_eq
                                      . del /"?/ "" . sto_integer
                                      . del /"?/ "" ]

let parameter_integer_bool_kw   = "loglinelen" | "passwd_timeout"
                                | "timestamp_timeout" | "umask"

let parameter_integer_bool      =
  negate_or_value
    (key parameter_integer_bool_kw)
    (sep_eq . del /"?/ "" . sto_integer . del /"?/ "")

let parameter_integer           = parameter_integer_nobool
                                | parameter_integer_bool

(************************************************************************
 * View: parameter_string
 *   A string parameter for <defaults>
 *
 *   An odd number of '!' operators negate the value of the item;
 *      an even number just cancel each other out.
 *************************************************************************)
let parameter_string_nobool_kw = "badpass_message" | "editor" | "mailsub"
                               | "noexec_file" | "passprompt" | "runas_default"
                               | "syslog_badpri" | "syslog_goodpri"
                               | "timestampdir" | "timestampowner" | "secure_path"

let parameter_string_nobool    = [ key parameter_string_nobool_kw . sep_eq
                                     . del /"?/ "" . sto_to_com_col
                                     . del /"?/ "" ]

let parameter_string_bool_kw   = "exempt_group" | "lecture" | "lecture_file"
                               | "listpw" | "logfile" | "mailerflags"
                               | "mailerpath" | "mailto" | "exempt_group"
                               | "syslog" | "verifypw" | "logfile"
                               | "mailerflags" | "mailerpath" | "mailto"
                               | "syslog" | "verifypw"

let parameter_string_bool      =
  negate_or_value
    (key parameter_string_bool_kw)
    (sep_eq . sto_to_com_col)

let parameter_string           = parameter_string_nobool
                               | parameter_string_bool

(************************************************************************
 * View: parameter_lists
 *   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.
 *************************************************************************)
let parameter_lists_kw           = "env_check" | "env_delete" | "env_keep"
let parameter_lists_value        = [ label "var" . sto_to_spc_no_dquote ]
let parameter_lists_value_dquote = [ label "var"
                                     . del /"?/ "" . sto_to_spc_no_dquote
                                     . del /"?/ "" ]

let parameter_lists_values = parameter_lists_value_dquote
                           | ( sep_dquote . parameter_lists_value
                               . ( sep_cont . parameter_lists_value )+
                               . sep_dquote )

let parameter_lists_sep    = sep_cont_opt
                             . ( [ del "+" "+" . label "append" ]
                               | [ del "-" "-" . label "remove" ] )?
                             . del "=" "=" . sep_cont_opt

let parameter_lists        =
  negate_or_value
    (key parameter_lists_kw)
    (parameter_lists_sep . parameter_lists_values)

(************************************************************************
 * View: parameter
 *   A single parameter for <defaults>
 *
 *   Definition:
 *     > Parameter ::= Parameter '=' Value |
 *     >               Parameter '+=' Value |
 *     >               Parameter '-=' Value |
 *     >               '!'* Parameter
 *
 *     Parameters may be flags, integer values, strings, or lists.
 *
 *************************************************************************)
let parameter        = parameter_flag | parameter_integer
                     | parameter_string | parameter_lists

(************************************************************************
 * View: paramater_list
 *   A list of comma-separated <parameters> for <defaults>
 *
 *   Definition:
 *     > Parameter_List ::= Parameter |
 *     >                    Parameter ',' Parameter_List
 *************************************************************************)
let parameter_list   = parameter . ( sep_com . parameter )*

(************************************************************************
 * View: defaults
 *   A Defaults entry
 *
 *   Definition:
 *     > Default_Entry ::= Default_Type Parameter_List
 *************************************************************************)
let defaults = [ indent . key "Defaults" . default_type? . sep_cont
                   . parameter_list . eol ]

(************************************************************************
 * Group:                     USER SPECIFICATION
 *************************************************************************)

(************************************************************************
 * View: runas_spec
 *   A runas specification for <spec>, using <alias_list>
 *
 *   Definition:
 *     > Runas_Spec ::= '(' Runas_List ')'
 *************************************************************************)
let runas_spec = Util.del_str "(" . alias_list "runas_user" sto_to_com
    . Util.del_str ")" . sep_cont_opt

(************************************************************************
 * View: tag_spec
 *   Tag specification for <spec>
 *
 *   Definition:
 *     > Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' |
 *     >              'SETENV:' | 'NOSETENV:')
 *************************************************************************)
let tag_spec   =
  [ label "tag" . store /(NO)?(PASSWD|EXEC|SETENV)/ . sep_col ]

(************************************************************************
 * View: cmnd_spec
 *   Command specification for <spec>,
 *     with optional <runas_spec> and any amount of <tag_specs>
 *
 *   Definition:
 *     > Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd
 *************************************************************************)
let cmnd_spec  =
  [ label "command" .  runas_spec? . tag_spec* . sto_to_com_cmnd ]

(************************************************************************
 * View: cmnd_spec_list
 *   A list of comma-separated <cmnd_specs>
 *
 *   Definition:
 *     > Cmnd_Spec_List ::= Cmnd_Spec |
 *     >                    Cmnd_Spec ',' Cmnd_Spec_List
 *************************************************************************)
let cmnd_spec_list = cmnd_spec . ( sep_com . cmnd_spec )*

(************************************************************************
 * View: spec_list
 *   Group of hosts with <cmnd_spec_list>
 *************************************************************************)
let spec_list = [ label "host_group" . alias_list "host" sto_to_com_host
                    . sep_eq . cmnd_spec_list ]

(************************************************************************
 * View: spec
 *   A user specification, listing colon-separated <spec_lists>
 *
 *   Definition:
 *     > User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
 *     >               (':' Host_List '=' Cmnd_Spec_List)*
 *************************************************************************)
let spec = [ label "spec" . indent
               . alias_list "user" sto_to_com_user . sep_cont
               . spec_list
               . ( sep_col . spec_list )* . eol ]

(************************************************************************
 * Group:                        LENS & FILTER
 *************************************************************************)

(* View: lns
     The sudoers lens, any amount of
       * <empty> lines
       * <comments>
       * <aliases>
       * <defaults>
       * <specs>
*)
let lns = ( empty | comment | alias | defaults | spec  )*

(* Variable: filter *)
let filter = (incl "/etc/sudoers")
    . Util.stdexcl

let xfm = transform lns filter


* lenses/tests/test_iptables.aug: fix syntax error

module Test_iptables =

let add_rule = Iptables.table_rule
let ipt_match = Iptables.ipt_match

test add_rule get
"-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\n" =
  { "append" = "INPUT"
      { "match" = "state" }
      { "state" = "ESTABLISHED,RELATED" }
      { "jump" = "ACCEPT" } }

test add_rule get
"-A INPUT -p icmp -j \tACCEPT \n" =
  { "append" = "INPUT"
      { "protocol" = "icmp" }
      { "jump" = "ACCEPT" } }

test add_rule get
"-A INPUT -i lo -j ACCEPT\n" =
  { "append" = "INPUT"
    { "in-interface" = "lo" }
    { "jump" = "ACCEPT" } }

test ipt_match get " -m tcp -p tcp --dport 53" =
  { "match" = "tcp" } { "protocol" = "tcp" } { "dport" = "53" }

let arule = " -m state --state NEW -m tcp -p tcp --dport 53 -j ACCEPT"

test add_rule get ("--append INPUT" . arule . "\n") =
  { "append" = "INPUT"
      { "match" = "state" }
      { "state" = "NEW" }
      { "match" = "tcp" }
      { "protocol" = "tcp" }
      { "dport" = "53" }
      { "jump" = "ACCEPT" } }

test ipt_match get arule =
  { "match" = "state" } { "state" = "NEW" } { "match" = "tcp" }
  { "protocol" = "tcp" } { "dport" = "53" } { "jump" = "ACCEPT" }

test ipt_match get ("-A INPUT" . arule) = *

test ipt_match get " -p esp -j ACCEPT" =
  { "protocol" = "esp" } { "jump" = "ACCEPT" }

test ipt_match get
  " -m state --state NEW -m udp -p udp --dport 5353 -d 224.0.0.251 -j ACCEPT"
 =
  { "match" = "state" } { "state" = "NEW" } { "match" = "udp" }
  { "protocol" = "udp" } { "dport" = "5353" }
  { "destination" = "224.0.0.251" } { "jump" = "ACCEPT" }

test add_rule get
  "-I FORWARD -m physdev --physdev-is-bridged -j ACCEPT\n" =
  { "insert" = "FORWARD"
      { "match" = "physdev" } { "physdev-is-bridged" } { "jump" = "ACCEPT" } }

test add_rule get
    "-A INPUT -j REJECT --reject-with icmp-host-prohibited\n" =
  { "append" = "INPUT"
      { "jump" = "REJECT" } { "reject-with" = "icmp-host-prohibited" } }

test add_rule get
  "-A RH-Firewall-1-INPUT -p icmp --icmp-type any -j ACCEPT\n" =
  { "append" = "RH-Firewall-1-INPUT"
      { "protocol" = "icmp" }
      { "icmp-type" = "any" }
      { "jump" = "ACCEPT" } }

test Iptables.table get "*filter
:RH-Firewall-1-INPUT - [0:0]
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
COMMIT\n" =
  { "table" = "filter"
      { "chain" = "RH-Firewall-1-INPUT"
          { "policy" = "-" } }
      { "append" = "FORWARD"
          { "jump" = "RH-Firewall-1-INPUT" } }
      { "append" = "RH-Firewall-1-INPUT"
          { "in-interface" = "lo" }
          { "jump" = "ACCEPT" } } }

let conf = "# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002
*filter
:INPUT DROP [1:229]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

-I FORWARD -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# comments and blank lines are allow between rules

-A FORWARD -i eth1 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
--append OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Completed on Wed Apr 24 10:19:55 2002
# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002
*mangle
:PREROUTING ACCEPT [658:32445]

:INPUT ACCEPT [658:32445]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [891:68234]
:POSTROUTING ACCEPT [891:68234]
COMMIT
# Completed on Wed Apr 24 10:19:55 2002
# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002
*nat
:PREROUTING ACCEPT [1:229]
:POSTROUTING ACCEPT [3:450]
# The output chain
:OUTPUT ACCEPT [3:450]
# insert something
--insert POSTROUTING -o eth0 -j SNAT --to-source 195.233.192.1 \t
# and now commit
COMMIT
# Completed on Wed Apr 24 10:19:55 2002\n"

test Iptables.lns get conf =
  { "#comment" =
      "Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002" }
  { "table" = "filter"
    { "chain" = "INPUT" { "policy" = "DROP" } }
    { "chain" = "FORWARD" { "policy" = "DROP" } }
    { "chain" = "OUTPUT" { "policy" = "DROP" } }
    { "append" = "INPUT"
      { "match" = "state" }
      { "state" = "RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } }
    {}
    { "insert" = "FORWARD"
      { "in-interface" = "eth0" }
      { "match" = "state" }
      { "state" = "RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } }
    {}
    { "#comment" = "comments and blank lines are allow between rules" }
    {}
    { "append" = "FORWARD"
      { "in-interface" = "eth1" }
      { "match" = "state" }
      { "state" = "NEW,RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } }
    { "append" = "OUTPUT"
      { "match" = "state" }
      { "state" = "NEW,RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } } }
  { "#comment" = "Completed on Wed Apr 24 10:19:55 2002" }
  { "#comment" =
      "Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002" }
  { "table" = "mangle"
    { "chain" = "PREROUTING" { "policy" = "ACCEPT" } }
    {}
    { "chain" = "INPUT" { "policy" = "ACCEPT" } }
    { "chain" = "FORWARD" { "policy" = "ACCEPT" } }
    { "chain" = "OUTPUT" { "policy" = "ACCEPT" } }
    { "chain" = "POSTROUTING" { "policy" = "ACCEPT" } } }
  { "#comment" = "Completed on Wed Apr 24 10:19:55 2002" }
  { "#comment" =
      "Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002" }
  { "table" = "nat"
    { "chain" = "PREROUTING" { "policy" = "ACCEPT" } }
    { "chain" = "POSTROUTING" { "policy" = "ACCEPT" } }
    { "#comment" = "The output chain" }
    { "chain" = "OUTPUT" { "policy" = "ACCEPT" } }
    { "#comment" = "insert something" }
    { "insert" = "POSTROUTING"
      { "out-interface" = "eth0" }
      { "jump" = "SNAT" }
      { "to-source" = "195.233.192.1" } }
    { "#comment" = "and now commit" } }
  { "#comment" = "Completed on Wed Apr 24 10:19:55 2002" }

test ipt_match get " -m comment --comment \"A comment\"" =
  { "match" = "comment" }
  { "comment" = "\"A comment\"" }

(*
 * Test the various schemes for negation that iptables supports
 *
 * Note that the two ways in which a parameter can be negated lead to
 * two different trees that mean the same.
 *)
test add_rule get "-I POSTROUTING ! -d 192.168.122.0/24 -j MASQUERADE\n" =
  { "insert" = "POSTROUTING"
    { "destination" = "192.168.122.0/24"
      { "not" } }
    { "jump" = "MASQUERADE" } }

test add_rule get "-I POSTROUTING -d ! 192.168.122.0/24 -j MASQUERADE\n" =
  { "insert" = "POSTROUTING"
    { "destination" = "! 192.168.122.0/24" }
    { "jump" = "MASQUERADE" } }

test add_rule put "-I POSTROUTING ! -d 192.168.122.0/24 -j MASQUERADE\n"
    after rm "/insert/destination/not" =
  "-I POSTROUTING -d 192.168.122.0/24 -j MASQUERADE\n"

(* I have no idea if iptables will accept double negations, but we
 * allow it syntactically *)
test add_rule put "-I POSTROUTING -d ! 192.168.122.0/24 -j MASQUERADE\n"
    after clear "/insert/destination/not" =
  "-I POSTROUTING ! -d ! 192.168.122.0/24 -j MASQUERADE\n"

test Iptables.chain get ":tcp_packets - [0:0]
" =
    { "chain" = "tcp_packets" { "policy" = "-" } }

(* Bug #157 *)
test ipt_match get " --tcp-flags SYN,RST,ACK,FIN SYN" =
  { "tcp-flags"
    { "mask" = "SYN" }
    { "mask" = "RST" }
    { "mask" = "ACK" }
    { "mask" = "FIN" }
    { "set" = "SYN" } }

(* Bug #224 *)
test ipt_match get " --icmpv6-type neighbor-solicitation" =
  { "icmpv6-type" = "neighbor-solicitation" }

module Test_iptables =

let add_rule = Iptables.table_rule
let ipt_match = Iptables.ipt_match

test add_rule get
"-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\n" =
  { "append" = "INPUT"
      { "match" = "state" }
      { "state" = "ESTABLISHED,RELATED" }
      { "jump" = "ACCEPT" } }

test add_rule get
"-A INPUT -p icmp -j \tACCEPT \n" =
  { "append" = "INPUT"
      { "protocol" = "icmp" }
      { "jump" = "ACCEPT" } }

test add_rule get
"-A INPUT -i lo -j ACCEPT\n" =
  { "append" = "INPUT"
    { "in-interface" = "lo" }
    { "jump" = "ACCEPT" } }

test ipt_match get " -m tcp -p tcp --dport 53" =
  { "match" = "tcp" } { "protocol" = "tcp" } { "dport" = "53" }

let arule = " -m state --state NEW -m tcp -p tcp --dport 53 -j ACCEPT"

test add_rule get ("--append INPUT" . arule . "\n") =
  { "append" = "INPUT"
      { "match" = "state" }
      { "state" = "NEW" }
      { "match" = "tcp" }
      { "protocol" = "tcp" }
      { "dport" = "53" }
      { "jump" = "ACCEPT" } }

test ipt_match get arule =
  { "match" = "state" } { "state" = "NEW" } { "match" = "tcp" }
  { "protocol" = "tcp" } { "dport" = "53" } { "jump" = "ACCEPT" }

test ipt_match get ("-A INPUT" . arule) = *

test ipt_match get " -p esp -j ACCEPT" =
  { "protocol" = "esp" } { "jump" = "ACCEPT" }

test ipt_match get
  " -m state --state NEW -m udp -p udp --dport 5353 -d 224.0.0.251 -j ACCEPT"
 =
  { "match" = "state" } { "state" = "NEW" } { "match" = "udp" }
  { "protocol" = "udp" } { "dport" = "5353" }
  { "destination" = "224.0.0.251" } { "jump" = "ACCEPT" }

test add_rule get
  "-I FORWARD -m physdev --physdev-is-bridged -j ACCEPT\n" =
  { "insert" = "FORWARD"
      { "match" = "physdev" } { "physdev-is-bridged" } { "jump" = "ACCEPT" } }

test add_rule get
    "-A INPUT -j REJECT --reject-with icmp-host-prohibited\n" =
  { "append" = "INPUT"
      { "jump" = "REJECT" } { "reject-with" = "icmp-host-prohibited" } }

test add_rule get
  "-A RH-Firewall-1-INPUT -p icmp --icmp-type any -j ACCEPT\n" =
  { "append" = "RH-Firewall-1-INPUT"
      { "protocol" = "icmp" }
      { "icmp-type" = "any" }
      { "jump" = "ACCEPT" } }

test Iptables.table get "*filter
:RH-Firewall-1-INPUT - [0:0]
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
COMMIT\n" =
  { "table" = "filter"
      { "chain" = "RH-Firewall-1-INPUT"
          { "policy" = "-" } }
      { "append" = "FORWARD"
          { "jump" = "RH-Firewall-1-INPUT" } }
      { "append" = "RH-Firewall-1-INPUT"
          { "in-interface" = "lo" }
          { "jump" = "ACCEPT" } } }

let conf = "# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002
*filter
:INPUT DROP [1:229]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

-I FORWARD -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# comments and blank lines are allow between rules

-A FORWARD -i eth1 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
--append OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Completed on Wed Apr 24 10:19:55 2002
# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002
*mangle
:PREROUTING ACCEPT [658:32445]

:INPUT ACCEPT [658:32445]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [891:68234]
:POSTROUTING ACCEPT [891:68234]
COMMIT
# Completed on Wed Apr 24 10:19:55 2002
# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002
*nat
:PREROUTING ACCEPT [1:229]
:POSTROUTING ACCEPT [3:450]
# The output chain
:OUTPUT ACCEPT [3:450]
# insert something
--insert POSTROUTING -o eth0 -j SNAT --to-source 195.233.192.1 \t
# and now commit
COMMIT
# Completed on Wed Apr 24 10:19:55 2002\n"

test Iptables.lns get conf =
  { "#comment" =
      "Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002" }
  { "table" = "filter"
    { "chain" = "INPUT" { "policy" = "DROP" } }
    { "chain" = "FORWARD" { "policy" = "DROP" } }
    { "chain" = "OUTPUT" { "policy" = "DROP" } }
    { "append" = "INPUT"
      { "match" = "state" }
      { "state" = "RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } }
    {}
    { "insert" = "FORWARD"
      { "in-interface" = "eth0" }
      { "match" = "state" }
      { "state" = "RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } }
    {}
    { "#comment" = "comments and blank lines are allow between rules" }
    {}
    { "append" = "FORWARD"
      { "in-interface" = "eth1" }
      { "match" = "state" }
      { "state" = "NEW,RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } }
    { "append" = "OUTPUT"
      { "match" = "state" }
      { "state" = "NEW,RELATED,ESTABLISHED" }
      { "jump" = "ACCEPT" } } }
  { "#comment" = "Completed on Wed Apr 24 10:19:55 2002" }
  { "#comment" =
      "Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002" }
  { "table" = "mangle"
    { "chain" = "PREROUTING" { "policy" = "ACCEPT" } }
    {}
    { "chain" = "INPUT" { "policy" = "ACCEPT" } }
    { "chain" = "FORWARD" { "policy" = "ACCEPT" } }
    { "chain" = "OUTPUT" { "policy" = "ACCEPT" } }
    { "chain" = "POSTROUTING" { "policy" = "ACCEPT" } } }
  { "#comment" = "Completed on Wed Apr 24 10:19:55 2002" }
  { "#comment" =
      "Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002" }
  { "table" = "nat"
    { "chain" = "PREROUTING" { "policy" = "ACCEPT" } }
    { "chain" = "POSTROUTING" { "policy" = "ACCEPT" } }
    { "#comment" = "The output chain" }
    { "chain" = "OUTPUT" { "policy" = "ACCEPT" } }
    { "#comment" = "insert something" }
    { "insert" = "POSTROUTING"
      { "out-interface" = "eth0" }
      { "jump" = "SNAT" }
      { "to-source" = "195.233.192.1" } }
    { "#comment" = "and now commit" } }
  { "#comment" = "Completed on Wed Apr 24 10:19:55 2002" }

test ipt_match get " -m comment --comment \"A comment\"" =
  { "match" = "comment" }
  { "comment" = "\"A comment\"" }

(*
 * Test the various schemes for negation that iptables supports
 *
 * Note that the two ways in which a parameter can be negated lead to
 * two different trees that mean the same.
 *)
test add_rule get "-I POSTROUTING ! -d 192.168.122.0/24 -j MASQUERADE\n" =
  { "insert" = "POSTROUTING"
    { "destination" = "192.168.122.0/24"
      { "not" } }
    { "jump" = "MASQUERADE" } }

test add_rule get "-I POSTROUTING -d ! 192.168.122.0/24 -j MASQUERADE\n" =
  { "insert" = "POSTROUTING"
    { "destination" = "! 192.168.122.0/24" }
    { "jump" = "MASQUERADE" } }

test add_rule put "-I POSTROUTING ! -d 192.168.122.0/24 -j MASQUERADE\n"
    after rm "/insert/destination/not" =
  "-I POSTROUTING -d 192.168.122.0/24 -j MASQUERADE\n"

(* I have no idea if iptables will accept double negations, but we
 * allow it syntactically *)
test add_rule put "-I POSTROUTING -d ! 192.168.122.0/24 -j MASQUERADE\n"
    after clear "/insert/destination/not" =
  "-I POSTROUTING ! -d ! 192.168.122.0/24 -j MASQUERADE\n"

test Iptables.chain get ":tcp_packets - [0:0]
" =
    { "chain" = "tcp_packets" { "policy" = "-" } }

(* Bug #157 *)
test ipt_match get " --tcp-flags SYN,RST,ACK,FIN SYN" =
  { "tcp-flags"
    { "mask" = "SYN" }
    { "mask" = "RST" }
    { "mask" = "ACK" }
    { "mask" = "FIN" }
    { "set" = "SYN" } }

(* Bug #224 *)
test ipt_match get " --icmpv6-type neighbor-solicitation" = ?
  { "icmpv6-type" = "neighbor-solicitation" }


adding network configuration paths according to 'systemd.network(5)' (#683)

Co-authored-by: Mario Rütti <436bc265f5db72f3ed4bff03b8806de2ed381384@kraemerag.ch>
Co-authored-by: Raphaël Pinson <0909132c360f4744d45149034acd6a9d36df8f8f@camptocamp.com>

(*
Module: Systemd
  Parses systemd unit files.

Author: Dominic Cleal <dcleal@redhat.com>

About: Reference
  This lens tries to keep as close as possible to systemd.unit(5) and
  systemd.service(5) etc where possible.

About: License
  This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
  To be documented

About: Configuration files
  This lens applies to /lib/systemd/system/* and /etc/systemd/system/*.
  See <filter>.
*)

module Systemd =
autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* View: eol *)
let eol = Util.eol

(* View: eol_comment
   An <IniFile.comment> entry for standalone comment lines (; or #) *)
let comment     = IniFile.comment IniFile.comment_re "#"

(* View: eol_comment
   An <IniFile.comment> entry for end of line comments (# only) *)
let eol_comment = IniFile.comment "#" "#"

(* View: sep
   An <IniFile.sep> entry *)
let sep        = IniFile.sep "=" "="

(* Variable: entry_single_kw *)
let entry_single_kw  = "Description"

(* Variable: entry_command_kw *)
let entry_command_kw = /Exec[A-Za-z][A-Za-z0-9._-]+/

(* Variable: entry_env_kw *)
let entry_env_kw     = "Environment"

(* Variable: entry_multi_kw *)
let entry_multi_kw   =
     let forbidden = entry_single_kw | entry_command_kw | entry_env_kw
  in /[A-Za-z][A-Za-z0-9._-]+/ - forbidden

(* Variable: value_single_re *)
let value_single_re  = /[^# \t\n\\][^#\n\\]*[^# \t\n\\]|[^# \t\n\\]/

(* View: sto_value_single
   Support multiline values with a backslash *)
let sto_value_single = Util.del_opt_ws ""
                       . store (value_single_re
                                . (/\\\\\n/ . value_single_re)*)

(* View: sto_value *)
let sto_value = store /[^# \t\n]*[^# \t\n\\]/

(* Variable: value_sep
   Multi-value entries separated by whitespace or backslash and newline *)
let value_sep = del /[ \t]+|[ \t]*\\\\[ \t]*\n[ \t]*/ " "

(* Variable: value_cmd_re
   Don't parse @ and - prefix flags *)
let value_cmd_re = /[^#@ \t\n\\-][^#@ \t\n\\-][^# \t\n\\]*/

(* Variable: env_key *)
let env_key = /[A-Za-z0-9_]+(\[[0-9]+\])?/

(************************************************************************
 * Group:                 ENTRIES
 *************************************************************************)

(*
Supported entry features, selected by key names:
  * multi-value space separated attrs (the default)
  * single-value attrs (Description)
  * systemd.service: Exec* attrs with flags, command and arguments
  * systemd.service: Environment NAME=arg
*)

(* View: entry_fn
   Prototype for our various key=value lines, with optional comment *)
let entry_fn (kw:regexp) (val:lens) =
    [ key kw . sep . val . (eol_comment|eol) ]

(* View: entry_value
   Store a value that doesn't contain spaces *)
let entry_value  = [ label "value" . sto_value ]

(* View: entry_single
   Entry that takes a single value containing spaces *)
let entry_single = entry_fn entry_single_kw
                     [ label "value" . sto_value_single ]?

(* View: entry_command
   Entry that takes a space separated set of values (the default) *)
let entry_multi  = entry_fn entry_multi_kw
                     ( Util.del_opt_ws ""
                       . Build.opt_list entry_value value_sep )?

(* View: entry_command_flags
   Exec* flags "@" and "-".  Order is important, see systemd.service(8) *)
let entry_command_flags =
     let exit  = [ label "ignoreexit" . Util.del_str "-" ]
  in let arg0  = [ label "arg0" . Util.del_str "@" ]
  in exit? . arg0?

(* View: entry_command
   Entry that takes a command, arguments and the optional prefix flags *)
let entry_command =
     let cmd  = [ label "command" . store value_cmd_re ]
  in let arg  = [ seq "args" . sto_value ]
  in let args = [ counter "args" . label "arguments"
                . (value_sep . arg)+ ]
  in entry_fn entry_command_kw ( entry_command_flags . cmd . args? )?

(* View: entry_env
   Entry that takes a space separated set of ENV=value key/value pairs *)
let entry_env =
     let envkv (env_val:lens) = key env_key . Util.del_str "=" . env_val
     (* bare has no spaces, and is optionally quoted *)
  in let bare = Quote.do_quote_opt (envkv (store /[^#'" \t\n]*[^#'" \t\n\\]/)?)
  in let bare_dqval = envkv (store /"[^#"\t\n]*[^#"\t\n\\]"/)
  in let bare_sqval = envkv (store /'[^#'\t\n]*[^#'\t\n\\]'/)
     (* quoted has at least one space, and must be quoted *)
  in let quoted = Quote.do_quote (envkv (store /[^#"'\n]*[ \t]+[^#"'\n]*/))
  in let envkv_quoted = [ bare ] | [ bare_dqval ] | [ bare_sqval ] | [ quoted ]
  in entry_fn entry_env_kw ( Build.opt_list envkv_quoted value_sep )

(************************************************************************
 * Group:                 LENS
 *************************************************************************)

(* View: entry
   An <IniFile.entry> *)
let entry   = entry_single | entry_multi | entry_command | entry_env | comment

(* View: include
   Includes another file at this position *)
let include = [ key ".include" . Util.del_ws_spc . sto_value
                . (eol_comment|eol) ]

(* View: title
   An <IniFile.title> *)
let title   = IniFile.title IniFile.record_re

(* View: record
   An <IniFile.record> *)
let record = IniFile.record title (entry|include)

(* View: lns
   An <IniFile.lns> *)
let lns    = IniFile.lns record (comment|include)

(* View: filter *)
let filter = incl "/lib/systemd/system/*"
           . incl "/lib/systemd/system/*/*"
           . incl "/etc/systemd/system/*"
           . incl "/etc/systemd/system/*/*"
           . incl "/etc/systemd/logind.conf"
           . incl "/etc/sysconfig/*.systemd"
           . incl "/lib/systemd/network/*"
           . incl "/usr/local/lib/systemd/network/*"
           . incl "/etc/systemd/network/*"
           . excl "/lib/systemd/system/*.d"
           . excl "/etc/systemd/system/*.d"
           . excl "/lib/systemd/system/*.wants"
           . excl "/etc/systemd/system/*.wants"
           . Util.stdexcl

let xfm = transform lns filter

(*
Module: Systemd
  Parses systemd unit files.

Author: Dominic Cleal <dcleal@redhat.com>

About: Reference
  This lens tries to keep as close as possible to systemd.unit(5) and
  systemd.service(5) etc where possible.

About: License
  This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
  To be documented

About: Configuration files
  This lens applies to /lib/systemd/system/* and /etc/systemd/system/*.
  See <filter>.
*)

module Systemd =
autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* View: eol *)
let eol = Util.eol

(* View: eol_comment
   An <IniFile.comment> entry for standalone comment lines (; or #) *)
let comment     = IniFile.comment IniFile.comment_re "#"

(* View: eol_comment
   An <IniFile.comment> entry for end of line comments (# only) *)
let eol_comment = IniFile.comment "#" "#"

(* View: sep
   An <IniFile.sep> entry *)
let sep        = IniFile.sep "=" "="

(* Variable: entry_single_kw *)
let entry_single_kw  = "Description"

(* Variable: entry_command_kw *)
let entry_command_kw = /Exec[A-Za-z][A-Za-z0-9._-]+/

(* Variable: entry_env_kw *)
let entry_env_kw     = "Environment"

(* Variable: entry_multi_kw *)
let entry_multi_kw   =
     let forbidden = entry_single_kw | entry_command_kw | entry_env_kw
  in /[A-Za-z][A-Za-z0-9._-]+/ - forbidden

(* Variable: value_single_re *)
let value_single_re  = /[^# \t\n\\][^#\n\\]*[^# \t\n\\]|[^# \t\n\\]/

(* View: sto_value_single
   Support multiline values with a backslash *)
let sto_value_single = Util.del_opt_ws ""
                       . store (value_single_re
                                . (/\\\\\n/ . value_single_re)*)

(* View: sto_value *)
let sto_value = store /[^# \t\n]*[^# \t\n\\]/

(* Variable: value_sep
   Multi-value entries separated by whitespace or backslash and newline *)
let value_sep = del /[ \t]+|[ \t]*\\\\[ \t]*\n[ \t]*/ " "

(* Variable: value_cmd_re
   Don't parse @ and - prefix flags *)
let value_cmd_re = /[^#@ \t\n\\-][^#@ \t\n\\-][^# \t\n\\]*/

(* Variable: env_key *)
let env_key = /[A-Za-z0-9_]+(\[[0-9]+\])?/

(************************************************************************
 * Group:                 ENTRIES
 *************************************************************************)

(*
Supported entry features, selected by key names:
  * multi-value space separated attrs (the default)
  * single-value attrs (Description)
  * systemd.service: Exec* attrs with flags, command and arguments
  * systemd.service: Environment NAME=arg
*)

(* View: entry_fn
   Prototype for our various key=value lines, with optional comment *)
let entry_fn (kw:regexp) (val:lens) =
    [ key kw . sep . val . (eol_comment|eol) ]

(* View: entry_value
   Store a value that doesn't contain spaces *)
let entry_value  = [ label "value" . sto_value ]

(* View: entry_single
   Entry that takes a single value containing spaces *)
let entry_single = entry_fn entry_single_kw
                     [ label "value" . sto_value_single ]?

(* View: entry_command
   Entry that takes a space separated set of values (the default) *)
let entry_multi  = entry_fn entry_multi_kw
                     ( Util.del_opt_ws ""
                       . Build.opt_list entry_value value_sep )?

(* View: entry_command_flags
   Exec* flags "@" and "-".  Order is important, see systemd.service(8) *)
let entry_command_flags =
     let exit  = [ label "ignoreexit" . Util.del_str "-" ]
  in let arg0  = [ label "arg0" . Util.del_str "@" ]
  in exit? . arg0?

(* View: entry_command
   Entry that takes a command, arguments and the optional prefix flags *)
let entry_command =
     let cmd  = [ label "command" . store value_cmd_re ]
  in let arg  = [ seq "args" . sto_value ]
  in let args = [ counter "args" . label "arguments"
                . (value_sep . arg)+ ]
  in entry_fn entry_command_kw ( entry_command_flags . cmd . args? )?

(* View: entry_env
   Entry that takes a space separated set of ENV=value key/value pairs *)
let entry_env =
     let envkv (env_val:lens) = key env_key . Util.del_str "=" . env_val
     (* bare has no spaces, and is optionally quoted *)
  in let bare = Quote.do_quote_opt (envkv (store /[^#'" \t\n]*[^#'" \t\n\\]/)?)
  in let bare_dqval = envkv (store /"[^#"\t\n]*[^#"\t\n\\]"/)
  in let bare_sqval = envkv (store /'[^#'\t\n]*[^#'\t\n\\]'/)
     (* quoted has at least one space, and must be quoted *)
  in let quoted = Quote.do_quote (envkv (store /[^#"'\n]*[ \t]+[^#"'\n]*/))
  in let envkv_quoted = [ bare ] | [ bare_dqval ] | [ bare_sqval ] | [ quoted ]
  in entry_fn entry_env_kw ( Build.opt_list envkv_quoted value_sep )

(************************************************************************
 * Group:                 LENS
 *************************************************************************)

(* View: entry
   An <IniFile.entry> *)
let entry   = entry_single | entry_multi | entry_command | entry_env | comment

(* View: include
   Includes another file at this position *)
let include = [ key ".include" . Util.del_ws_spc . sto_value
                . (eol_comment|eol) ]

(* View: title
   An <IniFile.title> *)
let title   = IniFile.title IniFile.record_re

(* View: record
   An <IniFile.record> *)
let record = IniFile.record title (entry|include)

(* View: lns
   An <IniFile.lns> *)
let lns    = IniFile.lns record (comment|include)

(* View: filter *)
let filter = incl "/lib/systemd/system/*"
           . incl "/lib/systemd/system/*/*"
           . incl "/etc/systemd/system/*"
           . incl "/etc/systemd/system/*/*"
           . incl "/etc/systemd/logind.conf"
           . incl "/etc/sysconfig/*.systemd"
           . excl "/lib/systemd/system/*.d"
           . excl "/etc/systemd/system/*.d"
           . excl "/lib/systemd/system/*.wants"
           . excl "/etc/systemd/system/*.wants"
           . Util.stdexcl

let xfm = transform lns filter


Pam: Use eol, indent, comment and empty from Util

(* Proces /etc/pam.d *)
module Pam =
  autoload xfm

  let eol = Util.eol
  let indent = Util.indent

  (* For the control syntax of [key=value ..] we could split the key value *)
  (* pairs into an array and generate a subtree control/N/KEY = VALUE      *)
  let control = /(\[[^]#\n]*\]|[^[ \t][^ \t]*)/
  let word = /[^# \t\n]+/
  (* Allowed types. FIXME: Should be case insensitive *)
  let types = /(auth|session|account|password)/

  (* This isn't entirely right: arguments enclosed in [ .. ] are allowed   *)
  (* and should be parsed as one                                           *)
  let argument = /[^#\n \t]+/

  let comment = Util.comment
  let empty   = Util.empty

  (* Not mentioned in the man page, but Debian uses the syntax             *)
  (*   @include module                                                     *)
  (* quite a bit                                                           *)
  let include = [ indent . Util.del_str "@" . key "include" .
                  Util.del_ws_spc . store word . eol ]

  let record = [ seq "record" . indent .
                   [ label "type" . store types ] .
                   Util.del_ws_tab .
                   [ label "control" . store control] .
                   Util.del_ws_tab .
                   [ label "module" . store word ] .
                   [ Util.del_ws_tab . label "argument" . store argument ]* .
		 (comment|eol)
               ]
  let lns = ( empty | comment | include | record ) *

  let xfm = transform lns ((incl "/etc/pam.d/*") . Util.stdexcl)

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

(* Proces /etc/pam.d *)
module Pam =
  autoload xfm

  let eol = del /[ \t]*\n/ "\n"
  let indent = del /[ \t]*/ ""

  (* For the control syntax of [key=value ..] we could split the key value *)
  (* pairs into an array and generate a subtree control/N/KEY = VALUE      *)
  let control = /(\[[^]#\n]*\]|[^[ \t][^ \t]*)/
  let word = /[^# \t\n]+/
  (* Allowed types. FIXME: Should be case insensitive *)
  let types = /(auth|session|account|password)/

  (* This isn't entirely right: arguments enclosed in [ .. ] are allowed   *)
  (* and should be parsed as one                                           *)
  let argument = /[^#\n \t]+/

  let comment = [ indent . label "comment" . del /#[ \t]*/ "# " . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . eol ]
  let empty   = [ del /[ \t]*#?[ \t]*\n/ "" ]

  (* Not mentioned in the man page, but Debian uses the syntax             *)
  (*   @include module                                                     *)
  (* quite a bit                                                           *)
  let include = [ indent . Util.del_str "@" . key "include" .
                  Util.del_ws_spc . store word . eol ]

  let record = [ seq "record" . indent .
                   [ label "type" . store types ] .
                   Util.del_ws_tab .
                   [ label "control" . store control] .
                   Util.del_ws_tab .
                   [ label "module" . store word ] .
                   [ Util.del_ws_tab . label "argument" . store argument ]* .
		 (comment|eol)
               ]
  let lns = ( empty | comment | include | record ) *

  let xfm = transform lns ((incl "/etc/pam.d/*") . Util.stdexcl)

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


Known_Hosts: fix description

Known_Hosts: fix description

(*
Module: Known_Hosts
  Parses SSH known_hosts files

Author: Raphaël Pinson <raphink@gmail.com>

About: Reference
  This lens manages OpenSSH's known_hosts files. See `man 8 sshd` for reference.

About: License
  This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
  Sample usage of this lens in augtool:

    * Get a key by name from ssh_known_hosts
      > print /files/etc/ssh_known_hosts/*[.="foo.example.com"]
      ...

    * Change a host's key
      > set /files/etc/ssh_known_hosts/*[.="foo.example.com"]/key "newkey"

About: Configuration files
  This lens applies to SSH known_hosts files. See <filter>.

*)

module Known_Hosts =

autoload xfm

(* View: entry
     A known_hosts entry *)
let entry =
  let alias = [ label "alias" . store Rx.neg1 ]
  in [ Util.indent . seq "entry" . store Rx.neg1
     . (Sep.comma . Build.opt_list alias Sep.comma)?
     . Sep.space
     . [ label "type" . store Rx.no_spaces ]
     . Sep.space
     . [ label "key" . store Rx.no_spaces ]
     . Util.eol ]

(* View: lns
     The known_hosts lens *)
let lns = (Util.empty | Util.comment | entry)*

(* Variable: filter *)
let filter = incl "/etc/ssh/ssh_known_hosts"
           . incl (Sys.getenv("HOME") . "/.ssh/known_hosts")

let xfm = transform lns filter

(*
Module: Known_Hosts
  Parses SSH known_hosts files

Author: Raphaël Pinson <raphink@gmail.com>

About: Reference
  This lens ensures that conf files included in ActiveMQ /FuseMQ are properly
  handled by Augeas.

About: License
  This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
  Sample usage of this lens in augtool:

    * Get a key by name from ssh_known_hosts
      > print /files/etc/ssh_known_hosts/*[.="foo.example.com"]
      ...

    * Change a host's key
      > set /files/etc/ssh_known_hosts/*[.="foo.example.com"]/key "newkey"

About: Configuration files
  This lens applies to SSH known_hosts files. See <filter>.

*)

module Known_Hosts =

autoload xfm

(* View: entry
     A known_hosts entry *)
let entry =
  let alias = [ label "alias" . store Rx.neg1 ]
  in [ Util.indent . seq "entry" . store Rx.neg1
     . (Sep.comma . Build.opt_list alias Sep.comma)?
     . Sep.space
     . [ label "type" . store Rx.no_spaces ]
     . Sep.space
     . [ label "key" . store Rx.no_spaces ]
     . Util.eol ]

(* View: lns
     The known_hosts lens *)
let lns = (Util.empty | Util.comment | entry)*

(* Variable: filter *)
let filter = incl "/etc/ssh/ssh_known_hosts"
           . incl (Sys.getenv("HOME") . "/.ssh/known_hosts")

let xfm = transform lns filter