pfexec.c revision 4321:a8930ec16e52
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
53extern int cannot_audit(int);
54
55static char *pathsearch(char *);
56static int getrealpath(const char *, char *);
57static int checkattrs(char *, int, char *[]);
58static void sanitize_environ();
59static uid_t get_uid(char *);
60static gid_t get_gid(char *);
61static priv_set_t *get_privset(const char *);
62static priv_set_t *get_granted_privs(uid_t);
63static void get_default_privs(priv_set_t *);
64static void get_profile_privs(char *, char **, int *, priv_set_t *);
65
66static int isnumber(char *);
67static void usage(void);
68
69extern char **environ;
70
71#define	PROFLIST_SEP	","
72
73int
74main(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 */
193static int
194getrealpath(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 */
208static int
209checkattrs(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
293set_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 */
328static void
329sanitize_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
346static uid_t
347get_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
364static uid_t
365get_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
382static int
383isnumber(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
399static priv_set_t *
400get_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
411static void
412usage(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 */
423static priv_set_t *
424get_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
463static void
464get_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
481static void
482get_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 */
526static char *
527pathsearch(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