1/*-
2 * Copyright (c) 2002 Tim J. Robbins.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27/*
28 * newgrp -- change to a new group
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: src/usr.bin/newgrp/newgrp.c,v 1.5 2009/12/13 03:14:06 delphij Exp $");
33
34#include <sys/types.h>
35
36#include <err.h>
37#include <errno.h>
38#include <grp.h>
39#include <libgen.h>
40#include <limits.h>
41#ifndef __APPLE__
42#include <login_cap.h>
43#endif /* !__APPLE__ */
44#ifdef __APPLE__
45#include <membership.h>
46#endif /* __APPLE__ */
47#include <pwd.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <unistd.h>
52#ifdef __APPLE__
53#include <paths.h>
54#endif /* __APPLE__ */
55static void	 addgroup(const char *grpname);
56static void	 doshell(void);
57static int	 inarray(gid_t, const gid_t[], int);
58static void	 loginshell(void);
59static void	 restoregrps(void);
60static void	 usage(void);
61
62static struct passwd *pwd;
63static uid_t euid;
64
65extern char **environ;
66
67/* Manipulate effective user ID. */
68#define PRIV_START do {				\
69		if (seteuid(euid) < 0)		\
70			err(1, "seteuid");	\
71	} while (0)
72#define PRIV_END do {				\
73		if (seteuid(getuid()) < 0)	\
74			err(1, "seteuid");	\
75	} while (0)
76
77int
78main(int argc, char *argv[])
79{
80	int ch, login;
81
82	euid = geteuid();
83	if (seteuid(getuid()) < 0)
84		err(1, "seteuid");
85
86	if ((pwd = getpwuid(getuid())) == NULL)
87		errx(1, "unknown user");
88
89	login = 0;
90	while ((ch = getopt(argc, argv, "-l")) != -1) {
91		switch (ch) {
92		case '-':		/* Obsolescent */
93		case 'l':
94			login = 1;
95			break;
96		default:
97			usage();
98		}
99	}
100	argc -= optind;
101	argv += optind;
102
103	switch (argc) {
104	case 0:
105		restoregrps();
106		break;
107	case 1:
108		addgroup(*argv);
109		break;
110	default:
111		usage();
112	}
113
114	if (seteuid(euid) < 0)
115		err(1, "seteuid");
116	if (setuid(getuid()) < 0)
117		err(1, "setuid");
118
119	if (login)
120		loginshell();
121	else
122		doshell();
123
124	/*NOTREACHED*/
125	exit(1);
126}
127
128static void
129usage(void)
130{
131
132	fprintf(stderr, "usage: newgrp [-l] [group]\n");
133	exit(1);
134}
135
136static void
137restoregrps(void)
138{
139	int initres, setres;
140
141	PRIV_START;
142	initres = initgroups(pwd->pw_name, pwd->pw_gid);
143	setres = setgid(pwd->pw_gid);
144	PRIV_END;
145
146	if (initres < 0)
147		warn("initgroups");
148	if (setres < 0)
149		warn("setgid");
150}
151
152static void
153addgroup(const char *grpname)
154{
155	gid_t *grps;
156	long lgid, ngrps_max;
157	int dbmember, i, ngrps;
158	gid_t egid;
159	struct group *grp;
160	char *ep, *pass;
161	char **p;
162	char *grp_passwd;
163#ifdef __APPLE__
164	uuid_t user_uuid;
165	uuid_t group_uuid;
166	int status;
167#endif
168
169	egid = getegid();
170
171	/* Try it as a group name, then a group id. */
172	if ((grp = getgrnam(grpname)) == NULL)
173		if ((lgid = strtol(grpname, &ep, 10)) <= 0 || *ep != '\0' ||
174		    (grp = getgrgid((gid_t)lgid)) == NULL ) {
175			warnx("%s: bad group name", grpname);
176			return;
177		}
178
179#ifdef __APPLE__
180	status = mbr_uid_to_uuid(pwd->pw_uid, user_uuid);
181	if (status)
182		errc(1, status, "mbr_uid_to_uuid");
183
184	status = mbr_gid_to_uuid(grp->gr_gid, group_uuid);
185	if (status)
186		errc(1, status, "mbr_gid_to_uuid");
187
188	status = mbr_check_membership(user_uuid, group_uuid, &dbmember);
189	if (status)
190		errc(1, status, "mbr_check_membership");
191#else
192	/*
193	 * If the user is not a member of the requested group and the group
194	 * has a password, prompt and check it.
195	 */
196	dbmember = 0;
197	if (pwd->pw_gid == grp->gr_gid)
198		dbmember = 1;
199	for (p = grp->gr_mem; *p != NULL; p++)
200		if (strcmp(*p, pwd->pw_name) == 0) {
201			dbmember = 1;
202			break;
203		}
204#endif
205
206	grp_passwd = grp->gr_passwd;
207	if ((grp_passwd == NULL) || (grp_passwd[0] == '\0'))
208		grp_passwd = "*";
209	if (!dbmember && getuid() != 0) {
210		pass = getpass("Password:");
211		if (pass == NULL ||
212		    strcmp(grp_passwd, crypt(pass, grp_passwd)) != 0) {
213			fprintf(stderr, "Sorry\n");
214			return;
215		}
216	}
217
218	ngrps_max = sysconf(_SC_NGROUPS_MAX) + 1;
219	if ((grps = malloc(sizeof(gid_t) * ngrps_max)) == NULL)
220		err(1, "malloc");
221	if ((ngrps = getgroups(ngrps_max, (gid_t *)grps)) < 0) {
222		warn("getgroups");
223		goto end;
224	}
225
226	/* Remove requested gid from supp. list if it exists. */
227	if (grp->gr_gid != egid && inarray(grp->gr_gid, grps, ngrps)) {
228		for (i = 0; i < ngrps; i++)
229			if (grps[i] == grp->gr_gid)
230				break;
231		ngrps--;
232		memmove(&grps[i], &grps[i + 1], (ngrps - i) * sizeof(gid_t));
233		PRIV_START;
234		if (setgroups(ngrps, (const gid_t *)grps) < 0) {
235			PRIV_END;
236			warn("setgroups");
237			goto end;
238		}
239		PRIV_END;
240	}
241
242	PRIV_START;
243	if (setgid(grp->gr_gid)) {
244		PRIV_END;
245		warn("setgid");
246		goto end;
247	}
248	PRIV_END;
249	grps[0] = grp->gr_gid;
250
251	/* Add old effective gid to supp. list if it does not exist. */
252	if (egid != grp->gr_gid && !inarray(egid, grps, ngrps)) {
253		if (ngrps + 1 >= ngrps_max)
254			warnx("too many groups");
255		else {
256			grps[ngrps++] = egid;
257			PRIV_START;
258			if (setgroups(ngrps, (const gid_t *)grps)) {
259				PRIV_END;
260				warn("setgroups");
261				goto end;
262			}
263			PRIV_END;
264		}
265	}
266
267end:
268	free(grps);
269}
270
271static int
272inarray(gid_t gid, const gid_t grps[], int ngrps)
273{
274	int i;
275
276	for (i = 0; i < ngrps; i++)
277		if (grps[i] == gid)
278			return (1);
279	return (0);
280}
281
282/*
283 * Set the environment to what would be expected if the user logged in
284 * again; this performs the same steps as su(1)'s -l option.
285 */
286static void
287loginshell(void)
288{
289	char *args[2], **cleanenv, *term, *ticket;
290	const char *shell;
291	char *prog, progbuf[PATH_MAX];
292#ifndef __APPLE__
293	login_cap_t *lc;
294#endif /* !__APPLE__ */
295	shell = pwd->pw_shell;
296	if (*shell == '\0')
297		shell = _PATH_BSHELL;
298	if (chdir(pwd->pw_dir) < 0) {
299		warn("%s", pwd->pw_dir);
300		chdir("/");
301	}
302
303	term = getenv("TERM");
304	ticket = getenv("KRBTKFILE");
305
306	if ((cleanenv = calloc(20, sizeof(char *))) == NULL)
307		err(1, "calloc");
308	*cleanenv = NULL;
309	environ = cleanenv;
310#ifndef __APPLE__
311	lc = login_getpwclass(pwd);
312	setusercontext(lc, pwd, pwd->pw_uid,
313	    LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
314	login_close(lc);
315#endif /* !__APPLE__ */
316	setenv("USER", pwd->pw_name, 1);
317	setenv("SHELL", shell, 1);
318	setenv("HOME", pwd->pw_dir, 1);
319	if (term != NULL)
320		setenv("TERM", term, 1);
321	if (ticket != NULL)
322		setenv("KRBTKFILE", ticket, 1);
323
324	strlcpy(progbuf, shell, sizeof(progbuf));
325	prog = basename(progbuf);
326
327	if (asprintf(args, "-%s", prog) < 0)
328		err(1, "asprintf");
329	args[1] = NULL;
330
331	execv(shell, args);
332	err(1, "%s", shell);
333}
334
335static void
336doshell(void)
337{
338	const char *shell;
339	char *prog, progbuf[PATH_MAX];
340
341	shell = pwd->pw_shell;
342	if (*shell == '\0')
343		shell = _PATH_BSHELL;
344
345	strlcpy(progbuf, shell, sizeof(progbuf));
346	prog = basename(progbuf);
347
348	execl(shell, prog, (char *)NULL);
349	err(1, "%s", shell);
350}
351