xref: /freebsd/usr.bin/su/su.c (revision ce4946daa5ce852d28008dac492029500ab2ee95)
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 
52 #include <err.h>
53 #include <errno.h>
54 #include <grp.h>
55 #include <paths.h>
56 #include <pwd.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <syslog.h>
61 #include <unistd.h>
62 #include <libutil.h>
63 #include <login_cap.h>
64 
65 #ifdef USE_PAM
66 #include <security/pam_appl.h>
67 #include <security/pam_misc.h>
68 #include <signal.h>
69 #include <sys/wait.h>
70 
71 static int export_pam_environment __P((void));
72 static int ok_to_export __P((const char *));
73 
74 static pam_handle_t *pamh = NULL;
75 static char **environ_pam;
76 
77 #define PAM_END { \
78 	if ((retcode = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) { \
79 		syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, retcode)); \
80 	} \
81 	if ((retcode = pam_end(pamh,retcode)) != PAM_SUCCESS) { \
82 		syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, retcode)); \
83 	} \
84 }
85 #else /* !USE_PAM */
86 #ifdef	SKEY
87 #include <skey.h>
88 #endif
89 #endif /* USE_PAM */
90 
91 #define	ARGSTR	"-flmc:"
92 
93 char   *ontty __P((void));
94 int	chshell __P((char *));
95 static void usage __P((void));
96 
97 int
98 main(argc, argv)
99 	int argc;
100 	char **argv;
101 {
102 	extern char **environ;
103 	struct passwd *pwd;
104 #ifdef WHEELSU
105 	char *targetpass;
106 	int iswheelsu;
107 #endif /* WHEELSU */
108 	char *p, *user, *shell=NULL, *username, *cleanenv = NULL, **nargv, **np;
109 	uid_t ruid;
110 	gid_t gid;
111 	int asme, ch, asthem, fastlogin, prio, i;
112 	enum { UNSET, YES, NO } iscsh = UNSET;
113 	login_cap_t *lc;
114 	char *class=NULL;
115 	int setwhat;
116 #ifdef USE_PAM
117 	int retcode;
118 	struct pam_conv conv = { misc_conv, NULL };
119 	char myhost[MAXHOSTNAMELEN + 1], *mytty;
120 	int statusp=0;
121 	int child_pid, child_pgrp, ret_pid;
122 #else /* !USE_PAM */
123 	char **g;
124 	struct group *gr;
125 #endif /* USE_PAM */
126 	char shellbuf[MAXPATHLEN];
127 
128 #ifdef WHEELSU
129 	iswheelsu =
130 #endif /* WHEELSU */
131 	asme = asthem = fastlogin = 0;
132 	user = "root";
133 	while((ch = getopt(argc, argv, ARGSTR)) != -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 (strlen(user) > MAXLOGNAME - 1) {
159 		errx(1, "username too long");
160 	}
161 
162 	if (user == NULL)
163 		usage();
164 
165 	if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
166 	    errx(1, "malloc failure");
167 	}
168 
169 	nargv[argc + 3] = NULL;
170 	for (i = argc; i >= optind; i--)
171 	    nargv[i + 3] = argv[i];
172 	np = &nargv[i + 3];
173 
174 	argv += optind;
175 
176 	errno = 0;
177 	prio = getpriority(PRIO_PROCESS, 0);
178 	if (errno)
179 		prio = 0;
180 	(void)setpriority(PRIO_PROCESS, 0, -2);
181 	openlog("su", LOG_CONS, LOG_AUTH);
182 
183 	/* get current login name and shell */
184 	ruid = getuid();
185 	username = getlogin();
186 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
187 	    pwd->pw_uid != ruid)
188 		pwd = getpwuid(ruid);
189 	if (pwd == NULL)
190 		errx(1, "who are you?");
191 	username = strdup(pwd->pw_name);
192 	gid = pwd->pw_gid;
193 	if (username == NULL)
194 		err(1, NULL);
195 	if (asme) {
196 		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
197 			/* copy: pwd memory is recycled */
198 			shell = strncpy(shellbuf,  pwd->pw_shell, sizeof shellbuf);
199 			shellbuf[sizeof shellbuf - 1] = '\0';
200 		} else {
201 			shell = _PATH_BSHELL;
202 			iscsh = NO;
203 		}
204 	}
205 
206 #ifdef USE_PAM
207 	retcode = pam_start("su", user, &conv, &pamh);
208 	if (retcode != PAM_SUCCESS) {
209 		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
210 		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
211 	}
212 
213 	gethostname(myhost, sizeof(myhost));
214 	retcode = pam_set_item(pamh, PAM_RHOST, myhost);
215 	if (retcode != PAM_SUCCESS) {
216 		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", pam_strerror(pamh, retcode));
217 		errx(1, "pam_set_item(PAM_RHOST): %s", pam_strerror(pamh, retcode));
218 	}
219 
220 	mytty = ttyname(STDERR_FILENO);
221 	if (!mytty)
222 		mytty = "tty";
223 	retcode = pam_set_item(pamh, PAM_TTY, mytty);
224 	if (retcode != PAM_SUCCESS) {
225 		syslog(LOG_ERR, "pam_set_item(PAM_TTY): %s", pam_strerror(pamh, retcode));
226 		errx(1, "pam_set_item(PAM_TTY): %s", pam_strerror(pamh, retcode));
227 	}
228 
229 	if (ruid) {
230 		retcode = pam_authenticate(pamh, 0);
231 		if (retcode != PAM_SUCCESS) {
232 			syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, retcode));
233 			errx(1, "Sorry");
234 		}
235 
236 		if ((retcode = pam_get_item(pamh, PAM_USER, (const void **) &p)) == PAM_SUCCESS) {
237 			user = p;
238 		} else
239 			syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
240 			       pam_strerror(pamh, retcode));
241 
242 		retcode = pam_acct_mgmt(pamh, 0);
243 		if (retcode == PAM_NEW_AUTHTOK_REQD) {
244 			retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
245 			if (retcode != PAM_SUCCESS) {
246 				syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, retcode));
247 				errx(1, "Sorry");
248 			}
249 		}
250 		if (retcode != PAM_SUCCESS) {
251 			syslog(LOG_ERR, "pam_acct_mgmt: %s", pam_strerror(pamh, retcode));
252 			errx(1, "Sorry");
253 		}
254 	}
255 #endif /* USE_PAM */
256 
257 	/* get target login information, default to root */
258 	if ((pwd = getpwnam(user)) == NULL) {
259 		errx(1, "unknown login: %s", user);
260 	}
261 	if (class==NULL) {
262 		lc = login_getpwclass(pwd);
263 	} else {
264 		if (ruid)
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 #ifndef USE_PAM
272 #ifdef WHEELSU
273 	targetpass = strdup(pwd->pw_passwd);
274 #endif /* WHEELSU */
275 
276 	if (ruid) {
277 		{
278 			/*
279 			 * Only allow those with pw_gid==0 or those listed in
280 			 * group zero to su to root.  If group zero entry is
281 			 * missing or empty, then allow anyone to su to root.
282 			 * iswheelsu will only be set if the user is EXPLICITLY
283 			 * listed in group zero.
284 			 */
285 			if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
286 			    gr->gr_mem && *(gr->gr_mem))
287 				for (g = gr->gr_mem;; ++g) {
288 					if (!*g) {
289 						if (gid == 0)
290 							break;
291 						else
292 							errx(1,
293 			     "you are not in the correct group (%s) to su %s.",
294 							    gr->gr_name,
295 							    user);
296 					}
297 					if (strcmp(username, *g) == 0) {
298 #ifdef WHEELSU
299 						iswheelsu = 1;
300 #endif /* WHEELSU */
301 						break;
302 					}
303 				}
304 		}
305 		/* if target requires a password, verify it */
306 		if (*pwd->pw_passwd) {
307 #ifdef	SKEY
308 #ifdef WHEELSU
309 			if (iswheelsu) {
310 				pwd = getpwnam(username);
311 			}
312 #endif /* WHEELSU */
313 			p = skey_getpass("Password:", pwd, 1);
314 			if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
315 #ifdef WHEELSU
316 			      || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
317 #endif /* WHEELSU */
318 			      ))
319 #else /* !SKEY */
320 			p = getpass("Password:");
321 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd)))
322 #endif /* SKEY */
323 			{
324 				{
325 					syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty());
326 					errx(1, "Sorry");
327 				}
328 			}
329 #ifdef WHEELSU
330 			if (iswheelsu) {
331 				pwd = getpwnam(user);
332 			}
333 #endif /* WHEELSU */
334 		}
335 		if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
336 			syslog(LOG_AUTH|LOG_WARNING,
337 				"BAD SU %s to %s%s", username,
338 				user, ontty());
339 			errx(1, "Sorry - account expired");
340 		}
341 	}
342 #endif /* USE_PAM */
343 
344 	if (asme) {
345 		/* if asme and non-standard target shell, must be root */
346 		if (ruid && !chshell(pwd->pw_shell))
347 			errx(1, "permission denied (shell).");
348 	} else if (pwd->pw_shell && *pwd->pw_shell) {
349 		shell = pwd->pw_shell;
350 		iscsh = UNSET;
351 	} else {
352 		shell = _PATH_BSHELL;
353 		iscsh = NO;
354 	}
355 
356 	/* if we're forking a csh, we want to slightly muck the args */
357 	if (iscsh == UNSET) {
358 		p = strrchr(shell, '/');
359 		if (p)
360 			++p;
361 		else
362 			p = shell;
363 		if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
364 		    iscsh = strcmp(p, "tcsh") ? NO : YES;
365 	}
366 
367 	(void)setpriority(PRIO_PROCESS, 0, prio);
368 
369 	/*
370 	 * PAM modules might add supplementary groups in
371 	 * pam_setcred(), so initialize them first.
372 	 */
373 	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
374 		err(1, "setusercontext");
375 
376 #ifdef USE_PAM
377 	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
378 	if (retcode != PAM_SUCCESS) {
379 		syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, retcode));
380 	}
381 
382 	/*
383 	 * We must fork() before setuid() because we need to call
384 	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
385 	 */
386 
387 	statusp = 1;
388 	switch ((child_pid = fork())) {
389 	default:
390             while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
391 		if (WIFSTOPPED(statusp)) {
392 		    child_pgrp = tcgetpgrp(1);
393 		    kill(getpid(), SIGSTOP);
394 		    tcsetpgrp(1, child_pgrp);
395 		    kill(child_pid, SIGCONT);
396 		    statusp = 1;
397 		    continue;
398 		}
399 		break;
400             }
401 	    if (ret_pid == -1)
402 		err(1, "waitpid");
403 	    PAM_END;
404 	    exit(statusp);
405 	case -1:
406 	    err(1, "fork");
407 	    PAM_END;
408 	    exit (1);
409 	case 0:
410 #endif /* USE_PAM */
411 
412 	/*
413 	 * Set all user context except for:
414 	 *   Environmental variables
415 	 *   Umask
416 	 *   Login records (wtmp, etc)
417 	 *   Path
418 	 */
419 	setwhat =  LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
420 	    LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
421 
422 	/*
423 	 * Don't touch resource/priority settings if -m has been
424 	 * used or -l and -c hasn't, and we're not su'ing to root.
425 	 */
426         if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
427 		setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
428 	if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
429 		err(1, "setusercontext");
430 
431 	if (!asme) {
432 		if (asthem) {
433 			p = getenv("TERM");
434 			environ = &cleanenv;
435 
436 #ifdef USE_PAM
437 			/*
438 			 * Add any environmental variables that the
439 			 * PAM modules may have set.
440 			 */
441 			environ_pam = pam_getenvlist(pamh);
442 			if (environ_pam)
443 				export_pam_environment();
444 #endif /* USE_PAM */
445 
446 			/* set the su'd user's environment & umask */
447 			setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
448 			if (p)
449 				(void)setenv("TERM", p, 1);
450 			if (chdir(pwd->pw_dir) < 0)
451 				errx(1, "no directory");
452 		}
453 		if (asthem || pwd->pw_uid)
454 			(void)setenv("USER", pwd->pw_name, 1);
455 		(void)setenv("HOME", pwd->pw_dir, 1);
456 		(void)setenv("SHELL", shell, 1);
457 	}
458 
459 	login_close(lc);
460 
461 	if (iscsh == YES) {
462 		if (fastlogin)
463 			*np-- = "-f";
464 		if (asme)
465 			*np-- = "-m";
466 	}
467 
468 	/* csh strips the first character... */
469 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
470 
471 	if (ruid != 0)
472 		syslog(LOG_NOTICE, "%s to %s%s",
473 		    username, user, ontty());
474 
475 	execv(shell, np);
476 	err(1, "%s", shell);
477 #ifdef USE_PAM
478 	}
479 #endif /* USE_PAM */
480 }
481 
482 #ifdef USE_PAM
483 static int
484 export_pam_environment()
485 {
486 	char	**pp;
487 
488 	for (pp = environ_pam; *pp != NULL; pp++) {
489 		if (ok_to_export(*pp))
490 			(void) putenv(*pp);
491 		free(*pp);
492 	}
493 	return PAM_SUCCESS;
494 }
495 
496 /*
497  * Sanity checks on PAM environmental variables:
498  * - Make sure there is an '=' in the string.
499  * - Make sure the string doesn't run on too long.
500  * - Do not export certain variables.  This list was taken from the
501  *   Solaris pam_putenv(3) man page.
502  */
503 static int
504 ok_to_export(s)
505 	const char *s;
506 {
507 	static const char *noexport[] = {
508 		"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
509 		"IFS", "PATH", NULL
510 	};
511 	const char **pp;
512 	size_t n;
513 
514 	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
515 		return 0;
516 	if (strncmp(s, "LD_", 3) == 0)
517 		return 0;
518 	for (pp = noexport; *pp != NULL; pp++) {
519 		n = strlen(*pp);
520 		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
521 			return 0;
522 	}
523 	return 1;
524 }
525 #endif /* USE_PAM */
526 
527 static void
528 usage()
529 {
530 	errx(1, "usage: su [%s] [login [args]]", ARGSTR);
531 }
532 
533 int
534 chshell(sh)
535 	char *sh;
536 {
537 	int  r = 0;
538 	char *cp;
539 
540 	setusershell();
541 	while (!r && (cp = getusershell()) != NULL)
542 		r = strcmp(cp, sh) == 0;
543 	endusershell();
544 	return r;
545 }
546 
547 char *
548 ontty()
549 {
550 	char *p;
551 	static char buf[MAXPATHLEN + 4];
552 
553 	buf[0] = 0;
554 	p = ttyname(STDERR_FILENO);
555 	if (p)
556 		snprintf(buf, sizeof(buf), " on %s", p);
557 	return (buf);
558 }
559