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.2 2003/06/17 04:29:32 dillon Exp $
39 #include <sys/param.h>
41 #include <sys/resource.h>
56 #include <login_cap.h>
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,
73 int use_kerberos5 = 1;
77 #include <openssl/des.h>
82 static int kerberos4(char *username, char *user, int uid, char *pword);
83 static int koktologin(char *name, char *toname);
85 int use_kerberos4 = 1;
86 #endif /* KERBEROS4 */
89 #define LOGIN_CAP_ARG(x) x
91 #define LOGIN_CAP_ARG(x)
93 #if defined(KERBEROS4) || defined(KERBEROS5)
94 #define KERBEROS_ARG(x) x
96 #define KERBEROS_ARG(x)
98 #define COMMON_ARG(x) x
99 #define ARGSTR "-" COMMON_ARG("flm") LOGIN_CAP_ARG("c:") KERBEROS_ARG("K")
101 char *ontty __P((void));
102 int chshell __P((char *));
103 static void usage __P((void));
110 extern char **environ;
116 char *p, **g, *user, *shell=NULL, *username, **cleanenv, **nargv, **np;
120 int asme, ch, asthem, fastlogin, prio, i;
121 enum { UNSET, YES, NO } iscsh = UNSET;
127 #if defined(KERBEROS4) || defined(KERBEROS5)
131 char *su_principal_name, *ccname;
132 krb5_context context;
133 krb5_principal su_principal;
135 char shellbuf[MAXPATHLEN];
140 asme = asthem = fastlogin = 0;
142 while((ch = getopt(argc, argv, ARGSTR)) != -1)
144 #if defined(KERBEROS4) || defined(KERBEROS5)
177 user = argv[optind++];
179 if (strlen(user) > MAXLOGNAME - 1) {
180 (void)fprintf(stderr, "su: username too long.\n");
187 if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
188 errx(1, "malloc failure");
191 nargv[argc + 3] = NULL;
192 for (i = argc; i >= optind; i--)
193 nargv[i + 3] = argv[i];
198 #if defined(KERBEROS4) || defined(KERBEROS5)
199 k = auth_getval("auth_list");
200 if (k && !strstr(k, "kerberos")) {
210 su_principal_name = NULL;
212 if (krb5_init_context(&context) != 0)
216 prio = getpriority(PRIO_PROCESS, 0);
219 (void)setpriority(PRIO_PROCESS, 0, -2);
220 openlog("su", LOG_CONS, 0);
222 /* get current login name and shell */
224 username = getlogin();
225 if (username == NULL || (pwd = getpwnam(username)) == NULL ||
227 pwd = getpwuid(ruid);
229 errx(1, "who are you?");
230 username = strdup(pwd->pw_name);
232 if (username == NULL)
235 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
236 /* copy: pwd memory is recycled */
237 shell = strncpy(shellbuf, pwd->pw_shell, sizeof shellbuf);
238 shellbuf[sizeof shellbuf - 1] = '\0';
240 shell = _PATH_BSHELL;
245 /* get target login information, default to root */
246 if ((pwd = getpwnam(user)) == NULL) {
247 errx(1, "unknown login: %s", user);
251 lc = login_getpwclass(pwd);
254 errx(1, "only root may use -c");
255 lc = login_getclass(class);
257 errx(1, "unknown class: %s", class);
262 targetpass = strdup(pwd->pw_passwd);
267 if (use_kerberos4 && koktologin(username, user)
269 warnx("kerberos4: not in %s's ACL.", user);
275 if (get_su_principal(context, user, username,
276 &su_principal_name, &su_principal) != 0 ||
277 !krb5_kuserok(context, su_principal, user)) {
278 warnx("kerberos5: not in %s's ACL.", user);
285 * Only allow those with pw_gid==0 or those listed in
286 * group zero to su to root. If group zero entry is
287 * missing or empty, then allow anyone to su to root.
288 * iswheelsu will only be set if the user is EXPLICITLY
289 * listed in group zero.
291 if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
292 gr->gr_mem && *(gr->gr_mem))
293 for (g = gr->gr_mem;; ++g) {
299 "you are not in the correct group (%s) to su %s.",
303 if (strcmp(username, *g) == 0) {
311 /* if target requires a password, verify it */
312 if (*pwd->pw_passwd) {
316 pwd = getpwnam(username);
319 p = skey_getpass("Password:", pwd, 1);
320 if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
322 || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
326 p = getpass("Password:");
327 if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
330 if (use_kerberos4 && kerberos4(username, user,
331 pwd->pw_uid, p) == 0)
335 if (use_kerberos5 && kerberos5(context,
336 username, user, su_principal, p) == 0)
339 fprintf(stderr, "Sorry\n");
340 syslog(LOG_AUTH|LOG_WARNING,
341 "BAD SU %s to %s%s", username, user,
345 #if defined(KERBEROS4) || defined(KERBEROS5)
350 pwd = getpwnam(user);
354 if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
355 fprintf(stderr, "Sorry - account expired\n");
356 syslog(LOG_AUTH|LOG_WARNING,
357 "BAD SU %s to %s%s", username,
364 /* if asme and non-standard target shell, must be root */
365 if (!chshell(pwd->pw_shell) && ruid)
366 errx(1, "permission denied (shell).");
367 } else if (pwd->pw_shell && *pwd->pw_shell) {
368 shell = pwd->pw_shell;
371 shell = _PATH_BSHELL;
375 /* if we're forking a csh, we want to slightly muck the args */
376 if (iscsh == UNSET) {
377 p = strrchr(shell, '/');
382 if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
383 iscsh = strcmp(p, "tcsh") ? NO : YES;
386 (void)setpriority(PRIO_PROCESS, 0, prio);
389 /* Set everything now except the environment & umask */
390 setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
392 * Don't touch resource/priority settings if -m has been
393 * used or -l and -c hasn't, and we're not su'ing to root.
395 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
396 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
397 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
398 err(1, "setusercontext");
400 /* set permissions */
401 if (setgid(pwd->pw_gid) < 0)
403 if (initgroups(user, pwd->pw_gid))
404 errx(1, "initgroups failed");
405 if (setuid(pwd->pw_uid) < 0)
413 k = getenv("KRBTKFILE");
416 ccname = getenv("KRB5CCNAME");
418 if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
423 /* set the su'd user's environment & umask */
424 setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
426 (void)setenv("PATH", _PATH_DEFPATH, 1);
429 (void)setenv("TERM", p, 1);
432 (void)setenv("KRBTKFILE", k, 1);
436 (void)setenv("KRB5CCNAME", ccname, 1);
438 if (chdir(pwd->pw_dir) < 0)
439 errx(1, "no directory");
441 if (asthem || pwd->pw_uid)
442 (void)setenv("USER", pwd->pw_name, 1);
443 (void)setenv("HOME", pwd->pw_dir, 1);
444 (void)setenv("SHELL", shell, 1);
453 /* csh strips the first character... */
454 *np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
457 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
458 username, user, ontty());
471 (void)fprintf(stderr, "usage: su [-] [-%s] %s[login [args]]\n",
472 KERBEROS_ARG("K") COMMON_ARG("flm"),
490 while (!r && (cp = getusershell()) != NULL)
491 r = strcmp(cp, sh) == 0;
500 static char buf[MAXPATHLEN + 4];
503 p = ttyname(STDERR_FILENO);
505 snprintf(buf, sizeof(buf), " on %s", p);
510 const char superuser[] = "root";
512 /* Authenticate using Kerberos 5.
513 * context -- An initialized krb5_context.
514 * current_user -- The current username.
515 * target_user -- The target account name.
516 * su_principal -- The target krb5_principal.
517 * pass -- The user's password.
518 * Note that a valid keytab in the default location with a host entry
520 * Returns 0 if authentication was successful, or a com_err error code if
524 kerberos5(krb5_context context, const char *current_user,
525 const char *target_user, krb5_principal su_principal,
529 krb5_get_init_creds_opt gic_opt;
530 krb5_verify_init_creds_opt vic_opt;
533 krb5_get_init_creds_opt_init(&gic_opt);
534 krb5_verify_init_creds_opt_init(&vic_opt);
535 rv = krb5_get_init_creds_password(context, &creds, su_principal,
536 pass, NULL, NULL, 0, NULL, &gic_opt);
538 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
539 current_user, target_user, ontty(),
540 krb5_get_err_text(context, rv));
543 krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1);
544 rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL,
546 krb5_free_cred_contents(context, &creds);
548 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
549 current_user, target_user, ontty(),
550 krb5_get_err_text(context, rv));
556 /* Determine the target principal given the current user and the target user.
557 * context -- An initialized krb5_context.
558 * target_user -- The target username.
559 * current_user -- The current username.
560 * su_principal_name -- (out) The target principal name.
561 * su_principal -- (out) The target krb5_principal.
563 * When target_user is `root', the su_principal will be a `root
564 * instance', e.g. `luser/root@REA.LM'. Otherwise, the su_principal
565 * will simply be the current user's default principal name. Note that
566 * in any case, if KRB5CCNAME is set and a credentials cache exists, the
567 * principal name found there will be the `starting point', rather than
568 * the current_user parameter.
570 * Returns 0 for success, or a com_err error code on failure.
573 get_su_principal(krb5_context context, const char *target_user,
574 const char *current_user, char **su_principal_name,
575 krb5_principal *su_principal)
577 krb5_principal default_principal;
579 char *principal_name, *ccname, *p;
583 *su_principal = NULL;
584 default_principal = NULL;
585 /* Lower privs while messing about with the credentials
590 rv = seteuid(getuid());
593 p = getenv("KRB5CCNAME");
597 (void)asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT,
598 (unsigned long)ruid);
601 rv = krb5_cc_resolve(context, ccname, &ccache);
604 rv = krb5_cc_get_principal(context, ccache,
606 krb5_cc_close(context, ccache);
608 default_principal = NULL; /* just to be safe */
613 if (default_principal == NULL) {
614 rv = krb5_make_principal(context, &default_principal, NULL,
617 warnx("Could not determine default principal name.");
621 /* Now that we have some principal, if the target account is
622 * `root', then transform it into a `root' instance, e.g.
623 * `user@REA.LM' -> `user/root@REA.LM'.
625 rv = krb5_unparse_name(context, default_principal, &principal_name);
626 krb5_free_principal(context, default_principal);
628 warnx("krb5_unparse_name: %s", krb5_get_err_text(context, rv));
631 if (strcmp(target_user, superuser) == 0) {
632 p = strrchr(principal_name, '@');
634 warnx("malformed principal name `%s'", principal_name);
635 free(principal_name);
639 (void)asprintf(su_principal_name, "%s/%s@%s", principal_name,
641 free(principal_name);
643 *su_principal_name = principal_name;
644 if (*su_principal_name == NULL)
646 rv = krb5_parse_name(context, *su_principal_name, &default_principal);
648 warnx("krb5_parse_name `%s': %s", *su_principal_name,
649 krb5_get_err_text(context, rv));
650 free(*su_principal_name);
653 *su_principal = default_principal;
661 kerberos4(username, user, uid, pword)
662 char *username, *user;
670 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
671 char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
672 char *krb_get_phost();
675 if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
677 (void)sprintf(krbtkfile, "%s_%s_%lu", TKT_ROOT, user,
678 (unsigned long)getuid());
680 (void)setenv("KRBTKFILE", krbtkfile, 1);
681 (void)krb_set_tkt_string(krbtkfile);
683 * Set real as well as effective ID to 0 for the moment,
684 * to make the kerberos library do the right thing.
692 * Little trick here -- if we are su'ing to root,
693 * we need to get a ticket for "xxx.root", where xxx represents
694 * the name of the person su'ing. Otherwise (non-root case),
695 * we need to get a ticket for "yyy.", where yyy represents
696 * the name of the person being su'd to, and the instance is null
698 * We should have a way to set the ticket lifetime,
699 * with a system default for root.
701 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
702 (uid == 0 ? "root" : ""), lrealm,
703 "krbtgt", lrealm, DEFAULT_TKT_LIFE, pword);
705 if (kerno != KSUCCESS) {
706 if (kerno == KDC_PR_UNKNOWN) {
707 warnx("kerberos4: principal unknown: %s.%s@%s",
708 (uid == 0 ? username : user),
709 (uid == 0 ? "root" : ""), lrealm);
712 warnx("kerberos4: unable to su: %s", krb_err_txt[kerno]);
713 syslog(LOG_NOTICE|LOG_AUTH,
714 "BAD Kerberos4 SU: %s to %s%s: %s",
715 username, user, ontty(), krb_err_txt[kerno]);
719 if (chown(krbtkfile, uid, -1) < 0) {
721 (void)unlink(krbtkfile);
725 (void)setpriority(PRIO_PROCESS, 0, -2);
727 if (gethostname(hostname, sizeof(hostname)) == -1) {
733 (void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
734 savehost[sizeof(savehost) - 1] = '\0';
736 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
738 if (kerno == KDC_PR_UNKNOWN) {
739 warnx("Warning: TGT not verified.");
740 syslog(LOG_NOTICE|LOG_AUTH,
741 "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
742 username, user, ontty(), krb_err_txt[kerno],
744 } else if (kerno != KSUCCESS) {
745 warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
746 syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
747 username, user, ontty(), krb_err_txt[kerno]);
751 if (!(hp = gethostbyname(hostname))) {
752 warnx("can't get addr of %s", hostname);
756 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
758 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
759 &authdata, "")) != KSUCCESS) {
760 warnx("kerberos4: unable to verify rcmd ticket: %s\n",
762 syslog(LOG_NOTICE|LOG_AUTH,
763 "failed su: %s to %s%s: %s", username,
764 user, ontty(), krb_err_txt[kerno]);
773 koktologin(name, toname)
778 char realm[REALM_SZ];
780 if (krb_get_lrealm(realm, 1) != KSUCCESS)
783 memset((char *)kdata, 0, sizeof(*kdata));
784 (void)strncpy(kdata->pname, name, sizeof kdata->pname - 1);
785 (void)strncpy(kdata->pinst,
786 ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof kdata->pinst - 1);
787 (void)strncpy(kdata->prealm, realm, sizeof kdata->prealm - 1);
788 return (kuserok(kdata, toname));