version 1.19.2.1, 2009/02/22 21:56:32 |
version 1.20, 2008/11/14 11:58:08 |
|
|
/* |
/* |
* Copyright (c) 1996, 1998-2005, 2007 |
* Copyright (c) 2004-2005, 2007-2008 Todd C. Miller <Todd.Miller@courtesan.com> |
* Todd C. Miller <Todd.Miller@courtesan.com> |
|
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
* |
|
* Sponsored in part by the Defense Advanced Research Projects |
|
* Agency (DARPA) and Air Force Research Laboratory, Air Force |
|
* Materiel Command, USAF, under agreement number F39502-99-1-0512. |
|
*/ |
*/ |
|
|
#include <config.h> |
#include <config.h> |
|
|
#include <sys/types.h> |
#include <sys/types.h> |
#include <sys/param.h> |
#include <sys/param.h> |
#include <sys/socket.h> |
|
#include <sys/stat.h> |
|
#include <stdio.h> |
#include <stdio.h> |
#ifdef STDC_HEADERS |
#ifdef STDC_HEADERS |
# include <stdlib.h> |
# include <stdlib.h> |
|
|
#ifdef HAVE_UNISTD_H |
#ifdef HAVE_UNISTD_H |
# include <unistd.h> |
# include <unistd.h> |
#endif /* HAVE_UNISTD_H */ |
#endif /* HAVE_UNISTD_H */ |
#ifdef HAVE_FNMATCH |
|
# include <fnmatch.h> |
|
#endif /* HAVE_FNMATCH */ |
|
#ifdef HAVE_EXTENDED_GLOB |
|
# include <glob.h> |
|
#endif /* HAVE_EXTENDED_GLOB */ |
|
#ifdef HAVE_NETGROUP_H |
|
# include <netgroup.h> |
|
#endif /* HAVE_NETGROUP_H */ |
|
#include <ctype.h> |
#include <ctype.h> |
#include <pwd.h> |
#include <pwd.h> |
#include <grp.h> |
#include <grp.h> |
#include <netinet/in.h> |
|
#include <arpa/inet.h> |
|
#include <netdb.h> |
|
#ifdef HAVE_DIRENT_H |
|
# include <dirent.h> |
|
# define NAMLEN(dirent) strlen((dirent)->d_name) |
|
#else |
|
# define dirent direct |
|
# define NAMLEN(dirent) (dirent)->d_namlen |
|
# ifdef HAVE_SYS_NDIR_H |
|
# include <sys/ndir.h> |
|
# endif |
|
# ifdef HAVE_SYS_DIR_H |
|
# include <sys/dir.h> |
|
# endif |
|
# ifdef HAVE_NDIR_H |
|
# include <ndir.h> |
|
# endif |
|
#endif |
|
|
|
#include "sudo.h" |
#include "sudo.h" |
#include "parse.h" |
#include "parse.h" |
#include "interfaces.h" |
#include "lbuf.h" |
|
#include <gram.h> |
|
|
#ifndef HAVE_FNMATCH |
|
# include "emul/fnmatch.h" |
|
#endif /* HAVE_FNMATCH */ |
|
#ifndef HAVE_EXTENDED_GLOB |
|
# include "emul/glob.h" |
|
#endif /* HAVE_EXTENDED_GLOB */ |
|
|
|
#ifndef lint |
#ifndef lint |
__unused static const char rcsid[] = "$Sudo: parse.c,v 1.160.2.16 2008/02/09 14:44:48 millert Exp $"; |
__unused static const char rcsid[] = "$Sudo: parse.c,v 1.236 2008/11/09 14:13:12 millert Exp $"; |
#endif /* lint */ |
#endif /* lint */ |
|
|
|
/* Characters that must be quoted in sudoers */ |
|
#define SUDOERS_QUOTED ":\\,=#\"" |
|
|
|
/* sudoers nsswitch routines */ |
|
struct sudo_nss sudo_nss_file = { |
|
&sudo_nss_file, |
|
NULL, |
|
sudo_file_open, |
|
sudo_file_close, |
|
sudo_file_parse, |
|
sudo_file_setdefs, |
|
sudo_file_lookup, |
|
sudo_file_display_cmnd, |
|
sudo_file_display_defaults, |
|
sudo_file_display_bound_defaults, |
|
sudo_file_display_privs |
|
}; |
|
|
/* |
/* |
* Globals |
* Parser externs. |
*/ |
*/ |
int parse_error = FALSE; |
extern FILE *yyin; |
extern int keepall; |
extern char *errorfile; |
extern FILE *yyin, *yyout; |
extern int errorlineno, parse_error; |
|
|
/* |
/* |
* Prototypes |
* Local prototypes. |
*/ |
*/ |
static int has_meta __P((char *)); |
static void print_member __P((struct lbuf *, char *, int, int, int)); |
void init_parser __P((void)); |
static int display_bound_defaults __P((int, struct lbuf *)); |
|
|
|
int |
|
sudo_file_open(nss) |
|
struct sudo_nss *nss; |
|
{ |
|
if (def_ignore_local_sudoers) |
|
return(-1); |
|
nss->handle = open_sudoers(_PATH_SUDOERS, NULL); |
|
return(nss->handle ? 0 : -1); |
|
} |
|
|
|
int |
|
sudo_file_close(nss) |
|
struct sudo_nss *nss; |
|
{ |
|
/* Free parser data structures and close sudoers file. */ |
|
init_parser(NULL, 0); |
|
if (nss->handle != NULL) { |
|
fclose(nss->handle); |
|
nss->handle = NULL; |
|
yyin = NULL; |
|
} |
|
return(0); |
|
} |
|
|
/* |
/* |
* Look up the user in the sudoers file and check to see if they are |
* Parse the specified sudoers file. |
* allowed to run the specified command on this host as the target user. |
|
*/ |
*/ |
int |
int |
sudoers_lookup(pwflag) |
sudo_file_parse(nss) |
int pwflag; |
struct sudo_nss *nss; |
{ |
{ |
int error, nopass; |
if (nss->handle == NULL) |
|
return(-1); |
|
|
/* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */ |
init_parser(_PATH_SUDOERS, 0); |
rewind(sudoers_fp); |
yyin = nss->handle; |
yyin = sudoers_fp; |
if (yyparse() != 0 || parse_error) { |
yyout = stdout; |
log_error(NO_EXIT, "parse error in %s near line %d", |
|
errorfile, errorlineno); |
|
return(-1); |
|
} |
|
return(0); |
|
} |
|
|
/* Allocate space for data structures in the parser. */ |
/* |
init_parser(); |
* Wrapper around update_defaults() for nsswitch code. |
|
*/ |
|
int |
|
sudo_file_setdefs(nss) |
|
struct sudo_nss *nss; |
|
{ |
|
if (nss->handle == NULL) |
|
return(-1); |
|
|
/* Keep more state for pseudo-commands so that listpw and verifypw work */ |
if (!update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER)) |
if (pwflag > 0) |
return(-1); |
keepall = TRUE; |
return(0); |
|
} |
|
|
/* Need to be runas user while stat'ing things in the parser. */ |
/* |
set_perms(PERM_RUNAS); |
* Look up the user in the parsed sudoers file and check to see if they are |
error = yyparse(); |
* allowed to run the specified command on this host as the target user. |
|
*/ |
|
int |
|
sudo_file_lookup(nss, validated, pwflag) |
|
struct sudo_nss *nss; |
|
int validated; |
|
int pwflag; |
|
{ |
|
int match, host_match, runas_match, cmnd_match; |
|
struct cmndspec *cs; |
|
struct cmndtag *tags = NULL; |
|
struct privilege *priv; |
|
struct userspec *us; |
|
|
/* Close the sudoers file now that we are done with it. */ |
if (nss->handle == NULL) |
(void) fclose(sudoers_fp); |
return(validated); |
sudoers_fp = NULL; |
|
|
|
if (error || parse_error) { |
|
set_perms(PERM_ROOT); |
|
return(VALIDATE_ERROR); |
|
} |
|
|
|
/* |
/* |
* Assume the worst. If the stack is empty the user was |
|
* not mentioned at all. |
|
*/ |
|
if (def_authenticate) |
|
error = VALIDATE_NOT_OK; |
|
else |
|
error = VALIDATE_NOT_OK | FLAG_NOPASS; |
|
if (pwflag) { |
|
SET(error, FLAG_NO_CHECK); |
|
} else { |
|
SET(error, FLAG_NO_HOST); |
|
if (!top) |
|
SET(error, FLAG_NO_USER); |
|
} |
|
|
|
/* |
|
* Only check the actual command if pwflag is not set. |
* Only check the actual command if pwflag is not set. |
* It is set for the "validate", "list" and "kill" pseudo-commands. |
* It is set for the "validate", "list" and "kill" pseudo-commands. |
* Always check the host and user. |
* Always check the host and user. |
*/ |
*/ |
nopass = -1; |
|
if (pwflag) { |
if (pwflag) { |
int found; |
int nopass = UNSPEC; |
enum def_tupple pwcheck; |
enum def_tupple pwcheck; |
|
|
pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple; |
pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple; |
|
|
if (pwcheck == always && def_authenticate) |
if (list_pw == NULL) |
nopass = FLAG_CHECK_USER; |
SET(validated, FLAG_NO_CHECK); |
else if (pwcheck == never || !def_authenticate) |
CLR(validated, FLAG_NO_USER); |
nopass = FLAG_NOPASS; |
CLR(validated, FLAG_NO_HOST); |
found = 0; |
match = DENY; |
while (top) { |
tq_foreach_rev(&userspecs, us) { |
if (host_matches == TRUE) { |
if (userlist_matches(sudo_user.pw, &us->users) != ALLOW) |
found = 1; |
continue; |
if (pwcheck == any && no_passwd == TRUE) |
tq_foreach_rev(&us->privileges, priv) { |
nopass = FLAG_NOPASS; |
if (hostlist_matches(&priv->hostlist) != ALLOW) |
else if (pwcheck == all && nopass != 0) |
continue; |
nopass = (no_passwd == TRUE) ? FLAG_NOPASS : 0; |
tq_foreach_rev(&priv->cmndlist, cs) { |
} |
/* Only check the command when listing another user. */ |
top--; |
if (user_uid == 0 || list_pw == NULL || |
} |
user_uid == list_pw->pw_uid || |
if (found) { |
cmnd_matches(cs->cmnd) == ALLOW) |
set_perms(PERM_ROOT); |
match = ALLOW; |
if (nopass == -1) |
if ((pwcheck == any && nopass != TRUE) || |
nopass = 0; |
(pwcheck == all && nopass != FALSE)) |
return(VALIDATE_OK | nopass); |
nopass = cs->tags.nopasswd; |
} |
if (match == ALLOW) |
} else { |
goto matched_pseudo; |
while (top) { |
|
if (host_matches == TRUE) { |
|
CLR(error, FLAG_NO_HOST); |
|
if (runas_matches == TRUE && cmnd_matches == TRUE) { |
|
/* |
|
* User was granted access to cmnd on host as user. |
|
*/ |
|
#ifdef HAVE_SELINUX |
|
/* Set role and type if not specified on command line. */ |
|
if (user_role == NULL) { |
|
if (match[top-1].role != NULL) |
|
user_role = match[top-1].role; |
|
else |
|
user_role = def_role; |
|
} |
|
if (user_type == NULL) { |
|
if (match[top-1].type != NULL) |
|
user_type = match[top-1].type; |
|
else |
|
user_type = def_type; |
|
} |
|
#endif |
|
set_perms(PERM_ROOT); |
|
return(VALIDATE_OK | |
|
(no_passwd == TRUE ? FLAG_NOPASS : 0) | |
|
(no_execve == TRUE ? FLAG_NOEXEC : 0) | |
|
(setenv_ok >= TRUE ? FLAG_SETENV : 0)); |
|
} else if ((runas_matches == TRUE && cmnd_matches == FALSE) || |
|
(runas_matches == FALSE && cmnd_matches == TRUE)) { |
|
/* |
|
* User was explicitly denied access to cmnd on host. |
|
*/ |
|
set_perms(PERM_ROOT); |
|
return(VALIDATE_NOT_OK | |
|
(no_passwd == TRUE ? FLAG_NOPASS : 0) | |
|
(no_execve == TRUE ? FLAG_NOEXEC : 0) | |
|
(setenv_ok >= TRUE ? FLAG_SETENV : 0)); |
|
} |
} |
} |
} |
top--; |
|
} |
} |
|
matched_pseudo: |
|
if (match == ALLOW || user_uid == 0) { |
|
/* User has an entry for this host. */ |
|
SET(validated, VALIDATE_OK); |
|
} else if (match == DENY) |
|
SET(validated, VALIDATE_NOT_OK); |
|
if (pwcheck == always && def_authenticate) |
|
SET(validated, FLAG_CHECK_USER); |
|
else if (pwcheck == never || nopass == TRUE) |
|
def_authenticate = FALSE; |
|
return(validated); |
} |
} |
set_perms(PERM_ROOT); |
|
|
|
/* |
/* Need to be runas user while stat'ing things. */ |
* The user was neither explicitly granted nor denied access. |
set_perms(PERM_RUNAS); |
*/ |
|
if (nopass == -1) |
|
nopass = 0; |
|
return(error | nopass); |
|
} |
|
|
|
/* |
match = UNSPEC; |
* If path doesn't end in /, return TRUE iff cmnd & path name the same inode; |
tq_foreach_rev(&userspecs, us) { |
* otherwise, return TRUE if user_cmnd names one of the inodes in path. |
if (userlist_matches(sudo_user.pw, &us->users) != ALLOW) |
*/ |
continue; |
int |
CLR(validated, FLAG_NO_USER); |
command_matches(sudoers_cmnd, sudoers_args) |
tq_foreach_rev(&us->privileges, priv) { |
char *sudoers_cmnd; |
host_match = hostlist_matches(&priv->hostlist); |
char *sudoers_args; |
if (host_match == ALLOW) |
{ |
CLR(validated, FLAG_NO_HOST); |
struct stat sudoers_stat; |
|
struct dirent *dent; |
|
char **ap, *base, buf[PATH_MAX]; |
|
glob_t gl; |
|
DIR *dirp; |
|
|
|
/* Check for pseudo-commands */ |
|
if (strchr(user_cmnd, '/') == NULL) { |
|
/* |
|
* Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND |
|
* a) there are no args in sudoers OR |
|
* b) there are no args on command line and none req by sudoers OR |
|
* c) there are args in sudoers and on command line and they match |
|
*/ |
|
if (strcmp(sudoers_cmnd, "sudoedit") != 0 || |
|
strcmp(user_cmnd, "sudoedit") != 0) |
|
return(FALSE); |
|
if (!sudoers_args || |
|
(!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) || |
|
(sudoers_args && |
|
fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) { |
|
efree(safe_cmnd); |
|
safe_cmnd = estrdup(sudoers_cmnd); |
|
return(TRUE); |
|
} else |
|
return(FALSE); |
|
} |
|
|
|
/* |
|
* If sudoers_cmnd has meta characters in it, use fnmatch(3) |
|
* to do the matching. |
|
*/ |
|
if (has_meta(sudoers_cmnd)) { |
|
/* |
|
* Return true if we find a match in the glob(3) results 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. |
|
* |
|
* Could optimize patterns ending in "/*" to "/user_base" |
|
*/ |
|
#define GLOB_FLAGS (GLOB_NOSORT | GLOB_MARK | GLOB_BRACE | GLOB_TILDE) |
|
if (glob(sudoers_cmnd, GLOB_FLAGS, NULL, &gl) != 0) { |
|
globfree(&gl); |
|
return(FALSE); |
|
} |
|
/* For each glob match, compare basename, st_dev and st_ino. */ |
|
for (ap = gl.gl_pathv; *ap != NULL; ap++) { |
|
/* only stat if basenames are the same */ |
|
if ((base = strrchr(*ap, '/')) != NULL) |
|
base++; |
|
else |
else |
base = *ap; |
|
if (strcmp(user_base, base) != 0 || |
|
stat(*ap, &sudoers_stat) == -1) |
|
continue; |
continue; |
if (user_stat->st_dev == sudoers_stat.st_dev && |
tq_foreach_rev(&priv->cmndlist, cs) { |
user_stat->st_ino == sudoers_stat.st_ino) { |
runas_match = runaslist_matches(&cs->runasuserlist, |
efree(safe_cmnd); |
&cs->runasgrouplist); |
safe_cmnd = estrdup(*ap); |
if (runas_match == ALLOW) { |
break; |
cmnd_match = cmnd_matches(cs->cmnd); |
|
if (cmnd_match != UNSPEC) { |
|
match = cmnd_match; |
|
tags = &cs->tags; |
|
#ifdef HAVE_SELINUX |
|
/* Set role and type if not specified on command line. */ |
|
if (user_role == NULL) |
|
user_role = cs->role ? estrdup(cs->role) : def_role; |
|
if (user_type == NULL) |
|
user_type = cs->type ? estrdup(cs->type) : def_type; |
|
#endif /* HAVE_SELINUX */ |
|
goto matched2; |
|
} |
|
} |
} |
} |
} |
} |
globfree(&gl); |
} |
if (*ap == NULL) |
matched2: |
return(FALSE); |
if (match == ALLOW) { |
|
SET(validated, VALIDATE_OK); |
if (!sudoers_args || |
CLR(validated, VALIDATE_NOT_OK); |
(!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) || |
if (tags != NULL) { |
(sudoers_args && |
if (tags->nopasswd != UNSPEC) |
fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) { |
def_authenticate = !tags->nopasswd; |
efree(safe_cmnd); |
if (tags->noexec != UNSPEC) |
safe_cmnd = estrdup(user_cmnd); |
def_noexec = tags->noexec; |
return(TRUE); |
if (tags->setenv != UNSPEC) |
} else |
def_setenv = tags->setenv; |
return(FALSE); |
|
} else { |
|
size_t dlen = strlen(sudoers_cmnd); |
|
|
|
/* |
|
* No meta characters |
|
* Check to make sure this is not a directory spec (doesn't end in '/') |
|
*/ |
|
if (sudoers_cmnd[dlen - 1] != '/') { |
|
/* Only proceed if user_base and basename(sudoers_cmnd) match */ |
|
if ((base = strrchr(sudoers_cmnd, '/')) == NULL) |
|
base = sudoers_cmnd; |
|
else |
|
base++; |
|
if (strcmp(user_base, base) != 0 || |
|
stat(sudoers_cmnd, &sudoers_stat) == -1) |
|
return(FALSE); |
|
|
|
/* |
|
* Return true if inode/device matches AND |
|
* a) there are no args in sudoers OR |
|
* b) there are no args on command line and none req by sudoers OR |
|
* c) there are args in sudoers and on command line and they match |
|
*/ |
|
if (user_stat->st_dev != sudoers_stat.st_dev || |
|
user_stat->st_ino != sudoers_stat.st_ino) |
|
return(FALSE); |
|
if (!sudoers_args || |
|
(!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) || |
|
(sudoers_args && |
|
fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) { |
|
efree(safe_cmnd); |
|
safe_cmnd = estrdup(sudoers_cmnd); |
|
return(TRUE); |
|
} else |
|
return(FALSE); |
|
} |
} |
|
} else if (match == DENY) { |
|
SET(validated, VALIDATE_NOT_OK); |
|
CLR(validated, VALIDATE_OK); |
|
} |
|
set_perms(PERM_ROOT); |
|
return(validated); |
|
} |
|
|
/* |
#define TAG_CHANGED(t) \ |
* Grot through sudoers_cmnd's directory entries, looking for user_base. |
(cs->tags.t != UNSPEC && cs->tags.t != IMPLIED && cs->tags.t != tags->t) |
*/ |
|
dirp = opendir(sudoers_cmnd); |
|
if (dirp == NULL) |
|
return(FALSE); |
|
|
|
if (strlcpy(buf, sudoers_cmnd, sizeof(buf)) >= sizeof(buf)) |
static void |
return(FALSE); |
sudo_file_append_cmnd(cs, tags, lbuf) |
while ((dent = readdir(dirp)) != NULL) { |
struct cmndspec *cs; |
/* ignore paths > PATH_MAX (XXX - log) */ |
struct cmndtag *tags; |
buf[dlen] = '\0'; |
struct lbuf *lbuf; |
if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf)) |
{ |
continue; |
struct member *m; |
|
|
/* only stat if basenames are the same */ |
#ifdef HAVE_SELINUX |
if (strcmp(user_base, dent->d_name) != 0 || |
if (cs->role) |
stat(buf, &sudoers_stat) == -1) |
lbuf_append(lbuf, "ROLE=", cs->role, " ", NULL); |
continue; |
if (cs->type) |
if (user_stat->st_dev == sudoers_stat.st_dev && |
lbuf_append(lbuf, "TYPE=", cs->type, " ", NULL); |
user_stat->st_ino == sudoers_stat.st_ino) { |
#endif /* HAVE_SELINUX */ |
efree(safe_cmnd); |
if (TAG_CHANGED(setenv)) { |
safe_cmnd = estrdup(buf); |
lbuf_append(lbuf, cs->tags.setenv ? "SETENV: " : |
break; |
"NOSETENV: ", NULL); |
} |
tags->setenv = cs->tags.setenv; |
} |
|
|
|
closedir(dirp); |
|
return(dent != NULL); |
|
} |
} |
|
if (TAG_CHANGED(noexec)) { |
|
lbuf_append(lbuf, cs->tags.noexec ? "NOEXEC: " : |
|
"EXEC: ", NULL); |
|
tags->noexec = cs->tags.noexec; |
|
} |
|
if (TAG_CHANGED(nopasswd)) { |
|
lbuf_append(lbuf, cs->tags.nopasswd ? "NOPASSWD: " : |
|
"PASSWD: ", NULL); |
|
tags->nopasswd = cs->tags.nopasswd; |
|
} |
|
m = cs->cmnd; |
|
print_member(lbuf, m->name, m->type, m->negated, |
|
CMNDALIAS); |
} |
} |
|
|
static int |
static int |
addr_matches_if(n) |
sudo_file_display_priv_short(pw, us, lbuf) |
char *n; |
struct passwd *pw; |
|
struct userspec *us; |
|
struct lbuf *lbuf; |
{ |
{ |
int i; |
struct cmndspec *cs; |
struct in_addr addr; |
struct member *m; |
struct interface *ifp; |
struct privilege *priv; |
#ifdef HAVE_IN6_ADDR |
struct cmndtag tags; |
struct in6_addr addr6; |
int nfound = 0; |
int j; |
|
#endif |
|
int family; |
|
|
|
#ifdef HAVE_IN6_ADDR |
tq_foreach_fwd(&us->privileges, priv) { |
if (inet_pton(AF_INET6, n, &addr6) > 0) { |
tags.noexec = UNSPEC; |
family = AF_INET6; |
tags.setenv = UNSPEC; |
} else |
tags.nopasswd = UNSPEC; |
#endif |
lbuf_append(lbuf, " ", NULL); |
{ |
tq_foreach_fwd(&priv->cmndlist, cs) { |
family = AF_INET; |
if (cs != tq_first(&priv->cmndlist)) |
addr.s_addr = inet_addr(n); |
lbuf_append(lbuf, ", ", NULL); |
} |
lbuf_append(lbuf, "(", NULL); |
|
if (!tq_empty(&cs->runasuserlist)) { |
for (i = 0; i < num_interfaces; i++) { |
tq_foreach_fwd(&cs->runasuserlist, m) { |
ifp = &interfaces[i]; |
if (m != tq_first(&cs->runasuserlist)) |
if (ifp->family != family) |
lbuf_append(lbuf, ", ", NULL); |
continue; |
print_member(lbuf, m->name, m->type, m->negated, |
switch(family) { |
RUNASALIAS); |
case AF_INET: |
|
if (ifp->addr.ip4.s_addr == addr.s_addr || |
|
(ifp->addr.ip4.s_addr & ifp->netmask.ip4.s_addr) |
|
== addr.s_addr) |
|
return(TRUE); |
|
break; |
|
#ifdef HAVE_IN6_ADDR |
|
case AF_INET6: |
|
if (memcmp(ifp->addr.ip6.s6_addr, addr6.s6_addr, |
|
sizeof(addr6.s6_addr)) == 0) |
|
return(TRUE); |
|
for (j = 0; j < sizeof(addr6.s6_addr); j++) { |
|
if ((ifp->addr.ip6.s6_addr[j] & ifp->netmask.ip6.s6_addr[j]) != addr6.s6_addr[j]) |
|
break; |
|
} |
} |
if (j == sizeof(addr6.s6_addr)) |
} else { |
return(TRUE); |
lbuf_append(lbuf, def_runas_default, NULL); |
#endif /* HAVE_IN6_ADDR */ |
} |
|
if (!tq_empty(&cs->runasgrouplist)) { |
|
lbuf_append(lbuf, " : ", NULL); |
|
tq_foreach_fwd(&cs->runasgrouplist, m) { |
|
if (m != tq_first(&cs->runasgrouplist)) |
|
lbuf_append(lbuf, ", ", NULL); |
|
print_member(lbuf, m->name, m->type, m->negated, |
|
RUNASALIAS); |
|
} |
|
} |
|
lbuf_append(lbuf, ") ", NULL); |
|
sudo_file_append_cmnd(cs, &tags, lbuf); |
|
nfound++; |
} |
} |
|
lbuf_print(lbuf); /* forces a newline */ |
} |
} |
|
return(nfound); |
return(FALSE); |
|
} |
} |
|
|
static int |
static int |
addr_matches_if_netmask(n, m) |
sudo_file_display_priv_long(pw, us, lbuf) |
char *n; |
struct passwd *pw; |
char *m; |
struct userspec *us; |
|
struct lbuf *lbuf; |
{ |
{ |
int i; |
struct cmndspec *cs; |
struct in_addr addr, mask; |
struct member *m; |
struct interface *ifp; |
struct privilege *priv; |
#ifdef HAVE_IN6_ADDR |
struct cmndtag tags; |
struct in6_addr addr6, mask6; |
int nfound = 0; |
int j; |
|
#endif |
|
int family; |
|
|
|
#ifdef HAVE_IN6_ADDR |
tq_foreach_fwd(&us->privileges, priv) { |
if (inet_pton(AF_INET6, n, &addr6) > 0) |
tags.noexec = UNSPEC; |
family = AF_INET6; |
tags.setenv = UNSPEC; |
else |
tags.nopasswd = UNSPEC; |
#endif |
lbuf_print(lbuf); /* force a newline */ |
{ |
lbuf_append(lbuf, "Sudoers entry:", NULL); |
family = AF_INET; |
lbuf_print(lbuf); |
addr.s_addr = inet_addr(n); |
tq_foreach_fwd(&priv->cmndlist, cs) { |
} |
lbuf_append(lbuf, " RunAsUsers: ", NULL); |
|
if (!tq_empty(&cs->runasuserlist)) { |
if (family == AF_INET) { |
tq_foreach_fwd(&cs->runasuserlist, m) { |
if (strchr(m, '.')) |
if (m != tq_first(&cs->runasuserlist)) |
mask.s_addr = inet_addr(m); |
lbuf_append(lbuf, ", ", NULL); |
else { |
print_member(lbuf, m->name, m->type, m->negated, |
i = 32 - atoi(m); |
RUNASALIAS); |
mask.s_addr = 0xffffffff; |
} |
mask.s_addr >>= i; |
} else { |
mask.s_addr <<= i; |
lbuf_append(lbuf, def_runas_default, NULL); |
mask.s_addr = htonl(mask.s_addr); |
|
} |
|
} |
|
#ifdef HAVE_IN6_ADDR |
|
else { |
|
if (inet_pton(AF_INET6, m, &mask6) <= 0) { |
|
j = atoi(m); |
|
for (i = 0; i < 16; i++) { |
|
if (j < i * 8) |
|
mask6.s6_addr[i] = 0; |
|
else if (i * 8 + 8 <= j) |
|
mask6.s6_addr[i] = 0xff; |
|
else |
|
mask6.s6_addr[i] = 0xff00 >> (j - i * 8); |
|
} |
} |
} |
lbuf_print(lbuf); |
} |
if (!tq_empty(&cs->runasgrouplist)) { |
#endif /* HAVE_IN6_ADDR */ |
lbuf_append(lbuf, " RunAsGroups: ", NULL); |
|
tq_foreach_fwd(&cs->runasgrouplist, m) { |
for (i = 0; i < num_interfaces; i++) { |
if (m != tq_first(&cs->runasgrouplist)) |
ifp = &interfaces[i]; |
lbuf_append(lbuf, ", ", NULL); |
if (ifp->family != family) |
print_member(lbuf, m->name, m->type, m->negated, |
continue; |
RUNASALIAS); |
switch(family) { |
|
case AF_INET: |
|
if ((ifp->addr.ip4.s_addr & mask.s_addr) == addr.s_addr) |
|
return(TRUE); |
|
#ifdef HAVE_IN6_ADDR |
|
case AF_INET6: |
|
for (j = 0; j < sizeof(addr6.s6_addr); j++) { |
|
if ((ifp->addr.ip6.s6_addr[j] & mask6.s6_addr[j]) != addr6.s6_addr[j]) |
|
break; |
|
} |
} |
if (j == sizeof(addr6.s6_addr)) |
lbuf_print(lbuf); |
return(TRUE); |
} |
#endif /* HAVE_IN6_ADDR */ |
lbuf_append(lbuf, " Commands: ", NULL); |
|
lbuf_print(lbuf); |
|
lbuf_append(lbuf, "\t", NULL); |
|
sudo_file_append_cmnd(cs, &tags, lbuf); |
|
lbuf_print(lbuf); |
|
nfound++; |
} |
} |
} |
} |
|
return(nfound); |
return(FALSE); |
|
} |
} |
|
|
/* |
|
* Returns TRUE if "n" is one of our ip addresses or if |
|
* "n" is a network that we are on, else returns FALSE. |
|
*/ |
|
int |
int |
addr_matches(n) |
sudo_file_display_privs(nss, pw, lbuf) |
char *n; |
struct sudo_nss *nss; |
|
struct passwd *pw; |
|
struct lbuf *lbuf; |
{ |
{ |
char *m; |
struct userspec *us; |
int retval; |
int nfound = 0; |
|
|
/* If there's an explicit netmask, use it. */ |
if (nss->handle == NULL) |
if ((m = strchr(n, '/'))) { |
return(-1); |
*m++ = '\0'; |
|
retval = addr_matches_if_netmask(n, m); |
|
*(m - 1) = '/'; |
|
} else |
|
retval = addr_matches_if(n); |
|
|
|
return(retval); |
tq_foreach_fwd(&userspecs, us) { |
} |
/* XXX - why only check the first privilege here? */ |
|
if (userlist_matches(pw, &us->users) != ALLOW || |
|
hostlist_matches(&us->privileges.first->hostlist) != ALLOW) |
|
continue; |
|
|
/* |
if (long_list) |
* Returns 0 if the hostname matches the pattern and non-zero otherwise. |
nfound += sudo_file_display_priv_long(pw, us, lbuf); |
*/ |
|
int |
|
hostname_matches(shost, lhost, pattern) |
|
char *shost; |
|
char *lhost; |
|
char *pattern; |
|
{ |
|
if (has_meta(pattern)) { |
|
if (strchr(pattern, '.')) |
|
return(fnmatch(pattern, lhost, FNM_CASEFOLD)); |
|
else |
else |
return(fnmatch(pattern, shost, FNM_CASEFOLD)); |
nfound += sudo_file_display_priv_short(pw, us, lbuf); |
} else { |
|
if (strchr(pattern, '.')) |
|
return(strcasecmp(lhost, pattern)); |
|
else |
|
return(strcasecmp(shost, pattern)); |
|
} |
} |
|
return(nfound); |
} |
} |
|
|
/* |
/* |
* Returns TRUE if the user/uid from sudoers matches the specified user/uid, |
* Display matching Defaults entries for the given user on this host. |
* else returns FALSE. |
|
*/ |
*/ |
int |
int |
userpw_matches(sudoers_user, user, pw) |
sudo_file_display_defaults(nss, pw, lbuf) |
char *sudoers_user; |
struct sudo_nss *nss; |
char *user; |
|
struct passwd *pw; |
struct passwd *pw; |
|
struct lbuf *lbuf; |
{ |
{ |
if (pw != NULL && *sudoers_user == '#') { |
struct defaults *d; |
uid_t uid = atoi(sudoers_user + 1); |
char *prefix = NULL; |
if (uid == pw->pw_uid) |
int nfound = 0; |
return(1); |
|
|
if (nss->handle == NULL) |
|
return(-1); |
|
|
|
if (lbuf->len == 0) |
|
prefix = " "; |
|
else |
|
prefix = ", "; |
|
|
|
tq_foreach_fwd(&defaults, d) { |
|
switch (d->type) { |
|
case DEFAULTS_HOST: |
|
if (hostlist_matches(&d->binding) != ALLOW) |
|
continue; |
|
break; |
|
case DEFAULTS_USER: |
|
if (userlist_matches(pw, &d->binding) != ALLOW) |
|
continue; |
|
break; |
|
case DEFAULTS_RUNAS: |
|
case DEFAULTS_CMND: |
|
continue; |
|
} |
|
lbuf_append(lbuf, prefix, NULL); |
|
if (d->val != NULL) { |
|
lbuf_append(lbuf, d->var, d->op == '+' ? "+=" : |
|
d->op == '-' ? "-=" : "=", NULL); |
|
if (strpbrk(d->val, " \t") != NULL) { |
|
lbuf_append(lbuf, "\"", NULL); |
|
lbuf_append_quoted(lbuf, "\"", d->val, NULL); |
|
lbuf_append(lbuf, "\"", NULL); |
|
} else |
|
lbuf_append_quoted(lbuf, SUDOERS_QUOTED, d->val, NULL); |
|
} else |
|
lbuf_append(lbuf, d->op == FALSE ? "!" : "", d->var, NULL); |
|
prefix = ", "; |
|
nfound++; |
} |
} |
return(strcmp(sudoers_user, user) == 0); |
|
|
return(nfound); |
} |
} |
|
|
/* |
/* |
* Returns TRUE if the given user belongs to the named group, |
* Display Defaults entries that are per-runas or per-command |
* else returns FALSE. |
|
* XXX - reduce the number of passwd/group lookups |
|
*/ |
*/ |
int |
int |
usergr_matches(group, user, pw) |
sudo_file_display_bound_defaults(nss, pw, lbuf) |
char *group; |
struct sudo_nss *nss; |
char *user; |
|
struct passwd *pw; |
struct passwd *pw; |
|
struct lbuf *lbuf; |
{ |
{ |
struct group *grp; |
int nfound = 0; |
gid_t pw_gid; |
|
char **cur; |
|
int i; |
|
|
|
/* make sure we have a valid usergroup, sudo style */ |
/* XXX - should only print ones that match what the user can do. */ |
if (*group++ != '%') |
nfound += display_bound_defaults(DEFAULTS_RUNAS, lbuf); |
return(FALSE); |
nfound += display_bound_defaults(DEFAULTS_CMND, lbuf); |
|
|
/* look up user's primary gid in the passwd file */ |
return(nfound); |
if (pw == NULL && (pw = getpwnam(user)) == NULL) |
} |
return(FALSE); |
|
pw_gid = pw->pw_gid; |
|
|
|
if ((grp = getgrnam(group)) == NULL) |
/* |
return(FALSE); |
* Display Defaults entries of the given type. |
|
*/ |
|
static int |
|
display_bound_defaults(dtype, lbuf) |
|
int dtype; |
|
struct lbuf *lbuf; |
|
{ |
|
struct defaults *d; |
|
struct member *m, *binding = NULL; |
|
char *dname, *dsep; |
|
int atype, nfound = 0; |
|
|
/* check against user's primary (passwd file) gid */ |
switch (dtype) { |
if (grp->gr_gid == pw_gid) |
case DEFAULTS_HOST: |
return(TRUE); |
atype = HOSTALIAS; |
|
dname = "host"; |
|
dsep = "@"; |
|
break; |
|
case DEFAULTS_USER: |
|
atype = USERALIAS; |
|
dname = "user"; |
|
dsep = ":"; |
|
break; |
|
case DEFAULTS_RUNAS: |
|
atype = RUNASALIAS; |
|
dname = "runas"; |
|
dsep = ">"; |
|
break; |
|
case DEFAULTS_CMND: |
|
atype = CMNDALIAS; |
|
dname = "cmnd"; |
|
dsep = "!"; |
|
break; |
|
default: |
|
return(-1); |
|
} |
|
/* printf("Per-%s Defaults entries:\n", dname); */ |
|
tq_foreach_fwd(&defaults, d) { |
|
if (d->type != dtype) |
|
continue; |
|
|
/* |
nfound++; |
* If the user has a supplementary group vector, check it first. |
if (binding != tq_first(&d->binding)) { |
*/ |
binding = tq_first(&d->binding); |
if (strcmp(user, user_name) == 0) { |
lbuf_append(lbuf, " Defaults", dsep, NULL); |
for (i = 0; i < user_ngroups; i++) { |
for (m = binding; m != NULL; m = m->next) { |
if (grp->gr_gid == user_groups[i]) |
if (m != binding) |
return(TRUE); |
lbuf_append(lbuf, ",", NULL); |
} |
print_member(lbuf, m->name, m->type, m->negated, atype); |
|
lbuf_append(lbuf, " ", NULL); |
|
} |
|
} else |
|
lbuf_append(lbuf, ", ", NULL); |
|
if (d->val != NULL) { |
|
lbuf_append(lbuf, d->var, d->op == '+' ? "+=" : |
|
d->op == '-' ? "-=" : "=", d->val, NULL); |
|
} else |
|
lbuf_append(lbuf, d->op == FALSE ? "!" : "", d->var, NULL); |
} |
} |
if (grp->gr_mem != NULL) { |
|
for (cur = grp->gr_mem; *cur; cur++) { |
|
if (strcmp(*cur, user) == 0) |
|
return(TRUE); |
|
} |
|
} |
|
|
|
return(FALSE); |
return(nfound); |
} |
} |
|
|
/* |
|
* Returns TRUE if "host" and "user" belong to the netgroup "netgr", |
|
* else return FALSE. Either of "host", "shost" or "user" may be NULL |
|
* in which case that argument is not checked... |
|
*/ |
|
int |
int |
netgr_matches(netgr, host, shost, user) |
sudo_file_display_cmnd(nss, pw) |
char *netgr; |
struct sudo_nss *nss; |
char *host; |
struct passwd *pw; |
char *shost; |
|
char *user; |
|
{ |
{ |
static char *domain; |
struct cmndspec *cs; |
#ifdef HAVE_GETDOMAINNAME |
struct member *match; |
static int initialized; |
struct privilege *priv; |
#endif |
struct userspec *us; |
|
int rval = 1; |
|
int host_match, runas_match, cmnd_match; |
|
|
/* make sure we have a valid netgroup, sudo style */ |
if (nss->handle == NULL) |
if (*netgr++ != '+') |
return(rval); |
return(FALSE); |
|
|
|
#ifdef HAVE_GETDOMAINNAME |
match = NULL; |
/* get the domain name (if any) */ |
tq_foreach_rev(&userspecs, us) { |
if (!initialized) { |
if (userlist_matches(pw, &us->users) != ALLOW) |
domain = (char *) emalloc(MAXHOSTNAMELEN); |
continue; |
if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') { |
|
efree(domain); |
tq_foreach_rev(&us->privileges, priv) { |
domain = NULL; |
host_match = hostlist_matches(&priv->hostlist); |
|
if (host_match != ALLOW) |
|
continue; |
|
tq_foreach_rev(&priv->cmndlist, cs) { |
|
runas_match = runaslist_matches(&cs->runasuserlist, |
|
&cs->runasgrouplist); |
|
if (runas_match == ALLOW) { |
|
cmnd_match = cmnd_matches(cs->cmnd); |
|
if (cmnd_match != UNSPEC) { |
|
match = host_match && runas_match ? |
|
cs->cmnd : NULL; |
|
goto matched; |
|
} |
|
} |
|
} |
} |
} |
initialized = 1; |
|
} |
} |
#endif /* HAVE_GETDOMAINNAME */ |
matched: |
|
if (match != NULL && !match->negated) { |
#ifdef HAVE_INNETGR |
printf("%s%s%s\n", safe_cmnd, user_args ? " " : "", |
if (innetgr(netgr, host, user, domain)) |
user_args ? user_args : ""); |
return(TRUE); |
rval = 0; |
else if (host != shost && innetgr(netgr, shost, user, domain)) |
} |
return(TRUE); |
return(rval); |
#endif /* HAVE_INNETGR */ |
|
|
|
return(FALSE); |
|
} |
} |
|
|
/* |
/* |
* Returns TRUE if "s" has shell meta characters in it, |
* Print the contents of a struct member to stdout |
* else returns FALSE. |
|
*/ |
*/ |
static int |
static void |
has_meta(s) |
_print_member(lbuf, name, type, negated, alias_type) |
char *s; |
struct lbuf *lbuf; |
|
char *name; |
|
int type, negated, alias_type; |
{ |
{ |
char *t; |
struct alias *a; |
|
struct member *m; |
for (t = s; *t; t++) { |
struct sudo_command *c; |
if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']') |
|
return(TRUE); |
switch (type) { |
|
case ALL: |
|
lbuf_append(lbuf, negated ? "!ALL" : "ALL", NULL); |
|
break; |
|
case COMMAND: |
|
c = (struct sudo_command *) name; |
|
if (negated) |
|
lbuf_append(lbuf, "!", NULL); |
|
lbuf_append_quoted(lbuf, SUDOERS_QUOTED, c->cmnd, NULL); |
|
if (c->args) { |
|
lbuf_append(lbuf, " ", NULL); |
|
lbuf_append_quoted(lbuf, SUDOERS_QUOTED, c->args, NULL); |
|
} |
|
break; |
|
case ALIAS: |
|
if ((a = find_alias(name, alias_type)) != NULL) { |
|
tq_foreach_fwd(&a->members, m) { |
|
if (m != tq_first(&a->members)) |
|
lbuf_append(lbuf, ", ", NULL); |
|
_print_member(lbuf, m->name, m->type, |
|
negated ? !m->negated : m->negated, alias_type); |
|
} |
|
break; |
|
} |
|
/* FALLTHROUGH */ |
|
default: |
|
lbuf_append(lbuf, negated ? "!" : "", name, NULL); |
|
break; |
} |
} |
return(FALSE); |
} |
|
|
|
static void |
|
print_member(lbuf, name, type, negated, alias_type) |
|
struct lbuf *lbuf; |
|
char *name; |
|
int type, negated, alias_type; |
|
{ |
|
alias_seqno++; |
|
_print_member(lbuf, name, type, negated, alias_type); |
} |
} |