sudoers = { SOI ~ NEWLINE* ~ (stmt ~ NEWLINE+)* ~ stmt? ~ EOI }

stmt = _{ expr }

expr = _{
    Default_Entry
  | Alias
  | User_Spec
  | Comment
  | Include
}

Alias = {
    "User_Alias " ~ User_Alias_Spec ~ (":" ~ User_Alias_Spec)*
  | "Runas_Alias " ~ Runas_Alias_Spec ~ (":" ~ Runas_Alias_Spec)*
  | "Host_Alias " ~ Host_Alias_Spec ~ (":" ~ Host_Alias_Spec)*
  | "Cmnd_Alias " ~ Cmnd_Alias_Spec ~ (":" ~ Cmnd_Alias_Spec)*
  | "Cmd_Alias " ~ Cmnd_Alias_Spec ~ (":" ~ Cmnd_Alias_Spec)*
}

User_Alias_Spec  = { Flag ~ "=" ~ User_List }
Runas_Alias_Spec = { Flag ~ "=" ~ Runas_List }
Host_Alias_Spec  = { Flag ~ "=" ~ Host_List }
Cmnd_Alias_Spec  = { Flag ~ "=" ~ Cmnd_List }

Default_Entry = { "Defaults" ~ Default_Type? ~ Parameter_List }

Default_Type = {
    "@" ~ Host_List
  | ":" ~ User_List
  | "!" ~ Cmnd_List
  | ">" ~ Runas_List
}

Parameter_List = _{ Parameter ~ "," ~ Parameter_List | Parameter }
Entry          = _{ Flag ~ "=" ~ Value }
Parameter      =  { Entry | Flag ~ "+=" ~ Value | Flag ~ "-=" ~ Value | "!"* ~ Flag | Flag }
Value          = _{ (Str | Flag) }

Str   = ${ "\"" ~ inner ~ "\"" }
inner = @{ char* }
char  =  {
    !("\"" | "\\") ~ ANY
  | "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
  | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}
Flag  =  { (CASED_LETTER | "_")+ }

// ### NETWORK ###
Host_List   =  { Host | Host ~ "," ~ Host_List }
Domain_Name = @{ (CASED_LETTER | NUMBER) ~ (CASED_LETTER | NUMBER | "-" | ".")* }
Host        =  {
    Negate* ~ Domain_Name
  | Negate* ~ ip_addr
  | Negate* ~ netgroup
}

ip_addr   = @{ ip4_addr ~ ("/" ~ netmask4)? | ip6_addr }
ip4_addr  = @{ ip4_octet ~ "." ~ ip4_octet ~ "." ~ ip4_octet ~ "." ~ ip4_octet }
ip4_octet = @{ "25" ~ '0'..'5' | "2" ~ '0'..'4' ~ ASCII_DIGIT | "1" ~ ASCII_DIGIT ~ ASCII_DIGIT | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT | ASCII_DIGIT }

ip6_addr = @{ ( h16 ~ ":" ){6} ~ ls32
  | "::" ~ ( h16 ~ ":" ){5} ~ ls32
  | ( h16 )? ~ "::" ~ ( h16 ~ ":" ){4} ~ ls32
  | ( ( h16 ~ ":" ){0,1} ~ h16 )? ~ "::" ~ ( h16 ~ ":" ){3} ~ ls32
  | ( ( h16 ~ ":" ){0,2} ~ h16 )? ~ "::" ~ ( h16 ~ ":" ){2} ~ ls32
  | ( ( h16 ~ ":" ){0,3} ~ h16 )? ~ "::" ~ h16 ~ ":" ~ ls32
  | ( ( h16 ~ ":" ){0,4} ~ h16 )? ~ "::" ~ ls32
  | ( ( h16 ~ ":" ){0,5} ~ h16 )? ~ "::" ~ h16
  | ( ( h16 ~ ":" ){0,6} ~ h16 )? ~ "::"
}

h16  = _{ ASCII_HEX_DIGIT{1, 4} }
ls32 = _{ (h16 ~ ":" ~ h16) ~ "/" ~ ip4_addr }

netmask4 = @{ "6" ~ '0'..'4' | '0'..'5' ~ ASCII_DIGIT | ASCII_DIGIT }

netgroup        =  { "+(" ~ netgroup_host ~ "," ~ netgroup_user ~ "," ~ netgroup_domain ~ ")" }
netgroup_host   = @{ hostname | "-" }
hostname        = @{ CASED_LETTER ~ (CASED_LETTER | NUMBER)* }
netgroup_user   = @{ username | "-" }
username        = @{ (CASED_LETTER | "_") ~ (CASED_LETTER | NUMBER | "-")* }
netgroup_domain = @{ domain_name | "-" }
domain_name     = @{ (CASED_LETTER | NUMBER) ~ (CASED_LETTER | NUMBER | "-" | ".")* }

// ### END NETWORK ###
// ### USER & COMMAND ###

User_List    = _{ User | User ~ "," ~ User_List }
User         =  {
    Negate* ~ User_Name
}
User_Name    = _{ User_Str | id | Group_Name | netgroup }
Group_Name   = @{ "%" ~ User_Str | "%:" ~ id | "%:" ~ SID_Group | "%" ~ Domain_Group }
SID_Group    = @{ "S" ~ ("-" | NUMBER)+ }
Domain_Group = @{ User_Str ~ "@" ~ Domain_Name }
User_Str     = @{ (CASED_LETTER | "_") ~ (((CASED_LETTER | ASCII_DIGIT | "-" | "_"){,30} ~ "$" | CASED_LETTER | ASCII_DIGIT | "-" | "_"){,31}) }

valid_name = _{ !("," | NEWLINE | ":") ~ ANY }
id         = @{ "#" ~ NUMBER+ }

Cmnd_List = _{ Cmnd ~ "," ~ Cmnd_List | Cmnd }

digest = @{
    HEX_DIGIT+
  | (CASED_LETTER | NUMBER | "+" | "/" | "=")+
}

Digest_Spec = @{
    "sha224" ~ ":" ~ digest
  | "sha256" ~ ":" ~ digest
  | "sha384" ~ ":" ~ digest
  | "sha512" ~ ":" ~ digest
}

Cmnd = _{ Digest_Spec? ~ "!"* ~ command_name }

command_name = { valid_name{1, } }

Runas_List = _{ User | User ~ "," ~ Runas_List }

// ### END USER & COMMAND ###
// ### RULES ###

User_Spec = { User_List ~ Host_List ~ "=" ~ Cmnd_Spec_List ~ (":" ~ Host_List ~ "=" ~ Cmnd_Spec_List)* }

Cmnd_Spec_List = _{ Cmnd_Spec ~ "," ~ Cmnd_Spec_List | Cmnd_Spec }

Cmnd_Spec = { Runas_Spec? ~ Option_Spec* ~ (Tag_Spec ~ ":")* ~ Cmnd }

Runas_Spec = { "(" ~ Runas_List? ~ (":" ~ Runas_List)? ~ ")" }

Option_Spec = _{ Entry }

Tag_Spec = {
    "EXEC"
  | "NOEXEC"
  | "FOLLOW"
  | "NOFOLLOW"
  | "LOG_INPUT"
  | "NOLOG_INPUT"
  | "LOG_OUTPUT"
  | "NOLOG_OUTPUT"
  | "MAIL"
  | "NOMAIL"
  | "INTERCEPT"
  | "NOINTERCEPT"
  | "PASSWD"
  | "NOPASSWD"
  | "SETENV"
  | "NOSETENV"
}

Include = { "@" ~ (!NEWLINE ~ ANY)* }

Negate = { "!" }

// ### END RULES ###
// ### COMMENTS ###

Comment    = _{ "#" ~ (!NEWLINE ~ ANY)* }
WHITESPACE = _{ " " | "\t" }

// For any plugin that wants to use the sudoers grammar, this is the entry point
