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/test1.out.ok
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.out.ok
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/test8.out.ok
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.sh
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,
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
* Fixed a test failure in the strsig_test regress test on FreeBSD.

View File

@ -25,7 +25,7 @@
.nr BA @BAMAN@
.nr LC @LCMAN@
.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
.if n .ad l
.SH "NAME"
@ -1864,12 +1864,17 @@ It is possible to include other
files from within the
\fIsudoers\fR
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
and
\fR#includedir\fR
directives.
are also accepted.
.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
file in addition to a local, per-machine file.
For the sake of this example the site-wide
@ -1882,13 +1887,12 @@ To include
\fI/etc/sudoers.local\fR
from within
\fI/etc/sudoers\fR
we would use the
following line in
one would use the following line in
\fI/etc/sudoers\fR:
.nf
.sp
.RS 4n
#include /etc/sudoers.local
@include /etc/sudoers.local
.RE
.fi
.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
file loops.
.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
begin with a
\(oq/\(cq),
@ -1918,7 +1932,7 @@ contains the line:
.nf
.sp
.RS 4n
\fR#include sudoers.local\fR
\fR@include sudoers.local\fR
.RE
.fi
.PP
@ -1934,7 +1948,7 @@ then
.nf
.sp
.RS 4n
#include /etc/sudoers.%h
@include /etc/sudoers.%h
.RE
.fi
.PP
@ -1944,7 +1958,7 @@ to include the file
\fI/etc/sudoers.xerxes\fR.
.PP
The
\fR#includedir\fR
\fR@includedir\fR
directive can be used to create a
\fIsudoers.d\fR
directory that the system package manager can drop
@ -1954,7 +1968,7 @@ For example, given:
.nf
.sp
.RS 4n
#includedir /etc/sudoers.d
@includedir /etc/sudoers.d
.RE
.fi
.PP
@ -1981,14 +1995,14 @@ Using a consistent number of leading zeroes in the file names can be used
to avoid such problems.
After parsing the files in the directory, control returns to the
file that contained the
\fR#includedir\fR
\fR@includedir\fR
directive.
.PP
Note that unlike files included via
\fR#include\fR,
\fR@include\fR,
\fBvisudo\fR
will not edit the files in a
\fR#includedir\fR
\fR@includedir\fR
directory unless one of them contains a syntax error.
It is still possible to run
\fBvisudo\fR
@ -4105,7 +4119,7 @@ Entries in this file should either be of the form
\(lq\fRVARIABLE=value\fR\(rq
or
\(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
exist in the environment.
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
or
\(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
exist in the environment.
Unlike

View File

@ -24,7 +24,7 @@
.nr BA @BAMAN@
.nr LC @LCMAN@
.nr PS @PSMAN@
.Dd April 30, 2020
.Dd May 19, 2020
.Dt SUDOERS @mansectform@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@ -1753,12 +1753,17 @@ It is possible to include other
files from within the
.Em sudoers
file currently being parsed using the
.Li @include
and
.Li @includedir
directives.
For compatibility with sudo versions prior to 1.9.1,
.Li #include
and
.Li #includedir
directives.
are also accepted.
.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
file in addition to a local, per-machine file.
For the sake of this example the site-wide
@ -1771,11 +1776,10 @@ To include
.Pa /etc/sudoers.local
from within
.Pa /etc/sudoers
we would use the
following line in
one would use the following line in
.Pa /etc/sudoers :
.Bd -literal -offset 4n
#include /etc/sudoers.local
@include /etc/sudoers.local
.Ed
.Pp
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
file loops.
.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
begin with a
.Ql / ) ,
@ -1802,7 +1816,7 @@ For example, if
.Pa /etc/sudoers
contains the line:
.Bd -literal -offset 4n
.Li #include sudoers.local
.Li @include sudoers.local
.Ed
.Pp
the file that will be included is
@ -1815,7 +1829,7 @@ In other words, if the machine's host name is
.Dq xerxes ,
then
.Bd -literal -offset 4n
#include /etc/sudoers.%h
@include /etc/sudoers.%h
.Ed
.Pp
will cause
@ -1824,7 +1838,7 @@ to include the file
.Pa /etc/sudoers.xerxes .
.Pp
The
.Li #includedir
.Li @includedir
directive can be used to create a
.Pa sudoers.d
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.
For example, given:
.Bd -literal -offset 4n
#includedir /etc/sudoers.d
@includedir /etc/sudoers.d
.Ed
.Pp
.Nm sudo
@ -1858,14 +1872,14 @@ Using a consistent number of leading zeroes in the file names can be used
to avoid such problems.
After parsing the files in the directory, control returns to the
file that contained the
.Li #includedir
.Li @includedir
directive.
.Pp
Note that unlike files included via
.Li #include ,
.Li @include ,
.Nm visudo
will not edit the files in a
.Li #includedir
.Li @includedir
directory unless one of them contains a syntax error.
It is still possible to run
.Nm visudo
@ -3849,7 +3863,7 @@ Entries in this file should either be of the form
.Dq Li VARIABLE=value
or
.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
exist in the environment.
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
or
.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
exist in the environment.
Unlike

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -92,6 +92,8 @@ static struct command_digest *new_digest(int, char *);
%token <string> USERGROUP /* a usergroup (%NAME) */
%token <string> WORD /* a word */
%token <string> DIGEST /* a SHA-2 digest */
%token <tok> INCLUDE /* @include */
%token <tok> INCLUDEDIR /* @includedir */
%token <tok> DEFAULTS /* Defaults entry */
%token <tok> DEFAULTS_HOST /* Host-specific defaults entry */
%token <tok> DEFAULTS_USER /* User-specific defaults entry */
@ -182,6 +184,20 @@ entry : COMMENT {
| error COMMENT {
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 {
if (!add_userspec($1, $2)) {
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.
Entries for user root:

View File

@ -1,11 +1,21 @@
#!/bin/sh
#
# Test #include facility
# Test @include facility
#
MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
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
#include $TESTDIR/test2.inc
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.
Entries for user root:

View File

@ -1,6 +1,6 @@
#!/bin/sh
#
# Test #include facility
# Test @includedir facility
#
parentdir="`echo $0 | sed 's:/[^/]*$::'`"
@ -15,9 +15,35 @@ if [ -d "$parentdir" ]; then
MYUID=`\ls -lnd $TESTDIR/test3.d | awk '{print $3}'`
MYGID=`\ls -lnd $TESTDIR/test3.d | awk '{print $4}'`
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
#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
rm -rf "${parentdir}/test3.d"
exit 0
fi

View File

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

View File

@ -19,13 +19,13 @@ exec 2>&1
# Test world writable
chmod 666 $TESTFILE
./testsudoers -U $MYUID -G $MYGID root id <<EOF
#include $TESTFILE
@include $TESTFILE
EOF
# Test group writable
chmod 664 $TESTFILE
./testsudoers -U $MYUID -G -2 root id <<EOF
#include $TESTFILE
@include $TESTFILE
EOF
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.
Entries for user root:

View File

@ -1,12 +1,21 @@
#!/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.
#
MYUID=`\ls -ln $TESTDIR/test2.inc | awk '{print $3}'`
MYGID=`\ls -ln $TESTDIR/test2.inc | awk '{print $4}'`
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" | \
./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.
* 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.
* 48 sudo 1.9.1, @include and @includedir, include path escaping/quoting.
*/
#ifndef SUDOERS_VERSION_H
#define SUDOERS_VERSION_H
#define SUDOERS_GRAMMAR_VERSION 47
#define SUDOERS_GRAMMAR_VERSION 48
#endif /* SUDOERS_VERSION_H */

View File

@ -450,12 +450,14 @@ open_sudoers(const char *sudoers, bool doedit, bool *keepopen)
{
struct stat sb;
FILE *fp = NULL;
char *sudoers_base;
const char *sudoers_base;
debug_decl(open_sudoers, SUDOERS_DEBUG_UTIL);
sudoers_base = strrchr(sudoers, '/');
if (sudoers_base != NULL)
sudoers_base++;
else
sudoers_base = sudoers;
switch (sudo_secure_file(sudoers, sudoers_uid, sudoers_gid, &sb)) {
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_txt(const char *, size_t, size_t);
bool ipv6_valid(const char *s);
int sudoers_trace_print(const char *msg);
int sudoers_trace_print(const char *);
void sudoerserror(const char *);
bool push_include(const char *, bool);
#ifndef FLEX_SCANNER
extern int (*trace_print)(const char *msg);

View File

@ -64,9 +64,7 @@ static bool continued, sawspace;
static int prev_state;
static int digest_type = -1;
static bool push_include_int(char *, bool);
static bool pop_include(void);
static char *parse_include_int(const char *, bool);
int (*trace_print)(const char *msg) = sudoers_trace_print;
@ -76,11 +74,6 @@ int (*trace_print)(const char *msg) = sudoers_trace_print;
} while (0)
#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}
@ -106,6 +99,7 @@ DEFVAR [a-z_]+
%x INDEFS
%x INSTR
%s WANTDIGEST
%x GOTINC
%%
<GOTDEFS>[[:blank:]]*,[[:blank:]]* {
@ -280,43 +274,54 @@ DEFVAR [a-z_]+
yyless(sudoersleng);
} /* base64 digest */
<INITIAL>^#include[[:blank:]]+.*(\r\n|\n)? {
char *path;
<INITIAL>@include {
if (continued) {
LEXTRACE("ERROR ");
LEXRETURN(ERROR);
}
if ((path = parse_include(sudoerstext)) == NULL)
yyterminate();
BEGIN GOTINC;
LEXTRACE("INCLUDE ");
LEXRETURN(INCLUDE);
}
LEXTRACE("INCLUDE\n");
<INITIAL>@includedir {
if (continued) {
LEXTRACE("ERROR ");
LEXRETURN(ERROR);
}
/* Push current buffer and switch to include file */
if (!push_include(path))
yyterminate();
BEGIN GOTINC;
LEXTRACE("INCLUDEDIR ");
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)? {
char *path;
if (continued) {
LEXTRACE("ERROR ");
LEXRETURN(ERROR);
}
if ((path = parse_includedir(sudoerstext)) == NULL)
yyterminate();
/* only consume #includedir */
yyless(sizeof("#includedir") - 1);
LEXTRACE("INCLUDEDIR\n");
/*
* Push current buffer and switch to include file,
* ignoring missing or empty directories.
*/
if (!push_includedir(path))
yyterminate();
BEGIN GOTINC;
LEXTRACE("INCLUDEDIR ");
LEXRETURN(INCLUDEDIR);
}
<INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
@ -653,6 +658,24 @@ sudoedit {
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("( ");
LEXRETURN('(');
@ -914,18 +937,91 @@ init_lexer(void)
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
* sudoers file buffer and switch to the new one.
* A missing or insecure include dir is simply ignored.
* Returns false on error, else true.
*/
static bool
push_include_int(char *path, bool isdir)
bool
push_include(const char *opath, bool isdir)
{
struct path_list *pl;
char *path;
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 */
if (idepth >= istacksize) {
@ -1065,72 +1161,6 @@ pop_include(void)
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
int
sudoers_trace_print(const char *msg)