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