/* * Copyright (c) 1988 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "bsd_locl.h" RCSID ("$Id: su.c,v 1.70.2.2 2000/12/07 14:04:19 assar Exp $"); #ifdef SYSV_SHADOW #include "sysv_shadow.h" #endif static int kerberos (char *username, char *user, char *realm, int uid); static int chshell (char *sh); static char *ontty (void); static int koktologin (char *name, char *realm, char *toname); static int chshell (char *sh); /* Handle '-' option after all the getopt options */ #define ARGSTR "Kkflmti:r:" int destroy_tickets = 0; static int use_kerberos = 1; static char *root_inst = "root"; int main (int argc, char **argv) { struct passwd *pwd; char *p, **g; struct group *gr; uid_t ruid; int asme, ch, asthem, fastlogin, prio; enum { UNSET, YES, NO } iscsh = UNSET; char *user, *shell, *avshell, *username, **np; char shellbuf[MaxPathLen], avshellbuf[MaxPathLen]; char *realm = NULL; set_progname (argv[0]); if (getuid() == 0) use_kerberos = 0; asme = asthem = fastlogin = 0; while ((ch = getopt (argc, argv, ARGSTR)) != -1) switch ((char) ch) { case 'K': use_kerberos = 0; break; case 'k': use_kerberos = 1; break; case 'f': fastlogin = 1; break; case 'l': asme = 0; asthem = 1; break; case 'm': asme = 1; asthem = 0; break; case 't': destroy_tickets = 1; break; case 'i': root_inst = optarg; break; case 'r': realm = optarg; break; case '?': default: fprintf (stderr, "usage: su [-Kkflmt] [-i root-instance] [-r realm] [-] [login]\n"); exit (1); } /* Don't handle '-' option with getopt */ if (optind < argc && strcmp (argv[optind], "-") == 0) { asme = 0; asthem = 1; optind++; } argv += optind; if (use_kerberos) { int fd = open (KEYFILE, O_RDONLY); if (fd >= 0) close (fd); else use_kerberos = 0; } errno = 0; prio = getpriority (PRIO_PROCESS, 0); if (errno) prio = 0; setpriority (PRIO_PROCESS, 0, -2); openlog ("su", LOG_CONS, LOG_AUTH); /* get current login name and shell */ ruid = getuid (); username = getlogin (); if (username == NULL || (pwd = k_getpwnam (username)) == NULL || pwd->pw_uid != ruid) pwd = k_getpwuid (ruid); if (pwd == NULL) errx (1, "who are you?"); username = strdup (pwd->pw_name); if (username == NULL) errx (1, "strdup: out of memory"); if (asme) { if (pwd->pw_shell && *pwd->pw_shell) { strlcpy (shellbuf, pwd->pw_shell, sizeof(shellbuf)); shell = shellbuf; } else { shell = _PATH_BSHELL; iscsh = NO; } } /* get target login information, default to root */ user = *argv ? *argv : "root"; np = *argv ? argv : argv - 1; pwd = k_getpwnam (user); if (pwd == NULL) errx (1, "unknown login %s", user); if (pwd->pw_uid == 0 && strcmp ("root", user) != 0) { syslog (LOG_ALERT, "NIS attack, user %s has uid 0", user); errx (1, "unknown login %s", user); } if (!use_kerberos || kerberos (username, user, realm, pwd->pw_uid)) { #ifndef PASSWD_FALLBACK errx (1, "won't use /etc/passwd authentication"); #endif /* getpwnam() is not reentrant and kerberos might use it! */ pwd = k_getpwnam (user); if (pwd == NULL) errx (1, "unknown login %s", user); /* only allow those in group zero to su to root. */ if (pwd->pw_uid == 0 && (gr = getgrgid ((gid_t) 0))) for (g = gr->gr_mem;; ++g) { if (!*g) { #if 1 /* if group 0 is empty or only contains root su is still ok. */ if (gr->gr_mem[0] == 0) break; /* group 0 is empty */ if (gr->gr_mem[1] == 0 && strcmp (gr->gr_mem[0], "root") == 0) break; /* only root in group 0 */ #endif errx (1, "you are not in the correct group to su %s.", user); } if (!strcmp (username, *g)) break; } /* if target requires a password, verify it */ if (ruid && *pwd->pw_passwd) { char prompt[128]; char passwd[256]; snprintf (prompt, sizeof(prompt), "%s's Password: ", pwd->pw_name); if (des_read_pw_string (passwd, sizeof (passwd), prompt, 0)) { memset (passwd, 0, sizeof (passwd)); exit (1); } if (strcmp (pwd->pw_passwd, crypt (passwd, pwd->pw_passwd))) { memset (passwd, 0, sizeof (passwd)); syslog (LOG_AUTH | LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty ()); errx (1, "Sorry"); } memset (passwd, 0, sizeof (passwd)); } } if (asme) { /* if asme and non-standard target shell, must be root */ if (!chshell (pwd->pw_shell) && ruid) errx (1, "permission denied (shell '%s' not in /etc/shells).", pwd->pw_shell); } else if (pwd->pw_shell && *pwd->pw_shell) { shell = pwd->pw_shell; iscsh = UNSET; } else { shell = _PATH_BSHELL; iscsh = NO; } if ((p = strrchr (shell, '/')) != 0) avshell = p + 1; else avshell = shell; /* if we're forking a csh, we want to slightly muck the args */ if (iscsh == UNSET) iscsh = strcmp (avshell, "csh") ? NO : YES; /* set permissions */ if (setgid (pwd->pw_gid) < 0) err (1, "setgid"); if (initgroups (user, pwd->pw_gid)) { if (errno == E2BIG) /* Member of too many groups! */ warn("initgroups failed."); else errx(1, "initgroups failed."); } if (setuid (pwd->pw_uid) < 0) err (1, "setuid"); if (pwd->pw_uid != 0 && setuid(0) != -1) { syslog(LOG_ALERT | LOG_AUTH, "Failed to drop privileges for user %s", pwd->pw_name); errx(1, "Sorry"); } if (!asme) { if (asthem) { char *k = getenv ("KRBTKFILE"); char *t = getenv ("TERM"); environ = malloc (10 * sizeof (char *)); if (environ == NULL) err (1, "malloc"); environ[0] = NULL; setenv ("PATH", _PATH_DEFPATH, 1); if (t) setenv ("TERM", t, 1); if (k) setenv ("KRBTKFILE", k, 1); if (chdir (pwd->pw_dir) < 0) errx (1, "no directory"); } if (asthem || pwd->pw_uid) setenv ("USER", pwd->pw_name, 1); setenv ("HOME", pwd->pw_dir, 1); setenv ("SHELL", shell, 1); } if (iscsh == YES) { if (fastlogin) *np-- = "-f"; if (asme) *np-- = "-m"; } if (asthem) { snprintf (avshellbuf, sizeof(avshellbuf), "-%s", avshell); avshell = avshellbuf; } else if (iscsh == YES) { /* csh strips the first character... */ snprintf (avshellbuf, sizeof(avshellbuf), "_%s", avshell); avshell = avshellbuf; } *np = avshell; if (ruid != 0) syslog (LOG_NOTICE | LOG_AUTH, "%s to %s%s", username, user, ontty ()); setpriority (PRIO_PROCESS, 0, prio); if (k_hasafs ()) { int code; if (k_setpag () != 0) warn ("setpag"); code = krb_afslog (0, 0); if (code != KSUCCESS && code != KDC_PR_UNKNOWN) warnx ("afsklog: %s", krb_get_err_text (code)); } if (destroy_tickets) dest_tkt (); execv (shell, np); warn ("execv(%s)", shell); if (getuid () == 0) { execv (_PATH_BSHELL, np); warn ("execv(%s)", _PATH_BSHELL); } exit (1); } static int chshell (char *sh) { char *cp; while ((cp = getusershell ()) != NULL) if (!strcmp (cp, sh)) return (1); return (0); } static char * ontty (void) { char *p; static char buf[MaxPathLen + 4]; buf[0] = 0; if ((p = ttyname (STDERR_FILENO)) != 0) snprintf (buf, sizeof(buf), " on %s", p); return (buf); } static int kerberos (char *username, char *user, char *lrealm, int uid) { KTEXT_ST ticket; AUTH_DAT authdata; struct hostent *hp; int kerno; u_long faddr; char tmp_realm[REALM_SZ], krbtkfile[MaxPathLen]; char hostname[MaxHostNameLen], savehost[MaxHostNameLen]; int n; int allowed = 0; if (lrealm != NULL) { allowed = koktologin (username, lrealm, user) == 0; } else { for (n = 1; !allowed && krb_get_lrealm (tmp_realm, n) == KSUCCESS; ++n) allowed = koktologin (username, tmp_realm, user) == 0; lrealm = tmp_realm; } if (!allowed && !uid) { #ifndef PASSWD_FALLBACK warnx ("not in %s's ACL.", user); #endif return (1); } snprintf (krbtkfile, sizeof(krbtkfile), "%s_%s_to_%s_%u", TKT_ROOT, username, user, (unsigned) getpid ()); setenv ("KRBTKFILE", krbtkfile, 1); krb_set_tkt_string (krbtkfile); /* * Set real as well as effective ID to 0 for the moment, * to make the kerberos library do the right thing. */ if (setuid(0) < 0) { warn("setuid"); return (1); } /* * Little trick here -- if we are su'ing to root, we need to get a ticket * for "xxx.root", where xxx represents the name of the person su'ing. * Otherwise (non-root case), we need to get a ticket for "yyy.", where * yyy represents the name of the person being su'd to, and the instance * is null * * We should have a way to set the ticket lifetime, with a system default * for root. */ { char prompt[128]; char passw[256]; snprintf (prompt, sizeof(prompt), "%s's Password: ", krb_unparse_name_long ((uid == 0 ? username : user), (uid == 0 ? root_inst : ""), lrealm)); if (des_read_pw_string (passw, sizeof (passw), prompt, 0)) { memset (passw, 0, sizeof (passw)); return (1); } if (strlen(passw) == 0) return (1); /* Empty passwords is not allowed */ kerno = krb_get_pw_in_tkt ((uid == 0 ? username : user), (uid == 0 ? root_inst : ""), lrealm, KRB_TICKET_GRANTING_TICKET, lrealm, DEFAULT_TKT_LIFE, passw); memset (passw, 0, strlen (passw)); } if (kerno != KSUCCESS) { if (kerno == KDC_PR_UNKNOWN) { warnx ("principal unknown: %s", krb_unparse_name_long ((uid == 0 ? username : user), (uid == 0 ? root_inst : ""), lrealm)); return (1); } warnx ("unable to su: %s", krb_get_err_text (kerno)); syslog (LOG_NOTICE | LOG_AUTH, "BAD SU: %s to %s%s: %s", username, user, ontty (), krb_get_err_text (kerno)); return (1); } if (chown (krbtkfile, uid, -1) < 0) { warn ("chown"); unlink (krbtkfile); return (1); } setpriority (PRIO_PROCESS, 0, -2); if (gethostname (hostname, sizeof (hostname)) == -1) { warn ("gethostname"); dest_tkt (); return (1); } strlcpy (savehost, krb_get_phost (hostname), sizeof (savehost)); for (n = 1; krb_get_lrealm (tmp_realm, n) == KSUCCESS; ++n) { kerno = krb_mk_req (&ticket, "rcmd", savehost, tmp_realm, 33); if (kerno == 0) break; } if (kerno == KDC_PR_UNKNOWN) { warnx ("Warning: TGT not verified."); syslog (LOG_NOTICE | LOG_AUTH, "%s to %s%s, TGT not verified (%s); " "%s.%s not registered?", username, user, ontty (), krb_get_err_text (kerno), "rcmd", savehost); #ifdef KLOGIN_PARANOID /* * if the "VERIFY_SERVICE" doesn't exist in the KDC for this host, * * don't allow kerberos login, also log the error condition. */ warnx ("Trying local password!"); return (1); #endif } else if (kerno != KSUCCESS) { warnx ("Unable to use TGT: %s", krb_get_err_text (kerno)); syslog (LOG_NOTICE | LOG_AUTH, "failed su: %s to %s%s: %s", username, user, ontty (), krb_get_err_text (kerno)); dest_tkt (); return (1); } else { if (!(hp = gethostbyname (hostname))) { warnx ("can't get addr of %s", hostname); dest_tkt (); return (1); } memcpy (&faddr, hp->h_addr, sizeof (faddr)); if ((kerno = krb_rd_req (&ticket, "rcmd", savehost, faddr, &authdata, "")) != KSUCCESS) { warnx ("unable to verify rcmd ticket: %s", krb_get_err_text (kerno)); syslog (LOG_NOTICE | LOG_AUTH, "failed su: %s to %s%s: %s", username, user, ontty (), krb_get_err_text (kerno)); dest_tkt (); return (1); } } if (!destroy_tickets) fprintf (stderr, "Don't forget to kdestroy before exiting the shell.\n"); return (0); } static int koktologin (char *name, char *realm, char *toname) { return krb_kuserok (name, strcmp (toname, "root") == 0 ? root_inst : "", realm, toname); }