Add support for matching command and args using regular expressions.
Either the command, its arguments or both may be (separate) regular expressions.
This commit is contained in:
13
MANIFEST
13
MANIFEST
@@ -921,6 +921,17 @@ plugins/sudoers/regress/sudoers/test27.ldif.ok
|
||||
plugins/sudoers/regress/sudoers/test27.ldif2sudo.ok
|
||||
plugins/sudoers/regress/sudoers/test27.out.ok
|
||||
plugins/sudoers/regress/sudoers/test27.toke.ok
|
||||
plugins/sudoers/regress/sudoers/test28.in
|
||||
plugins/sudoers/regress/sudoers/test28.json.ok
|
||||
plugins/sudoers/regress/sudoers/test28.ldif.ok
|
||||
plugins/sudoers/regress/sudoers/test28.ldif2sudo.ok
|
||||
plugins/sudoers/regress/sudoers/test28.out.ok
|
||||
plugins/sudoers/regress/sudoers/test28.toke.ok
|
||||
plugins/sudoers/regress/sudoers/test29.in
|
||||
plugins/sudoers/regress/sudoers/test29.json.ok
|
||||
plugins/sudoers/regress/sudoers/test29.ldif.ok
|
||||
plugins/sudoers/regress/sudoers/test29.out.ok
|
||||
plugins/sudoers/regress/sudoers/test29.toke.ok
|
||||
plugins/sudoers/regress/sudoers/test3.in
|
||||
plugins/sudoers/regress/sudoers/test3.json.ok
|
||||
plugins/sudoers/regress/sudoers/test3.ldif.ok
|
||||
@@ -977,6 +988,8 @@ plugins/sudoers/regress/testsudoers/test16.out.ok
|
||||
plugins/sudoers/regress/testsudoers/test16.sh
|
||||
plugins/sudoers/regress/testsudoers/test17.out.ok
|
||||
plugins/sudoers/regress/testsudoers/test17.sh
|
||||
plugins/sudoers/regress/testsudoers/test18.out.ok
|
||||
plugins/sudoers/regress/testsudoers/test18.sh
|
||||
plugins/sudoers/regress/testsudoers/test2.inc
|
||||
plugins/sudoers/regress/testsudoers/test2.out.ok
|
||||
plugins/sudoers/regress/testsudoers/test2.sh
|
||||
|
18
configure
vendored
18
configure
vendored
@@ -830,6 +830,7 @@ NOEXECFILE
|
||||
intercept_file
|
||||
INTERCEPTDIR
|
||||
INTERCEPTFILE
|
||||
mansectmisc
|
||||
mansectform
|
||||
mansectsu
|
||||
devdir
|
||||
@@ -3567,6 +3568,7 @@ ac_config_headers="$ac_config_headers config.h pathnames.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
@@ -5209,6 +5211,7 @@ printf "%s\n" "$as_me: adding CSOps standard options" >&6;}
|
||||
with_env_editor=yes
|
||||
: ${mansectsu='8'}
|
||||
: ${mansectform='5'}
|
||||
: ${mansectmisc='7'}
|
||||
;;
|
||||
no) ;;
|
||||
*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: ignoring unknown argument to --with-csops: $with_csops" >&5
|
||||
@@ -17094,6 +17097,7 @@ case "$host" in
|
||||
fi
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
test -z "$with_pam" && AUTH_EXCL_DEF="PAM"
|
||||
|
||||
for ac_func in priv_set
|
||||
@@ -17339,6 +17343,7 @@ printf "%s\n" "#define HAVE_DECL_SETAUTHDB $ac_have_decl" >>confdefs.h
|
||||
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
|
||||
# HP-UX does not clear /var/run so we need to do it
|
||||
INIT_SCRIPT=hpux.sh
|
||||
@@ -17376,6 +17381,7 @@ fi
|
||||
fi
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
|
||||
# HP-UX does not clear /var/run so we need to do it
|
||||
INIT_SCRIPT=hpux.sh
|
||||
@@ -17587,6 +17593,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
|
||||
RTLD_PRELOAD_DEFAULT="DEFAULT"
|
||||
: ${mansectsu='8'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-irix*)
|
||||
printf "%s\n" "#define _BSD_TYPES 1" >>confdefs.h
|
||||
@@ -17646,6 +17653,7 @@ fi
|
||||
RTLD_PRELOAD_DEFAULT="DEFAULT"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-linux*|*-*-k*bsd*-gnu)
|
||||
shadow_funcs="getspnam"
|
||||
@@ -17689,18 +17697,21 @@ fi
|
||||
shadow_libs="-lprot -lx"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
m88k-motorola-sysv*)
|
||||
# motorolla's cc (a variant of gcc) does -O but not -O2
|
||||
CFLAGS=`echo $CFLAGS | sed 's/-O2/-O/g'`
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-sequent-sysv*)
|
||||
shadow_funcs="getspnam"
|
||||
shadow_libs="-lsec"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-ncr-sysv4*|*-ncr-sysvr4*)
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for strcasecmp in -lc89" >&5
|
||||
@@ -17745,11 +17756,13 @@ fi
|
||||
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-ccur-sysv4*|*-ccur-sysvr4*)
|
||||
LIBS="${LIBS} -lgen"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-bsdi*)
|
||||
SKIP_SETREUID=yes
|
||||
@@ -18022,10 +18035,12 @@ fi
|
||||
*-*-*sysv4*)
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-*sco3.2*) # SCO OpenServer 5
|
||||
: ${mansectsu='1'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
shadow_funcs="getprpwnam"
|
||||
shadow_libs="-lprot"
|
||||
;;
|
||||
@@ -18033,6 +18048,7 @@ fi
|
||||
*-*-*sysv5*)
|
||||
: ${mansectsu='1'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
case "$host" in
|
||||
*-*-sysv5SCO_SV*) # SCO OpenServer 6.x
|
||||
shadow_funcs="getprpwnam"
|
||||
@@ -18046,6 +18062,7 @@ fi
|
||||
*-*-sysv*)
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -18122,6 +18139,7 @@ fi
|
||||
|
||||
: ${mansectsu='8'}
|
||||
: ${mansectform='5'}
|
||||
: ${mansectmisc='7'}
|
||||
|
||||
if test -n "$with_libpath"; then
|
||||
for i in ${with_libpath}; do
|
||||
|
17
configure.ac
17
configure.ac
@@ -70,6 +70,7 @@ AC_SUBST([SEMAN])
|
||||
AC_SUBST([devdir])
|
||||
AC_SUBST([mansectsu])
|
||||
AC_SUBST([mansectform])
|
||||
AC_SUBST([mansectmisc])
|
||||
AC_SUBST([INTERCEPTFILE])
|
||||
AC_SUBST([INTERCEPTDIR])
|
||||
AC_SUBST([intercept_file])
|
||||
@@ -495,6 +496,7 @@ AC_ARG_WITH(csops, [AS_HELP_STRING([--with-csops], [add CSOps standard options])
|
||||
with_env_editor=yes
|
||||
: ${mansectsu='8'}
|
||||
: ${mansectform='5'}
|
||||
: ${mansectmisc='7'}
|
||||
;;
|
||||
no) ;;
|
||||
*) AC_MSG_WARN([ignoring unknown argument to --with-csops: $with_csops])
|
||||
@@ -1790,6 +1792,7 @@ case "$host" in
|
||||
fi
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
test -z "$with_pam" && AUTH_EXCL_DEF="PAM"
|
||||
AC_CHECK_FUNCS([priv_set], [PSMAN=1])
|
||||
;;
|
||||
@@ -1884,6 +1887,7 @@ case "$host" in
|
||||
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
|
||||
# HP-UX does not clear /var/run so we need to do it
|
||||
INIT_SCRIPT=hpux.sh
|
||||
@@ -1908,6 +1912,7 @@ case "$host" in
|
||||
fi
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
|
||||
# HP-UX does not clear /var/run so we need to do it
|
||||
INIT_SCRIPT=hpux.sh
|
||||
@@ -2022,6 +2027,7 @@ case "$host" in
|
||||
RTLD_PRELOAD_DEFAULT="DEFAULT"
|
||||
: ${mansectsu='8'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-irix*)
|
||||
AC_DEFINE([_BSD_TYPES])
|
||||
@@ -2041,6 +2047,7 @@ case "$host" in
|
||||
RTLD_PRELOAD_DEFAULT="DEFAULT"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-linux*|*-*-k*bsd*-gnu)
|
||||
shadow_funcs="getspnam"
|
||||
@@ -2069,28 +2076,33 @@ case "$host" in
|
||||
shadow_libs="-lprot -lx"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
m88k-motorola-sysv*)
|
||||
# motorolla's cc (a variant of gcc) does -O but not -O2
|
||||
CFLAGS=`echo $CFLAGS | sed 's/-O2/-O/g'`
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-sequent-sysv*)
|
||||
shadow_funcs="getspnam"
|
||||
shadow_libs="-lsec"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-ncr-sysv4*|*-ncr-sysvr4*)
|
||||
AC_CHECK_LIB(c89, strcasecmp, [LIBS="${LIBS} -lc89"])
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-ccur-sysv4*|*-ccur-sysvr4*)
|
||||
LIBS="${LIBS} -lgen"
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-bsdi*)
|
||||
SKIP_SETREUID=yes
|
||||
@@ -2239,10 +2251,12 @@ case "$host" in
|
||||
*-*-*sysv4*)
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
*-*-*sco3.2*) # SCO OpenServer 5
|
||||
: ${mansectsu='1'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
shadow_funcs="getprpwnam"
|
||||
shadow_libs="-lprot"
|
||||
;;
|
||||
@@ -2250,6 +2264,7 @@ case "$host" in
|
||||
*-*-*sysv5*)
|
||||
: ${mansectsu='1'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
case "$host" in
|
||||
*-*-sysv5SCO_SV*) # SCO OpenServer 6.x
|
||||
shadow_funcs="getprpwnam"
|
||||
@@ -2263,6 +2278,7 @@ case "$host" in
|
||||
*-*-sysv*)
|
||||
: ${mansectsu='1m'}
|
||||
: ${mansectform='4'}
|
||||
: ${mansectmisc='5'}
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -2339,6 +2355,7 @@ dnl Use BSD-style man sections by default
|
||||
dnl
|
||||
: ${mansectsu='8'}
|
||||
: ${mansectform='5'}
|
||||
: ${mansectmisc='7'}
|
||||
|
||||
dnl
|
||||
dnl Add in any libpaths or libraries specified via configure
|
||||
|
@@ -1008,13 +1008,20 @@ Digest_List ::= Digest_Spec |
|
||||
Cmnd_List ::= Cmnd |
|
||||
Cmnd ',' Cmnd_List
|
||||
|
||||
command name ::= file name |
|
||||
file name args |
|
||||
file name '""'
|
||||
command name ::= regex |
|
||||
file name
|
||||
|
||||
Edit_Spec ::= "sudoedit" file name+
|
||||
command ::= command name |
|
||||
command name args |
|
||||
command name regex |
|
||||
command name '""' |
|
||||
ALL
|
||||
|
||||
Cmnd ::= Digest_List? '!'* command name |
|
||||
Edit_Spec ::= "sudoedit" file name+ |
|
||||
"sudoedit" regex |
|
||||
"sudoedit"
|
||||
|
||||
Cmnd ::= Digest_List? '!'* command |
|
||||
'!'* directory |
|
||||
'!'* Edit_Spec |
|
||||
'!'* Cmnd_Alias
|
||||
@@ -1023,21 +1030,18 @@ Cmnd ::= Digest_List? '!'* command name |
|
||||
.PP
|
||||
A
|
||||
\fRCmnd_List\fR
|
||||
is a list of one or more command names, directories, and other aliases.
|
||||
A command name is a fully qualified file name which may include
|
||||
is a list of one or more commands, directories, or aliases.
|
||||
A command is a fully qualified file name, which may include
|
||||
shell-style wildcards (see the
|
||||
\fIWildcards\fR
|
||||
section below),
|
||||
or a regular expression that starts with
|
||||
\(oq^\(cq
|
||||
and ends with
|
||||
\(oq$\(cq
|
||||
(see the
|
||||
\fIRegular expressions\fR
|
||||
section below).
|
||||
A simple file name allows the user to run the command with any
|
||||
arguments they wish.
|
||||
However, you may also specify command line arguments (including
|
||||
wildcards).
|
||||
Alternately, you can specify
|
||||
\fR\&""\fR
|
||||
to indicate that the command
|
||||
may only be run
|
||||
\fBwithout\fR
|
||||
command line arguments.
|
||||
A directory is a
|
||||
fully qualified path name ending in a
|
||||
\(oq/\(cq.
|
||||
@@ -1045,21 +1049,49 @@ When you specify a directory in a
|
||||
\fRCmnd_List\fR,
|
||||
the user will be able to run any file within that directory
|
||||
(but not in any sub-directories therein).
|
||||
If no command line arguments are specified, the user may run the
|
||||
command with any arguments they choose.
|
||||
Command line arguments can include wildcards or be a regular
|
||||
expression that starts with
|
||||
\(oq^\(cq
|
||||
and ends with
|
||||
\(oq$\(cq.
|
||||
If the command line arguments consist of
|
||||
\fR\&""\fR,
|
||||
the command may only be run with
|
||||
\fIno\fR
|
||||
arguments.
|
||||
.PP
|
||||
If a
|
||||
\fRCmnd\fR
|
||||
has associated command line arguments, then the arguments
|
||||
has associated command line arguments, the arguments
|
||||
in the
|
||||
\fRCmnd\fR
|
||||
must match exactly those given by the user on the command line
|
||||
(or match the wildcards if there are any).
|
||||
Note that the following characters must be escaped with a
|
||||
must match those given by the user on the command line.
|
||||
If the arguments in a
|
||||
\fRCmnd\fR
|
||||
begin with the
|
||||
\(oq^\(cq
|
||||
character, they will be interpreted as a regular expression
|
||||
and matched accordingly.
|
||||
Otherwise, shell-style wildcards are used when matching.
|
||||
Unless a regular expression is specified, the following characters must
|
||||
be escaped with a
|
||||
\(oq\e\(cq
|
||||
if they are used in command arguments:
|
||||
\(oq,\&\(cq,
|
||||
\(oq:\&\(cq,
|
||||
\(oq=\&\(cq,
|
||||
\(oq\e\(cq.
|
||||
To prevent arguments in a
|
||||
\fRCmnd\fR
|
||||
that begin with a
|
||||
\(oq^\(cq
|
||||
character from being interpreted as a regular expression, the
|
||||
\(oq^\(cq
|
||||
must be escaped with a
|
||||
\(oq\e\(cq.
|
||||
.PP
|
||||
The built-in command
|
||||
\(lq\fRsudoedit\fR\(rq
|
||||
is used to permit a user to run
|
||||
@@ -1076,7 +1108,7 @@ is a command built into
|
||||
itself and must be specified in the
|
||||
\fIsudoers\fR
|
||||
file
|
||||
\fBwithout\fR
|
||||
\fIwithout\fR
|
||||
a leading path.
|
||||
If a leading path is present, for example
|
||||
\fI/usr/bin/sudoedit\fR,
|
||||
@@ -1088,7 +1120,7 @@ is treated as an error by
|
||||
\fBvisudo\fR.
|
||||
.PP
|
||||
A
|
||||
\fRcommand name\fR
|
||||
\fRcommand\fR
|
||||
may be preceded by a
|
||||
\fRDigest_List\fR,
|
||||
a comma-separated list of one or more
|
||||
@@ -1206,6 +1238,8 @@ This is due to there being two levels of escaping, one in the
|
||||
\fIsudoers\fR
|
||||
parser itself and another when command line arguments are matched by the
|
||||
fnmatch(3)
|
||||
or
|
||||
regexec(3)
|
||||
function.
|
||||
.PP
|
||||
Lists have two additional assignment operators,
|
||||
@@ -2101,6 +2135,75 @@ Command line arguments to the
|
||||
built-in command should always be path names, so a forward slash
|
||||
(\(oq/\(cq)
|
||||
will not be matched by a wildcard.
|
||||
.SS "Regular expressions"
|
||||
Starting with version 1.9.10, it is possible to use
|
||||
regular expressions for path names and command line arguments.
|
||||
Regular expressions are more expressive than shell-style
|
||||
\fIwildcards\fR
|
||||
and are usually safer because they provide a greater degree of
|
||||
control when matching.
|
||||
The type of regular expressions supported by
|
||||
\fBsudoers\fR
|
||||
are POSIX extended regular expressions, similar to those used by the
|
||||
egrep(1)
|
||||
utility.
|
||||
They are usually documented in the
|
||||
regex(@mansectmisc@)
|
||||
or
|
||||
re_format(@mansectmisc@)
|
||||
manual, depending on the system.
|
||||
As an extension, if the regular expression begins with
|
||||
\(lq(?i)\(rq,
|
||||
it will be matched in a case-insensitive manner.
|
||||
.PP
|
||||
In
|
||||
\fIsudoers\fR,
|
||||
regular expressions must start with a
|
||||
\(oq^\(cq
|
||||
character and end with a
|
||||
\(oq$\(cq.
|
||||
This makes it explicit what is, or is not, a regular expression.
|
||||
Either the path name, the command line arguments or both may
|
||||
be regular expressions.
|
||||
Because the path name and arguments are matched separately, it is
|
||||
even possible to use wildcards for the path name and regular
|
||||
expressions for the arguments.
|
||||
It is not possible to use a single regular expression to match
|
||||
both the command and its arguments.
|
||||
.PP
|
||||
There is no need to escape
|
||||
\fIsudoers\fR
|
||||
special characters in a regular expression other than the pound sign
|
||||
(\(oq#\(cq).
|
||||
.PP
|
||||
In the following example, user
|
||||
\fBjohn\fR
|
||||
can run the passwd, chsh and chfn commands as root on any host but
|
||||
is not allowed to change root's password database entry.
|
||||
This kind of rule is impossible to express safely using wildcards.
|
||||
.nf
|
||||
.sp
|
||||
.RS 4n
|
||||
john ALL = ^/usr/bin/(passwd|chsh|chfn)$ ^[a-zA-Z0-9_]+$,\e
|
||||
!^/usr/bin/(passwd|chsh|chfn)$ root
|
||||
.RE
|
||||
.fi
|
||||
.PP
|
||||
It is also possible to use a regular expression in conjunction with
|
||||
\fBsudoedit\fR
|
||||
rules.
|
||||
The following rule would give user bob the ability to edit the
|
||||
\fI/etc/motd\fR,
|
||||
\fI/etc/issue\fR,
|
||||
and
|
||||
\fI/etc/hosts\fR
|
||||
files only.
|
||||
.nf
|
||||
.sp
|
||||
.RS 4n
|
||||
bob ALL = sudoedit ^/etc/(motd|issue|hosts)$
|
||||
.RE
|
||||
.fi
|
||||
.SS "Including other files from within sudoers"
|
||||
It is possible to include other
|
||||
\fIsudoers\fR
|
||||
|
@@ -968,13 +968,20 @@ Digest_List ::= Digest_Spec |
|
||||
Cmnd_List ::= Cmnd |
|
||||
Cmnd ',' Cmnd_List
|
||||
|
||||
command name ::= file name |
|
||||
file name args |
|
||||
file name '""'
|
||||
command name ::= regex |
|
||||
file name
|
||||
|
||||
Edit_Spec ::= "sudoedit" file name+
|
||||
command ::= command name |
|
||||
command name args |
|
||||
command name regex |
|
||||
command name '""' |
|
||||
ALL
|
||||
|
||||
Cmnd ::= Digest_List? '!'* command name |
|
||||
Edit_Spec ::= "sudoedit" file name+ |
|
||||
"sudoedit" regex |
|
||||
"sudoedit"
|
||||
|
||||
Cmnd ::= Digest_List? '!'* command |
|
||||
'!'* directory |
|
||||
'!'* Edit_Spec |
|
||||
'!'* Cmnd_Alias
|
||||
@@ -982,21 +989,18 @@ Cmnd ::= Digest_List? '!'* command name |
|
||||
.Pp
|
||||
A
|
||||
.Li Cmnd_List
|
||||
is a list of one or more command names, directories, and other aliases.
|
||||
A command name is a fully qualified file name which may include
|
||||
is a list of one or more commands, directories, or aliases.
|
||||
A command is a fully qualified file name, which may include
|
||||
shell-style wildcards (see the
|
||||
.Sx Wildcards
|
||||
section below),
|
||||
or a regular expression that starts with
|
||||
.Ql ^
|
||||
and ends with
|
||||
.Ql $
|
||||
(see the
|
||||
.Sx Regular expressions
|
||||
section below).
|
||||
A simple file name allows the user to run the command with any
|
||||
arguments they wish.
|
||||
However, you may also specify command line arguments (including
|
||||
wildcards).
|
||||
Alternately, you can specify
|
||||
.Li \&""
|
||||
to indicate that the command
|
||||
may only be run
|
||||
.Sy without
|
||||
command line arguments.
|
||||
A directory is a
|
||||
fully qualified path name ending in a
|
||||
.Ql / .
|
||||
@@ -1004,21 +1008,49 @@ When you specify a directory in a
|
||||
.Li Cmnd_List ,
|
||||
the user will be able to run any file within that directory
|
||||
(but not in any sub-directories therein).
|
||||
If no command line arguments are specified, the user may run the
|
||||
command with any arguments they choose.
|
||||
Command line arguments can include wildcards or be a regular
|
||||
expression that starts with
|
||||
.Ql ^
|
||||
and ends with
|
||||
.Ql $ .
|
||||
If the command line arguments consist of
|
||||
.Li \&"" ,
|
||||
the command may only be run with
|
||||
.Em no
|
||||
arguments.
|
||||
.Pp
|
||||
If a
|
||||
.Li Cmnd
|
||||
has associated command line arguments, then the arguments
|
||||
has associated command line arguments, the arguments
|
||||
in the
|
||||
.Li Cmnd
|
||||
must match exactly those given by the user on the command line
|
||||
(or match the wildcards if there are any).
|
||||
Note that the following characters must be escaped with a
|
||||
must match those given by the user on the command line.
|
||||
If the arguments in a
|
||||
.Li Cmnd
|
||||
begin with the
|
||||
.Ql ^
|
||||
character, they will be interpreted as a regular expression
|
||||
and matched accordingly.
|
||||
Otherwise, shell-style wildcards are used when matching.
|
||||
Unless a regular expression is specified, the following characters must
|
||||
be escaped with a
|
||||
.Ql \e
|
||||
if they are used in command arguments:
|
||||
.Ql ,\& ,
|
||||
.Ql :\& ,
|
||||
.Ql =\& ,
|
||||
.Ql \e .
|
||||
To prevent arguments in a
|
||||
.Li Cmnd
|
||||
that begin with a
|
||||
.Ql ^
|
||||
character from being interpreted as a regular expression, the
|
||||
.Ql ^
|
||||
must be escaped with a
|
||||
.Ql \e .
|
||||
.Pp
|
||||
The built-in command
|
||||
.Dq Li sudoedit
|
||||
is used to permit a user to run
|
||||
@@ -1035,7 +1067,7 @@ is a command built into
|
||||
itself and must be specified in the
|
||||
.Em sudoers
|
||||
file
|
||||
.Sy without
|
||||
.Em without
|
||||
a leading path.
|
||||
If a leading path is present, for example
|
||||
.Pa /usr/bin/sudoedit ,
|
||||
@@ -1047,7 +1079,7 @@ is treated as an error by
|
||||
.Nm visudo .
|
||||
.Pp
|
||||
A
|
||||
.Li command name
|
||||
.Li command
|
||||
may be preceded by a
|
||||
.Li Digest_List ,
|
||||
a comma-separated list of one or more
|
||||
@@ -1156,6 +1188,8 @@ This is due to there being two levels of escaping, one in the
|
||||
.Em sudoers
|
||||
parser itself and another when command line arguments are matched by the
|
||||
.Xr fnmatch 3
|
||||
or
|
||||
.Xr regexec 3
|
||||
function.
|
||||
.Pp
|
||||
Lists have two additional assignment operators,
|
||||
@@ -1979,6 +2013,69 @@ built-in command should always be path names, so a forward slash
|
||||
.Pq Ql /
|
||||
will not be matched by a wildcard.
|
||||
.El
|
||||
.Ss Regular expressions
|
||||
Starting with version 1.9.10, it is possible to use
|
||||
regular expressions for path names and command line arguments.
|
||||
Regular expressions are more expressive than shell-style
|
||||
.Em wildcards
|
||||
and are usually safer because they provide a greater degree of
|
||||
control when matching.
|
||||
The type of regular expressions supported by
|
||||
.Nm
|
||||
are POSIX extended regular expressions, similar to those used by the
|
||||
.Xr egrep 1
|
||||
utility.
|
||||
They are usually documented in the
|
||||
.Xr regex @mansectmisc@
|
||||
or
|
||||
.Xr re_format @mansectmisc@
|
||||
manual, depending on the system.
|
||||
As an extension, if the regular expression begins with
|
||||
.Dq (?i) ,
|
||||
it will be matched in a case-insensitive manner.
|
||||
.Pp
|
||||
In
|
||||
.Em sudoers ,
|
||||
regular expressions must start with a
|
||||
.Ql ^
|
||||
character and end with a
|
||||
.Ql $ .
|
||||
This makes it explicit what is, or is not, a regular expression.
|
||||
Either the path name, the command line arguments or both may
|
||||
be regular expressions.
|
||||
Because the path name and arguments are matched separately, it is
|
||||
even possible to use wildcards for the path name and regular
|
||||
expressions for the arguments.
|
||||
It is not possible to use a single regular expression to match
|
||||
both the command and its arguments.
|
||||
.Pp
|
||||
There is no need to escape
|
||||
.Em sudoers
|
||||
special characters in a regular expression other than the pound sign
|
||||
.Pq Ql # .
|
||||
.Pp
|
||||
In the following example, user
|
||||
.Sy john
|
||||
can run the passwd, chsh and chfn commands as root on any host but
|
||||
is not allowed to change root's password database entry.
|
||||
This kind of rule is impossible to express safely using wildcards.
|
||||
.Bd -literal -offset 4n
|
||||
john ALL = ^/usr/bin/(passwd|chsh|chfn)$ ^[a-zA-Z0-9_]+$,\e
|
||||
!^/usr/bin/(passwd|chsh|chfn)$ root
|
||||
.Ed
|
||||
.Pp
|
||||
It is also possible to use a regular expression in conjunction with
|
||||
.Nm sudoedit
|
||||
rules.
|
||||
The following rule would give user bob the ability to edit the
|
||||
.Pa /etc/motd ,
|
||||
.Pa /etc/issue ,
|
||||
and
|
||||
.Pa /etc/hosts
|
||||
files only.
|
||||
.Bd -literal -offset 4n
|
||||
bob ALL = sudoedit ^/etc/(motd|issue|hosts)$
|
||||
.Ed
|
||||
.Ss Including other files from within sudoers
|
||||
It is possible to include other
|
||||
.Em sudoers
|
||||
|
@@ -69,9 +69,9 @@ root ALL = (ALL:ALL) ALL
|
||||
%wheel ALL = (ALL:ALL) ALL
|
||||
|
||||
# full time sysadmins can run anything on any machine without a password
|
||||
FULLTIMERS ALL = NOPASSWD: ALL
|
||||
FULLTIMERS ALL = (ALL:ALL) NOPASSWD: ALL
|
||||
|
||||
# part time sysadmins may run anything but need a password
|
||||
# part time sysadmins may run anything as root but need a password
|
||||
PARTTIMERS ALL = ALL
|
||||
|
||||
# jack may run anything on machines in CSNETS
|
||||
@@ -88,7 +88,7 @@ operator ALL = DUMPS, KILL, SHUTDOWN, HALT, REBOOT, PRINTING,\
|
||||
joe ALL = /usr/bin/su operator
|
||||
|
||||
# pete may change passwords for anyone but root on the hp snakes
|
||||
pete HPPA = /usr/bin/passwd [A-Za-z]*, !/usr/bin/passwd *root*
|
||||
pete HPPA = /usr/bin/passwd ^[a-zA-Z0-9_]+$, !/usr/bin/passwd root
|
||||
|
||||
# bob may run anything on the sparc and sgi machines as any user
|
||||
# listed in the Runas_Alias "OP" (ie: root and operator)
|
||||
@@ -104,8 +104,8 @@ jim +biglab = ALL
|
||||
# fred can run commands as oracle or sybase without a password
|
||||
fred ALL = (DB) NOPASSWD: ALL
|
||||
|
||||
# on the alphas, john may su to anyone but root and flags are not allowed
|
||||
john ALPHA = /usr/bin/su [!-]*, !/usr/bin/su *root*
|
||||
# on the alphas, john may su to anyone except root, no flags are allowed.
|
||||
john ALPHA = /usr/bin/su ^[a-zA-Z0-9_]+$, !/usr/bin/su root
|
||||
|
||||
# jen can run anything on all machines except the ones
|
||||
# in the "SERVERS" Host_Alias
|
||||
|
@@ -68,11 +68,22 @@ sudoers_format_member_int(struct sudo_lbuf *lbuf,
|
||||
}
|
||||
if (negated)
|
||||
sudo_lbuf_append(lbuf, "!");
|
||||
if (c->cmnd == NULL || c->cmnd[0] == '^') {
|
||||
/* No additional quoting of characters inside a regex. */
|
||||
sudo_lbuf_append(lbuf, "%s", c->cmnd ? c->cmnd : "ALL");
|
||||
} else {
|
||||
sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED_CMD, "%s",
|
||||
c->cmnd ? c->cmnd : "ALL");
|
||||
if (c->args) {
|
||||
c->cmnd);
|
||||
}
|
||||
if (c->args != NULL) {
|
||||
sudo_lbuf_append(lbuf, " ");
|
||||
sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED_ARG, "%s", c->args);
|
||||
if (c->args[0] == '^') {
|
||||
/* No additional quoting of characters inside a regex. */
|
||||
sudo_lbuf_append(lbuf, "%s", c->args);
|
||||
} else {
|
||||
sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED_ARG, "%s",
|
||||
c->args);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case USERGROUP:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 1996, 1998-2005, 2007-2019
|
||||
* Copyright (c) 1996, 1998-2005, 2007-2022
|
||||
* Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
@@ -48,6 +48,7 @@
|
||||
#else
|
||||
# include "compat/fnmatch.h"
|
||||
#endif /* HAVE_FNMATCH */
|
||||
#include <regex.h>
|
||||
|
||||
#include "sudoers.h"
|
||||
#include <gram.h>
|
||||
@@ -56,9 +57,47 @@
|
||||
# define O_EXEC O_PATH
|
||||
#endif
|
||||
|
||||
static bool
|
||||
regex_matches(const char *pattern, const char *str)
|
||||
{
|
||||
int errcode, cflags = REG_EXTENDED|REG_NOSUB;
|
||||
char *copy = NULL;
|
||||
regex_t re;
|
||||
debug_decl(regex_matches, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
/* Check for (?i) to enable case-insensitive matching. */
|
||||
if (strncmp(pattern, "^(?i)", 5) == 0) {
|
||||
cflags |= REG_ICASE;
|
||||
copy = strdup(pattern + 4);
|
||||
if (copy == NULL) {
|
||||
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
||||
debug_return_bool(false);
|
||||
}
|
||||
copy[0] = '^';
|
||||
pattern = copy;
|
||||
}
|
||||
|
||||
errcode = regcomp(&re, pattern, cflags);
|
||||
if (errcode == 0) {
|
||||
errcode = regexec(&re, str, 0, NULL, 0);
|
||||
regfree(&re);
|
||||
} else {
|
||||
char errbuf[1024];
|
||||
|
||||
regerror(errcode, &re, errbuf, sizeof(errbuf));
|
||||
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
|
||||
"unable to compile regular expression \"%s\": %s",
|
||||
pattern, errbuf);
|
||||
}
|
||||
free(copy);
|
||||
|
||||
debug_return_bool(errcode == 0);
|
||||
}
|
||||
|
||||
static bool
|
||||
command_args_match(const char *sudoers_cmnd, const char *sudoers_args)
|
||||
{
|
||||
const char *args = user_args ? user_args : "";
|
||||
int flags = 0;
|
||||
debug_decl(command_args_match, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
@@ -71,14 +110,18 @@ command_args_match(const char *sudoers_cmnd, const char *sudoers_args)
|
||||
|
||||
/*
|
||||
* If args are specified in sudoers, they must match the user args.
|
||||
* If running as sudoedit, all args are assumed to be paths.
|
||||
* Args are matched either as a regular expression or glob pattern.
|
||||
*/
|
||||
if (sudoers_args[0] == '^') {
|
||||
size_t len = strlen(sudoers_args);
|
||||
if (len > 0 && sudoers_args[len - 1] == '$')
|
||||
debug_return_bool(regex_matches(sudoers_args, args));
|
||||
}
|
||||
|
||||
/* If running as sudoedit, all args are assumed to be paths. */
|
||||
if (strcmp(sudoers_cmnd, "sudoedit") == 0)
|
||||
flags = FNM_PATHNAME;
|
||||
if (fnmatch(sudoers_args, user_args ? user_args : "", flags) == 0)
|
||||
debug_return_bool(true);
|
||||
|
||||
debug_return_bool(false);
|
||||
debug_return_bool(fnmatch(sudoers_args, args, flags) == 0);
|
||||
}
|
||||
|
||||
#ifndef SUDOERS_NAME_MATCH
|
||||
@@ -416,6 +459,49 @@ bad:
|
||||
debug_return_bool(false);
|
||||
}
|
||||
|
||||
static bool
|
||||
command_matches_regex(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
const char *runchroot, bool intercepted,
|
||||
const struct command_digest_list *digests)
|
||||
{
|
||||
int fd = -1;
|
||||
debug_decl(command_matches_regex, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
/*
|
||||
* Return true if sudoers_cmnd regex matches user_cmnd AND
|
||||
* a) there are no args in sudoers OR
|
||||
* b) there are no args on command line and none required by sudoers OR
|
||||
* c) there are args in sudoers and on command line and they match
|
||||
* else return false.
|
||||
*
|
||||
* Neither sudoers_cmnd nor user_cmnd are relative to runchroot.
|
||||
*/
|
||||
if (!regex_matches(sudoers_cmnd, user_cmnd))
|
||||
debug_return_bool(false);
|
||||
|
||||
if (command_args_match(sudoers_cmnd, sudoers_args)) {
|
||||
/* Open the file for fdexec or for digest matching. */
|
||||
if (!open_cmnd(user_cmnd, runchroot, digests, &fd))
|
||||
goto bad;
|
||||
#ifndef SUDOERS_NAME_MATCH
|
||||
if (!do_stat(fd, user_cmnd, runchroot, intercepted, NULL))
|
||||
goto bad;
|
||||
#endif
|
||||
/* Check digest of user_cmnd since sudoers_cmnd is a pattern. */
|
||||
if (!digest_matches(fd, user_cmnd, runchroot, digests))
|
||||
goto bad;
|
||||
set_cmnd_fd(fd);
|
||||
|
||||
/* No need to set safe_cmnd since user_cmnd matches sudoers_cmnd */
|
||||
debug_return_bool(true);
|
||||
bad:
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
debug_return_bool(false);
|
||||
}
|
||||
debug_return_bool(false);
|
||||
}
|
||||
|
||||
#ifndef SUDOERS_NAME_MATCH
|
||||
static bool
|
||||
command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
@@ -724,6 +810,13 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Check for regular expressions first. */
|
||||
if (sudoers_cmnd[0] == '^') {
|
||||
rc = command_matches_regex(sudoers_cmnd, sudoers_args, runchroot,
|
||||
intercepted, digests);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Check for pseudo-commands */
|
||||
if (sudoers_cmnd[0] != '/') {
|
||||
/*
|
||||
|
@@ -24,9 +24,9 @@
|
||||
#include "sudo_queue.h"
|
||||
|
||||
/* Characters that must be quoted in sudoers. */
|
||||
#define SUDOERS_QUOTED ":\\,=#\""
|
||||
#define SUDOERS_QUOTED_CMD ":\\,= \t#"
|
||||
#define SUDOERS_QUOTED_ARG ":\\,=#"
|
||||
#define SUDOERS_QUOTED ":,=#\""
|
||||
#define SUDOERS_QUOTED_CMD ":,= \t#"
|
||||
#define SUDOERS_QUOTED_ARG ":,=#"
|
||||
|
||||
/* Returns true if string 's' contains meta characters. */
|
||||
#define has_meta(s) (strpbrk(s, "\\?*[]") != NULL)
|
||||
|
33
plugins/sudoers/regress/sudoers/test28.in
Normal file
33
plugins/sudoers/regress/sudoers/test28.in
Normal file
@@ -0,0 +1,33 @@
|
||||
# Test simple command with regex args
|
||||
user ALL = /bin/ls ^/etc/(hosts|motd|issue)$
|
||||
|
||||
# Test wildcard command with regex args
|
||||
user ALL = /usr/bin/c* ^/etc/(hosts|motd|issue)$
|
||||
|
||||
# Test regex command with no args
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$
|
||||
|
||||
# Test regex command with empty args
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ ""
|
||||
|
||||
# Test regex command with simple args
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ root
|
||||
|
||||
# Test regex command with wildcard args
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ -*
|
||||
|
||||
# Test regex command with regex args
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$
|
||||
|
||||
# Test sudoedit with regex args
|
||||
user ALL = sudoedit ^/etc/(hosts|motd|issue)$
|
||||
|
||||
# Test regex command with escapted '$', no args
|
||||
user ALL = ^/usr/bin/\$tree$
|
||||
|
||||
# Combined entry
|
||||
user host1 = /bin/ls ^/etc/(hosts|motd|issue)$, \
|
||||
/usr/bin/c* ^/etc/(hosts|motd|issue)$ : \
|
||||
host2 = ^/usr/bin/(who|w|id|whoami)$ "", \
|
||||
^/usr/bin/(who|w|id|whoami)$ root : \
|
||||
host3 = /bin/echo ^\$foo$
|
186
plugins/sudoers/regress/sudoers/test28.json.ok
Normal file
186
plugins/sudoers/regress/sudoers/test28.json.ok
Normal file
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"User_Specs": [
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "/bin/ls ^/etc/(hosts|motd|issue)$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "/usr/bin/c* ^/etc/(hosts|motd|issue)$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "^/usr/bin/(who|w|id|whoami)$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "^/usr/bin/(who|w|id|whoami)$ \"\"" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "^/usr/bin/(who|w|id|whoami)$ root" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "^/usr/bin/(who|w|id|whoami)$ -*" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "sudoedit ^/etc/(hosts|motd|issue)$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "ALL" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "^/usr/bin/\\$tree$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "host1" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "/bin/ls ^/etc/(hosts|motd|issue)$" },
|
||||
{ "command": "/usr/bin/c* ^/etc/(hosts|motd|issue)$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "host2" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "^/usr/bin/(who|w|id|whoami)$ \"\"" },
|
||||
{ "command": "^/usr/bin/(who|w|id|whoami)$ root" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"User_List": [
|
||||
{ "username": "user" }
|
||||
],
|
||||
"Host_List": [
|
||||
{ "hostname": "host3" }
|
||||
],
|
||||
"Cmnd_Specs": [
|
||||
{
|
||||
"Commands": [
|
||||
{ "command": "/bin/echo ^\\$foo$" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
110
plugins/sudoers/regress/sudoers/test28.ldif.ok
Normal file
110
plugins/sudoers/regress/sudoers/test28.ldif.ok
Normal file
@@ -0,0 +1,110 @@
|
||||
dn: cn=user,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: /bin/ls ^/etc/(hosts|motd|issue)$
|
||||
sudoOrder: 1
|
||||
|
||||
dn: cn=user_1,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_1
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: /usr/bin/c* ^/etc/(hosts|motd|issue)$
|
||||
sudoOrder: 2
|
||||
|
||||
dn: cn=user_2,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_2
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: ^/usr/bin/(who|w|id|whoami)$
|
||||
sudoOrder: 3
|
||||
|
||||
dn: cn=user_3,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_3
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: ^/usr/bin/(who|w|id|whoami)$ ""
|
||||
sudoOrder: 4
|
||||
|
||||
dn: cn=user_4,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_4
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: ^/usr/bin/(who|w|id|whoami)$ root
|
||||
sudoOrder: 5
|
||||
|
||||
dn: cn=user_5,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_5
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: ^/usr/bin/(who|w|id|whoami)$ -*
|
||||
sudoOrder: 6
|
||||
|
||||
dn: cn=user_6,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_6
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$
|
||||
sudoOrder: 7
|
||||
|
||||
dn: cn=user_7,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_7
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: sudoedit ^/etc/(hosts|motd|issue)$
|
||||
sudoOrder: 8
|
||||
|
||||
dn: cn=user_8,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_8
|
||||
sudoUser: user
|
||||
sudoHost: ALL
|
||||
sudoCommand: ^/usr/bin/\$tree$
|
||||
sudoOrder: 9
|
||||
|
||||
dn: cn=user_9,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_9
|
||||
sudoUser: user
|
||||
sudoHost: host1
|
||||
sudoCommand: /bin/ls ^/etc/(hosts|motd|issue)$
|
||||
sudoCommand: /usr/bin/c* ^/etc/(hosts|motd|issue)$
|
||||
sudoOrder: 10
|
||||
|
||||
dn: cn=user_10,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_10
|
||||
sudoUser: user
|
||||
sudoHost: host2
|
||||
sudoCommand: ^/usr/bin/(who|w|id|whoami)$ ""
|
||||
sudoCommand: ^/usr/bin/(who|w|id|whoami)$ root
|
||||
sudoOrder: 11
|
||||
|
||||
dn: cn=user_11,ou=SUDOers,dc=sudo,dc=ws
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
cn: user_11
|
||||
sudoUser: user
|
||||
sudoHost: host3
|
||||
sudoCommand: /bin/echo ^\$foo$
|
||||
sudoOrder: 12
|
||||
|
10
plugins/sudoers/regress/sudoers/test28.ldif2sudo.ok
Normal file
10
plugins/sudoers/regress/sudoers/test28.ldif2sudo.ok
Normal file
@@ -0,0 +1,10 @@
|
||||
# sudoRole user, user_1, user_2, user_3, user_4, user_5, user_6, user_7,
|
||||
# user_8, user_9, user_10, user_11
|
||||
user ALL = /bin/ls ^/etc/(hosts|motd|issue)$, /usr/bin/c*\
|
||||
^/etc/(hosts|motd|issue)$, ^/usr/bin/(who|w|id|whoami)$,\
|
||||
^/usr/bin/(who|w|id|whoami)$ "", ^/usr/bin/(who|w|id|whoami)$ root,\
|
||||
^/usr/bin/(who|w|id|whoami)$ -*, ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi]\
|
||||
?)+$, sudoedit ^/etc/(hosts|motd|issue)$, ^/usr/bin/\$tree$ : host1 =\
|
||||
/bin/ls ^/etc/(hosts|motd|issue)$, /usr/bin/c* ^/etc/(hosts|motd|issue)$ :\
|
||||
host2 = ^/usr/bin/(who|w|id|whoami)$ "", ^/usr/bin/(who|w|id|whoami)$ root\
|
||||
: host3 = /bin/echo ^\$foo$
|
12
plugins/sudoers/regress/sudoers/test28.out.ok
Normal file
12
plugins/sudoers/regress/sudoers/test28.out.ok
Normal file
@@ -0,0 +1,12 @@
|
||||
Parses OK
|
||||
|
||||
user ALL = /bin/ls ^/etc/(hosts|motd|issue)$
|
||||
user ALL = /usr/bin/c* ^/etc/(hosts|motd|issue)$
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ ""
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ root
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ -*
|
||||
user ALL = ^/usr/bin/(who|w|id|whoami)$ ^(-[ahi] ?)+$
|
||||
user ALL = sudoedit ^/etc/(hosts|motd|issue)$
|
||||
user ALL = ^/usr/bin/\$tree$
|
||||
user host1 = /bin/ls ^/etc/(hosts|motd|issue)$, /usr/bin/c* ^/etc/(hosts|motd|issue)$ : host2 = ^/usr/bin/(who|w|id|whoami)$ "", ^/usr/bin/(who|w|id|whoami)$ root : host3 = /bin/echo ^\$foo$
|
29
plugins/sudoers/regress/sudoers/test28.toke.ok
Normal file
29
plugins/sudoers/regress/sudoers/test28.toke.ok
Normal file
@@ -0,0 +1,29 @@
|
||||
#
|
||||
WORD(6) ALL = COMMAND ARG REGEX
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND ARG REGEX
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND ARG
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND ARG
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND ARG
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND ARG REGEX
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND ARG REGEX
|
||||
|
||||
#
|
||||
WORD(6) ALL = COMMAND
|
||||
|
||||
#
|
||||
WORD(6) WORD(6) = COMMAND ARG REGEX , COMMAND ARG REGEX : WORD(6) = COMMAND ARG , COMMAND ARG : WORD(6) = COMMAND ARG REGEX QUOTEDCHAR
|
11
plugins/sudoers/regress/sudoers/test29.in
Normal file
11
plugins/sudoers/regress/sudoers/test29.in
Normal file
@@ -0,0 +1,11 @@
|
||||
# Test lexer regex syntax errors
|
||||
# We don't test regcomp() errors since regerror() strings are not
|
||||
# standardized.
|
||||
|
||||
user ALL = /bin/ls ^/etc/(hosts|motd|issue
|
||||
|
||||
user ALL = ^/bin/ls
|
||||
|
||||
user ALL = ^/bin/ls$ ^error
|
||||
|
||||
user ALL = ^/bin/ls$ ^error # comment
|
0
plugins/sudoers/regress/sudoers/test29.json.ok
Normal file
0
plugins/sudoers/regress/sudoers/test29.json.ok
Normal file
0
plugins/sudoers/regress/sudoers/test29.ldif.ok
Normal file
0
plugins/sudoers/regress/sudoers/test29.ldif.ok
Normal file
1
plugins/sudoers/regress/sudoers/test29.out.ok
Normal file
1
plugins/sudoers/regress/sudoers/test29.out.ok
Normal file
@@ -0,0 +1 @@
|
||||
|
11
plugins/sudoers/regress/sudoers/test29.toke.ok
Normal file
11
plugins/sudoers/regress/sudoers/test29.toke.ok
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
WORD(6) ALL = COMMAND ARG REGEX ERROR <*>
|
||||
|
||||
WORD(6) ALL = WORD(6) <*>
|
||||
|
||||
WORD(6) ALL = COMMAND ARG REGEX ERROR <*>
|
||||
|
||||
WORD(6) ALL = COMMAND ARG REGEX ERROR <*> #
|
60
plugins/sudoers/regress/testsudoers/test18.out.ok
Normal file
60
plugins/sudoers/regress/testsudoers/test18.out.ok
Normal file
@@ -0,0 +1,60 @@
|
||||
Parses OK
|
||||
|
||||
Entries for user root:
|
||||
|
||||
ALL = ^/usr/bin/w$ ^-[abc]$
|
||||
host matched
|
||||
runas matched
|
||||
cmnd allowed
|
||||
|
||||
Command allowed
|
||||
Parses OK
|
||||
|
||||
Entries for user root:
|
||||
|
||||
ALL = ^/bin/cat$ /var/log/*
|
||||
host matched
|
||||
runas matched
|
||||
cmnd allowed
|
||||
|
||||
Command allowed
|
||||
Parses OK
|
||||
|
||||
Entries for user root:
|
||||
|
||||
ALL = /bin/cat ^/var/log/[^/]+$
|
||||
host matched
|
||||
runas matched
|
||||
cmnd allowed
|
||||
|
||||
Command allowed
|
||||
Parses OK
|
||||
|
||||
Entries for user root:
|
||||
|
||||
ALL = /bin/*at ^/var/log/[^/]+$
|
||||
host matched
|
||||
runas matched
|
||||
cmnd allowed
|
||||
|
||||
Command allowed
|
||||
Parses OK
|
||||
|
||||
Entries for user root:
|
||||
|
||||
ALL = /usr/bin/grep \^foo$
|
||||
host matched
|
||||
runas matched
|
||||
cmnd allowed
|
||||
|
||||
Command allowed
|
||||
Parses OK
|
||||
|
||||
Entries for user root:
|
||||
|
||||
ALL = sudoedit ^/etc/(motd|issue|hosts)$
|
||||
host matched
|
||||
runas matched
|
||||
cmnd allowed
|
||||
|
||||
Command allowed
|
40
plugins/sudoers/regress/testsudoers/test18.sh
Executable file
40
plugins/sudoers/regress/testsudoers/test18.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Test regular expressions
|
||||
#
|
||||
|
||||
: ${TESTSUDOERS=testsudoers}
|
||||
|
||||
exec 2>&1
|
||||
|
||||
# Command and args: regex
|
||||
$TESTSUDOERS root /usr/bin/w -a <<'EOF'
|
||||
root ALL = ^/usr/bin/w$ ^-[abc]$
|
||||
EOF
|
||||
|
||||
# Command: regex, args: wildcard
|
||||
$TESTSUDOERS root /bin/cat /var/log/syslog <<'EOF'
|
||||
root ALL = ^/bin/cat$ /var/log/*
|
||||
EOF
|
||||
|
||||
# Command: path, args: regex
|
||||
$TESTSUDOERS root /bin/cat /var/log/authlog <<'EOF'
|
||||
root ALL = /bin/cat ^/var/log/[^/]+$
|
||||
EOF
|
||||
|
||||
# Command: wildcard, args: regex
|
||||
$TESTSUDOERS root /bin/cat /var/log/mail <<'EOF'
|
||||
root ALL = /bin/*at ^/var/log/[^/]+$
|
||||
EOF
|
||||
|
||||
# Command: path, args: args start with escaped ^
|
||||
$TESTSUDOERS root /usr/bin/grep ^foo$ <<'EOF'
|
||||
root ALL = /usr/bin/grep \^foo$
|
||||
EOF
|
||||
|
||||
# Command: sudoedit, args: regex
|
||||
$TESTSUDOERS root sudoedit /etc/motd <<'EOF'
|
||||
root ALL = sudoedit ^/etc/(motd|issue|hosts)$
|
||||
EOF
|
||||
|
||||
exit 0
|
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ bool fill_args(const char *, size_t, int);
|
||||
bool fill_cmnd(const char *, size_t);
|
||||
bool fill(const char *, size_t);
|
||||
bool ipv6_valid(const char *s);
|
||||
bool regex_valid(const char *pattern, char **errstr);
|
||||
int sudoers_trace_print(const char *);
|
||||
void sudoerserrorf(const char *, ...) __printf0like(1, 2);
|
||||
void sudoerserror(const char *);
|
||||
|
@@ -2,7 +2,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 1996, 1998-2005, 2007-2021
|
||||
* Copyright (c) 1996, 1998-2005, 2007-2022
|
||||
* Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
@@ -102,6 +102,7 @@ HOSTNAME [[:alnum:]_-]+
|
||||
WORD ([^#>!=:,\(\) \t\r\n\\\"]|\\[^\t\n])+
|
||||
ID #-?[0-9]+
|
||||
PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\r\n#])+
|
||||
REGEX \^([^#\r\n\$]|\\[#\$])*\$
|
||||
ENVAR ([^#!=, \t\r\n\\\"]|\\[^\r\n])([^#=, \t\r\n\\\"]|\\[^\r\n])*
|
||||
DEFVAR [a-z_]+
|
||||
|
||||
@@ -112,6 +113,7 @@ DEFVAR [a-z_]+
|
||||
|
||||
%s GOTDEFS
|
||||
%x GOTCMND
|
||||
%x GOTREGEX
|
||||
%x STARTDEFS
|
||||
%x INDEFS
|
||||
%x INSTR
|
||||
@@ -232,7 +234,7 @@ DEFVAR [a-z_]+
|
||||
}
|
||||
|
||||
<GOTCMND>{
|
||||
\\[\*\?\[\]\!] {
|
||||
\\[\*\?\[\]\!^] {
|
||||
/* quoted fnmatch glob char, pass verbatim */
|
||||
LEXTRACE("QUOTEDCHAR ");
|
||||
if (!fill_args(sudoerstext, 2, sawspace))
|
||||
@@ -256,13 +258,69 @@ DEFVAR [a-z_]+
|
||||
} /* end of command line args */
|
||||
|
||||
[^#\\:, \t\r\n]+ {
|
||||
if (sudoerslval.command.args == NULL && sudoerstext[0] == '^') {
|
||||
LEXTRACE("ARG REGEX ");
|
||||
BEGIN GOTREGEX;
|
||||
sudoersless(0);
|
||||
yy_set_bol(0);
|
||||
} else {
|
||||
LEXTRACE("ARG ");
|
||||
if (!fill_args(sudoerstext, sudoersleng, sawspace))
|
||||
yyterminate();
|
||||
sawspace = false;
|
||||
}
|
||||
} /* a command line arg */
|
||||
}
|
||||
|
||||
<GOTREGEX>{
|
||||
\\[^\r\n] {
|
||||
/* quoted character, pass verbatim */
|
||||
LEXTRACE("QUOTEDCHAR ");
|
||||
if (!fill_args(sudoerstext, 2, false))
|
||||
yyterminate();
|
||||
}
|
||||
|
||||
[#\r\n] {
|
||||
/* Let the parser attempt to recover. */
|
||||
sudoersless(0);
|
||||
yy_set_bol(0);
|
||||
BEGIN INITIAL;
|
||||
|
||||
sudoers_errstr = N_("unterminated regular expression");
|
||||
LEXTRACE("ERROR ");
|
||||
return ERROR;
|
||||
} /* illegal inside regex */
|
||||
|
||||
\$ {
|
||||
if (!fill_args("$", 1, false))
|
||||
yyterminate();
|
||||
BEGIN INITIAL;
|
||||
continued = false;
|
||||
if (sudoers_strict) {
|
||||
if (!regex_valid(sudoerstext, &sudoers_errstr)) {
|
||||
LEXTRACE("ERROR ");
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
return COMMAND;
|
||||
}
|
||||
|
||||
[^#\\\r\n$]+ {
|
||||
if (continued) {
|
||||
/* remove whitespace after line continuation */
|
||||
while (isblank((unsigned char)*sudoerstext)) {
|
||||
sudoerstext++;
|
||||
sudoersleng--;
|
||||
}
|
||||
continued = false;
|
||||
}
|
||||
if (sudoersleng != 0) {
|
||||
if (!fill_args(sudoerstext, sudoersleng, false))
|
||||
yyterminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<WANTDIGEST>[[:xdigit:]]+ {
|
||||
/* Only return DIGEST if the length is correct. */
|
||||
yy_size_t digest_len =
|
||||
@@ -647,7 +705,7 @@ ALL {
|
||||
return ALIAS;
|
||||
}
|
||||
|
||||
<GOTDEFS>({PATH}|sudoedit) {
|
||||
<GOTDEFS>({PATH}|{REGEX}|sudoedit) {
|
||||
/* XXX - no way to specify digest for command */
|
||||
/* no command args allowed for Defaults!/path */
|
||||
if (!fill_cmnd(sudoerstext, sudoersleng))
|
||||
@@ -713,6 +771,19 @@ sudoedit {
|
||||
yyterminate();
|
||||
} /* a pathname */
|
||||
|
||||
{REGEX} {
|
||||
if (sudoers_strict) {
|
||||
if (!regex_valid(sudoerstext, &sudoers_errstr)) {
|
||||
LEXTRACE("ERROR ");
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
BEGIN GOTCMND;
|
||||
LEXTRACE("COMMAND ");
|
||||
if (!fill_cmnd(sudoerstext, sudoersleng))
|
||||
yyterminate();
|
||||
} /* a regex */
|
||||
|
||||
<INITIAL,GOTDEFS>\" {
|
||||
LEXTRACE("BEGINSTR ");
|
||||
sudoerslval.string = NULL;
|
||||
|
@@ -31,6 +31,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <regex.h>
|
||||
|
||||
#include "sudoers.h"
|
||||
#include "toke.h"
|
||||
@@ -38,6 +39,7 @@
|
||||
|
||||
static unsigned int arg_len = 0;
|
||||
static unsigned int arg_size = 0;
|
||||
static char errbuf[1024];
|
||||
|
||||
/*
|
||||
* Copy the string and collapse any escaped characters.
|
||||
@@ -133,6 +135,11 @@ fill_cmnd(const char *src, size_t len)
|
||||
}
|
||||
sudoerslval.command.args = NULL;
|
||||
|
||||
if (src[0] == '^') {
|
||||
/* Copy the regular expression, no escaped sudo-specific characters. */
|
||||
memcpy(dst, src, len);
|
||||
dst[len] = '\0';
|
||||
} else {
|
||||
/* Copy the string and collapse any escaped sudo-specific characters. */
|
||||
for (i = 0; i < len; i++) {
|
||||
if (src[i] == '\\' && i != len - 1 && SPECIAL(src[i + 1]))
|
||||
@@ -157,6 +164,7 @@ fill_cmnd(const char *src, size_t len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parser_leak_add(LEAK_PTR, sudoerslval.command.cmnd);
|
||||
debug_return_bool(true);
|
||||
@@ -239,3 +247,36 @@ ipv6_valid(const char *s)
|
||||
|
||||
debug_return_bool(nmatch <= 1);
|
||||
}
|
||||
|
||||
bool
|
||||
regex_valid(const char *pattern, char **errstr)
|
||||
{
|
||||
int errcode, cflags = REG_EXTENDED|REG_NOSUB;
|
||||
char *copy = NULL;
|
||||
regex_t re;
|
||||
debug_decl(regex_valid, SUDOERS_DEBUG_PARSER);
|
||||
|
||||
/* Check for (?i) to enable case-insensitive matching. */
|
||||
if (strncmp(pattern, "^(?i)", 5) == 0) {
|
||||
cflags |= REG_ICASE;
|
||||
copy = strdup(pattern + 4);
|
||||
if (copy == NULL) {
|
||||
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
||||
sudoerserror(NULL);
|
||||
debug_return_bool(false);
|
||||
}
|
||||
copy[0] = '^';
|
||||
pattern = copy;
|
||||
}
|
||||
|
||||
errcode = regcomp(&re, pattern, cflags);
|
||||
if (errcode == 0) {
|
||||
regfree(&re);
|
||||
} else {
|
||||
regerror(errcode, &re, errbuf, sizeof(errbuf));
|
||||
*errstr = errbuf;
|
||||
}
|
||||
free(copy);
|
||||
|
||||
debug_return_bool(errcode == 0);
|
||||
}
|
||||
|
Reference in New Issue
Block a user