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