freebsd-dev/usr.bin/su/su.c
Robert Watson e292984cd3 o Make comment match reality, synch code with comment.
o In practice: the comment indicates that all but umask and
  environmental variables of the users login class are applied when su
  occurs, unless -m is used to specify a class.  This was incorrect;
  in practice, the uid, gids, resources, and priority were set, and
  then resources and priority were selectively removed.  This meant
  that some aspects of the user context were not set, including handling
  of login events (wtmp, utmp), as well as the path specified in
  login.conf.
o I changed it so that the behavior is the same, but instead,
  LOGIN_SETALL is used, and appropriate flags are removed, including
  the LOGIN_SETLOGIN and LOGIN_SETPATH entries that were implicitly
  not present before.  I also updated the comment to reflect
  reality, selecting reality as the "correct" behavior.
o This has the practical benefit that as new LOGIN_SET* flags are
  introduced, they are supported by su unless specifically disabled.
  For example, of a LOGIN_SETLABEL flag is introduced to support
  MAC labels determined by the user's login class, then su no longer
  has to be modified.
o It might be desirable to have su use LOGIN_SETPATH depending on
  its command line parameters, as it might or might not be
  considered part of the "environment".

Obtained from:	TrustedBSD Project
2000-11-30 23:14:55 +00:00

582 lines
14 KiB
C

/*
* Copyright (c) 1988, 1993, 1994
* 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.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1988, 1993, 1994\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
#if 0
static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";
#endif
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
#include <sys/param.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <err.h>
#include <errno.h>
#include <grp.h>
#include <paths.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <libutil.h>
#ifdef LOGIN_CAP
#include <login_cap.h>
#endif
#ifdef SKEY
#include <skey.h>
#endif
#ifdef KERBEROS
#include <openssl/des.h>
#include <krb.h>
#include <netdb.h>
#ifdef LOGIN_CAP
#define ARGSTR "-Kflmc:"
#else
#define ARGSTR "-Kflm"
#endif
static int kerberos(char *username, char *user, int uid, char *pword);
static int koktologin(char *name, char *toname);
int use_kerberos = 1;
#else /* !KERBEROS */
#ifdef LOGIN_CAP
#define ARGSTR "-flmc:"
#else
#define ARGSTR "-flm"
#endif
#endif /* KERBEROS */
char *ontty __P((void));
int chshell __P((char *));
static void usage __P((void));
int
main(argc, argv)
int argc;
char **argv;
{
extern char **environ;
struct passwd *pwd;
#ifdef WHEELSU
char *targetpass;
int iswheelsu;
#endif /* WHEELSU */
char *p, **g, *user, *shell=NULL, *username, **cleanenv, **nargv, **np;
struct group *gr;
uid_t ruid;
gid_t gid;
int asme, ch, asthem, fastlogin, prio, i;
enum { UNSET, YES, NO } iscsh = UNSET;
#ifdef LOGIN_CAP
login_cap_t *lc;
char *class=NULL;
int setwhat;
#endif
#ifdef KERBEROS
char *k;
#endif
char shellbuf[MAXPATHLEN];
#ifdef WHEELSU
iswheelsu =
#endif /* WHEELSU */
asme = asthem = fastlogin = 0;
user = "root";
while((ch = getopt(argc, argv, ARGSTR)) != -1)
switch((char)ch) {
#ifdef KERBEROS
case 'K':
use_kerberos = 0;
break;
#endif
case 'f':
fastlogin = 1;
break;
case '-':
case 'l':
asme = 0;
asthem = 1;
break;
case 'm':
asme = 1;
asthem = 0;
break;
#ifdef LOGIN_CAP
case 'c':
class = optarg;
break;
#endif
case '?':
default:
usage();
}
if (optind < argc)
user = argv[optind++];
if (strlen(user) > MAXLOGNAME - 1) {
(void)fprintf(stderr, "su: username too long.\n");
exit(1);
}
if (user == NULL)
usage();
if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
errx(1, "malloc failure");
}
nargv[argc + 3] = NULL;
for (i = argc; i >= optind; i--)
nargv[i + 3] = argv[i];
np = &nargv[i + 3];
argv += optind;
#ifdef KERBEROS
k = auth_getval("auth_list");
if (k && !strstr(k, "kerberos"))
use_kerberos = 0;
#endif
errno = 0;
prio = getpriority(PRIO_PROCESS, 0);
if (errno)
prio = 0;
(void)setpriority(PRIO_PROCESS, 0, -2);
openlog("su", LOG_CONS, 0);
/* get current login name and shell */
ruid = getuid();
username = getlogin();
if (username == NULL || (pwd = getpwnam(username)) == NULL ||
pwd->pw_uid != ruid)
pwd = getpwuid(ruid);
if (pwd == NULL)
errx(1, "who are you?");
username = strdup(pwd->pw_name);
gid = pwd->pw_gid;
if (username == NULL)
err(1, NULL);
if (asme) {
if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
/* copy: pwd memory is recycled */
shell = strncpy(shellbuf, pwd->pw_shell, sizeof shellbuf);
shellbuf[sizeof shellbuf - 1] = '\0';
} else {
shell = _PATH_BSHELL;
iscsh = NO;
}
}
/* get target login information, default to root */
if ((pwd = getpwnam(user)) == NULL) {
errx(1, "unknown login: %s", user);
}
#ifdef LOGIN_CAP
if (class==NULL) {
lc = login_getpwclass(pwd);
} else {
if (ruid)
errx(1, "only root may use -c");
lc = login_getclass(class);
if (lc == NULL)
errx(1, "unknown class: %s", class);
}
#endif
#ifdef WHEELSU
targetpass = strdup(pwd->pw_passwd);
#endif /* WHEELSU */
if (ruid) {
#ifdef KERBEROS
if (use_kerberos && koktologin(username, user)
&& !pwd->pw_uid) {
warnx("kerberos: not in %s's ACL.", user);
use_kerberos = 0;
}
#endif
{
/*
* Only allow those with pw_gid==0 or those listed in
* group zero to su to root. If group zero entry is
* missing or empty, then allow anyone to su to root.
* iswheelsu will only be set if the user is EXPLICITLY
* listed in group zero.
*/
if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
gr->gr_mem && *(gr->gr_mem))
for (g = gr->gr_mem;; ++g) {
if (!*g) {
if (gid == 0)
break;
else
errx(1, "you are not in the correct group to su %s.", user);
}
if (strcmp(username, *g) == 0) {
#ifdef WHEELSU
iswheelsu = 1;
#endif /* WHEELSU */
break;
}
}
}
/* if target requires a password, verify it */
if (*pwd->pw_passwd) {
#ifdef SKEY
#ifdef WHEELSU
if (iswheelsu) {
pwd = getpwnam(username);
}
#endif /* WHEELSU */
p = skey_getpass("Password:", pwd, 1);
if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
#ifdef WHEELSU
|| (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
#endif /* WHEELSU */
)) {
#else
p = getpass("Password:");
if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
#endif
#ifdef KERBEROS
if (!use_kerberos || (use_kerberos && kerberos(username, user, pwd->pw_uid, p)))
#endif
{
fprintf(stderr, "Sorry\n");
syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty());
exit(1);
}
}
#ifdef WHEELSU
if (iswheelsu) {
pwd = getpwnam(user);
}
#endif /* WHEELSU */
}
if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
fprintf(stderr, "Sorry - account expired\n");
syslog(LOG_AUTH|LOG_WARNING,
"BAD SU %s to %s%s", username,
user, ontty());
exit(1);
}
}
if (asme) {
/* if asme and non-standard target shell, must be root */
if (!chshell(pwd->pw_shell) && ruid)
errx(1, "permission denied (shell).");
} else if (pwd->pw_shell && *pwd->pw_shell) {
shell = pwd->pw_shell;
iscsh = UNSET;
} else {
shell = _PATH_BSHELL;
iscsh = NO;
}
/* if we're forking a csh, we want to slightly muck the args */
if (iscsh == UNSET) {
p = strrchr(shell, '/');
if (p)
++p;
else
p = shell;
if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
iscsh = strcmp(p, "tcsh") ? NO : YES;
}
(void)setpriority(PRIO_PROCESS, 0, prio);
#ifdef LOGIN_CAP
/*
* Set all user context except for:
* Environmental variables
* Umask
* Login records (wtmp, etc)
* Path
*/
setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
LOGIN_SETLOGIN | LOGIN_SETPATH);
/*
* Don't touch resource/priority settings if -m has been
* used or -l and -c hasn't, and we're not su'ing to root.
*/
if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
err(1, "setusercontext");
#else
/* set permissions */
if (setgid(pwd->pw_gid) < 0)
err(1, "setgid");
if (initgroups(user, pwd->pw_gid))
errx(1, "initgroups failed");
if (setuid(pwd->pw_uid) < 0)
err(1, "setuid");
#endif
if (!asme) {
if (asthem) {
p = getenv("TERM");
#ifdef KERBEROS
k = getenv("KRBTKFILE");
#endif
if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
errx(1, "calloc");
cleanenv[0] = NULL;
environ = cleanenv;
#ifdef LOGIN_CAP
/* set the su'd user's environment & umask */
setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
#else
(void)setenv("PATH", _PATH_DEFPATH, 1);
#endif
if (p)
(void)setenv("TERM", p, 1);
#ifdef KERBEROS
if (k)
(void)setenv("KRBTKFILE", k, 1);
#endif
if (chdir(pwd->pw_dir) < 0)
errx(1, "no directory");
}
if (asthem || pwd->pw_uid)
(void)setenv("USER", pwd->pw_name, 1);
(void)setenv("HOME", pwd->pw_dir, 1);
(void)setenv("SHELL", shell, 1);
}
if (iscsh == YES) {
if (fastlogin)
*np-- = "-f";
if (asme)
*np-- = "-m";
}
/* csh strips the first character... */
*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
if (ruid != 0)
syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
username, user, ontty());
login_close(lc);
execv(shell, np);
err(1, "%s", shell);
}
static void
usage()
{
(void)fprintf(stderr, "usage: su [%s] [login [args]]\n", ARGSTR);
exit(1);
}
int
chshell(sh)
char *sh;
{
int r = 0;
char *cp;
setusershell();
while (!r && (cp = getusershell()) != NULL)
r = strcmp(cp, sh) == 0;
endusershell();
return r;
}
char *
ontty()
{
char *p;
static char buf[MAXPATHLEN + 4];
buf[0] = 0;
p = ttyname(STDERR_FILENO);
if (p)
snprintf(buf, sizeof(buf), " on %s", p);
return (buf);
}
#ifdef KERBEROS
int
kerberos(username, user, uid, pword)
char *username, *user;
int uid;
char *pword;
{
KTEXT_ST ticket;
AUTH_DAT authdata;
int kerno;
u_long faddr;
char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
char *krb_get_phost();
struct hostent *hp;
if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
return (1);
(void)sprintf(krbtkfile, "%s_%s_%lu", TKT_ROOT, user,
(unsigned long)getuid());
(void)setenv("KRBTKFILE", krbtkfile, 1);
(void)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.
*/
kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
(uid == 0 ? "root" : ""), lrealm,
"krbtgt", lrealm, DEFAULT_TKT_LIFE, pword);
if (kerno != KSUCCESS) {
if (kerno == KDC_PR_UNKNOWN) {
warnx("kerberos: principal unknown: %s.%s@%s",
(uid == 0 ? username : user),
(uid == 0 ? "root" : ""), lrealm);
return (1);
}
warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
syslog(LOG_NOTICE|LOG_AUTH,
"BAD Kerberos SU: %s to %s%s: %s",
username, user, ontty(), krb_err_txt[kerno]);
return (1);
}
if (chown(krbtkfile, uid, -1) < 0) {
warn("chown");
(void)unlink(krbtkfile);
return (1);
}
(void)setpriority(PRIO_PROCESS, 0, -2);
if (gethostname(hostname, sizeof(hostname)) == -1) {
warn("gethostname");
dest_tkt();
return (1);
}
(void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
savehost[sizeof(savehost) - 1] = '\0';
kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
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_err_txt[kerno],
"rcmd", savehost);
} else if (kerno != KSUCCESS) {
warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
username, user, ontty(), krb_err_txt[kerno]);
dest_tkt();
return (1);
} else {
if (!(hp = gethostbyname(hostname))) {
warnx("can't get addr of %s", hostname);
dest_tkt();
return (1);
}
memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
&authdata, "")) != KSUCCESS) {
warnx("kerberos: unable to verify rcmd ticket: %s\n",
krb_err_txt[kerno]);
syslog(LOG_NOTICE|LOG_AUTH,
"failed su: %s to %s%s: %s", username,
user, ontty(), krb_err_txt[kerno]);
dest_tkt();
return (1);
}
}
return (0);
}
int
koktologin(name, toname)
char *name, *toname;
{
AUTH_DAT *kdata;
AUTH_DAT kdata_st;
char realm[REALM_SZ];
if (krb_get_lrealm(realm, 1) != KSUCCESS)
return (1);
kdata = &kdata_st;
memset((char *)kdata, 0, sizeof(*kdata));
(void)strncpy(kdata->pname, name, sizeof kdata->pname - 1);
(void)strncpy(kdata->pinst,
((strcmp(toname, "root") == 0) ? "root" : ""), sizeof kdata->pinst - 1);
(void)strncpy(kdata->prealm, realm, sizeof kdata->prealm - 1);
return (kuserok(kdata, toname));
}
#endif