xref: /dragonfly/usr.bin/su/su.c (revision 1847e88f)
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  * @(#) Copyright (c) 1988, 1993, 1994 The Regents of the University of California.  All rights reserved.
34  * @(#)su.c	8.3 (Berkeley) 4/2/94
35  * $FreeBSD: src/usr.bin/su/su.c,v 1.34.2.4 2002/06/16 21:04:15 nectar Exp $
36  * $DragonFly: src/usr.bin/su/su.c,v 1.9 2006/01/12 13:43:11 corecode Exp $
37  */
38 
39 #include <sys/cdefs.h>
40 #include <sys/param.h>
41 #include <sys/time.h>
42 #include <sys/resource.h>
43 
44 #include <err.h>
45 #include <errno.h>
46 #include <grp.h>
47 #include <paths.h>
48 #include <pwd.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <syslog.h>
53 #include <unistd.h>
54 #include <libutil.h>
55 
56 #ifdef LOGIN_CAP
57 #include <login_cap.h>
58 #endif
59 
60 #ifdef	SKEY
61 #include <skey.h>
62 #endif
63 
64 #ifdef KERBEROS5
65 #include <krb5.h>
66 
67 static long get_su_principal(krb5_context context, const char *target_user,
68     const char *current_user, char **su_principal_name,
69     krb5_principal *su_principal);
70 static long kerberos5(krb5_context context, const char *current_user,
71     const char *target_user, krb5_principal su_principal,
72     const char *pass);
73 
74 int use_kerberos5 = 1;
75 #endif
76 
77 #ifdef LOGIN_CAP
78 #define LOGIN_CAP_ARG(x) x
79 #else
80 #define LOGIN_CAP_ARG(x)
81 #endif
82 #if defined(KERBEROS5)
83 #define KERBEROS_ARG(x) x
84 #else
85 #define KERBEROS_ARG(x)
86 #endif
87 #define COMMON_ARG(x) x
88 #define ARGSTR	"-" COMMON_ARG("flm") LOGIN_CAP_ARG("c:") KERBEROS_ARG("K")
89 
90 char   *ontty(void);
91 int	chshell(char *);
92 static void usage(void);
93 
94 extern char **environ;
95 
96 int
97 main(int argc, char **argv)
98 {
99 	struct passwd *pwd;
100 #ifdef WHEELSU
101 	char *targetpass;
102 	int iswheelsu;
103 #endif /* WHEELSU */
104 	const char *p, *user, *shell = NULL;
105 	const char **nargv, **np;
106 	char **g, **cleanenv, *username, *entered_pass;
107 	struct group *gr;
108 	uid_t ruid;
109 	gid_t gid;
110 	int asme, ch, asthem, fastlogin, prio, i;
111 	enum { UNSET, YES, NO } iscsh = UNSET;
112 #ifdef LOGIN_CAP
113 	login_cap_t *lc;
114 	char *class=NULL;
115 	int setwhat;
116 #endif
117 #if defined(KERBEROS5)
118 	char *k;
119 #endif
120 #ifdef KERBEROS5
121 	char *su_principal_name, *ccname;
122 	krb5_context context;
123 	krb5_principal su_principal;
124 #endif
125 	char shellbuf[MAXPATHLEN];
126 
127 #ifdef WHEELSU
128 	iswheelsu =
129 #endif /* WHEELSU */
130 	asme = asthem = fastlogin = 0;
131 	user = "root";
132 	while((ch = getopt(argc, argv, ARGSTR)) != -1)
133 		switch((char)ch) {
134 #if defined(KERBEROS5)
135 		case 'K':
136 			use_kerberos5 = 0;
137 			break;
138 #endif
139 		case 'f':
140 			fastlogin = 1;
141 			break;
142 		case '-':
143 		case 'l':
144 			asme = 0;
145 			asthem = 1;
146 			break;
147 		case 'm':
148 			asme = 1;
149 			asthem = 0;
150 			break;
151 #ifdef LOGIN_CAP
152 		case 'c':
153 			class = optarg;
154 			break;
155 #endif
156 		case '?':
157 		default:
158 			usage();
159 		}
160 
161 	if (optind < argc && strcmp(argv[optind], "-") == 0) {
162 		asme = 0;
163 		asthem = 1;
164 		optind++;
165 	}
166 
167 	if (optind < argc)
168 		user = argv[optind++];
169 
170 	if (strlen(user) > MAXLOGNAME - 1) {
171 		fprintf(stderr, "su: username too long.\n");
172 		exit(1);
173 	}
174 
175 	if (user == NULL)
176 		usage();
177 
178 	if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
179 	    errx(1, "malloc failure");
180 	}
181 
182 	nargv[argc + 3] = NULL;
183 	for (i = argc; i >= optind; i--)
184 	    nargv[i + 3] = argv[i];
185 	np = &nargv[i + 3];
186 
187 	argv += optind;
188 
189 #if defined(KERBEROS5)
190 	k = auth_getval("auth_list");
191 	if (k && !strstr(k, "kerberos")) {
192 	    use_kerberos5 = 0;
193 	}
194 	su_principal_name = NULL;
195 	su_principal = NULL;
196 	if (krb5_init_context(&context) != 0)
197 		use_kerberos5 = 0;
198 #endif
199 	errno = 0;
200 	prio = getpriority(PRIO_PROCESS, 0);
201 	if (errno)
202 		prio = 0;
203 	setpriority(PRIO_PROCESS, 0, -2);
204 	openlog("su", LOG_CONS, 0);
205 
206 	/* get current login name and shell */
207 	ruid = getuid();
208 	username = getlogin();
209 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
210 	    pwd->pw_uid != ruid)
211 		pwd = getpwuid(ruid);
212 	if (pwd == NULL)
213 		errx(1, "who are you?");
214 	username = strdup(pwd->pw_name);
215 	gid = pwd->pw_gid;
216 	if (username == NULL)
217 		err(1, NULL);
218 	if (asme) {
219 		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
220 			/* copy: pwd memory is recycled */
221 			shell = strncpy(shellbuf,  pwd->pw_shell, sizeof shellbuf);
222 			shellbuf[sizeof shellbuf - 1] = '\0';
223 		} else {
224 			shell = _PATH_BSHELL;
225 			iscsh = NO;
226 		}
227 	}
228 
229 	/* get target login information, default to root */
230 	if ((pwd = getpwnam(user)) == NULL) {
231 		errx(1, "unknown login: %s", user);
232 	}
233 #ifdef LOGIN_CAP
234 	if (class==NULL) {
235 		lc = login_getpwclass(pwd);
236 	} else {
237 		if (ruid)
238 			errx(1, "only root may use -c");
239 		lc = login_getclass(class);
240 		if (lc == NULL)
241 			errx(1, "unknown class: %s", class);
242 	}
243 #endif
244 
245 #ifdef WHEELSU
246 	targetpass = strdup(pwd->pw_passwd);
247 #endif /* WHEELSU */
248 
249 	if (ruid) {
250 #ifdef KERBEROS5
251 		if (use_kerberos5) {
252 			if (get_su_principal(context, user, username,
253 			    &su_principal_name, &su_principal) != 0 ||
254 			    !krb5_kuserok(context, su_principal, user)) {
255 				warnx("kerberos5: not in %s's ACL.", user);
256 				use_kerberos5 = 0;
257 			}
258 		}
259 #endif
260 		/*
261 		 * Only allow those with pw_gid==0 or those listed in
262 		 * group zero to su to root.  If group zero entry is
263 		 * missing or empty, then allow anyone to su to root.
264 		 * iswheelsu will only be set if the user is EXPLICITLY
265 		 * listed in group zero.
266 		 */
267 		if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
268 		    gr->gr_mem && *(gr->gr_mem)) {
269 			for (g = gr->gr_mem;; ++g) {
270 				if (!*g) {
271 					if (gid == 0)
272 						break;
273 					else
274 						errx(1,
275 			     "you are not in the correct group (%s) to su %s.",
276 						    gr->gr_name,
277 						    user);
278 				}
279 				if (strcmp(username, *g) == 0) {
280 #ifdef WHEELSU
281 					iswheelsu = 1;
282 #endif /* WHEELSU */
283 					break;
284 				}
285 			}
286 		}
287 		/* if target requires a password, verify it */
288 		if (*pwd->pw_passwd) {
289 #ifdef	SKEY
290 #ifdef WHEELSU
291 			if (iswheelsu) {
292 				pwd = getpwnam(username);
293 			}
294 #endif /* WHEELSU */
295 			entered_pass = skey_getpass("Password:", pwd, 1);
296 			if (!(!strcmp(pwd->pw_passwd, skey_crypt(entered_pass,
297 			    pwd->pw_passwd, pwd, 1))
298 #ifdef WHEELSU
299 			      || (iswheelsu && !strcmp(targetpass,
300 				  crypt(entered_pass, targetpass)))
301 #endif /* WHEELSU */
302 			      )) {
303 #else
304 			entered_pass = getpass("Password:");
305 			if (strcmp(pwd->pw_passwd, crypt(entered_pass,
306 			    pwd->pw_passwd))) {
307 #endif
308 #ifdef KERBEROS5
309 				if (use_kerberos5 && kerberos5(context,
310 				    username, user, su_principal,
311 				    entered_pass) == 0)
312 					goto authok;
313 #endif
314 				fprintf(stderr, "Sorry\n");
315 				syslog(LOG_AUTH|LOG_WARNING,
316 				    "BAD SU %s to %s%s", username, user,
317 				    ontty());
318 				exit(1);
319 			}
320 #if defined(KERBEROS5)
321 		authok:
322 			;
323 #endif
324 #ifdef WHEELSU
325 			if (iswheelsu) {
326 				pwd = getpwnam(user);
327 			}
328 #endif /* WHEELSU */
329 		}
330 		if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
331 			fprintf(stderr, "Sorry - account expired\n");
332 			syslog(LOG_AUTH|LOG_WARNING,
333 				"BAD SU %s to %s%s", username,
334 				user, ontty());
335 			exit(1);
336 		}
337 	}
338 
339 	if (asme) {
340 		/* if asme and non-standard target shell, must be root */
341 		if (!chshell(pwd->pw_shell) && ruid)
342 			errx(1, "permission denied (shell).");
343 	} else if (pwd->pw_shell && *pwd->pw_shell) {
344 		shell = pwd->pw_shell;
345 		iscsh = UNSET;
346 	} else {
347 		shell = _PATH_BSHELL;
348 		iscsh = NO;
349 	}
350 
351 	/* if we're forking a csh, we want to slightly muck the args */
352 	if (iscsh == UNSET) {
353 		p = strrchr(shell, '/');
354 		if (p)
355 			++p;
356 		else
357 			p = shell;
358 		if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
359 		    iscsh = strcmp(p, "tcsh") ? NO : YES;
360 	}
361 
362 	setpriority(PRIO_PROCESS, 0, prio);
363 
364 #ifdef LOGIN_CAP
365 	/* Set everything now except the environment & umask */
366 	setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
367 	/*
368 	 * Don't touch resource/priority settings if -m has been
369 	 * used or -l and -c hasn't, and we're not su'ing to root.
370 	 */
371         if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
372 		setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
373 	if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
374 		err(1, "setusercontext");
375 #else
376 	/* set permissions */
377 	if (setgid(pwd->pw_gid) < 0)
378 		err(1, "setgid");
379 	if (initgroups(user, pwd->pw_gid))
380 		errx(1, "initgroups failed");
381 	if (setuid(pwd->pw_uid) < 0)
382 		err(1, "setuid");
383 #endif
384 
385 	if (!asme) {
386 		if (asthem) {
387 			p = getenv("TERM");
388 #ifdef KERBEROS5
389 			ccname = getenv("KRB5CCNAME");
390 #endif
391 			if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
392 				errx(1, "calloc");
393 			cleanenv[0] = NULL;
394 			environ = cleanenv;
395 #ifdef LOGIN_CAP
396 			/* set the su'd user's environment & umask */
397 			setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
398 #else
399 			if (setenv("PATH", _PATH_DEFPATH, 1) == -1)
400 				err(1, "setenv: cannot set PATH=%s", _PATH_DEFPATH);
401 #endif
402 			if (p) {
403 				if (setenv("TERM", p, 1) == -1)
404 					err(1, "setenv: cannot set TERM=%s", p);
405 			}
406 #ifdef KERBEROS5
407 			if (ccname) {
408 				if (setenv("KRB5CCNAME", ccname, 1) == -1)
409 					err(1, "setenv: cannot set KRB5CCNAME=%s", ccname);
410 			}
411 #endif
412 			if (chdir(pwd->pw_dir) < 0)
413 				errx(1, "no directory");
414 		}
415 		if (asthem || pwd->pw_uid) {
416 			if (setenv("USER", pwd->pw_name, 1) == -1)
417 				err(1, "setenv: cannot set USER=%s", pwd->pw_name);
418 		}
419 		if (setenv("HOME", pwd->pw_dir, 1) == -1)
420 			err(1, "setenv: cannot set HOME=%s", pwd->pw_dir);
421 		if (setenv("SHELL", shell, 1) == -1)
422 			err(1, "setenv: cannot set SHELL=%s", shell);
423 	}
424 	if (iscsh == YES) {
425 		if (fastlogin)
426 			*np-- = "-f";
427 		if (asme)
428 			*np-- = "-m";
429 	}
430 
431 	/* csh strips the first character... */
432 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
433 
434 	if (ruid != 0)
435 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
436 		    username, user, ontty());
437 
438 #ifdef LOGIN_CAP
439 	login_close(lc);
440 #endif
441 
442 	execv(shell, __DECONST(char * const *, np));
443 	err(1, "%s", shell);
444 }
445 
446 static void
447 usage(void)
448 {
449 	fprintf(stderr, "usage: su [-] [-%s] %s[login [args]]\n",
450 	    KERBEROS_ARG("K") COMMON_ARG("flm"),
451 #ifdef LOGIN_CAP
452 	    "[-c class] "
453 #else
454 	    ""
455 #endif
456 	    );
457 	exit(1);
458 }
459 
460 int
461 chshell(char *sh)
462 {
463 	int  r = 0;
464 	char *cp;
465 
466 	setusershell();
467 	while (!r && (cp = getusershell()) != NULL)
468 		r = strcmp(cp, sh) == 0;
469 	endusershell();
470 	return r;
471 }
472 
473 char *
474 ontty(void)
475 {
476 	char *p;
477 	static char buf[MAXPATHLEN + 4];
478 
479 	buf[0] = 0;
480 	p = ttyname(STDERR_FILENO);
481 	if (p)
482 		snprintf(buf, sizeof(buf), " on %s", p);
483 	return (buf);
484 }
485 
486 #ifdef KERBEROS5
487 const char superuser[] = "root";
488 
489 /* Authenticate using Kerberos 5.
490  *   context           -- An initialized krb5_context.
491  *   current_user      -- The current username.
492  *   target_user       -- The target account name.
493  *   su_principal      -- The target krb5_principal.
494  *   pass              -- The user's password.
495  * Note that a valid keytab in the default location with a host entry
496  * must be available.
497  * Returns 0 if authentication was successful, or a com_err error code if
498  * it was not.
499  */
500 static long
501 kerberos5(krb5_context context, const char *current_user,
502     const char *target_user, krb5_principal su_principal,
503     const char *pass)
504 {
505 	krb5_creds	 creds;
506 	krb5_get_init_creds_opt gic_opt;
507 	krb5_verify_init_creds_opt vic_opt;
508 	long		 rv;
509 
510 	krb5_get_init_creds_opt_init(&gic_opt);
511 	krb5_verify_init_creds_opt_init(&vic_opt);
512 	rv = krb5_get_init_creds_password(context, &creds, su_principal,
513 	    pass, NULL, NULL, 0, NULL, &gic_opt);
514 	if (rv != 0) {
515 		syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
516 		    current_user, target_user, ontty(),
517 		    krb5_get_err_text(context, rv));
518 		return (rv);
519 	}
520 	krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1);
521 	rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL,
522 	    &vic_opt);
523 	krb5_free_cred_contents(context, &creds);
524 	if (rv != 0) {
525 		syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
526 		    current_user, target_user, ontty(),
527 		    krb5_get_err_text(context, rv));
528 		return (rv);
529 	}
530 	return (0);
531 }
532 
533 /* Determine the target principal given the current user and the target user.
534  *   context           -- An initialized krb5_context.
535  *   target_user       -- The target username.
536  *   current_user      -- The current username.
537  *   su_principal_name -- (out) The target principal name.
538  *   su_principal      -- (out) The target krb5_principal.
539  *
540  * When target_user is `root', the su_principal will be a `root
541  * instance', e.g. `luser/root@REA.LM'.  Otherwise, the su_principal
542  * will simply be the current user's default principal name.  Note that
543  * in any case, if KRB5CCNAME is set and a credentials cache exists, the
544  * principal name found there will be the `starting point', rather than
545  * the current_user parameter.
546  *
547  * Returns 0 for success, or a com_err error code on failure.
548  */
549 static long
550 get_su_principal(krb5_context context, const char *target_user,
551     const char *current_user, char **su_principal_name,
552     krb5_principal *su_principal)
553 {
554 	krb5_principal	 default_principal;
555 	krb5_ccache	 ccache;
556 	char		*principal_name, *ccname, *p;
557 	long		 rv;
558 	uid_t		 euid, ruid;
559 
560 	*su_principal = NULL;
561 	default_principal = NULL;
562 	/* Lower privs while messing about with the credentials
563 	 * cache.
564 	 */
565 	ruid = getuid();
566 	euid = geteuid();
567 	rv = seteuid(getuid());
568 	if (rv != 0)
569 		return (errno);
570 	p = getenv("KRB5CCNAME");
571 	if (p != NULL)
572 		ccname = strdup(p);
573 	else {
574 		asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT,
575 		    (unsigned long)ruid);
576 	}
577 	if (ccname == NULL)
578 		return (errno);
579 	rv = krb5_cc_resolve(context, ccname, &ccache);
580 	free(ccname);
581 	if (rv == 0) {
582 		rv = krb5_cc_get_principal(context, ccache,
583 		    &default_principal);
584 		krb5_cc_close(context, ccache);
585 		if (rv != 0)
586 			default_principal = NULL; /* just to be safe */
587 	}
588 	rv = seteuid(euid);
589 	if (rv != 0)
590 		return (errno);
591 	if (default_principal == NULL) {
592 		rv = krb5_make_principal(context, &default_principal, NULL,
593 		    current_user, NULL);
594 		if (rv != 0) {
595 			warnx("Could not determine default principal name.");
596 			return (rv);
597 		}
598 	}
599 	/* Now that we have some principal, if the target account is
600 	 * `root', then transform it into a `root' instance, e.g.
601 	 * `user@REA.LM' -> `user/root@REA.LM'.
602 	 */
603 	rv = krb5_unparse_name(context, default_principal, &principal_name);
604 	krb5_free_principal(context, default_principal);
605 	if (rv != 0) {
606 		warnx("krb5_unparse_name: %s", krb5_get_err_text(context, rv));
607 		return (rv);
608 	}
609 	if (strcmp(target_user, superuser) == 0) {
610 		p = strrchr(principal_name, '@');
611 		if (p == NULL) {
612 			warnx("malformed principal name `%s'", principal_name);
613 			free(principal_name);
614 			return (rv);
615 		}
616 		*p++ = '\0';
617 		asprintf(su_principal_name, "%s/%s@%s", principal_name,
618 		    superuser, p);
619 		free(principal_name);
620 	} else
621 		*su_principal_name = principal_name;
622 	if (*su_principal_name == NULL)
623 		return errno;
624 	rv = krb5_parse_name(context, *su_principal_name, &default_principal);
625 	if (rv != 0) {
626 		warnx("krb5_parse_name `%s': %s", *su_principal_name,
627 		    krb5_get_err_text(context, rv));
628 		free(*su_principal_name);
629 		return (rv);
630 	}
631 	*su_principal = default_principal;
632 	return 0;
633 }
634 
635 #endif
636