2 * Copyright (c) 1988, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
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.6 2004/12/19 21:10:48 cpressey Exp $
39 #include <sys/cdefs.h>
40 #include <sys/param.h>
42 #include <sys/resource.h>
57 #include <login_cap.h>
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,
74 int use_kerberos5 = 1;
78 #define LOGIN_CAP_ARG(x) x
80 #define LOGIN_CAP_ARG(x)
82 #if defined(KERBEROS5)
83 #define KERBEROS_ARG(x) x
85 #define KERBEROS_ARG(x)
87 #define COMMON_ARG(x) x
88 #define ARGSTR "-" COMMON_ARG("flm") LOGIN_CAP_ARG("c:") KERBEROS_ARG("K")
92 static void usage(void);
94 extern char **environ;
97 main(int argc, char **argv)
104 const char *p, *user, *shell = NULL;
105 const char **nargv, **np;
106 char **g, **cleanenv, *username, *entered_pass;
110 int asme, ch, asthem, fastlogin, prio, i;
111 enum { UNSET, YES, NO } iscsh = UNSET;
117 #if defined(KERBEROS5)
121 char *su_principal_name, *ccname;
122 krb5_context context;
123 krb5_principal su_principal;
125 char shellbuf[MAXPATHLEN];
130 asme = asthem = fastlogin = 0;
132 while((ch = getopt(argc, argv, ARGSTR)) != -1)
134 #if defined(KERBEROS5)
162 user = argv[optind++];
164 if (strlen(user) > MAXLOGNAME - 1) {
165 (void)fprintf(stderr, "su: username too long.\n");
172 if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
173 errx(1, "malloc failure");
176 nargv[argc + 3] = NULL;
177 for (i = argc; i >= optind; i--)
178 nargv[i + 3] = argv[i];
183 #if defined(KERBEROS5)
184 k = auth_getval("auth_list");
185 if (k && !strstr(k, "kerberos")) {
188 su_principal_name = NULL;
190 if (krb5_init_context(&context) != 0)
194 prio = getpriority(PRIO_PROCESS, 0);
197 (void)setpriority(PRIO_PROCESS, 0, -2);
198 openlog("su", LOG_CONS, 0);
200 /* get current login name and shell */
202 username = getlogin();
203 if (username == NULL || (pwd = getpwnam(username)) == NULL ||
205 pwd = getpwuid(ruid);
207 errx(1, "who are you?");
208 username = strdup(pwd->pw_name);
210 if (username == NULL)
213 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
214 /* copy: pwd memory is recycled */
215 shell = strncpy(shellbuf, pwd->pw_shell, sizeof shellbuf);
216 shellbuf[sizeof shellbuf - 1] = '\0';
218 shell = _PATH_BSHELL;
223 /* get target login information, default to root */
224 if ((pwd = getpwnam(user)) == NULL) {
225 errx(1, "unknown login: %s", user);
229 lc = login_getpwclass(pwd);
232 errx(1, "only root may use -c");
233 lc = login_getclass(class);
235 errx(1, "unknown class: %s", class);
240 targetpass = strdup(pwd->pw_passwd);
246 if (get_su_principal(context, user, username,
247 &su_principal_name, &su_principal) != 0 ||
248 !krb5_kuserok(context, su_principal, user)) {
249 warnx("kerberos5: not in %s's ACL.", user);
256 * Only allow those with pw_gid==0 or those listed in
257 * group zero to su to root. If group zero entry is
258 * missing or empty, then allow anyone to su to root.
259 * iswheelsu will only be set if the user is EXPLICITLY
260 * listed in group zero.
262 if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
263 gr->gr_mem && *(gr->gr_mem))
264 for (g = gr->gr_mem;; ++g) {
270 "you are not in the correct group (%s) to su %s.",
274 if (strcmp(username, *g) == 0) {
282 /* if target requires a password, verify it */
283 if (*pwd->pw_passwd) {
287 pwd = getpwnam(username);
290 entered_pass = skey_getpass("Password:", pwd, 1);
291 if (!(!strcmp(pwd->pw_passwd, skey_crypt(entered_pass,
292 pwd->pw_passwd, pwd, 1))
294 || (iswheelsu && !strcmp(targetpass,
295 crypt(entered_pass, targetpass)))
299 entered_pass = getpass("Password:");
300 if (strcmp(pwd->pw_passwd, crypt(entered_pass,
304 if (use_kerberos5 && kerberos5(context,
305 username, user, su_principal,
309 fprintf(stderr, "Sorry\n");
310 syslog(LOG_AUTH|LOG_WARNING,
311 "BAD SU %s to %s%s", username, user,
315 #if defined(KERBEROS5)
321 pwd = getpwnam(user);
325 if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
326 fprintf(stderr, "Sorry - account expired\n");
327 syslog(LOG_AUTH|LOG_WARNING,
328 "BAD SU %s to %s%s", username,
335 /* if asme and non-standard target shell, must be root */
336 if (!chshell(pwd->pw_shell) && ruid)
337 errx(1, "permission denied (shell).");
338 } else if (pwd->pw_shell && *pwd->pw_shell) {
339 shell = pwd->pw_shell;
342 shell = _PATH_BSHELL;
346 /* if we're forking a csh, we want to slightly muck the args */
347 if (iscsh == UNSET) {
348 p = strrchr(shell, '/');
353 if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
354 iscsh = strcmp(p, "tcsh") ? NO : YES;
357 (void)setpriority(PRIO_PROCESS, 0, prio);
360 /* Set everything now except the environment & umask */
361 setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
363 * Don't touch resource/priority settings if -m has been
364 * used or -l and -c hasn't, and we're not su'ing to root.
366 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
367 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
368 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
369 err(1, "setusercontext");
371 /* set permissions */
372 if (setgid(pwd->pw_gid) < 0)
374 if (initgroups(user, pwd->pw_gid))
375 errx(1, "initgroups failed");
376 if (setuid(pwd->pw_uid) < 0)
384 ccname = getenv("KRB5CCNAME");
386 if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
391 /* set the su'd user's environment & umask */
392 setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
394 (void)setenv("PATH", _PATH_DEFPATH, 1);
397 (void)setenv("TERM", p, 1);
400 (void)setenv("KRB5CCNAME", ccname, 1);
402 if (chdir(pwd->pw_dir) < 0)
403 errx(1, "no directory");
405 if (asthem || pwd->pw_uid)
406 (void)setenv("USER", pwd->pw_name, 1);
407 (void)setenv("HOME", pwd->pw_dir, 1);
408 (void)setenv("SHELL", shell, 1);
417 /* csh strips the first character... */
418 *np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
421 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
422 username, user, ontty());
428 execv(shell, __DECONST(char * const *, np));
435 (void)fprintf(stderr, "usage: su [-] [-%s] %s[login [args]]\n",
436 KERBEROS_ARG("K") COMMON_ARG("flm"),
453 while (!r && (cp = getusershell()) != NULL)
454 r = strcmp(cp, sh) == 0;
463 static char buf[MAXPATHLEN + 4];
466 p = ttyname(STDERR_FILENO);
468 snprintf(buf, sizeof(buf), " on %s", p);
473 const char superuser[] = "root";
475 /* Authenticate using Kerberos 5.
476 * context -- An initialized krb5_context.
477 * current_user -- The current username.
478 * target_user -- The target account name.
479 * su_principal -- The target krb5_principal.
480 * pass -- The user's password.
481 * Note that a valid keytab in the default location with a host entry
483 * Returns 0 if authentication was successful, or a com_err error code if
487 kerberos5(krb5_context context, const char *current_user,
488 const char *target_user, krb5_principal su_principal,
492 krb5_get_init_creds_opt gic_opt;
493 krb5_verify_init_creds_opt vic_opt;
496 krb5_get_init_creds_opt_init(&gic_opt);
497 krb5_verify_init_creds_opt_init(&vic_opt);
498 rv = krb5_get_init_creds_password(context, &creds, su_principal,
499 pass, NULL, NULL, 0, NULL, &gic_opt);
501 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
502 current_user, target_user, ontty(),
503 krb5_get_err_text(context, rv));
506 krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1);
507 rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL,
509 krb5_free_cred_contents(context, &creds);
511 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
512 current_user, target_user, ontty(),
513 krb5_get_err_text(context, rv));
519 /* Determine the target principal given the current user and the target user.
520 * context -- An initialized krb5_context.
521 * target_user -- The target username.
522 * current_user -- The current username.
523 * su_principal_name -- (out) The target principal name.
524 * su_principal -- (out) The target krb5_principal.
526 * When target_user is `root', the su_principal will be a `root
527 * instance', e.g. `luser/root@REA.LM'. Otherwise, the su_principal
528 * will simply be the current user's default principal name. Note that
529 * in any case, if KRB5CCNAME is set and a credentials cache exists, the
530 * principal name found there will be the `starting point', rather than
531 * the current_user parameter.
533 * Returns 0 for success, or a com_err error code on failure.
536 get_su_principal(krb5_context context, const char *target_user,
537 const char *current_user, char **su_principal_name,
538 krb5_principal *su_principal)
540 krb5_principal default_principal;
542 char *principal_name, *ccname, *p;
546 *su_principal = NULL;
547 default_principal = NULL;
548 /* Lower privs while messing about with the credentials
553 rv = seteuid(getuid());
556 p = getenv("KRB5CCNAME");
560 (void)asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT,
561 (unsigned long)ruid);
564 rv = krb5_cc_resolve(context, ccname, &ccache);
567 rv = krb5_cc_get_principal(context, ccache,
569 krb5_cc_close(context, ccache);
571 default_principal = NULL; /* just to be safe */
576 if (default_principal == NULL) {
577 rv = krb5_make_principal(context, &default_principal, NULL,
580 warnx("Could not determine default principal name.");
584 /* Now that we have some principal, if the target account is
585 * `root', then transform it into a `root' instance, e.g.
586 * `user@REA.LM' -> `user/root@REA.LM'.
588 rv = krb5_unparse_name(context, default_principal, &principal_name);
589 krb5_free_principal(context, default_principal);
591 warnx("krb5_unparse_name: %s", krb5_get_err_text(context, rv));
594 if (strcmp(target_user, superuser) == 0) {
595 p = strrchr(principal_name, '@');
597 warnx("malformed principal name `%s'", principal_name);
598 free(principal_name);
602 (void)asprintf(su_principal_name, "%s/%s@%s", principal_name,
604 free(principal_name);
606 *su_principal_name = principal_name;
607 if (*su_principal_name == NULL)
609 rv = krb5_parse_name(context, *su_principal_name, &default_principal);
611 warnx("krb5_parse_name `%s': %s", *su_principal_name,
612 krb5_get_err_text(context, rv));
613 free(*su_principal_name);
616 *su_principal = default_principal;