#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <unistd.h>
#ifdef HAVE_GLOB
# include <glob.h>
#else
# include "compat/glob.h"
#endif
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include "sudoers.h"
#include <gram.h>
#ifdef HAVE_FNMATCH
# include <fnmatch.h>
#else
# include "compat/fnmatch.h"
#endif
#if !defined(O_EXEC) && defined(O_PATH)
# define O_EXEC O_PATH
#endif
static bool
command_args_match(const char *sudoers_cmnd, const char *sudoers_args)
{
int flags = 0;
debug_decl(command_args_match, SUDOERS_DEBUG_MATCH)
if (!sudoers_args || (!user_args && !strcmp("\"\"", sudoers_args)))
debug_return_bool(true);
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);
}
static bool
do_stat(int fd, const char *path, struct stat *sb)
{
debug_decl(do_stat, SUDOERS_DEBUG_MATCH)
if (fd != -1)
debug_return_bool(fstat(fd, sb) == 0);
debug_return_bool(stat(path, sb) == 0);
}
static bool
is_script(int fd)
{
bool ret = false;
char magic[2];
debug_decl(is_script, SUDOERS_DEBUG_MATCH)
if (read(fd, magic, 2) == 2) {
if (magic[0] == '#' && magic[1] == '!')
ret = true;
}
if (lseek(fd, (off_t)0, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to rewind script fd");
}
debug_return_int(ret);
}
static bool
open_cmnd(const char *path, const struct command_digest *digest, int *fdp)
{
int fd = -1;
debug_decl(open_cmnd, SUDOERS_DEBUG_MATCH)
if (def_fdexec != always && digest == NULL)
debug_return_bool(true);
fd = open(path, O_RDONLY|O_NONBLOCK);
# ifdef O_EXEC
if (fd == -1 && errno == EACCES && digest == NULL) {
const int saved_errno = errno;
if ((fd = open(path, O_EXEC)) == -1)
errno = saved_errno;
}
# endif
if (fd == -1)
debug_return_bool(false);
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
*fdp = fd;
debug_return_bool(true);
}
static void
set_cmnd_fd(int fd)
{
debug_decl(set_cmnd_fd, SUDOERS_DEBUG_MATCH)
if (cmnd_fd != -1)
close(cmnd_fd);
if (fd != -1) {
if (def_fdexec == never) {
close(fd);
fd = -1;
} else if (is_script(fd)) {
char fdpath[PATH_MAX];
struct stat sb;
int flags;
(void)snprintf(fdpath, sizeof(fdpath), "/dev/fd/%d", fd);
if (stat(fdpath, &sb) != 0) {
close(fd);
fd = -1;
} else {
flags = fcntl(fd, F_GETFD) & ~FD_CLOEXEC;
(void)fcntl(fd, F_SETFD, flags);
}
}
}
cmnd_fd = fd;
debug_return;
}
static bool
command_matches_dir(const char *sudoers_dir, size_t dlen,
const struct command_digest *digest)
{
struct stat sudoers_stat;
struct dirent *dent;
char buf[PATH_MAX];
int fd = -1;
DIR *dirp;
debug_decl(command_matches_dir, SUDOERS_DEBUG_MATCH)
dirp = opendir(sudoers_dir);
if (dirp == NULL)
debug_return_bool(false);
if (strlcpy(buf, sudoers_dir, sizeof(buf)) >= sizeof(buf)) {
closedir(dirp);
debug_return_bool(false);
}
while ((dent = readdir(dirp)) != NULL) {
if (fd != -1) {
close(fd);
fd = -1;
}
buf[dlen] = '\0';
if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
continue;
if (strcmp(user_base, dent->d_name) != 0)
continue;
if (!open_cmnd(buf, digest, &fd))
continue;
if (!do_stat(fd, buf, &sudoers_stat))
continue;
if (user_stat == NULL ||
(user_stat->st_dev == sudoers_stat.st_dev &&
user_stat->st_ino == sudoers_stat.st_ino)) {
if (digest != NULL && !digest_matches(fd, buf, digest))
continue;
free(safe_cmnd);
if ((safe_cmnd = strdup(buf)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
dent = NULL;
}
break;
}
}
closedir(dirp);
if (dent != NULL) {
set_cmnd_fd(fd);
debug_return_bool(true);
}
if (fd != -1)
close(fd);
debug_return_bool(false);
}
static bool
command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args,
const struct command_digest *digest)
{
struct stat sb;
int fd = -1;
debug_decl(command_matches_fnmatch, SUDOERS_DEBUG_MATCH)
if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
debug_return_bool(false);
if (command_args_match(sudoers_cmnd, sudoers_args)) {
if (!open_cmnd(user_cmnd, digest, &fd))
goto bad;
if (!do_stat(fd, user_cmnd, &sb))
goto bad;
if (digest != NULL && !digest_matches(fd, user_cmnd, digest))
goto bad;
set_cmnd_fd(fd);
debug_return_bool(true);
bad:
if (fd != -1) {
close(fd);
fd = -1;
}
debug_return_bool(false);
}
debug_return_bool(false);
}
static bool
command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
const struct command_digest *digest)
{
struct stat sudoers_stat;
bool bad_digest = false;
char **ap, *base, *cp;
int fd = -1;
size_t dlen;
glob_t gl;
debug_decl(command_matches_glob, SUDOERS_DEBUG_MATCH)
dlen = strlen(sudoers_cmnd);
if (sudoers_cmnd[dlen - 1] != '/') {
if ((base = strrchr(sudoers_cmnd, '/')) != NULL) {
base++;
if (!has_meta(base) && strcmp(user_base, base) != 0)
debug_return_bool(false);
}
}
if (glob(sudoers_cmnd, GLOB_NOSORT, NULL, &gl) != 0 || gl.gl_pathc == 0) {
globfree(&gl);
debug_return_bool(false);
}
if (user_cmnd[0] == '/') {
for (ap = gl.gl_pathv; (cp = *ap) != NULL; ap++) {
if (fd != -1) {
close(fd);
fd = -1;
}
if (strcmp(cp, user_cmnd) != 0)
continue;
if (!open_cmnd(cp, digest, &fd))
continue;
if (!do_stat(fd, cp, &sudoers_stat))
continue;
if (user_stat == NULL ||
(user_stat->st_dev == sudoers_stat.st_dev &&
user_stat->st_ino == sudoers_stat.st_ino)) {
if (digest != NULL && !digest_matches(fd, cp, digest)) {
bad_digest = true;
continue;
}
free(safe_cmnd);
if ((safe_cmnd = strdup(cp)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
cp = NULL;
}
} else {
cp = NULL;
}
goto done;
}
}
if (!bad_digest) {
for (ap = gl.gl_pathv; (cp = *ap) != NULL; ap++) {
if (fd != -1) {
close(fd);
fd = -1;
}
dlen = strlen(cp);
if (cp[dlen - 1] == '/') {
if (command_matches_dir(cp, dlen, digest))
debug_return_bool(true);
continue;
}
if ((base = strrchr(cp, '/')) != NULL)
base++;
else
base = cp;
if (strcmp(user_base, base) != 0)
continue;
if (!open_cmnd(cp, digest, &fd))
continue;
if (!do_stat(fd, cp, &sudoers_stat))
continue;
if (user_stat == NULL ||
(user_stat->st_dev == sudoers_stat.st_dev &&
user_stat->st_ino == sudoers_stat.st_ino)) {
if (digest != NULL && !digest_matches(fd, cp, digest))
continue;
free(safe_cmnd);
if ((safe_cmnd = strdup(cp)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
cp = NULL;
}
goto done;
}
}
}
done:
globfree(&gl);
if (cp != NULL) {
if (command_args_match(sudoers_cmnd, sudoers_args)) {
set_cmnd_fd(fd);
debug_return_bool(true);
}
}
if (fd != -1)
close(fd);
debug_return_bool(false);
}
static bool
command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest *digest)
{
struct stat sudoers_stat;
const char *base;
size_t dlen;
int fd = -1;
debug_decl(command_matches_normal, SUDOERS_DEBUG_MATCH)
dlen = strlen(sudoers_cmnd);
if (sudoers_cmnd[dlen - 1] == '/')
debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, digest));
if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
base = sudoers_cmnd;
else
base++;
if (strcmp(user_base, base) != 0)
debug_return_bool(false);
if (!open_cmnd(sudoers_cmnd, digest, &fd))
goto bad;
if (user_stat != NULL && do_stat(fd, sudoers_cmnd, &sudoers_stat)) {
if (user_stat->st_dev != sudoers_stat.st_dev ||
user_stat->st_ino != sudoers_stat.st_ino)
goto bad;
} else {
if (strcmp(user_cmnd, sudoers_cmnd) != 0)
goto bad;
}
if (!command_args_match(sudoers_cmnd, sudoers_args))
goto bad;
if (digest != NULL && !digest_matches(fd, sudoers_cmnd, digest)) {
goto bad;
}
free(safe_cmnd);
if ((safe_cmnd = strdup(sudoers_cmnd)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
set_cmnd_fd(fd);
debug_return_bool(true);
bad:
if (fd != -1)
close(fd);
debug_return_bool(false);
}
bool
command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest *digest)
{
bool rc = false;
debug_decl(command_matches, SUDOERS_DEBUG_MATCH)
if (sudoers_cmnd[0] != '/') {
if (strcmp(sudoers_cmnd, "sudoedit") == 0 &&
strcmp(user_cmnd, "sudoedit") == 0 &&
command_args_match(sudoers_cmnd, sudoers_args)) {
rc = true;
}
goto done;
}
if (has_meta(sudoers_cmnd)) {
if (def_fast_glob)
rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args, digest);
else
rc = command_matches_glob(sudoers_cmnd, sudoers_args, digest);
} else {
rc = command_matches_normal(sudoers_cmnd, sudoers_args, digest);
}
done:
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"user command \"%s%s%s\" matches sudoers command \"%s%s%s\": %s",
user_cmnd, user_args ? " " : "", user_args ? user_args : "",
sudoers_cmnd, sudoers_args ? " " : "", sudoers_args ? sudoers_args : "",
rc ? "true" : "false");
debug_return_bool(rc);
}