su.c revision 83209
1/*
2 * Copyright (c) 1988, 1993, 1994
3 *	The Regents of the University of California.  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 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1988, 1993, 1994\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41#if 0
42static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
43#endif
44static const char rcsid[] =
45  "$FreeBSD: head/usr.bin/su/su.c 83209 2001-09-07 16:20:38Z markm $";
46#endif /* not lint */
47
48#include <sys/param.h>
49#include <sys/time.h>
50#include <sys/resource.h>
51#include <sys/wait.h>
52
53#include <err.h>
54#include <errno.h>
55#include <grp.h>
56#include <libutil.h>
57#include <login_cap.h>
58#include <paths.h>
59#include <pwd.h>
60#include <signal.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64#include <syslog.h>
65#include <unistd.h>
66
67#include <security/pam_appl.h>
68#include <security/pam_misc.h>
69
70#define PAM_END() do {						\
71	int local_ret;						\
72	if (pamh != NULL && creds_set) {			\
73		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);	\
74		if (local_ret != PAM_SUCCESS)			\
75			syslog(LOG_ERR, "pam_setcred: %s",	\
76				pam_strerror(pamh, local_ret));	\
77		local_ret = pam_end(pamh, local_ret);		\
78		if (local_ret != PAM_SUCCESS)			\
79			syslog(LOG_ERR, "pam_end: %s",		\
80				pam_strerror(pamh, local_ret));	\
81	}							\
82} while (0)
83
84
85#define PAM_SET_ITEM(what, item) do {				\
86	int local_ret;						\
87	local_ret = pam_set_item(pamh, what, item);		\
88	if (local_ret != PAM_SUCCESS) {				\
89		syslog(LOG_ERR, "pam_set_item(" #what "): %s",	\
90			pam_strerror(pamh, local_ret));		\
91		errx(1, "pam_set_item(" #what "): %s",		\
92			pam_strerror(pamh, local_ret));		\
93	}							\
94} while (0)
95
96enum tristate { UNSET, YES, NO };
97
98static pam_handle_t *pamh = NULL;
99static int	creds_set = 0;
100static char	**environ_pam;
101
102static char	*ontty(void);
103static int	chshell(char *);
104static void	usage(void);
105static int	export_pam_environment(void);
106static int	ok_to_export(const char *);
107
108extern char	**environ;
109
110int
111main(int argc, char *argv[])
112{
113	struct passwd	*pwd;
114	struct pam_conv	conv = {misc_conv, NULL};
115	enum tristate	iscsh;
116	login_cap_t	*lc;
117	uid_t		ruid;
118	gid_t		gid;
119	int		asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
120			statusp, child_pid, child_pgrp, ret_pid;
121	char		*username, *cleanenv, *class, shellbuf[MAXPATHLEN],
122			myhost[MAXHOSTNAMELEN + 1];
123	const char	*p, *user, *shell, *mytty, **nargv, **np;
124
125	shell = class = cleanenv = NULL;
126	asme = asthem = fastlogin = statusp = 0;
127	user = "root";
128	iscsh = UNSET;
129
130	while ((ch = getopt(argc, argv, "-flmc:")) != -1)
131		switch ((char)ch) {
132		case 'f':
133			fastlogin = 1;
134			break;
135		case '-':
136		case 'l':
137			asme = 0;
138			asthem = 1;
139			break;
140		case 'm':
141			asme = 1;
142			asthem = 0;
143			break;
144		case 'c':
145			class = optarg;
146			break;
147		case '?':
148		default:
149			usage();
150		}
151
152	if (optind < argc)
153		user = argv[optind++];
154
155	if (user == NULL)
156		usage();
157
158	if (strlen(user) > MAXLOGNAME - 1)
159		errx(1, "username too long");
160
161	nargv = malloc(sizeof(char *) * (argc + 4));
162	if (nargv == NULL)
163		errx(1, "malloc failure");
164
165	nargv[argc + 3] = NULL;
166	for (i = argc; i >= optind; i--)
167		nargv[i + 3] = argv[i];
168	np = &nargv[i + 3];
169
170	argv += optind;
171
172	errno = 0;
173	prio = getpriority(PRIO_PROCESS, 0);
174	if (errno)
175		prio = 0;
176
177	setpriority(PRIO_PROCESS, 0, -2);
178	openlog("su", LOG_CONS, LOG_AUTH);
179
180	/* get current login name, real uid and shell */
181	ruid = getuid();
182	username = getlogin();
183	pwd = getpwnam(username);
184	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
185		pwd = getpwuid(ruid);
186	if (pwd == NULL)
187		errx(1, "who are you?");
188	gid = pwd->pw_gid;
189
190	username = strdup(pwd->pw_name);
191	if (username == NULL)
192		err(1, "strdup failure");
193
194	if (asme) {
195		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
196			/* must copy - pwd memory is recycled */
197			shell = strncpy(shellbuf, pwd->pw_shell,
198			    sizeof(shellbuf));
199			shellbuf[sizeof(shellbuf) - 1] = '\0';
200		}
201		else {
202			shell = _PATH_BSHELL;
203			iscsh = NO;
204		}
205	}
206
207	/* Do the whole PAM startup thing */
208	retcode = pam_start("su", user, &conv, &pamh);
209	if (retcode != PAM_SUCCESS) {
210		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
211		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
212	}
213
214	PAM_SET_ITEM(PAM_RUSER, getlogin());
215
216	gethostname(myhost, sizeof(myhost));
217	PAM_SET_ITEM(PAM_RHOST, myhost);
218
219	mytty = ttyname(STDERR_FILENO);
220	if (!mytty)
221		mytty = "tty";
222	PAM_SET_ITEM(PAM_TTY, mytty);
223
224	retcode = pam_authenticate(pamh, 0);
225	if (retcode != PAM_SUCCESS) {
226		syslog(LOG_ERR, "pam_authenticate: %s",
227		    pam_strerror(pamh, retcode));
228		errx(1, "Sorry");
229	}
230	retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
231	if (retcode == PAM_SUCCESS)
232		user = p;
233	else
234		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
235		    pam_strerror(pamh, retcode));
236
237	retcode = pam_acct_mgmt(pamh, 0);
238	if (retcode == PAM_NEW_AUTHTOK_REQD) {
239		retcode = pam_chauthtok(pamh,
240			PAM_CHANGE_EXPIRED_AUTHTOK);
241		if (retcode != PAM_SUCCESS) {
242			syslog(LOG_ERR, "pam_chauthtok: %s",
243			    pam_strerror(pamh, retcode));
244			errx(1, "Sorry");
245		}
246	}
247	if (retcode != PAM_SUCCESS) {
248		syslog(LOG_ERR, "pam_acct_mgmt: %s",
249			pam_strerror(pamh, retcode));
250		errx(1, "Sorry");
251	}
252
253	/* get target login information, default to root */
254	pwd = getpwnam(user);
255	if (pwd == NULL)
256		errx(1, "unknown login: %s", user);
257	if (class == NULL)
258		lc = login_getpwclass(pwd);
259	else {
260		if (ruid != 0)
261			errx(1, "only root may use -c");
262		lc = login_getclass(class);
263		if (lc == NULL)
264			errx(1, "unknown class: %s", class);
265	}
266
267	/* if asme and non-standard target shell, must be root */
268	if (asme) {
269		if (ruid != 0 && !chshell(pwd->pw_shell))
270			errx(1, "permission denied (shell).");
271	}
272	else if (pwd->pw_shell && *pwd->pw_shell) {
273		shell = pwd->pw_shell;
274		iscsh = UNSET;
275	}
276	else {
277		shell = _PATH_BSHELL;
278		iscsh = NO;
279	}
280
281	/* if we're forking a csh, we want to slightly muck the args */
282	if (iscsh == UNSET) {
283		p = strrchr(shell, '/');
284		if (p)
285			++p;
286		else
287			p = shell;
288		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
289	}
290	setpriority(PRIO_PROCESS, 0, prio);
291
292	/*
293	 * PAM modules might add supplementary groups in pam_setcred(), so
294	 * initialize them first.
295	 */
296	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
297		err(1, "setusercontext");
298
299	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
300	if (retcode != PAM_SUCCESS)
301		syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
302		    pam_strerror(pamh, retcode));
303	else
304		creds_set = 1;
305
306	/*
307	 * We must fork() before setuid() because we need to call
308	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
309	 */
310
311	statusp = 1;
312	child_pid = fork();
313	switch (child_pid) {
314	default:
315		while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
316			if (WIFSTOPPED(statusp)) {
317				child_pgrp = tcgetpgrp(1);
318				kill(getpid(), SIGSTOP);
319				tcsetpgrp(1, child_pgrp);
320				kill(child_pid, SIGCONT);
321				statusp = 1;
322				continue;
323			}
324			break;
325		}
326		if (ret_pid == -1)
327			err(1, "waitpid");
328		PAM_END();
329		exit(statusp);
330	case -1:
331		err(1, "fork");
332		PAM_END();
333		exit(1);
334	case 0:
335		/*
336		 * Set all user context except for: Environmental variables
337		 * Umask Login records (wtmp, etc) Path
338		 */
339		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
340			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
341		/*
342		 * Don't touch resource/priority settings if -m has been used
343		 * or -l and -c hasn't, and we're not su'ing to root.
344		 */
345		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
346			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
347		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
348			err(1, "setusercontext");
349
350		if (!asme) {
351			if (asthem) {
352				p = getenv("TERM");
353				environ = &cleanenv;
354
355				/*
356				 * Add any environmental variables that the
357				 * PAM modules may have set.
358				 */
359				environ_pam = pam_getenvlist(pamh);
360				if (environ_pam)
361					export_pam_environment();
362
363				/* set the su'd user's environment & umask */
364				setusercontext(lc, pwd, pwd->pw_uid,
365					LOGIN_SETPATH | LOGIN_SETUMASK |
366					LOGIN_SETENV);
367				if (p)
368					setenv("TERM", p, 1);
369				if (chdir(pwd->pw_dir) < 0)
370					errx(1, "no directory");
371			}
372			if (asthem || pwd->pw_uid)
373				setenv("USER", pwd->pw_name, 1);
374			setenv("HOME", pwd->pw_dir, 1);
375			setenv("SHELL", shell, 1);
376		}
377		login_close(lc);
378
379		if (iscsh == YES) {
380			if (fastlogin)
381				*np-- = "-f";
382			if (asme)
383				*np-- = "-m";
384		}
385		/* csh strips the first character... */
386		*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
387
388		if (ruid != 0)
389			syslog(LOG_NOTICE, "%s to %s%s", username, user,
390			    ontty());
391
392		execv(shell, np);
393		err(1, "%s", shell);
394	}
395}
396
397static int
398export_pam_environment(void)
399{
400	char	**pp;
401
402	for (pp = environ_pam; *pp != NULL; pp++) {
403		if (ok_to_export(*pp))
404			putenv(*pp);
405		free(*pp);
406	}
407	return PAM_SUCCESS;
408}
409
410/*
411 * Sanity checks on PAM environmental variables:
412 * - Make sure there is an '=' in the string.
413 * - Make sure the string doesn't run on too long.
414 * - Do not export certain variables.  This list was taken from the
415 *   Solaris pam_putenv(3) man page.
416 */
417static int
418ok_to_export(const char *s)
419{
420	static const char *noexport[] = {
421		"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
422		"IFS", "PATH", NULL
423	};
424	const char **pp;
425	size_t n;
426
427	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
428		return 0;
429	if (strncmp(s, "LD_", 3) == 0)
430		return 0;
431	for (pp = noexport; *pp != NULL; pp++) {
432		n = strlen(*pp);
433		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
434			return 0;
435	}
436	return 1;
437}
438
439static void
440usage(void)
441{
442
443	fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
444	exit(1);
445}
446
447static int
448chshell(char *sh)
449{
450	int r;
451	char *cp;
452
453	r = 0;
454	setusershell();
455	do {
456		cp = getusershell();
457		r = strcmp(cp, sh);
458	} while (!r && cp != NULL);
459	endusershell();
460	return r;
461}
462
463static char *
464ontty(void)
465{
466	char *p;
467	static char buf[MAXPATHLEN + 4];
468
469	buf[0] = 0;
470	p = ttyname(STDERR_FILENO);
471	if (p)
472		snprintf(buf, sizeof(buf), " on %s", p);
473	return buf;
474}
475