su.c revision 83373
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 83373 2001-09-12 19:15:02Z 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	union {
118		const char	**a;
119		char		* const *b;
120	} 		np;
121	uid_t		ruid;
122	gid_t		gid;
123	int		asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
124			statusp, child_pid, child_pgrp, ret_pid;
125	char		*username, *cleanenv, *class, shellbuf[MAXPATHLEN],
126			myhost[MAXHOSTNAMELEN + 1];
127	const char	*p, *user, *shell, *mytty, **nargv;
128
129	shell = class = cleanenv = NULL;
130	asme = asthem = fastlogin = statusp = 0;
131	user = "root";
132	iscsh = UNSET;
133
134	while ((ch = getopt(argc, argv, "-flmc:")) != -1)
135		switch ((char)ch) {
136		case 'f':
137			fastlogin = 1;
138			break;
139		case '-':
140		case 'l':
141			asme = 0;
142			asthem = 1;
143			break;
144		case 'm':
145			asme = 1;
146			asthem = 0;
147			break;
148		case 'c':
149			class = optarg;
150			break;
151		case '?':
152		default:
153			usage();
154		}
155
156	if (optind < argc)
157		user = argv[optind++];
158
159	if (user == NULL)
160		usage();
161
162	if (strlen(user) > MAXLOGNAME - 1)
163		errx(1, "username too long");
164
165	nargv = malloc(sizeof(char *) * (argc + 4));
166	if (nargv == NULL)
167		errx(1, "malloc failure");
168
169	nargv[argc + 3] = NULL;
170	for (i = argc; i >= optind; i--)
171		nargv[i + 3] = argv[i];
172	np.a = &nargv[i + 3];
173
174	argv += optind;
175
176	errno = 0;
177	prio = getpriority(PRIO_PROCESS, 0);
178	if (errno)
179		prio = 0;
180
181	setpriority(PRIO_PROCESS, 0, -2);
182	openlog("su", LOG_CONS, LOG_AUTH);
183
184	/* get current login name, real uid and shell */
185	ruid = getuid();
186	username = getlogin();
187	pwd = getpwnam(username);
188	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
189		pwd = getpwuid(ruid);
190	if (pwd == NULL)
191		errx(1, "who are you?");
192	gid = pwd->pw_gid;
193
194	username = strdup(pwd->pw_name);
195	if (username == NULL)
196		err(1, "strdup failure");
197
198	if (asme) {
199		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
200			/* must copy - pwd memory is recycled */
201			shell = strncpy(shellbuf, pwd->pw_shell,
202			    sizeof(shellbuf));
203			shellbuf[sizeof(shellbuf) - 1] = '\0';
204		}
205		else {
206			shell = _PATH_BSHELL;
207			iscsh = NO;
208		}
209	}
210
211	/* Do the whole PAM startup thing */
212	retcode = pam_start("su", user, &conv, &pamh);
213	if (retcode != PAM_SUCCESS) {
214		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
215		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
216	}
217
218	PAM_SET_ITEM(PAM_RUSER, getlogin());
219
220	gethostname(myhost, sizeof(myhost));
221	PAM_SET_ITEM(PAM_RHOST, myhost);
222
223	mytty = ttyname(STDERR_FILENO);
224	if (!mytty)
225		mytty = "tty";
226	PAM_SET_ITEM(PAM_TTY, mytty);
227
228	retcode = pam_authenticate(pamh, 0);
229	if (retcode != PAM_SUCCESS) {
230		syslog(LOG_ERR, "pam_authenticate: %s",
231		    pam_strerror(pamh, retcode));
232		errx(1, "Sorry");
233	}
234	retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
235	if (retcode == PAM_SUCCESS)
236		user = p;
237	else
238		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
239		    pam_strerror(pamh, retcode));
240
241	retcode = pam_acct_mgmt(pamh, 0);
242	if (retcode == PAM_NEW_AUTHTOK_REQD) {
243		retcode = pam_chauthtok(pamh,
244			PAM_CHANGE_EXPIRED_AUTHTOK);
245		if (retcode != PAM_SUCCESS) {
246			syslog(LOG_ERR, "pam_chauthtok: %s",
247			    pam_strerror(pamh, retcode));
248			errx(1, "Sorry");
249		}
250	}
251	if (retcode != PAM_SUCCESS) {
252		syslog(LOG_ERR, "pam_acct_mgmt: %s",
253			pam_strerror(pamh, retcode));
254		errx(1, "Sorry");
255	}
256
257	/* get target login information, default to root */
258	pwd = getpwnam(user);
259	if (pwd == NULL)
260		errx(1, "unknown login: %s", user);
261	if (class == NULL)
262		lc = login_getpwclass(pwd);
263	else {
264		if (ruid != 0)
265			errx(1, "only root may use -c");
266		lc = login_getclass(class);
267		if (lc == NULL)
268			errx(1, "unknown class: %s", class);
269	}
270
271	/* if asme and non-standard target shell, must be root */
272	if (asme) {
273		if (ruid != 0 && !chshell(pwd->pw_shell))
274			errx(1, "permission denied (shell).");
275	}
276	else if (pwd->pw_shell && *pwd->pw_shell) {
277		shell = pwd->pw_shell;
278		iscsh = UNSET;
279	}
280	else {
281		shell = _PATH_BSHELL;
282		iscsh = NO;
283	}
284
285	/* if we're forking a csh, we want to slightly muck the args */
286	if (iscsh == UNSET) {
287		p = strrchr(shell, '/');
288		if (p)
289			++p;
290		else
291			p = shell;
292		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
293	}
294	setpriority(PRIO_PROCESS, 0, prio);
295
296	/*
297	 * PAM modules might add supplementary groups in pam_setcred(), so
298	 * initialize them first.
299	 */
300	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
301		err(1, "setusercontext");
302
303	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
304	if (retcode != PAM_SUCCESS)
305		syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
306		    pam_strerror(pamh, retcode));
307	else
308		creds_set = 1;
309
310	/*
311	 * We must fork() before setuid() because we need to call
312	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
313	 */
314
315	statusp = 1;
316	child_pid = fork();
317	switch (child_pid) {
318	default:
319		while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
320			if (WIFSTOPPED(statusp)) {
321				child_pgrp = tcgetpgrp(1);
322				kill(getpid(), SIGSTOP);
323				tcsetpgrp(1, child_pgrp);
324				kill(child_pid, SIGCONT);
325				statusp = 1;
326				continue;
327			}
328			break;
329		}
330		if (ret_pid == -1)
331			err(1, "waitpid");
332		PAM_END();
333		exit(statusp);
334	case -1:
335		err(1, "fork");
336		PAM_END();
337		exit(1);
338	case 0:
339		/*
340		 * Set all user context except for: Environmental variables
341		 * Umask Login records (wtmp, etc) Path
342		 */
343		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
344			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
345		/*
346		 * Don't touch resource/priority settings if -m has been used
347		 * or -l and -c hasn't, and we're not su'ing to root.
348		 */
349		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
350			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
351		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
352			err(1, "setusercontext");
353
354		if (!asme) {
355			if (asthem) {
356				p = getenv("TERM");
357				environ = &cleanenv;
358
359				/*
360				 * Add any environmental variables that the
361				 * PAM modules may have set.
362				 */
363				environ_pam = pam_getenvlist(pamh);
364				if (environ_pam)
365					export_pam_environment();
366
367				/* set the su'd user's environment & umask */
368				setusercontext(lc, pwd, pwd->pw_uid,
369					LOGIN_SETPATH | LOGIN_SETUMASK |
370					LOGIN_SETENV);
371				if (p)
372					setenv("TERM", p, 1);
373				if (chdir(pwd->pw_dir) < 0)
374					errx(1, "no directory");
375			}
376			if (asthem || pwd->pw_uid)
377				setenv("USER", pwd->pw_name, 1);
378			setenv("HOME", pwd->pw_dir, 1);
379			setenv("SHELL", shell, 1);
380		}
381		login_close(lc);
382
383		if (iscsh == YES) {
384			if (fastlogin)
385				*np.a-- = "-f";
386			if (asme)
387				*np.a-- = "-m";
388		}
389		/* csh strips the first character... */
390		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
391
392		if (ruid != 0)
393			syslog(LOG_NOTICE, "%s to %s%s", username, user,
394			    ontty());
395
396		execv(shell, np.b);
397		err(1, "%s", shell);
398	}
399}
400
401static int
402export_pam_environment(void)
403{
404	char	**pp;
405
406	for (pp = environ_pam; *pp != NULL; pp++) {
407		if (ok_to_export(*pp))
408			putenv(*pp);
409		free(*pp);
410	}
411	return PAM_SUCCESS;
412}
413
414/*
415 * Sanity checks on PAM environmental variables:
416 * - Make sure there is an '=' in the string.
417 * - Make sure the string doesn't run on too long.
418 * - Do not export certain variables.  This list was taken from the
419 *   Solaris pam_putenv(3) man page.
420 */
421static int
422ok_to_export(const char *s)
423{
424	static const char *noexport[] = {
425		"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
426		"IFS", "PATH", NULL
427	};
428	const char **pp;
429	size_t n;
430
431	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
432		return 0;
433	if (strncmp(s, "LD_", 3) == 0)
434		return 0;
435	for (pp = noexport; *pp != NULL; pp++) {
436		n = strlen(*pp);
437		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
438			return 0;
439	}
440	return 1;
441}
442
443static void
444usage(void)
445{
446
447	fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
448	exit(1);
449}
450
451static int
452chshell(char *sh)
453{
454	int r;
455	char *cp;
456
457	r = 0;
458	setusershell();
459	do {
460		cp = getusershell();
461		r = strcmp(cp, sh);
462	} while (!r && cp != NULL);
463	endusershell();
464	return r;
465}
466
467static char *
468ontty(void)
469{
470	char *p;
471	static char buf[MAXPATHLEN + 4];
472
473	buf[0] = 0;
474	p = ttyname(STDERR_FILENO);
475	if (p)
476		snprintf(buf, sizeof(buf), " on %s", p);
477	return buf;
478}
479