/* Copyright (c) 1990, 1991 UNIX System Laboratories, Inc. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T */ /* All Rights Reserved */ /* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF */ /* UNIX System Laboratories, Inc. */ /* The copyright notice above does not evidence any */ /* actual or intended publication of such source code. */ #ident "$Revision: 1.55 $" /* Copyright (c) 1987, 1988 Microsoft Corporation */ /* All Rights Reserved */ /* This Module contains Proprietary Information of Microsoft */ /* Corporation and should be treated as Confidential. */ /*************************************************************************** * Command: su * * Files: /var/adm/sulog * /etc/default/su * * Notes: change userid, `-' changes environment. * * If SULOG is defined, all attempts to su to another user are * logged there. * * If CONSOLE is defined, all successful attempts to su to a * privileged ID (in an ID-based privilege environment) are also * logged there. * * If PROMPT is defined (and ``No''), the user will not be * prompted for a password. * * If SYSLOG is defined, log to syslog all su failures * (SYSLOG=FAIL) or all successes and failures (SYSLOG=ALL). * Log entries are written to the LOG_AUTH facility. * * If su cannot create, open, or write entries into SULOG, * (or on the CONSOLE, if defined), the entry will not be logged * thus losing a record of the su commands attempted during this * period. * **************************************************************************/ /* * Additional SGI specific notes: * * The aformentioned "ID-based privilege environment" is the vanilla IRIX * SuperUser environment. * * If the capabilities system is installed (sysconf(_SC_CAP) != 0) * having a uid of 0 does not necessarily grant privilege. * In such a configuration, this program should not be installed with the * setuid bit set, rather with the necessary privileges (both allowed and * forced) set in the program file's capability set. * * The new user's inheritable capability set is cleared, unless otherwise * specified (with -C). If specified, the requested capability set is checked * to see if the new user is cleared for it. * * Each required privilege must be explicitly requested as needed. * The privileges required are: * * CAP_SETUID Allowed to change its [er]uid * CAP_SETGID Allowed to change its [er]gid, group list * CAP_SETPCAP Allowed to change its capability set * CAP_DAC_READ_SEARCH Allowed to read "sensitive" administrative data * (i.e. /etc/shadow) * CAP_DAC_WRITE Allowed to write "sensitive" administrative * data (i.e. SULOG) *(MAC) CAP_MAC_READ Allowed to read "sensitive" administrative data * (i.e. /etc/clearance) * CAP_MAC_WRITE Allowed to write "sensitive" administrative * data (i.e. SULOG) *(SAT) CAP_AUDIT_WRITE Allowed to write to the audit trail * * Note that being able to shift the userid is sufficient to allow for * access to the /etc/shadow file, and that CAP_DAC_READ_SEARCH isn't strictly * necessary. It is better to use the "appropriate" privilege for a purpose * than one which does the trick as a side effect, even if it means more * privileges are used. * * If the Mandatory Access Control system is installed (sysconf(_SC_MAC) != 0) * both the old & new user must be "cleared" for the current process label. * Since the clearance database /etc/clearance is itself protected by MAC, * privilege is more often than not required to get this information. * * If the Security Audit Trail system is installed (sysconf(_SC_SAT) != 0) * the program must be have sufficient privilege to write audit records. * In the vanilla case, only the SuperUser can do this. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _SHAREII #include #include SH_DECLARE_HOOK(SETMYNAME); SH_DECLARE_HOOK(SU); SH_DECLARE_HOOK(SU0); SH_DECLARE_HOOK(SUMSG); static int SHshareRunning = 0; #endif /* _SHAREII */ #define ELIM 1024 #define DEFFILE "su" /* default file M000 */ #define PATH _PATH_USERPATH #define SUPATH _PATH_ROOTPATH #ifdef sgi #define AUX_LANG /* LANG handling on */ #endif /* sgi */ #if defined(sgi) #define CHECK_MALLOC(_x) \ if ((_x) == NULL) { \ fprintf(stderr, "su: malloc failed. Program terminating\n"); \ exit(1); \ } #endif /* sgi */ #ifdef AUX_LANG #include #include #define LANG_FILE "/.lang" /* default lang file */ #endif /* AUX_LANG */ extern char **environ; extern int __ruserok_x(char *, int, char *, char *, uid_t, char *); static uid_t uid; static uinfo_t uinfo; static char *ttyn; static void log(FILE *fptr, char *towho, int how, int flag); static void err_out(int err, int astatus, char *alt_msg, char *arg); static char *get_lang(char *to, char *home); static int ask_prompt(char *usr_pwdp, uid_t uid); static FILE *open_con(char *namep); static void to(void), envalt(char *); static int prompt = 1, /* default is to prompt as always */ syslog_fail = 0, /* log failures to syslog */ syslog_success = 0, /* log failures and successes */ cap_enabled = 0, /* set iff capabilities are in use */ mac_enabled = 0; /* set iff MAC is available */ static char *nptr, *Sulog = NULL, *Syslog = NULL, *Prompt = NULL, *Console = NULL; static FILE *su_ptr, *con_ptr; #define ENV_SIZE 64 static char *term, *path, *supath, *hz, *tz, *ia_pwdp, *ia_dirp, *password, *ia_shellp, *Path = NULL, *Supath = NULL, /* M004 */ *envinit[ELIM], *str = "PATH=", *pshell = _PATH_BSHELL, /*default shell*/ su[ENV_SIZE+3] = "su", /*arg0 for exec of pshell*/ tznam[ENV_SIZE+4] = "TZ=", hzname[ENV_SIZE+4] = "HZ=", termtyp[ENV_SIZE+6] = "TERM=",/* M002 */ logname[ENV_SIZE+9] = "LOGNAME=", env_user[ENV_SIZE+6] = "USER=", env_shell[ENV_SIZE+7] = "SHELL=", rem_user[ENV_SIZE+12] = "REMOTEUSER=", rem_host[ENV_SIZE+12] = "REMOTEHOST=", #ifdef AUX_LANG envlang[NL_LANGMAX + 8] = "LANG=", #endif /* AUX_LANG */ mail[ENV_SIZE] = "MAIL=/usr/mail/", homedir[MAXPATHLEN] = "HOME="; static char *myname; #ifdef AUX_LANG /* * get LANG from .lang */ static char * get_lang(char *to, char *home) { int fd, n; char *nlp, uhlang[MAXPATHLEN + sizeof(LANG_FILE)]; (void)strncpy(uhlang, home, MAXPATHLEN); (void)strcat(uhlang, LANG_FILE); if((fd = open(uhlang, O_RDONLY)) < 0) return((char *)0); n = read(fd, uhlang, NL_LANGMAX + 2); if(n > 0) { if(nlp = strchr(uhlang, '\n')) *nlp = 0; uhlang[n] = 0; (void)strcat(to, uhlang); } (void)close(fd); return((n > 0)? to : (char *)0); } #endif /* AUX_LANG */ static int mac_user_cleared (const char *user, mac_t label) { if (mac_enabled) { struct clearance *clp; clp = sgi_getclearancebyname (user); if (clp == (struct clearance *) NULL || mac_clearedlbl (clp, label) != MAC_CLEARED) return (0); } return (1); } /* * Mark all non-std{in,out,err} file descriptors close-on-exec. Redirect * stdin/stdout/stderr to /dev/null. This is done when the * MAC label being requested doesn't match the current one. */ static void nuke_fds (void) { int max_fd, i; max_fd = (int) sysconf (_SC_OPEN_MAX); if (max_fd == -1) return; for (i = 0; i < 3; i++) { (void) close (i); (void) open ("/dev/null", O_RDWR); } for (; i < max_fd; i++) (void) fcntl (i, F_SETFD, FD_CLOEXEC); } /* * Determine if `user' is cleared for capability state `cap'. * This function is dependent upon the internal representation * of cap_t. */ static int cap_user_cleared (const char *user, const cap_t cap) { struct user_cap *clp; cap_t pcap; int result; if (!cap_enabled) return 1; if ((clp = sgi_getcapabilitybyname(user)) == NULL) pcap = cap_init(); else pcap = cap_from_text(clp->ca_allowed); if (pcap == NULL) return 0; result = CAP_ID_ISSET(cap->cap_effective, pcap->cap_effective) && CAP_ID_ISSET(cap->cap_permitted, pcap->cap_permitted) && CAP_ID_ISSET(cap->cap_inheritable, pcap->cap_inheritable); (void) cap_free(pcap); return result; } /* * Decide if the program is running with sufficient privilege to complete * successfully. If this is a "uid-based privilege" environment * (e.g. vanilla IRIX) the effective user id may be that of the SuperUser (0). * If this is a capabilities environment check that each of the necessary * capabilities is available. */ static const cap_value_t privs[] = {CAP_SETUID, CAP_SETGID, CAP_SETPCAP, CAP_DAC_READ_SEARCH, CAP_DAC_WRITE, CAP_MAC_READ, CAP_MAC_WRITE, CAP_MAC_RELABEL_SUBJ, CAP_MAC_MLD, CAP_PRIV_PORT, CAP_AUDIT_WRITE, CAP_AUDIT_CONTROL}; static const size_t privs_size = (sizeof (privs) / sizeof (privs[0])); /* * Procedure: main * * Notes: main procedure of "su" command. */ int main(int argc, char *argv[]) { int eflag = 0, mflag = 0, Sflag = 0, envidx = 0; char *Marg = (char *) NULL, *Carg = (char *) NULL, *DEFCAP = "all="; int c, optoffset = 1, close_fds = 0; FILE *def_fp; gid_t gid; gid_t *ia_sgidp; long gidcnt; mac_t newlabel = (mac_t) NULL; cap_t newcap = (cap_t) NULL, ocap; cap_value_t capv; /* no longer allow env variables significant to rld * to be passed, to avoid potential security problems. * we could do this later only if(!eflag), but since shareII * does an sgidladd, and I'm not sure what the "real" liblim * might spawn off, we'll do it early on */ { char **e = environ, **eb = e; while(*e) { if(strncmp(*e, "_RLD", 4) && strncmp(*e, "LD_LIBRARY", 10)) *eb++ = *e; e++; } *eb = NULL; } #ifdef _SHAREII /* * Remember our original lnode before it is * potentially changed by setuid() or setreuid() * calls used in the various library routines before * the next hook. * * Note that we dynamically load liblim so we don't need to * ship a stub dso to execute these commands. */ if (sgidladd(SH_LIMITS_LIB, RTLD_LAZY)) { static const char *Myname = "su"; SHshareRunning = 1; SH_HOOK_SETMYNAME(Myname); SH_HOOK_SU0(); } #endif /* _SHAREII */ /* * Find out a few things about the system's configuration * * For capabilities (_SC_CAP) it may matter which of the * possible values is returned. */ if ((cap_enabled = sysconf(_SC_CAP)) < 0) cap_enabled = 0; mac_enabled = (sysconf(_SC_MAC) > 0); (void)setlocale(LC_ALL, ""); (void)setcat("uxcore.abi"); (void)setlabel("UX:su"); errno = 0; if (argc > 1 && *argv[1] == '-') { eflag++; /*set eflag if `-' is specified*/ optind++; } nptr = (argc > optind) ? argv[optind++] : "root"; opterr = 0; while ((c = getopt (argc, argv, "mM:C:S")) != EOF) { switch (c) { case 'M': Marg = optarg; break; case 'C': Carg = optarg; break; case 'S': Sflag = (sysconf(_SC_AUDIT) > 0); break; case 'm': mflag = 1; break; case '?': if (optopt == 'M' || optopt == 'C') { fprintf (stderr, "usage: su [-] [name] -M