xref: /freebsd/usr.bin/su/su.c (revision 68e7a217f8019b955f87547f218e95ab237597af)
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
35 static 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
42 static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
43 #endif
44 static const char rcsid[] =
45   "$FreeBSD$";
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/openpam.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 
96 enum tristate { UNSET, YES, NO };
97 
98 static pam_handle_t *pamh = NULL;
99 static int	creds_set = 0;
100 static char	**environ_pam;
101 
102 static char	*ontty(void);
103 static int	chshell(char *);
104 static void	usage(void);
105 static int	export_pam_environment(void);
106 static int	ok_to_export(const char *);
107 
108 extern char	**environ;
109 
110 int
111 main(int argc, char *argv[])
112 {
113 	struct passwd	*pwd;
114 	struct pam_conv	conv = { openpam_ttyconv, 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 	const char	*p, *user, *shell, *mytty, **nargv;
127 
128 	shell = class = cleanenv = NULL;
129 	asme = asthem = fastlogin = statusp = 0;
130 	user = "root";
131 	iscsh = UNSET;
132 
133 	while ((ch = getopt(argc, argv, "-flmc:")) != -1)
134 		switch ((char)ch) {
135 		case 'f':
136 			fastlogin = 1;
137 			break;
138 		case '-':
139 		case 'l':
140 			asme = 0;
141 			asthem = 1;
142 			break;
143 		case 'm':
144 			asme = 1;
145 			asthem = 0;
146 			break;
147 		case 'c':
148 			class = optarg;
149 			break;
150 		case '?':
151 		default:
152 			usage();
153 		}
154 
155 	if (optind < argc)
156 		user = argv[optind++];
157 
158 	if (user == NULL)
159 		usage();
160 
161 	if (strlen(user) > MAXLOGNAME - 1)
162 		errx(1, "username too long");
163 
164 	nargv = malloc(sizeof(char *) * (argc + 4));
165 	if (nargv == NULL)
166 		errx(1, "malloc failure");
167 
168 	nargv[argc + 3] = NULL;
169 	for (i = argc; i >= optind; i--)
170 		nargv[i + 3] = argv[i];
171 	np.a = &nargv[i + 3];
172 
173 	argv += optind;
174 
175 	errno = 0;
176 	prio = getpriority(PRIO_PROCESS, 0);
177 	if (errno)
178 		prio = 0;
179 
180 	setpriority(PRIO_PROCESS, 0, -2);
181 	openlog("su", LOG_CONS, LOG_AUTH);
182 
183 	/* get current login name, real uid and shell */
184 	ruid = getuid();
185 	username = getlogin();
186 	pwd = getpwnam(username);
187 	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
188 		pwd = getpwuid(ruid);
189 	if (pwd == NULL)
190 		errx(1, "who are you?");
191 	gid = pwd->pw_gid;
192 
193 	username = strdup(pwd->pw_name);
194 	if (username == NULL)
195 		err(1, "strdup failure");
196 
197 	if (asme) {
198 		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
199 			/* must copy - pwd memory is recycled */
200 			shell = strncpy(shellbuf, pwd->pw_shell,
201 			    sizeof(shellbuf));
202 			shellbuf[sizeof(shellbuf) - 1] = '\0';
203 		}
204 		else {
205 			shell = _PATH_BSHELL;
206 			iscsh = NO;
207 		}
208 	}
209 
210 	/* Do the whole PAM startup thing */
211 	retcode = pam_start("su", user, &conv, &pamh);
212 	if (retcode != PAM_SUCCESS) {
213 		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
214 		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
215 	}
216 
217 	PAM_SET_ITEM(PAM_RUSER, getlogin());
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.a-- = "-f";
382 			if (asme)
383 				*np.a-- = "-m";
384 		}
385 		/* csh strips the first character... */
386 		*np.a = 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.b);
393 		err(1, "%s", shell);
394 	}
395 }
396 
397 static int
398 export_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  */
417 static int
418 ok_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 
439 static void
440 usage(void)
441 {
442 
443 	fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
444 	exit(1);
445 }
446 
447 static int
448 chshell(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 
463 static char *
464 ontty(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