xref: /freebsd/usr.bin/su/su.c (revision 17d6c636720d00f77e5d098daf4c278f89d84f7b)
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/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 
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 = {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 
401 static int
402 export_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  */
421 static int
422 ok_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 
443 static void
444 usage(void)
445 {
446 
447 	fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
448 	exit(1);
449 }
450 
451 static int
452 chshell(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 
467 static char *
468 ontty(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