xref: /illumos-gate/usr/src/cmd/pfexec/pfexec.c (revision e9af4bc0b1cc30cea75d6ad4aa2fde97d985e9be)
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