Home | History | Annotate | Download | only in pfexec
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     27 
     28 #include <errno.h>
     29 #include <deflt.h>
     30 #include <locale.h>
     31 #include <sys/types.h>
     32 #include <sys/param.h>
     33 #include <sys/stat.h>
     34 #include <stdlib.h>
     35 #include <unistd.h>
     36 #include <ctype.h>
     37 #include <pwd.h>
     38 #include <grp.h>
     39 #include <string.h>
     40 #include <exec_attr.h>
     41 #include <user_attr.h>
     42 #include <auth_attr.h>
     43 #include <prof_attr.h>
     44 #include <errno.h>
     45 #include <priv.h>
     46 
     47 #include <bsm/adt.h>
     48 #include <bsm/adt_event.h>
     49 
     50 #ifndef	TEXT_DOMAIN			/* Should be defined by cc -D */
     51 #define	TEXT_DOMAIN	"SYS_TEST"
     52 #endif
     53 
     54 extern int cannot_audit(int);
     55 
     56 static char *pathsearch(char *);
     57 static int getrealpath(const char *, char *);
     58 static int checkattrs(char *, int, char *[]);
     59 static void sanitize_environ();
     60 static uid_t get_uid(char *);
     61 static gid_t get_gid(char *);
     62 static priv_set_t *get_privset(const char *);
     63 static priv_set_t *get_granted_privs(uid_t);
     64 static void get_default_privs(const char *, priv_set_t *);
     65 static void get_profile_privs(char *, char **, int *, priv_set_t *);
     66 
     67 static int isnumber(char *);
     68 static void usage(void);
     69 
     70 extern char **environ;
     71 
     72 #define	PROFLIST_SEP	","
     73 
     74 int
     75 main(int argc, char *argv[])
     76 {
     77 	char		*cmd;
     78 	char		**cmdargs;
     79 	char		cmd_realpath[MAXPATHLEN];
     80 	int		c;
     81 	char 		*pset = NULL;
     82 
     83 	(void) setlocale(LC_ALL, "");
     84 	(void) textdomain(TEXT_DOMAIN);
     85 
     86 	while ((c = getopt(argc, argv, "P:")) != EOF) {
     87 		switch (c) {
     88 		case 'P':
     89 			if (pset == NULL) {
     90 				pset = optarg;
     91 				break;
     92 			}
     93 			/* FALLTHROUGH */
     94 		default:
     95 			usage();
     96 		}
     97 	}
     98 	argc -= optind;
     99 	argv += optind;
    100 
    101 	if (argc < 1)
    102 		usage();
    103 
    104 	cmd = argv[0];
    105 	cmdargs = &argv[0];
    106 
    107 	if (pset != NULL) {
    108 		uid_t uid = getuid();
    109 		priv_set_t *wanted = get_privset(pset);
    110 		priv_set_t *granted;
    111 
    112 		adt_session_data_t *ah;		/* audit session handle */
    113 		adt_event_data_t *event;	/* event to be generated */
    114 		char cwd[MAXPATHLEN];
    115 
    116 		granted = get_granted_privs(uid);
    117 
    118 		/* Audit use */
    119 		if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
    120 			perror("pfexec: adt_start_session");
    121 			exit(EXIT_FAILURE);
    122 		}
    123 		if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) {
    124 			perror("pfexec: adt_alloc_event");
    125 			exit(EXIT_FAILURE);
    126 		}
    127 		if ((event->adt_prof_cmd.cwdpath =
    128 		    getcwd(cwd, sizeof (cwd))) == NULL) {
    129 			(void) fprintf(stderr,
    130 			    gettext("pfexec: can't add cwd path\n"));
    131 			exit(EXIT_FAILURE);
    132 		}
    133 
    134 		event->adt_prof_cmd.cmdpath = cmd;
    135 		event->adt_prof_cmd.argc = argc - 1;
    136 		event->adt_prof_cmd.argv = &argv[1];
    137 		event->adt_prof_cmd.envp = environ;
    138 
    139 		if (granted != NULL) {
    140 			priv_intersect(granted, wanted);
    141 			event->adt_prof_cmd.inherit_set = wanted;
    142 			if (adt_put_event(event, ADT_SUCCESS,
    143 			    ADT_SUCCESS) != 0) {
    144 				perror("pfexec: adt_put_event");
    145 				exit(EXIT_FAILURE);
    146 			}
    147 			if (setppriv(PRIV_ON, PRIV_INHERITABLE, wanted) != 0) {
    148 				(void) fprintf(stderr,
    149 				    gettext("setppriv(): %s\n"),
    150 				    strerror(errno));
    151 				exit(EXIT_FAILURE);
    152 			}
    153 			/* Trick exec into thinking we're not suid */
    154 			(void) setppriv(PRIV_ON, PRIV_PERMITTED, wanted);
    155 			priv_freeset(event->adt_prof_cmd.inherit_set);
    156 		} else {
    157 			if (adt_put_event(event, ADT_SUCCESS,
    158 			    ADT_SUCCESS) != 0) {
    159 				perror("pfexec: adt_put_event");
    160 				exit(EXIT_FAILURE);
    161 			}
    162 		}
    163 		adt_free_event(event);
    164 		(void) adt_end_session(ah);
    165 		(void) setreuid(uid, uid);
    166 		(void) execvp(cmd, cmdargs);
    167 		(void) fprintf(stderr,
    168 		    gettext("pfexec: can't execute %s: %s\n"),
    169 		    cmd, strerror(errno));
    170 		exit(EXIT_FAILURE);
    171 	}
    172 
    173 	if ((cmd = pathsearch(cmd)) == NULL)
    174 		exit(EXIT_FAILURE);
    175 
    176 	if (getrealpath(cmd, cmd_realpath) == 0)
    177 		exit(EXIT_FAILURE);
    178 
    179 	if (checkattrs(cmd_realpath, argc, argv) == 0)
    180 		exit(EXIT_FAILURE);
    181 
    182 	(void) execv(cmd, cmdargs);
    183 	/*
    184 	 * We'd be here only if execv fails.
    185 	 */
    186 	(void) fprintf(stderr, gettext("pfexec: can't execute %s: %s\n"),
    187 	    cmd, strerror(errno));
    188 	exit(EXIT_FAILURE);
    189 /* LINTED */
    190 }
    191 
    192 
    193 /*
    194  * gets realpath for cmd.
    195  * return 1 on success, 0 on failure.
    196  */
    197 static int
    198 getrealpath(const char *cmd, char *cmd_realpath)
    199 {
    200 	if (realpath(cmd, cmd_realpath) == NULL) {
    201 		(void) fprintf(stderr,
    202 		    gettext("pfexec: can't get real path of ``%s''\n"), cmd);
    203 		return (0);
    204 	}
    205 	return (1);
    206 }
    207 
    208 /*
    209  * gets execution attributed for cmd, sets uids/gids, checks environ.
    210  * returns 1 on success, 0 on failure.
    211  */
    212 static int
    213 checkattrs(char *cmd_realpath, int argc, char *argv[])
    214 {
    215 	char			*value;
    216 	uid_t			uid, euid;
    217 	gid_t			gid = (gid_t)-1;
    218 	gid_t			egid = (gid_t)-1;
    219 	struct passwd		*pwent;
    220 	execattr_t		*exec;
    221 	priv_set_t		*lset = NULL;
    222 	priv_set_t		*iset = NULL;
    223 
    224 	adt_session_data_t	*ah;		/* audit session handle */
    225 	adt_event_data_t	*event;		/* event to be generated */
    226 	char			cwd[MAXPATHLEN];
    227 
    228 	uid = euid = getuid();
    229 	if ((pwent = getpwuid(uid)) == NULL) {
    230 		(void) fprintf(stderr, "%d: ", (int)uid);
    231 		(void) fprintf(stderr, gettext("can't get passwd entry\n"));
    232 		return (0);
    233 	}
    234 	/* Set up to audit use */
    235 	if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
    236 		perror("pfexec: adt_start_session");
    237 		return (0);
    238 	}
    239 	if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) {
    240 		perror("pfexec: adt_alloc_event");
    241 		return (0);
    242 	}
    243 	if ((event->adt_prof_cmd.cwdpath = getcwd(cwd, sizeof (cwd))) == NULL) {
    244 		(void) fprintf(stderr, gettext("pfexec: can't add cwd path\n"));
    245 		return (0);
    246 	}
    247 	/*
    248 	 * Get the exec attrs: uid, gid, euid and egid
    249 	 */
    250 	if ((exec = getexecuser(pwent->pw_name,
    251 	    KV_COMMAND, (char *)cmd_realpath, GET_ONE)) == NULL) {
    252 		(void) fprintf(stderr, "%s: ", cmd_realpath);
    253 		(void) fprintf(stderr,
    254 		    gettext("can't get execution attributes\n"));
    255 		return (0);
    256 	}
    257 	if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL) {
    258 		euid = uid = get_uid(value);
    259 		event->adt_prof_cmd.proc_euid = uid;
    260 		event->adt_prof_cmd.proc_ruid = uid;
    261 	}
    262 	if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL) {
    263 		egid = gid = get_gid(value);
    264 		event->adt_prof_cmd.proc_egid = gid;
    265 		event->adt_prof_cmd.proc_rgid = gid;
    266 	}
    267 	if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL) {
    268 		event->adt_prof_cmd.proc_euid = euid = get_uid(value);
    269 	}
    270 	if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL) {
    271 		event->adt_prof_cmd.proc_egid = egid = get_gid(value);
    272 	}
    273 	if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL) {
    274 		lset = get_privset(value);
    275 		event->adt_prof_cmd.limit_set = lset;
    276 	}
    277 	if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL) {
    278 		iset = get_privset(value);
    279 		event->adt_prof_cmd.inherit_set = iset;
    280 	}
    281 	if (euid == uid || iset != NULL) {
    282 		sanitize_environ();
    283 	}
    284 
    285 	/* Finish audit info */
    286 	event->adt_prof_cmd.cmdpath = cmd_realpath;
    287 	event->adt_prof_cmd.argc = argc - 1;
    288 	event->adt_prof_cmd.argv = &argv[1];
    289 	event->adt_prof_cmd.envp = environ;
    290 	if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
    291 		perror("pfexec: adt_put_event");
    292 		return (0);
    293 	}
    294 	adt_free_event(event);
    295 	(void) adt_end_session(ah);
    296 
    297 set_attrs:
    298 	/*
    299 	 * Set gids/uids and privileges.
    300 	 *
    301 	 */
    302 	if ((gid != (gid_t)-1) || (egid != (gid_t)-1)) {
    303 		if ((setregid(gid, egid) == -1)) {
    304 			(void) fprintf(stderr, "%s: ", cmd_realpath);
    305 			(void) fprintf(stderr, gettext("can't set gid\n"));
    306 			return (0);
    307 		}
    308 	}
    309 	if (lset != NULL && setppriv(PRIV_SET, PRIV_LIMIT, lset) != 0 ||
    310 	    iset != NULL && setppriv(PRIV_ON, PRIV_INHERITABLE, iset) != 0) {
    311 		(void) fprintf(stderr, gettext("%s: can't set privileges\n"),
    312 		    cmd_realpath);
    313 		return (0);
    314 	}
    315 	if (setreuid(uid, euid) == -1) {
    316 		(void) fprintf(stderr, "%s: ", cmd_realpath);
    317 		(void) fprintf(stderr, gettext("can't set uid\n"));
    318 		return (0);
    319 	}
    320 	if (iset != NULL && getppriv(PRIV_INHERITABLE, iset) == 0)
    321 		(void) setppriv(PRIV_SET, PRIV_PERMITTED, iset);
    322 
    323 	free_execattr(exec);
    324 
    325 	return (1);
    326 }
    327 
    328 
    329 /*
    330  * cleans up environ. code from su.c
    331  */
    332 static void
    333 sanitize_environ()
    334 {
    335 	char	**pp = environ;
    336 	char	**qq, *p;
    337 
    338 	while ((p = *pp) != NULL) {
    339 		if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
    340 			for (qq = pp; (*qq = qq[1]) != NULL; qq++) {
    341 				;
    342 			}
    343 		} else {
    344 			pp++;
    345 		}
    346 	}
    347 }
    348 
    349 
    350 static uid_t
    351 get_uid(char *value)
    352 {
    353 	struct passwd *passwd_ent;
    354 
    355 	if ((passwd_ent = getpwnam(value)) != NULL)
    356 		return (passwd_ent->pw_uid);
    357 
    358 	if (isnumber(value))
    359 		return (atoi(value));
    360 
    361 	(void) fprintf(stderr, "pfexec: %s: ", value);
    362 	(void) fprintf(stderr, gettext("can't get user entry\n"));
    363 	exit(EXIT_FAILURE);
    364 	/*NOTREACHED*/
    365 }
    366 
    367 
    368 static uid_t
    369 get_gid(char *value)
    370 {
    371 	struct group *group_ent;
    372 
    373 	if ((group_ent = getgrnam(value)) != NULL)
    374 		return (group_ent->gr_gid);
    375 
    376 	if (isnumber(value))
    377 		return (atoi(value));
    378 
    379 	(void) fprintf(stderr, "pfexec: %s: ", value);
    380 	(void) fprintf(stderr, gettext("can't get group entry\n"));
    381 	exit(EXIT_FAILURE);
    382 	/*NOTREACHED*/
    383 }
    384 
    385 
    386 static int
    387 isnumber(char *s)
    388 {
    389 	int c;
    390 
    391 	if (*s == '\0')
    392 		return (0);
    393 
    394 	while ((c = *s++) != '\0') {
    395 		if (!isdigit(c)) {
    396 			return (0);
    397 		}
    398 	}
    399 
    400 	return (1);
    401 }
    402 
    403 static priv_set_t *
    404 get_privset(const char *s)
    405 {
    406 	priv_set_t *res;
    407 
    408 	if ((res = priv_str_to_set(s, ",", NULL)) == NULL) {
    409 		(void) fprintf(stderr, "%s: bad privilege set\n", s);
    410 		exit(EXIT_FAILURE);
    411 	}
    412 	return (res);
    413 }
    414 
    415 static void
    416 usage(void)
    417 {
    418 	(void) fprintf(stderr, gettext("pfexec [-P privset] cmd [arg ..]\n"));
    419 	exit(EXIT_FAILURE);
    420 }
    421 
    422 
    423 /*
    424  * This routine exists on failure and returns NULL if no granted privileges
    425  * are set.
    426  */
    427 static priv_set_t *
    428 get_granted_privs(uid_t uid)
    429 {
    430 	struct passwd *pwent;
    431 	userattr_t *ua;
    432 	char *profs;
    433 	priv_set_t *res;
    434 	char *profArray[MAXPROFS];
    435 	int profcnt = 0;
    436 
    437 	res = priv_allocset();
    438 	if (res == NULL) {
    439 		perror("priv_allocset");
    440 		exit(EXIT_FAILURE);
    441 	}
    442 
    443 	priv_emptyset(res);
    444 
    445 	if ((pwent = getpwuid(uid)) == NULL) {
    446 		(void) fprintf(stderr, "%d: ", (int)uid);
    447 		(void) fprintf(stderr, gettext("can't get passwd entry\n"));
    448 		exit(EXIT_FAILURE);
    449 	}
    450 
    451 	ua = getusernam(pwent->pw_name);
    452 
    453 	if (ua != NULL && ua->attr != NULL &&
    454 	    (profs = kva_match(ua->attr, USERATTR_PROFILES_KW)) != NULL) {
    455 		get_profile_privs(profs, profArray, &profcnt, res);
    456 		free_proflist(profArray, profcnt);
    457 	}
    458 
    459 	get_default_privs(pwent->pw_name, res);
    460 
    461 	if (ua != NULL)
    462 		free_userattr(ua);
    463 
    464 	return (res);
    465 }
    466 
    467 static void
    468 get_default_privs(const char *user, priv_set_t *pset)
    469 {
    470 	char *profs = NULL;
    471 	char *profArray[MAXPROFS];
    472 	int profcnt = 0;
    473 
    474 	if (_get_user_defs(user, NULL, &profs) == 0) {
    475 		/* get privileges from default profiles */
    476 		if (profs != NULL) {
    477 			get_profile_privs(profs, profArray, &profcnt, pset);
    478 			free_proflist(profArray, profcnt);
    479 			_free_user_defs(NULL, profs);
    480 		}
    481 	}
    482 }
    483 
    484 static void
    485 get_profile_privs(char *profiles, char **profArray, int *profcnt,
    486 	priv_set_t *pset)
    487 {
    488 
    489 	char		*prof;
    490 	char		*lasts;
    491 	profattr_t	*pa;
    492 	char		*privs;
    493 	int		i;
    494 
    495 	for (prof = strtok_r(profiles, PROFLIST_SEP, &lasts);
    496 	    prof != NULL;
    497 	    prof = strtok_r(NULL, PROFLIST_SEP, &lasts))
    498 		getproflist(prof, profArray, profcnt);
    499 
    500 	/* get the privileges from list of profiles */
    501 	for (i = 0; i < *profcnt; i++) {
    502 
    503 		if ((pa = getprofnam(profArray[i])) == NULL) {
    504 			/*
    505 			 *  this should never happen.
    506 			 *  unless the database has an undefined profile
    507 			 */
    508 			continue;
    509 		}
    510 
    511 		/* get privs from this profile */
    512 		privs = kva_match(pa->attr, PROFATTR_PRIVS_KW);
    513 		if (privs != NULL) {
    514 			priv_set_t *tmp = priv_str_to_set(privs, ",", NULL);
    515 			if (tmp != NULL) {
    516 				priv_union(tmp, pset);
    517 				priv_freeset(tmp);
    518 			}
    519 		}
    520 
    521 		free_profattr(pa);
    522 	}
    523 }
    524 
    525 /*
    526  * True if someone (user, group, other) can execute this file.
    527  */
    528 #define	S_ISEXEC(mode)	(((mode)&(S_IXUSR|S_IXGRP|S_IXOTH)) != 0)
    529 
    530 /*
    531  * This function can return either the first argument or dynamically
    532  * allocated memory.  Reuse with care.
    533  */
    534 static char *
    535 pathsearch(char *cmd)
    536 {
    537 	char *path, *dir, *result;
    538 	char buf[MAXPATHLEN];
    539 	struct stat stbuf;
    540 
    541 	/*
    542 	 * Implement shell like PATH searching; if the pathname contains
    543 	 * one or more slashes, don't search the path, even if the '/'
    544 	 * isn't the first character. (E.g., ./command or dir/command)
    545 	 * No path equals to a search in ".", just like the shell.
    546 	 */
    547 	if (strchr(cmd, '/') != NULL)
    548 		return (cmd);
    549 
    550 	path = getenv("PATH");
    551 	if (path == NULL)
    552 		return (cmd);
    553 
    554 	/*
    555 	 * We need to copy $PATH because our sub processes may need it.
    556 	 */
    557 	path = strdup(path);
    558 	if (path == NULL) {
    559 		perror("pfexec: strdup $PATH");
    560 		exit(EXIT_FAILURE);
    561 	}
    562 
    563 	result = NULL;
    564 	for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) {
    565 		if (snprintf(buf, sizeof (buf), "%s/%s", dir, cmd) >=
    566 		    sizeof (buf)) {
    567 			continue;
    568 		}
    569 		if (stat(buf, &stbuf) < 0)
    570 			continue;
    571 		/*
    572 		 * Shells typically call access() with E_OK flag
    573 		 * to determine if the effective uid can execute
    574 		 * the file. We don't know what the eventual euid
    575 		 * will be; it is determined by the exec_attr
    576 		 * attributes which depend on the full pathname of
    577 		 * the command. Therefore, we match the first regular
    578 		 * file we find that is executable by someone.
    579 		 */
    580 		if (S_ISREG(stbuf.st_mode) && S_ISEXEC(stbuf.st_mode)) {
    581 			result = strdup(buf);
    582 			break;
    583 		}
    584 	}
    585 	free(path);
    586 	if (result == NULL)
    587 		(void) fprintf(stderr, gettext("%s: Command not found\n"), cmd);
    588 	return (result);
    589 }
    590