xref: /freebsd/usr.bin/su/su.c (revision ec09ef4ff8d0a377fd86c0c2e96a807a538fce4b)
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, "you are not in the correct group to su %s.", user);
293 					}
294 					if (strcmp(username, *g) == 0) {
295 #ifdef WHEELSU
296 						iswheelsu = 1;
297 #endif /* WHEELSU */
298 						break;
299 					}
300 				}
301 		}
302 		/* if target requires a password, verify it */
303 		if (*pwd->pw_passwd) {
304 #ifdef	SKEY
305 #ifdef WHEELSU
306 			if (iswheelsu) {
307 				pwd = getpwnam(username);
308 			}
309 #endif /* WHEELSU */
310 			p = skey_getpass("Password:", pwd, 1);
311 			if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
312 #ifdef WHEELSU
313 			      || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
314 #endif /* WHEELSU */
315 			      ))
316 #else /* !SKEY */
317 			p = getpass("Password:");
318 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd)))
319 #endif /* SKEY */
320 			{
321 				{
322 					syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty());
323 					errx(1, "Sorry");
324 				}
325 			}
326 #ifdef WHEELSU
327 			if (iswheelsu) {
328 				pwd = getpwnam(user);
329 			}
330 #endif /* WHEELSU */
331 		}
332 		if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
333 			syslog(LOG_AUTH|LOG_WARNING,
334 				"BAD SU %s to %s%s", username,
335 				user, ontty());
336 			errx(1, "Sorry - account expired");
337 		}
338 	}
339 #endif /* USE_PAM */
340 
341 	if (asme) {
342 		/* if asme and non-standard target shell, must be root */
343 		if (ruid && !chshell(pwd->pw_shell))
344 			errx(1, "permission denied (shell).");
345 	} else if (pwd->pw_shell && *pwd->pw_shell) {
346 		shell = pwd->pw_shell;
347 		iscsh = UNSET;
348 	} else {
349 		shell = _PATH_BSHELL;
350 		iscsh = NO;
351 	}
352 
353 	/* if we're forking a csh, we want to slightly muck the args */
354 	if (iscsh == UNSET) {
355 		p = strrchr(shell, '/');
356 		if (p)
357 			++p;
358 		else
359 			p = shell;
360 		if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
361 		    iscsh = strcmp(p, "tcsh") ? NO : YES;
362 	}
363 
364 	(void)setpriority(PRIO_PROCESS, 0, prio);
365 
366 	/*
367 	 * PAM modules might add supplementary groups in
368 	 * pam_setcred(), so initialize them first.
369 	 */
370 	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
371 		err(1, "setusercontext");
372 
373 #ifdef USE_PAM
374 	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
375 	if (retcode != PAM_SUCCESS) {
376 		syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, retcode));
377 	}
378 
379 	/*
380 	 * We must fork() before setuid() because we need to call
381 	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
382 	 */
383 
384 	statusp = 1;
385 	switch ((child_pid = fork())) {
386 	default:
387             while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
388 		if (WIFSTOPPED(statusp)) {
389 		    child_pgrp = tcgetpgrp(1);
390 		    kill(getpid(), SIGSTOP);
391 		    tcsetpgrp(1, child_pgrp);
392 		    kill(child_pid, SIGCONT);
393 		    statusp = 1;
394 		    continue;
395 		}
396 		break;
397             }
398 	    if (ret_pid == -1)
399 		err(1, "waitpid");
400 	    PAM_END;
401 	    exit(statusp);
402 	case -1:
403 	    err(1, "fork");
404 	    PAM_END;
405 	    exit (1);
406 	case 0:
407 #endif /* USE_PAM */
408 
409 	/*
410 	 * Set all user context except for:
411 	 *   Environmental variables
412 	 *   Umask
413 	 *   Login records (wtmp, etc)
414 	 *   Path
415 	 */
416 	setwhat =  LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
417 	    LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
418 
419 	/*
420 	 * Don't touch resource/priority settings if -m has been
421 	 * used or -l and -c hasn't, and we're not su'ing to root.
422 	 */
423         if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
424 		setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
425 	if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
426 		err(1, "setusercontext");
427 
428 	if (!asme) {
429 		if (asthem) {
430 			p = getenv("TERM");
431 			environ = &cleanenv;
432 
433 #ifdef USE_PAM
434 			/*
435 			 * Add any environmental variables that the
436 			 * PAM modules may have set.
437 			 */
438 			environ_pam = pam_getenvlist(pamh);
439 			if (environ_pam)
440 				export_pam_environment();
441 #endif /* USE_PAM */
442 
443 			/* set the su'd user's environment & umask */
444 			setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
445 			if (p)
446 				(void)setenv("TERM", p, 1);
447 			if (chdir(pwd->pw_dir) < 0)
448 				errx(1, "no directory");
449 		}
450 		if (asthem || pwd->pw_uid)
451 			(void)setenv("USER", pwd->pw_name, 1);
452 		(void)setenv("HOME", pwd->pw_dir, 1);
453 		(void)setenv("SHELL", shell, 1);
454 	}
455 
456 	login_close(lc);
457 
458 	if (iscsh == YES) {
459 		if (fastlogin)
460 			*np-- = "-f";
461 		if (asme)
462 			*np-- = "-m";
463 	}
464 
465 	/* csh strips the first character... */
466 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
467 
468 	if (ruid != 0)
469 		syslog(LOG_NOTICE, "%s to %s%s",
470 		    username, user, ontty());
471 
472 	execv(shell, np);
473 	err(1, "%s", shell);
474 #ifdef USE_PAM
475 	}
476 #endif /* USE_PAM */
477 }
478 
479 #ifdef USE_PAM
480 static int
481 export_pam_environment()
482 {
483 	char	**pp;
484 
485 	for (pp = environ_pam; *pp != NULL; pp++) {
486 		if (ok_to_export(*pp))
487 			(void) putenv(*pp);
488 		free(*pp);
489 	}
490 	return PAM_SUCCESS;
491 }
492 
493 /*
494  * Sanity checks on PAM environmental variables:
495  * - Make sure there is an '=' in the string.
496  * - Make sure the string doesn't run on too long.
497  * - Do not export certain variables.  This list was taken from the
498  *   Solaris pam_putenv(3) man page.
499  */
500 static int
501 ok_to_export(s)
502 	const char *s;
503 {
504 	static const char *noexport[] = {
505 		"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
506 		"IFS", "PATH", NULL
507 	};
508 	const char **pp;
509 	size_t n;
510 
511 	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
512 		return 0;
513 	if (strncmp(s, "LD_", 3) == 0)
514 		return 0;
515 	for (pp = noexport; *pp != NULL; pp++) {
516 		n = strlen(*pp);
517 		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
518 			return 0;
519 	}
520 	return 1;
521 }
522 #endif /* USE_PAM */
523 
524 static void
525 usage()
526 {
527 	errx(1, "usage: su [%s] [login [args]]", ARGSTR);
528 }
529 
530 int
531 chshell(sh)
532 	char *sh;
533 {
534 	int  r = 0;
535 	char *cp;
536 
537 	setusershell();
538 	while (!r && (cp = getusershell()) != NULL)
539 		r = strcmp(cp, sh) == 0;
540 	endusershell();
541 	return r;
542 }
543 
544 char *
545 ontty()
546 {
547 	char *p;
548 	static char buf[MAXPATHLEN + 4];
549 
550 	buf[0] = 0;
551 	p = ttyname(STDERR_FILENO);
552 	if (p)
553 		snprintf(buf, sizeof(buf), " on %s", p);
554 	return (buf);
555 }
556