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:
Todd C. Miller
2022-02-10 18:26:24 -07:00
parent c8bf591042
commit 86d2173937
26 changed files with 3854 additions and 2491 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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] != '/') {
/*

View File

@@ -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)

View 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$

View 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$" }
]
}
]
}
]
}

View 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

View 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$

View 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$

View 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

View 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

View File

@@ -0,0 +1 @@

View 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 <*> #

View 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

View 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

View File

@@ -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 *);

View File

@@ -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;

View File

@@ -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);
}