xref: /illumos-gate/usr/src/cmd/pfexec/pfexec.c (revision 7c478bd9)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <errno.h>
30 #include <deflt.h>
31 #include <locale.h>
32 #include <sys/types.h>
33 #include <sys/param.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(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 		perror(cmd);
168 		exit(EXIT_FAILURE);
169 	}
170 
171 	if ((cmd = pathsearch(cmd)) == NULL)
172 		exit(EXIT_FAILURE);
173 
174 	if (getrealpath(cmd, cmd_realpath) == 0)
175 		exit(EXIT_FAILURE);
176 
177 	if (checkattrs(cmd_realpath, argc, argv) == 0)
178 		exit(EXIT_FAILURE);
179 
180 	(void) execv(cmd, cmdargs);
181 	/*
182 	 * We'd be here only if execv fails.
183 	 */
184 	perror("pfexec");
185 	exit(EXIT_FAILURE);
186 /* LINTED */
187 }
188 
189 
190 /*
191  * gets realpath for cmd.
192  * return 1 on success, 0 on failure.
193  */
194 static int
195 getrealpath(const char *cmd, char *cmd_realpath)
196 {
197 	if (realpath(cmd, cmd_realpath) == NULL) {
198 		(void) fprintf(stderr,
199 		    gettext("pfexec: can't get real path of ``%s''\n"), cmd);
200 		return (0);
201 	}
202 	return (1);
203 }
204 
205 /*
206  * gets execution attributed for cmd, sets uids/gids, checks environ.
207  * returns 1 on success, 0 on failure.
208  */
209 static int
210 checkattrs(char *cmd_realpath, int argc, char *argv[])
211 {
212 	char			*value;
213 	uid_t			uid, euid;
214 	gid_t			gid = -1;
215 	gid_t			egid = -1;
216 	struct passwd		*pwent;
217 	execattr_t		*exec;
218 	priv_set_t		*lset = NULL;
219 	priv_set_t		*iset = NULL;
220 
221 	adt_session_data_t	*ah;		/* audit session handle */
222 	adt_event_data_t	*event;		/* event to be generated */
223 	char			cwd[MAXPATHLEN];
224 
225 	uid = euid = getuid();
226 	if ((pwent = getpwuid(uid)) == NULL) {
227 		(void) fprintf(stderr, "%d: ", (int)uid);
228 		(void) fprintf(stderr, gettext("can't get passwd entry\n"));
229 		return (0);
230 	}
231 	/* Set up to audit use */
232 	if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
233 		perror("pfexec: adt_start_session");
234 		return (0);
235 	}
236 	if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) {
237 		perror("pfexec: adt_alloc_event");
238 		return (0);
239 	}
240 	if ((event->adt_prof_cmd.cwdpath = getcwd(cwd, sizeof (cwd))) == NULL) {
241 		(void) fprintf(stderr, gettext("pfexec: can't add cwd path\n"));
242 		return (0);
243 	}
244 	/*
245 	 * Get the exec attrs: uid, gid, euid and egid
246 	 */
247 	if ((exec = getexecuser(pwent->pw_name,
248 	    KV_COMMAND, (char *)cmd_realpath, GET_ONE)) == NULL) {
249 		(void) fprintf(stderr, "%s: ", cmd_realpath);
250 		(void) fprintf(stderr,
251 		    gettext("can't get execution attributes\n"));
252 		return (0);
253 	}
254 	if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL) {
255 		euid = uid = get_uid(value);
256 		event->adt_prof_cmd.proc_euid = uid;
257 		event->adt_prof_cmd.proc_ruid = uid;
258 	}
259 	if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL) {
260 		egid = gid = get_gid(value);
261 		event->adt_prof_cmd.proc_egid = gid;
262 		event->adt_prof_cmd.proc_rgid = gid;
263 	}
264 	if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL) {
265 		event->adt_prof_cmd.proc_euid = euid = get_uid(value);
266 	}
267 	if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL) {
268 		event->adt_prof_cmd.proc_egid = egid = get_gid(value);
269 	}
270 	if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL) {
271 		lset = get_privset(value);
272 		event->adt_prof_cmd.limit_set = lset;
273 	}
274 	if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL) {
275 		iset = get_privset(value);
276 		event->adt_prof_cmd.inherit_set = iset;
277 	}
278 	if (euid == uid || iset != NULL) {
279 		sanitize_environ();
280 	}
281 
282 	/* Finish audit info */
283 	event->adt_prof_cmd.cmdpath = cmd_realpath;
284 	event->adt_prof_cmd.argc = argc - 1;
285 	event->adt_prof_cmd.argv = &argv[1];
286 	event->adt_prof_cmd.envp = environ;
287 	if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
288 		perror("pfexec: adt_put_event");
289 		return (0);
290 	}
291 	adt_free_event(event);
292 	(void) adt_end_session(ah);
293 
294 set_attrs:
295 	/*
296 	 * Set gids/uids and privileges.
297 	 *
298 	 */
299 	if ((gid != -1) || (egid != -1)) {
300 		if ((setregid(gid, egid) == -1)) {
301 			(void) fprintf(stderr, "%s: ", cmd_realpath);
302 			(void) fprintf(stderr, gettext("can't set gid\n"));
303 			return (0);
304 		}
305 	}
306 	if (lset != NULL && setppriv(PRIV_SET, PRIV_LIMIT, lset) != 0 ||
307 	    iset != NULL && setppriv(PRIV_ON, PRIV_INHERITABLE, iset) != 0) {
308 		(void) fprintf(stderr, gettext("%s: can't set privileges\n"),
309 			cmd_realpath);
310 		return (0);
311 	}
312 	if (setreuid(uid, euid) == -1) {
313 		(void) fprintf(stderr, "%s: ", cmd_realpath);
314 		(void) fprintf(stderr, gettext("can't set uid\n"));
315 		return (0);
316 	}
317 	if (iset != NULL && getppriv(PRIV_INHERITABLE, iset) == 0)
318 		(void) setppriv(PRIV_SET, PRIV_PERMITTED, iset);
319 
320 	free_execattr(exec);
321 
322 	return (1);
323 }
324 
325 
326 /*
327  * cleans up environ. code from su.c
328  */
329 static void
330 sanitize_environ()
331 {
332 	char	**pp = environ;
333 	char	**qq, *p;
334 
335 	while ((p = *pp) != NULL) {
336 		if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
337 			for (qq = pp; (*qq = qq[1]) != NULL; qq++) {
338 				;
339 			}
340 		} else {
341 			pp++;
342 		}
343 	}
344 }
345 
346 
347 static uid_t
348 get_uid(char *value)
349 {
350 	struct passwd *passwd_ent;
351 
352 	if ((passwd_ent = getpwnam(value)) != NULL)
353 		return (passwd_ent->pw_uid);
354 
355 	if (isnumber(value))
356 		return (atoi(value));
357 
358 	(void) fprintf(stderr, "pfexec: %s: ", value);
359 	(void) fprintf(stderr, gettext("can't get user entry\n"));
360 	exit(EXIT_FAILURE);
361 	/*NOTREACHED*/
362 }
363 
364 
365 static uid_t
366 get_gid(char *value)
367 {
368 	struct group *group_ent;
369 
370 	if ((group_ent = getgrnam(value)) != NULL)
371 		return (group_ent->gr_gid);
372 
373 	if (isnumber(value))
374 		return (atoi(value));
375 
376 	(void) fprintf(stderr, "pfexec: %s: ", value);
377 	(void) fprintf(stderr, gettext("can't get group entry\n"));
378 	exit(EXIT_FAILURE);
379 	/*NOTREACHED*/
380 }
381 
382 
383 static int
384 isnumber(char *s)
385 {
386 	int c;
387 
388 	if (*s == '\0')
389 		return (0);
390 
391 	while ((c = *s++) != '\0') {
392 		if (!isdigit(c)) {
393 			return (0);
394 		}
395 	}
396 
397 	return (1);
398 }
399 
400 static priv_set_t *
401 get_privset(const char *s)
402 {
403 	priv_set_t *res;
404 
405 	if ((res = priv_str_to_set(s, ",", NULL)) == NULL) {
406 		(void) fprintf(stderr, "%s: bad privilege set\n", s);
407 		exit(EXIT_FAILURE);
408 	}
409 	return (res);
410 }
411 
412 static void
413 usage(void)
414 {
415 	(void) fprintf(stderr, gettext("pfexec [-P privset] cmd [arg ..]\n"));
416 	exit(EXIT_FAILURE);
417 }
418 
419 
420 /*
421  * This routine exists on failure and returns NULL if no granted privileges
422  * are set.
423  */
424 static priv_set_t *
425 get_granted_privs(uid_t uid)
426 {
427 	struct passwd *pwent;
428 	userattr_t *ua;
429 	char *profs;
430 	priv_set_t *res;
431 	char *profArray[MAXPROFS];
432 	int profcnt = 0;
433 
434 	res = priv_allocset();
435 	if (res == NULL) {
436 		perror("priv_allocset");
437 		exit(EXIT_FAILURE);
438 	}
439 
440 	priv_emptyset(res);
441 
442 	if ((pwent = getpwuid(uid)) == NULL) {
443 		(void) fprintf(stderr, "%d: ", (int)uid);
444 		(void) fprintf(stderr, gettext("can't get passwd entry\n"));
445 		exit(EXIT_FAILURE);
446 	}
447 
448 	ua = getusernam(pwent->pw_name);
449 
450 	if (ua != NULL && ua->attr != NULL &&
451 	    (profs = kva_match(ua->attr, USERATTR_PROFILES_KW)) != NULL) {
452 		get_profile_privs(profs, profArray, &profcnt, res);
453 		free_proflist(profArray, profcnt);
454 	}
455 
456 	get_default_privs(res);
457 
458 	if (ua != NULL)
459 		free_userattr(ua);
460 
461 	return (res);
462 }
463 
464 static void
465 get_default_privs(priv_set_t *pset)
466 {
467 	char *profs = NULL;
468 	char *profArray[MAXPROFS];
469 	int profcnt = 0;
470 
471 	if (defopen(AUTH_POLICY) == 0) {
472 		/* get privileges from default profiles */
473 		profs = defread(DEF_PROF);
474 		if (profs != NULL) {
475 			get_profile_privs(profs, profArray, &profcnt, pset);
476 			free_proflist(profArray, profcnt);
477 		}
478 	}
479 	(void) defopen(NULL);
480 }
481 
482 static void
483 get_profile_privs(char *profiles, char **profArray, int *profcnt,
484 	priv_set_t *pset)
485 {
486 
487 	char		*prof;
488 	char		*lasts;
489 	profattr_t	*pa;
490 	char		*privs;
491 	int		i;
492 
493 	for (prof = strtok_r(profiles, PROFLIST_SEP, &lasts);
494 	    prof != NULL;
495 	    prof = strtok_r(NULL, PROFLIST_SEP, &lasts))
496 		getproflist(prof, profArray, profcnt);
497 
498 	/* get the privileges from list of profiles */
499 	for (i = 0; i < *profcnt; i++) {
500 
501 		if ((pa = getprofnam(profArray[i])) == NULL) {
502 			/*
503 			 *  this should never happen.
504 			 *  unless the database has an undefined profile
505 			 */
506 			continue;
507 		}
508 
509 		/* get privs from this profile */
510 		privs = kva_match(pa->attr, PROFATTR_PRIVS_KW);
511 		if (privs != NULL) {
512 			priv_set_t *tmp = priv_str_to_set(privs, ",", NULL);
513 			if (tmp != NULL) {
514 				priv_union(tmp, pset);
515 				priv_freeset(tmp);
516 			}
517 		}
518 
519 		free_profattr(pa);
520 	}
521 }
522 
523 /*
524  * This function can return either the first argument or dynamically
525  * allocated memory.  Reuse with care.
526  */
527 static char *
528 pathsearch(char *cmd)
529 {
530 	char *path, *dir;
531 	char buf[MAXPATHLEN];
532 
533 	/*
534 	 * Implement shell like PATH searching; if the pathname contains
535 	 * one or more slashes, don't search the path, even if the '/'
536 	 * isn't the first character. (E.g., ./command or dir/command)
537 	 * No path equals to a search in ".", just like the shell.
538 	 */
539 	if (strchr(cmd, '/') != NULL)
540 		return (cmd);
541 
542 	path = getenv("PATH");
543 	if (path == NULL)
544 		return (cmd);
545 
546 	/*
547 	 * We need to copy $PATH because our sub processes may need it.
548 	 */
549 	path = strdup(path);
550 	if (path == NULL) {
551 		perror("pfexec: strdup $PATH");
552 		exit(EXIT_FAILURE);
553 	}
554 
555 	for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) {
556 		if (snprintf(buf, sizeof (buf), "%s/%s", dir, cmd) >=
557 		    sizeof (buf)) {
558 			continue;
559 		}
560 		if (access(buf, X_OK) == 0) {
561 			free(path);
562 			return (strdup(buf));
563 		}
564 	}
565 	free(path);
566 	(void) fprintf(stderr, gettext("%s: Command not found\n"), cmd);
567 	return (NULL);
568 }
569