2
0
mirror of https://github.com/sudo-project/sudo.git synced 2025-08-22 09:57:41 +00:00

Add support for @include and @includedir

These are less confusing than #include and #includedir when the
hash character is also the comment character.

This commit also adds real parsing of include directives as opposed
to the pure lexer approach used previously.  As a result, it is now
possible to include files with spaces by either using a double-quoted
string or escaping the space characters with a backslash.
This commit is contained in:
Todd C. Miller 2020-05-20 13:10:53 -06:00
parent c63ba01e0e
commit 741c6f274e
26 changed files with 3070 additions and 2602 deletions

View File

@ -809,6 +809,10 @@ plugins/sudoers/regress/sudoers/test9.toke.ok
plugins/sudoers/regress/testsudoers/group plugins/sudoers/regress/testsudoers/group
plugins/sudoers/regress/testsudoers/test1.out.ok plugins/sudoers/regress/testsudoers/test1.out.ok
plugins/sudoers/regress/testsudoers/test1.sh plugins/sudoers/regress/testsudoers/test1.sh
plugins/sudoers/regress/testsudoers/test10.out.ok
plugins/sudoers/regress/testsudoers/test10.sh
plugins/sudoers/regress/testsudoers/test11.out.ok
plugins/sudoers/regress/testsudoers/test11.sh
plugins/sudoers/regress/testsudoers/test2.inc plugins/sudoers/regress/testsudoers/test2.inc
plugins/sudoers/regress/testsudoers/test2.out.ok plugins/sudoers/regress/testsudoers/test2.out.ok
plugins/sudoers/regress/testsudoers/test2.sh plugins/sudoers/regress/testsudoers/test2.sh
@ -824,6 +828,8 @@ plugins/sudoers/regress/testsudoers/test7.out.ok
plugins/sudoers/regress/testsudoers/test7.sh plugins/sudoers/regress/testsudoers/test7.sh
plugins/sudoers/regress/testsudoers/test8.out.ok plugins/sudoers/regress/testsudoers/test8.out.ok
plugins/sudoers/regress/testsudoers/test8.sh plugins/sudoers/regress/testsudoers/test8.sh
plugins/sudoers/regress/testsudoers/test9.out.ok
plugins/sudoers/regress/testsudoers/test9.sh
plugins/sudoers/regress/visudo/test1.out.ok plugins/sudoers/regress/visudo/test1.out.ok
plugins/sudoers/regress/visudo/test1.sh plugins/sudoers/regress/visudo/test1.sh
plugins/sudoers/regress/visudo/test10.out.ok plugins/sudoers/regress/visudo/test10.out.ok

5
NEWS
View File

@ -19,6 +19,11 @@ What's new in Sudo 1.9.1
option to allow replaying a session that is still in progress, option to allow replaying a session that is still in progress,
similar to "tail -f". similar to "tail -f".
* The @include and @includedir directives can be used in sudoers
instead of #include and #includedir. In addition, include paths
may now have embedded white space by either using a double-quoted
string or escaping the space characters with a backslash.
What's new in Sudo 1.9.0 What's new in Sudo 1.9.0
* Fixed a test failure in the strsig_test regress test on FreeBSD. * Fixed a test failure in the strsig_test regress test on FreeBSD.

View File

@ -25,7 +25,7 @@
.nr BA @BAMAN@ .nr BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .nr PS @PSMAN@
.TH "SUDOERS" "@mansectform@" "April 30, 2020" "Sudo @PACKAGE_VERSION@" "File Formats Manual" .TH "SUDOERS" "@mansectform@" "May 19, 2020" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.nh .nh
.if n .ad l .if n .ad l
.SH "NAME" .SH "NAME"
@ -1864,12 +1864,17 @@ It is possible to include other
files from within the files from within the
\fIsudoers\fR \fIsudoers\fR
file currently being parsed using the file currently being parsed using the
\fR@include\fR
and
\fR@includedir\fR
directives.
For compatibility with sudo versions prior to 1.9.1,
\fR#include\fR \fR#include\fR
and and
\fR#includedir\fR \fR#includedir\fR
directives. are also accepted.
.PP .PP
This can be used, for example, to keep a site-wide An include file can be used, for example, to keep a site-wide
\fIsudoers\fR \fIsudoers\fR
file in addition to a local, per-machine file. file in addition to a local, per-machine file.
For the sake of this example the site-wide For the sake of this example the site-wide
@ -1882,13 +1887,12 @@ To include
\fI/etc/sudoers.local\fR \fI/etc/sudoers.local\fR
from within from within
\fI/etc/sudoers\fR \fI/etc/sudoers\fR
we would use the one would use the following line in
following line in
\fI/etc/sudoers\fR: \fI/etc/sudoers\fR:
.nf .nf
.sp .sp
.RS 4n .RS 4n
#include /etc/sudoers.local @include /etc/sudoers.local
.RE .RE
.fi .fi
.PP .PP
@ -1907,6 +1911,16 @@ Files that are included may themselves include other files.
A hard limit of 128 nested include files is enforced to prevent include A hard limit of 128 nested include files is enforced to prevent include
file loops. file loops.
.PP .PP
The path to the include file may contain white space if it is
escaped with a backslash
(\(oq\e\(cq).
Alternately, the entire path may be enclosed in double quotes
(\&""),
in which case no escaping is necessary.
To include a literal backslash in the path,
\(oq\e\e\(cq
should be used.
.PP
If the path to the include file is not fully-qualified (does not If the path to the include file is not fully-qualified (does not
begin with a begin with a
\(oq/\(cq), \(oq/\(cq),
@ -1918,7 +1932,7 @@ contains the line:
.nf .nf
.sp .sp
.RS 4n .RS 4n
\fR#include sudoers.local\fR \fR@include sudoers.local\fR
.RE .RE
.fi .fi
.PP .PP
@ -1934,7 +1948,7 @@ then
.nf .nf
.sp .sp
.RS 4n .RS 4n
#include /etc/sudoers.%h @include /etc/sudoers.%h
.RE .RE
.fi .fi
.PP .PP
@ -1944,7 +1958,7 @@ to include the file
\fI/etc/sudoers.xerxes\fR. \fI/etc/sudoers.xerxes\fR.
.PP .PP
The The
\fR#includedir\fR \fR@includedir\fR
directive can be used to create a directive can be used to create a
\fIsudoers.d\fR \fIsudoers.d\fR
directory that the system package manager can drop directory that the system package manager can drop
@ -1954,7 +1968,7 @@ For example, given:
.nf .nf
.sp .sp
.RS 4n .RS 4n
#includedir /etc/sudoers.d @includedir /etc/sudoers.d
.RE .RE
.fi .fi
.PP .PP
@ -1981,14 +1995,14 @@ Using a consistent number of leading zeroes in the file names can be used
to avoid such problems. to avoid such problems.
After parsing the files in the directory, control returns to the After parsing the files in the directory, control returns to the
file that contained the file that contained the
\fR#includedir\fR \fR@includedir\fR
directive. directive.
.PP .PP
Note that unlike files included via Note that unlike files included via
\fR#include\fR, \fR@include\fR,
\fBvisudo\fR \fBvisudo\fR
will not edit the files in a will not edit the files in a
\fR#includedir\fR \fR@includedir\fR
directory unless one of them contains a syntax error. directory unless one of them contains a syntax error.
It is still possible to run It is still possible to run
\fBvisudo\fR \fBvisudo\fR
@ -4105,7 +4119,7 @@ Entries in this file should either be of the form
\(lq\fRVARIABLE=value\fR\(rq \(lq\fRVARIABLE=value\fR\(rq
or or
\(lq\fRexport VARIABLE=value\fR\(rq. \(lq\fRexport VARIABLE=value\fR\(rq.
The value may optionally be surrounded by single or double quotes. The value may optionally be enclosed in single or double quotes.
Variables in this file are only added if the variable does not already Variables in this file are only added if the variable does not already
exist in the environment. exist in the environment.
This file is considered to be part of the security policy, This file is considered to be part of the security policy,
@ -4332,7 +4346,7 @@ Entries in this file should either be of the form
\(lq\fRVARIABLE=value\fR\(rq \(lq\fRVARIABLE=value\fR\(rq
or or
\(lq\fRexport VARIABLE=value\fR\(rq. \(lq\fRexport VARIABLE=value\fR\(rq.
The value may optionally be surrounded by single or double quotes. The value may optionally be enclosed in single or double quotes.
Variables in this file are only added if the variable does not already Variables in this file are only added if the variable does not already
exist in the environment. exist in the environment.
Unlike Unlike

View File

@ -24,7 +24,7 @@
.nr BA @BAMAN@ .nr BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .nr PS @PSMAN@
.Dd April 30, 2020 .Dd May 19, 2020
.Dt SUDOERS @mansectform@ .Dt SUDOERS @mansectform@
.Os Sudo @PACKAGE_VERSION@ .Os Sudo @PACKAGE_VERSION@
.Sh NAME .Sh NAME
@ -1753,12 +1753,17 @@ It is possible to include other
files from within the files from within the
.Em sudoers .Em sudoers
file currently being parsed using the file currently being parsed using the
.Li @include
and
.Li @includedir
directives.
For compatibility with sudo versions prior to 1.9.1,
.Li #include .Li #include
and and
.Li #includedir .Li #includedir
directives. are also accepted.
.Pp .Pp
This can be used, for example, to keep a site-wide An include file can be used, for example, to keep a site-wide
.Em sudoers .Em sudoers
file in addition to a local, per-machine file. file in addition to a local, per-machine file.
For the sake of this example the site-wide For the sake of this example the site-wide
@ -1771,11 +1776,10 @@ To include
.Pa /etc/sudoers.local .Pa /etc/sudoers.local
from within from within
.Pa /etc/sudoers .Pa /etc/sudoers
we would use the one would use the following line in
following line in
.Pa /etc/sudoers : .Pa /etc/sudoers :
.Bd -literal -offset 4n .Bd -literal -offset 4n
#include /etc/sudoers.local @include /etc/sudoers.local
.Ed .Ed
.Pp .Pp
When When
@ -1793,6 +1797,16 @@ Files that are included may themselves include other files.
A hard limit of 128 nested include files is enforced to prevent include A hard limit of 128 nested include files is enforced to prevent include
file loops. file loops.
.Pp .Pp
The path to the include file may contain white space if it is
escaped with a backslash
.Pq Ql \e .
Alternately, the entire path may be enclosed in double quotes
.Pq \&"" ,
in which case no escaping is necessary.
To include a literal backslash in the path,
.Ql \e\e
should be used.
.Pp
If the path to the include file is not fully-qualified (does not If the path to the include file is not fully-qualified (does not
begin with a begin with a
.Ql / ) , .Ql / ) ,
@ -1802,7 +1816,7 @@ For example, if
.Pa /etc/sudoers .Pa /etc/sudoers
contains the line: contains the line:
.Bd -literal -offset 4n .Bd -literal -offset 4n
.Li #include sudoers.local .Li @include sudoers.local
.Ed .Ed
.Pp .Pp
the file that will be included is the file that will be included is
@ -1815,7 +1829,7 @@ In other words, if the machine's host name is
.Dq xerxes , .Dq xerxes ,
then then
.Bd -literal -offset 4n .Bd -literal -offset 4n
#include /etc/sudoers.%h @include /etc/sudoers.%h
.Ed .Ed
.Pp .Pp
will cause will cause
@ -1824,7 +1838,7 @@ to include the file
.Pa /etc/sudoers.xerxes . .Pa /etc/sudoers.xerxes .
.Pp .Pp
The The
.Li #includedir .Li @includedir
directive can be used to create a directive can be used to create a
.Pa sudoers.d .Pa sudoers.d
directory that the system package manager can drop directory that the system package manager can drop
@ -1832,7 +1846,7 @@ directory that the system package manager can drop
file rules into as part of package installation. file rules into as part of package installation.
For example, given: For example, given:
.Bd -literal -offset 4n .Bd -literal -offset 4n
#includedir /etc/sudoers.d @includedir /etc/sudoers.d
.Ed .Ed
.Pp .Pp
.Nm sudo .Nm sudo
@ -1858,14 +1872,14 @@ Using a consistent number of leading zeroes in the file names can be used
to avoid such problems. to avoid such problems.
After parsing the files in the directory, control returns to the After parsing the files in the directory, control returns to the
file that contained the file that contained the
.Li #includedir .Li @includedir
directive. directive.
.Pp .Pp
Note that unlike files included via Note that unlike files included via
.Li #include , .Li @include ,
.Nm visudo .Nm visudo
will not edit the files in a will not edit the files in a
.Li #includedir .Li @includedir
directory unless one of them contains a syntax error. directory unless one of them contains a syntax error.
It is still possible to run It is still possible to run
.Nm visudo .Nm visudo
@ -3849,7 +3863,7 @@ Entries in this file should either be of the form
.Dq Li VARIABLE=value .Dq Li VARIABLE=value
or or
.Dq Li export VARIABLE=value . .Dq Li export VARIABLE=value .
The value may optionally be surrounded by single or double quotes. The value may optionally be enclosed in single or double quotes.
Variables in this file are only added if the variable does not already Variables in this file are only added if the variable does not already
exist in the environment. exist in the environment.
This file is considered to be part of the security policy, This file is considered to be part of the security policy,
@ -4045,7 +4059,7 @@ Entries in this file should either be of the form
.Dq Li VARIABLE=value .Dq Li VARIABLE=value
or or
.Dq Li export VARIABLE=value . .Dq Li export VARIABLE=value .
The value may optionally be surrounded by single or double quotes. The value may optionally be enclosed in single or double quotes.
Variables in this file are only added if the variable does not already Variables in this file are only added if the variable does not already
exist in the environment. exist in the environment.
Unlike Unlike

File diff suppressed because it is too large Load Diff

View File

@ -6,44 +6,46 @@
#define USERGROUP 262 #define USERGROUP 262
#define WORD 263 #define WORD 263
#define DIGEST 264 #define DIGEST 264
#define DEFAULTS 265 #define INCLUDE 265
#define DEFAULTS_HOST 266 #define INCLUDEDIR 266
#define DEFAULTS_USER 267 #define DEFAULTS 267
#define DEFAULTS_RUNAS 268 #define DEFAULTS_HOST 268
#define DEFAULTS_CMND 269 #define DEFAULTS_USER 269
#define NOPASSWD 270 #define DEFAULTS_RUNAS 270
#define PASSWD 271 #define DEFAULTS_CMND 271
#define NOEXEC 272 #define NOPASSWD 272
#define EXEC 273 #define PASSWD 273
#define SETENV 274 #define NOEXEC 274
#define NOSETENV 275 #define EXEC 275
#define LOG_INPUT 276 #define SETENV 276
#define NOLOG_INPUT 277 #define NOSETENV 277
#define LOG_OUTPUT 278 #define LOG_INPUT 278
#define NOLOG_OUTPUT 279 #define NOLOG_INPUT 279
#define MAIL 280 #define LOG_OUTPUT 280
#define NOMAIL 281 #define NOLOG_OUTPUT 281
#define FOLLOWLNK 282 #define MAIL 282
#define NOFOLLOWLNK 283 #define NOMAIL 283
#define ALL 284 #define FOLLOWLNK 284
#define COMMENT 285 #define NOFOLLOWLNK 285
#define HOSTALIAS 286 #define ALL 286
#define CMNDALIAS 287 #define COMMENT 287
#define USERALIAS 288 #define HOSTALIAS 288
#define RUNASALIAS 289 #define CMNDALIAS 289
#define ERROR 290 #define USERALIAS 290
#define TYPE 291 #define RUNASALIAS 291
#define ROLE 292 #define ERROR 292
#define PRIVS 293 #define TYPE 293
#define LIMITPRIVS 294 #define ROLE 294
#define CMND_TIMEOUT 295 #define PRIVS 295
#define NOTBEFORE 296 #define LIMITPRIVS 296
#define NOTAFTER 297 #define CMND_TIMEOUT 297
#define MYSELF 298 #define NOTBEFORE 298
#define SHA224_TOK 299 #define NOTAFTER 299
#define SHA256_TOK 300 #define MYSELF 300
#define SHA384_TOK 301 #define SHA224_TOK 301
#define SHA512_TOK 302 #define SHA256_TOK 302
#define SHA384_TOK 303
#define SHA512_TOK 304
#ifndef YYSTYPE_DEFINED #ifndef YYSTYPE_DEFINED
#define YYSTYPE_DEFINED #define YYSTYPE_DEFINED
typedef union { typedef union {

View File

@ -92,6 +92,8 @@ static struct command_digest *new_digest(int, char *);
%token <string> USERGROUP /* a usergroup (%NAME) */ %token <string> USERGROUP /* a usergroup (%NAME) */
%token <string> WORD /* a word */ %token <string> WORD /* a word */
%token <string> DIGEST /* a SHA-2 digest */ %token <string> DIGEST /* a SHA-2 digest */
%token <tok> INCLUDE /* @include */
%token <tok> INCLUDEDIR /* @includedir */
%token <tok> DEFAULTS /* Defaults entry */ %token <tok> DEFAULTS /* Defaults entry */
%token <tok> DEFAULTS_HOST /* Host-specific defaults entry */ %token <tok> DEFAULTS_HOST /* Host-specific defaults entry */
%token <tok> DEFAULTS_USER /* User-specific defaults entry */ %token <tok> DEFAULTS_USER /* User-specific defaults entry */
@ -182,6 +184,20 @@ entry : COMMENT {
| error COMMENT { | error COMMENT {
yyerrok; yyerrok;
} }
| INCLUDE WORD {
if (!push_include($2, false)) {
free($2);
YYERROR;
}
free($2);
}
| INCLUDEDIR WORD {
if (!push_include($2, true)) {
free($2);
YYERROR;
}
free($2);
}
| userlist privileges { | userlist privileges {
if (!add_userspec($1, $2)) { if (!add_userspec($1, $2)) {
sudoerserror(N_("unable to allocate memory")); sudoerserror(N_("unable to allocate memory"));

View File

@ -0,0 +1,51 @@
Testing @include of a path with escaped white space
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing @include of a double-quoted path with white space
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing #include of a path with escaped white space
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing #include of a double-quoted path with white space
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed

View File

@ -0,0 +1,44 @@
#!/bin/sh
#
# Test @include of a file with embedded white space
#
# Create test file
TESTDIR="`pwd`/regress/testsudoers"
cat >"$TESTDIR/test 10.inc" <<EOF
root ALL = ALL
EOF
MYUID=`\ls -lnd "$TESTDIR/test 10.inc" | awk '{print $3}'`
MYGID=`\ls -lnd "$TESTDIR/test 10.inc" | awk '{print $4}'`
exec 2>&1
echo "Testing @include of a path with escaped white space"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF
@include $TESTDIR/test\ 10.inc
EOF
echo ""
echo "Testing @include of a double-quoted path with white space"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF
@include "$TESTDIR/test 10.inc"
EOF
echo ""
echo "Testing #include of a path with escaped white space"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF
#include $TESTDIR/test\ 10.inc
EOF
echo ""
echo "Testing #include of a double-quoted path with white space"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF
#include "$TESTDIR/test 10.inc"
EOF
rm -f "$TESTDIR/test 10.inc"
exit 0

View File

@ -0,0 +1,27 @@
Testing @include with garbage after the path name
>>> sudoers: syntax error near line 1 <<<
Parse error in sudoers near line 1.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing #include with garbage after the path name
>>> sudoers: syntax error near line 1 <<<
Parse error in sudoers near line 1.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed

View File

@ -0,0 +1,26 @@
#!/bin/sh
#
# Test @include with garbage after the path name
#
# Avoid warnings about memory leaks when there is a syntax error
ASAN_OPTIONS=detect_leaks=0; export ASAN_OPTIONS
MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
exec 2>&1
echo "Testing @include with garbage after the path name"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<EOF
@include $TESTDIR/test2.inc womp womp
EOF
echo ""
echo "Testing #include with garbage after the path name"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<EOF
#include $TESTDIR/test2.inc womp womp
EOF
exit 0

View File

@ -1,3 +1,18 @@
Testing @include
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing #include
Parses OK. Parses OK.
Entries for user root: Entries for user root:

View File

@ -1,11 +1,21 @@
#!/bin/sh #!/bin/sh
# #
# Test #include facility # Test @include facility
# #
MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'` MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'` MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
exec 2>&1 exec 2>&1
echo "Testing @include"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<EOF
@include $TESTDIR/test2.inc
EOF
echo ""
echo "Testing #include"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<EOF ./testsudoers -U $MYUID -G $MYGID root id <<EOF
#include $TESTDIR/test2.inc #include $TESTDIR/test2.inc
EOF EOF

View File

@ -1,3 +1,44 @@
Testing @includedir of an unquoted path
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing @includedir of a double-quoted path
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing #includedir of an unquoted path
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing #includedir of a double-quoted path
Parses OK. Parses OK.
Entries for user root: Entries for user root:

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# #
# Test #include facility # Test @includedir facility
# #
parentdir="`echo $0 | sed 's:/[^/]*$::'`" parentdir="`echo $0 | sed 's:/[^/]*$::'`"
@ -15,9 +15,35 @@ if [ -d "$parentdir" ]; then
MYUID=`\ls -lnd $TESTDIR/test3.d | awk '{print $3}'` MYUID=`\ls -lnd $TESTDIR/test3.d | awk '{print $3}'`
MYGID=`\ls -lnd $TESTDIR/test3.d | awk '{print $4}'` MYGID=`\ls -lnd $TESTDIR/test3.d | awk '{print $4}'`
exec 2>&1 exec 2>&1
echo "Testing @includedir of an unquoted path"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF
@includedir $TESTDIR/test3.d
EOF
echo ""
echo "Testing @includedir of a double-quoted path"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF
@includedir "$TESTDIR/test3.d"
EOF
echo ""
echo "Testing #includedir of an unquoted path"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF ./testsudoers -U $MYUID -G $MYGID root id <<-EOF
#includedir $TESTDIR/test3.d #includedir $TESTDIR/test3.d
EOF EOF
echo ""
echo "Testing #includedir of a double-quoted path"
echo ""
./testsudoers -U $MYUID -G $MYGID root id <<-EOF
#includedir "$TESTDIR/test3.d"
EOF
rm -rf "${parentdir}/test3.d"
exit 0 exit 0
fi fi

View File

@ -8,7 +8,7 @@ ASAN_OPTIONS=detect_leaks=0; export ASAN_OPTIONS
exec 2>&1 exec 2>&1
./testsudoers -U 1 root id <<EOF ./testsudoers -U 1 root id <<EOF
#include $TESTDIR/test2.inc @include $TESTDIR/test2.inc
EOF EOF
exit 0 exit 0

View File

@ -19,13 +19,13 @@ exec 2>&1
# Test world writable # Test world writable
chmod 666 $TESTFILE chmod 666 $TESTFILE
./testsudoers -U $MYUID -G $MYGID root id <<EOF ./testsudoers -U $MYUID -G $MYGID root id <<EOF
#include $TESTFILE @include $TESTFILE
EOF EOF
# Test group writable # Test group writable
chmod 664 $TESTFILE chmod 664 $TESTFILE
./testsudoers -U $MYUID -G -2 root id <<EOF ./testsudoers -U $MYUID -G -2 root id <<EOF
#include $TESTFILE @include $TESTFILE
EOF EOF
rm -f $TESTFILE rm -f $TESTFILE

View File

@ -1,3 +1,18 @@
Testing @include without a newline
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed
Testing #include without a newline
Parses OK. Parses OK.
Entries for user root: Entries for user root:

View File

@ -1,12 +1,21 @@
#!/bin/sh #!/bin/sh
# #
# Test #include facility w/o a final newline. # Test @include facility w/o a final newline.
# Same as test2.sh but missing the final newline. # Same as test2.sh but missing the final newline.
# #
MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'` MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'` MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
exec 2>&1 exec 2>&1
echo "Testing @include without a newline"
echo ""
printf "@include $TESTDIR/test2.inc" | \
./testsudoers -U $MYUID -G $MYGID root id
echo ""
echo "Testing #include without a newline"
echo ""
printf "#include $TESTDIR/test2.inc" | \ printf "#include $TESTDIR/test2.inc" | \
./testsudoers -U $MYUID -G $MYGID root id ./testsudoers -U $MYUID -G $MYGID root id

View File

@ -0,0 +1,10 @@
Parses OK.
Entries for user root:
ALL = ALL
host matched
runas matched
cmnd allowed
Command allowed

View File

@ -0,0 +1,13 @@
#!/bin/sh
#
# Test #include facility
#
MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
exec 2>&1
./testsudoers -U $MYUID -G $MYGID root id <<EOF
#include $TESTDIR/test2.inc
EOF
exit 0

View File

@ -68,11 +68,12 @@
* 45 sudo 1.8.15, added FOLLOW/NOFOLLOW tags as well as sudoedit_follow and sudoedit_checkdir Defaults. * 45 sudo 1.8.15, added FOLLOW/NOFOLLOW tags as well as sudoedit_follow and sudoedit_checkdir Defaults.
* 46 sudo 1.8.20, added TIMEOUT, NOTBEFORE and NOTAFTER options. * 46 sudo 1.8.20, added TIMEOUT, NOTBEFORE and NOTAFTER options.
* 47 sudo 1.9.0, Cmd_Alias treated as Cmnd_Alias, support for multiple digests per command and for ALL. * 47 sudo 1.9.0, Cmd_Alias treated as Cmnd_Alias, support for multiple digests per command and for ALL.
* 48 sudo 1.9.1, @include and @includedir, include path escaping/quoting.
*/ */
#ifndef SUDOERS_VERSION_H #ifndef SUDOERS_VERSION_H
#define SUDOERS_VERSION_H #define SUDOERS_VERSION_H
#define SUDOERS_GRAMMAR_VERSION 47 #define SUDOERS_GRAMMAR_VERSION 48
#endif /* SUDOERS_VERSION_H */ #endif /* SUDOERS_VERSION_H */

View File

@ -450,12 +450,14 @@ open_sudoers(const char *sudoers, bool doedit, bool *keepopen)
{ {
struct stat sb; struct stat sb;
FILE *fp = NULL; FILE *fp = NULL;
char *sudoers_base; const char *sudoers_base;
debug_decl(open_sudoers, SUDOERS_DEBUG_UTIL); debug_decl(open_sudoers, SUDOERS_DEBUG_UTIL);
sudoers_base = strrchr(sudoers, '/'); sudoers_base = strrchr(sudoers, '/');
if (sudoers_base != NULL) if (sudoers_base != NULL)
sudoers_base++; sudoers_base++;
else
sudoers_base = sudoers;
switch (sudo_secure_file(sudoers, sudoers_uid, sudoers_gid, &sb)) { switch (sudo_secure_file(sudoers, sudoers_uid, sudoers_gid, &sb)) {
case SUDO_PATH_SECURE: case SUDO_PATH_SECURE:

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,9 @@ bool fill_args(const char *, size_t, int);
bool fill_cmnd(const char *, size_t); bool fill_cmnd(const char *, size_t);
bool fill_txt(const char *, size_t, size_t); bool fill_txt(const char *, size_t, size_t);
bool ipv6_valid(const char *s); bool ipv6_valid(const char *s);
int sudoers_trace_print(const char *msg); int sudoers_trace_print(const char *);
void sudoerserror(const char *); void sudoerserror(const char *);
bool push_include(const char *, bool);
#ifndef FLEX_SCANNER #ifndef FLEX_SCANNER
extern int (*trace_print)(const char *msg); extern int (*trace_print)(const char *msg);

View File

@ -64,9 +64,7 @@ static bool continued, sawspace;
static int prev_state; static int prev_state;
static int digest_type = -1; static int digest_type = -1;
static bool push_include_int(char *, bool);
static bool pop_include(void); static bool pop_include(void);
static char *parse_include_int(const char *, bool);
int (*trace_print)(const char *msg) = sudoers_trace_print; int (*trace_print)(const char *msg) = sudoers_trace_print;
@ -76,11 +74,6 @@ int (*trace_print)(const char *msg) = sudoers_trace_print;
} while (0) } while (0)
#define ECHO ignore_result(fwrite(sudoerstext, sudoersleng, 1, sudoersout)) #define ECHO ignore_result(fwrite(sudoerstext, sudoersleng, 1, sudoersout))
#define parse_include(_p) (parse_include_int((_p), false))
#define parse_includedir(_p) (parse_include_int((_p), true))
#define push_include(_p) (push_include_int((_p), false))
#define push_includedir(_p) (push_include_int((_p), true))
%} %}
HEX16 [0-9A-Fa-f]{1,4} HEX16 [0-9A-Fa-f]{1,4}
@ -106,6 +99,7 @@ DEFVAR [a-z_]+
%x INDEFS %x INDEFS
%x INSTR %x INSTR
%s WANTDIGEST %s WANTDIGEST
%x GOTINC
%% %%
<GOTDEFS>[[:blank:]]*,[[:blank:]]* { <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
@ -280,43 +274,54 @@ DEFVAR [a-z_]+
yyless(sudoersleng); yyless(sudoersleng);
} /* base64 digest */ } /* base64 digest */
<INITIAL>^#include[[:blank:]]+.*(\r\n|\n)? { <INITIAL>@include {
char *path;
if (continued) { if (continued) {
LEXTRACE("ERROR "); LEXTRACE("ERROR ");
LEXRETURN(ERROR); LEXRETURN(ERROR);
} }
if ((path = parse_include(sudoerstext)) == NULL) BEGIN GOTINC;
yyterminate(); LEXTRACE("INCLUDE ");
LEXRETURN(INCLUDE);
}
LEXTRACE("INCLUDE\n"); <INITIAL>@includedir {
if (continued) {
LEXTRACE("ERROR ");
LEXRETURN(ERROR);
}
/* Push current buffer and switch to include file */ BEGIN GOTINC;
if (!push_include(path)) LEXTRACE("INCLUDEDIR ");
yyterminate(); LEXRETURN(INCLUDEDIR);
}
<INITIAL>^#include[[:blank:]]+.*(\r\n|\n)? {
if (continued) {
LEXTRACE("ERROR ");
LEXRETURN(ERROR);
}
/* only consume #include */
yyless(sizeof("#include") - 1);
BEGIN GOTINC;
LEXTRACE("INCLUDE ");
LEXRETURN(INCLUDE);
} }
<INITIAL>^#includedir[[:blank:]]+.*(\r\n|\n)? { <INITIAL>^#includedir[[:blank:]]+.*(\r\n|\n)? {
char *path;
if (continued) { if (continued) {
LEXTRACE("ERROR "); LEXTRACE("ERROR ");
LEXRETURN(ERROR); LEXRETURN(ERROR);
} }
if ((path = parse_includedir(sudoerstext)) == NULL) /* only consume #includedir */
yyterminate(); yyless(sizeof("#includedir") - 1);
LEXTRACE("INCLUDEDIR\n"); BEGIN GOTINC;
LEXTRACE("INCLUDEDIR ");
/* LEXRETURN(INCLUDEDIR);
* Push current buffer and switch to include file,
* ignoring missing or empty directories.
*/
if (!push_includedir(path))
yyterminate();
} }
<INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? { <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
@ -638,7 +643,7 @@ sudoedit {
} }
} /* a pathname */ } /* a pathname */
<INITIAL,GOTDEFS>\" { <INITIAL,GOTDEFS>\" {
LEXTRACE("BEGINSTR "); LEXTRACE("BEGINSTR ");
sudoerslval.string = NULL; sudoerslval.string = NULL;
prev_state = YY_START; prev_state = YY_START;
@ -653,6 +658,24 @@ sudoedit {
LEXRETURN(WORD); LEXRETURN(WORD);
} }
<GOTINC>{
[^\"[:space:]]([^[:space:]]|\\[[:blank:]])* {
/* include file/directory */
if (!fill(sudoerstext, sudoersleng))
yyterminate();
BEGIN INITIAL;
LEXTRACE("WORD(6) ");
LEXRETURN(WORD);
}
\" {
LEXTRACE("BEGINSTR ");
sudoerslval.string = NULL;
prev_state = INITIAL;
BEGIN INSTR;
}
}
\( { \( {
LEXTRACE("( "); LEXTRACE("( ");
LEXRETURN('('); LEXRETURN('(');
@ -914,18 +937,91 @@ init_lexer(void)
debug_return; debug_return;
} }
/*
* Expand any embedded %h (host) escapes in the given path and makes
* a relative path fully-qualified based on the current sudoers file.
* Returns a reference-counted string.
*/
static char *
expand_include(const char *opath, size_t olen)
{
const char *cp, *ep;
char *path, *pp;
int dirlen = 0, len;
size_t shost_len = 0;
bool subst = false;
debug_decl(expand_include, SUDOERS_DEBUG_PARSER);
/* Strip double quotes if present. */
if (*opath == '"') {
opath++;
olen -= 2;
}
/* Relative paths are located in the same dir as the sudoers file. */
if (*opath != '/') {
char *dirend = strrchr(sudoers, '/');
if (dirend != NULL)
dirlen = (int)(dirend - sudoers) + 1;
}
len = olen;
for (cp = opath, ep = opath + olen; cp < ep; cp++) {
if (cp[0] == '%' && cp[1] == 'h') {
shost_len = strlen(user_shost);
len += shost_len - 2;
subst = true;
}
}
/* Make a copy of the fully-qualified path and return it. */
path = pp = rcstr_alloc(len + dirlen);
if (path == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
sudoerserror(NULL);
debug_return_str(NULL);
}
if (dirlen) {
memcpy(path, sudoers, dirlen);
pp += dirlen;
}
if (subst) {
/* substitute for %h */
cp = opath;
while (cp < ep) {
if (cp[0] == '%' && cp[1] == 'h') {
memcpy(pp, user_shost, shost_len);
pp += shost_len;
cp += 2;
continue;
}
*pp++ = *cp++;
}
*pp = '\0';
} else {
memcpy(pp, opath, len);
pp[len] = '\0';
}
debug_return_str(path);
}
/* /*
* Open an include file (or file from a directory), push the old * Open an include file (or file from a directory), push the old
* sudoers file buffer and switch to the new one. * sudoers file buffer and switch to the new one.
* A missing or insecure include dir is simply ignored. * A missing or insecure include dir is simply ignored.
* Returns false on error, else true. * Returns false on error, else true.
*/ */
static bool bool
push_include_int(char *path, bool isdir) push_include(const char *opath, bool isdir)
{ {
struct path_list *pl; struct path_list *pl;
char *path;
FILE *fp; FILE *fp;
debug_decl(push_include_int, SUDOERS_DEBUG_PARSER); debug_decl(push_include, SUDOERS_DEBUG_PARSER);
if ((path = expand_include(opath, strlen(opath))) == NULL)
debug_return_bool(false);
/* push current state onto stack */ /* push current state onto stack */
if (idepth >= istacksize) { if (idepth >= istacksize) {
@ -1065,72 +1161,6 @@ pop_include(void)
debug_return_bool(true); debug_return_bool(true);
} }
static char *
parse_include_int(const char *base, bool isdir)
{
const char *cp, *ep;
char *path, *pp;
int dirlen = 0, len = 0, subst = 0;
size_t shost_len = 0;
debug_decl(parse_include, SUDOERS_DEBUG_PARSER);
/* Pull out path from #include line. */
cp = base + (isdir ? sizeof("#includedir") : sizeof("#include"));
while (isblank((unsigned char) *cp))
cp++;
ep = cp;
while (*ep != '\0' && !isspace((unsigned char) *ep)) {
if (ep[0] == '%' && ep[1] == 'h') {
shost_len = strlen(user_shost);
len += shost_len - 2;
subst = 1;
}
ep++;
}
/* Relative paths are located in the same dir as the sudoers file. */
if (*cp != '/') {
char *dirend = strrchr(sudoers, '/');
if (dirend != NULL)
dirlen = (int)(dirend - sudoers) + 1;
}
/* Make a copy of the fully-qualified path and return it. */
len += (int)(ep - cp);
path = pp = rcstr_alloc(len + dirlen);
if (path == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
sudoerserror(NULL);
debug_return_str(NULL);
}
if (dirlen) {
memcpy(path, sudoers, dirlen);
pp += dirlen;
}
if (subst) {
/* substitute for %h */
while (cp < ep) {
if (cp[0] == '%' && cp[1] == 'h') {
memcpy(pp, user_shost, shost_len);
pp += shost_len;
cp += 2;
continue;
}
*pp++ = *cp++;
}
*pp = '\0';
} else {
memcpy(pp, cp, len);
pp[len] = '\0';
}
/* Push any excess characters (e.g. comment, newline) back to the lexer */
if (*ep != '\0')
yyless((int)(ep - base));
debug_return_str(path);
}
#ifdef TRACELEXER #ifdef TRACELEXER
int int
sudoers_trace_print(const char *msg) sudoers_trace_print(const char *msg)