pfexec.c revision 499fd60129a966ad9d9e752e65f591c3a6a1c697
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <errno.h>
#include <deflt.h>
#include <locale.h>
#include <sys/types.h>
#include <sys/param.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <exec_attr.h>
#include <user_attr.h>
#include <auth_attr.h>
#include <prof_attr.h>
#include <errno.h>
#include <priv.h>
#include <bsm/adt.h>
#include <bsm/adt_event.h>
#ifndef TEXT_DOMAIN /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"
#endif
extern int cannot_audit(int);
static char *pathsearch(char *);
static int getrealpath(const char *, char *);
static int checkattrs(char *, int, char *[]);
static void sanitize_environ();
static uid_t get_uid(char *);
static gid_t get_gid(char *);
static priv_set_t *get_privset(const char *);
static priv_set_t *get_granted_privs(uid_t);
static void get_default_privs(const char *, priv_set_t *);
static void get_profile_privs(char *, char **, int *, priv_set_t *);
static int isnumber(char *);
static void usage(void);
extern char **environ;
#define PROFLIST_SEP ","
int
main(int argc, char *argv[])
{
char *cmd;
char **cmdargs;
char cmd_realpath[MAXPATHLEN];
int c;
char *pset = NULL;
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
while ((c = getopt(argc, argv, "P:")) != EOF) {
switch (c) {
case 'P':
if (pset == NULL) {
pset = optarg;
break;
}
/* FALLTHROUGH */
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 1)
usage();
cmd = argv[0];
cmdargs = &argv[0];
if (pset != NULL) {
uid_t uid = getuid();
priv_set_t *wanted = get_privset(pset);
priv_set_t *granted;
adt_session_data_t *ah; /* audit session handle */
adt_event_data_t *event; /* event to be generated */
char cwd[MAXPATHLEN];
granted = get_granted_privs(uid);
/* Audit use */
if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
perror("pfexec: adt_start_session");
exit(EXIT_FAILURE);
}
if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) {
perror("pfexec: adt_alloc_event");
exit(EXIT_FAILURE);
}
if ((event->adt_prof_cmd.cwdpath =
getcwd(cwd, sizeof (cwd))) == NULL) {
(void) fprintf(stderr,
gettext("pfexec: can't add cwd path\n"));
exit(EXIT_FAILURE);
}
event->adt_prof_cmd.cmdpath = cmd;
event->adt_prof_cmd.argc = argc - 1;
event->adt_prof_cmd.argv = &argv[1];
event->adt_prof_cmd.envp = environ;
if (granted != NULL) {
priv_intersect(granted, wanted);
event->adt_prof_cmd.inherit_set = wanted;
if (adt_put_event(event, ADT_SUCCESS,
ADT_SUCCESS) != 0) {
perror("pfexec: adt_put_event");
exit(EXIT_FAILURE);
}
if (setppriv(PRIV_ON, PRIV_INHERITABLE, wanted) != 0) {
(void) fprintf(stderr,
gettext("setppriv(): %s\n"),
strerror(errno));
exit(EXIT_FAILURE);
}
/* Trick exec into thinking we're not suid */
(void) setppriv(PRIV_ON, PRIV_PERMITTED, wanted);
priv_freeset(event->adt_prof_cmd.inherit_set);
} else {
if (adt_put_event(event, ADT_SUCCESS,
ADT_SUCCESS) != 0) {
perror("pfexec: adt_put_event");
exit(EXIT_FAILURE);
}
}
adt_free_event(event);
(void) adt_end_session(ah);
(void) setreuid(uid, uid);
(void) execvp(cmd, cmdargs);
perror(cmd);
exit(EXIT_FAILURE);
}
if ((cmd = pathsearch(cmd)) == NULL)
exit(EXIT_FAILURE);
if (getrealpath(cmd, cmd_realpath) == 0)
exit(EXIT_FAILURE);
if (checkattrs(cmd_realpath, argc, argv) == 0)
exit(EXIT_FAILURE);
(void) execv(cmd, cmdargs);
/*
* We'd be here only if execv fails.
*/
perror("pfexec");
exit(EXIT_FAILURE);
/* LINTED */
}
/*
* gets realpath for cmd.
* return 1 on success, 0 on failure.
*/
static int
getrealpath(const char *cmd, char *cmd_realpath)
{
if (realpath(cmd, cmd_realpath) == NULL) {
(void) fprintf(stderr,
gettext("pfexec: can't get real path of ``%s''\n"), cmd);
return (0);
}
return (1);
}
/*
* gets execution attributed for cmd, sets uids/gids, checks environ.
* returns 1 on success, 0 on failure.
*/
static int
checkattrs(char *cmd_realpath, int argc, char *argv[])
{
char *value;
uid_t uid, euid;
gid_t gid = (gid_t)-1;
gid_t egid = (gid_t)-1;
struct passwd *pwent;
execattr_t *exec;
priv_set_t *lset = NULL;
priv_set_t *iset = NULL;
adt_session_data_t *ah; /* audit session handle */
adt_event_data_t *event; /* event to be generated */
char cwd[MAXPATHLEN];
uid = euid = getuid();
if ((pwent = getpwuid(uid)) == NULL) {
(void) fprintf(stderr, "%d: ", (int)uid);
(void) fprintf(stderr, gettext("can't get passwd entry\n"));
return (0);
}
/* Set up to audit use */
if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
perror("pfexec: adt_start_session");
return (0);
}
if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) {
perror("pfexec: adt_alloc_event");
return (0);
}
if ((event->adt_prof_cmd.cwdpath = getcwd(cwd, sizeof (cwd))) == NULL) {
(void) fprintf(stderr, gettext("pfexec: can't add cwd path\n"));
return (0);
}
/*
* Get the exec attrs: uid, gid, euid and egid
*/
if ((exec = getexecuser(pwent->pw_name,
KV_COMMAND, (char *)cmd_realpath, GET_ONE)) == NULL) {
(void) fprintf(stderr, "%s: ", cmd_realpath);
(void) fprintf(stderr,
gettext("can't get execution attributes\n"));
return (0);
}
if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL) {
euid = uid = get_uid(value);
event->adt_prof_cmd.proc_euid = uid;
event->adt_prof_cmd.proc_ruid = uid;
}
if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL) {
egid = gid = get_gid(value);
event->adt_prof_cmd.proc_egid = gid;
event->adt_prof_cmd.proc_rgid = gid;
}
if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL) {
event->adt_prof_cmd.proc_euid = euid = get_uid(value);
}
if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL) {
event->adt_prof_cmd.proc_egid = egid = get_gid(value);
}
if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL) {
lset = get_privset(value);
event->adt_prof_cmd.limit_set = lset;
}
if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL) {
iset = get_privset(value);
event->adt_prof_cmd.inherit_set = iset;
}
if (euid == uid || iset != NULL) {
sanitize_environ();
}
/* Finish audit info */
event->adt_prof_cmd.cmdpath = cmd_realpath;
event->adt_prof_cmd.argc = argc - 1;
event->adt_prof_cmd.argv = &argv[1];
event->adt_prof_cmd.envp = environ;
if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
perror("pfexec: adt_put_event");
return (0);
}
adt_free_event(event);
(void) adt_end_session(ah);
set_attrs:
/*
* Set gids/uids and privileges.
*
*/
if ((gid != (gid_t)-1) || (egid != (gid_t)-1)) {
if ((setregid(gid, egid) == -1)) {
(void) fprintf(stderr, "%s: ", cmd_realpath);
(void) fprintf(stderr, gettext("can't set gid\n"));
return (0);
}
}
if (lset != NULL && setppriv(PRIV_SET, PRIV_LIMIT, lset) != 0 ||
iset != NULL && setppriv(PRIV_ON, PRIV_INHERITABLE, iset) != 0) {
(void) fprintf(stderr, gettext("%s: can't set privileges\n"),
cmd_realpath);
return (0);
}
if (setreuid(uid, euid) == -1) {
(void) fprintf(stderr, "%s: ", cmd_realpath);
(void) fprintf(stderr, gettext("can't set uid\n"));
return (0);
}
if (iset != NULL && getppriv(PRIV_INHERITABLE, iset) == 0)
(void) setppriv(PRIV_SET, PRIV_PERMITTED, iset);
free_execattr(exec);
return (1);
}
/*
* cleans up environ. code from su.c
*/
static void
sanitize_environ()
{
char **pp = environ;
char **qq, *p;
while ((p = *pp) != NULL) {
if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
for (qq = pp; (*qq = qq[1]) != NULL; qq++) {
;
}
} else {
pp++;
}
}
}
static uid_t
get_uid(char *value)
{
struct passwd *passwd_ent;
if ((passwd_ent = getpwnam(value)) != NULL)
return (passwd_ent->pw_uid);
if (isnumber(value))
return (atoi(value));
(void) fprintf(stderr, "pfexec: %s: ", value);
(void) fprintf(stderr, gettext("can't get user entry\n"));
exit(EXIT_FAILURE);
/*NOTREACHED*/
}
static uid_t
get_gid(char *value)
{
struct group *group_ent;
if ((group_ent = getgrnam(value)) != NULL)
return (group_ent->gr_gid);
if (isnumber(value))
return (atoi(value));
(void) fprintf(stderr, "pfexec: %s: ", value);
(void) fprintf(stderr, gettext("can't get group entry\n"));
exit(EXIT_FAILURE);
/*NOTREACHED*/
}
static int
isnumber(char *s)
{
int c;
if (*s == '\0')
return (0);
while ((c = *s++) != '\0') {
if (!isdigit(c)) {
return (0);
}
}
return (1);
}
static priv_set_t *
get_privset(const char *s)
{
priv_set_t *res;
if ((res = priv_str_to_set(s, ",", NULL)) == NULL) {
(void) fprintf(stderr, "%s: bad privilege set\n", s);
exit(EXIT_FAILURE);
}
return (res);
}
static void
usage(void)
{
(void) fprintf(stderr, gettext("pfexec [-P privset] cmd [arg ..]\n"));
exit(EXIT_FAILURE);
}
/*
* This routine exists on failure and returns NULL if no granted privileges
* are set.
*/
static priv_set_t *
get_granted_privs(uid_t uid)
{
struct passwd *pwent;
userattr_t *ua;
char *profs;
priv_set_t *res;
char *profArray[MAXPROFS];
int profcnt = 0;
res = priv_allocset();
if (res == NULL) {
perror("priv_allocset");
exit(EXIT_FAILURE);
}
priv_emptyset(res);
if ((pwent = getpwuid(uid)) == NULL) {
(void) fprintf(stderr, "%d: ", (int)uid);
(void) fprintf(stderr, gettext("can't get passwd entry\n"));
exit(EXIT_FAILURE);
}
ua = getusernam(pwent->pw_name);
if (ua != NULL && ua->attr != NULL &&
(profs = kva_match(ua->attr, USERATTR_PROFILES_KW)) != NULL) {
get_profile_privs(profs, profArray, &profcnt, res);
free_proflist(profArray, profcnt);
}
get_default_privs(pwent->pw_name, res);
if (ua != NULL)
free_userattr(ua);
return (res);
}
static void
get_default_privs(const char *user, priv_set_t *pset)
{
char *profs = NULL;
char *profArray[MAXPROFS];
int profcnt = 0;
if (_get_user_defs(user, NULL, &profs) == 0) {
/* get privileges from default profiles */
if (profs != NULL) {
get_profile_privs(profs, profArray, &profcnt, pset);
free_proflist(profArray, profcnt);
_free_user_defs(NULL, profs);
}
}
}
static void
get_profile_privs(char *profiles, char **profArray, int *profcnt,
priv_set_t *pset)
{
char *prof;
char *lasts;
profattr_t *pa;
char *privs;
int i;
for (prof = strtok_r(profiles, PROFLIST_SEP, &lasts);
prof != NULL;
prof = strtok_r(NULL, PROFLIST_SEP, &lasts))
getproflist(prof, profArray, profcnt);
/* get the privileges from list of profiles */
for (i = 0; i < *profcnt; i++) {
if ((pa = getprofnam(profArray[i])) == NULL) {
/*
* this should never happen.
* unless the database has an undefined profile
*/
continue;
}
/* get privs from this profile */
privs = kva_match(pa->attr, PROFATTR_PRIVS_KW);
if (privs != NULL) {
priv_set_t *tmp = priv_str_to_set(privs, ",", NULL);
if (tmp != NULL) {
priv_union(tmp, pset);
priv_freeset(tmp);
}
}
free_profattr(pa);
}
}
/*
* This function can return either the first argument or dynamically
* allocated memory. Reuse with care.
*/
static char *
pathsearch(char *cmd)
{
char *path, *dir;
char buf[MAXPATHLEN];
/*
* Implement shell like PATH searching; if the pathname contains
* one or more slashes, don't search the path, even if the '/'
* isn't the first character. (E.g., ./command or dir/command)
* No path equals to a search in ".", just like the shell.
*/
if (strchr(cmd, '/') != NULL)
return (cmd);
path = getenv("PATH");
if (path == NULL)
return (cmd);
/*
* We need to copy $PATH because our sub processes may need it.
*/
path = strdup(path);
if (path == NULL) {
perror("pfexec: strdup $PATH");
exit(EXIT_FAILURE);
}
for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) {
if (snprintf(buf, sizeof (buf), "%s/%s", dir, cmd) >=
sizeof (buf)) {
continue;
}
if (access(buf, X_OK) == 0) {
free(path);
return (strdup(buf));
}
}
free(path);
(void) fprintf(stderr, gettext("%s: Command not found\n"), cmd);
return (NULL);
}