su.c revision 91745
1167465Smp/* 259243Sobrien * Copyright (c) 1988, 1993, 1994 359243Sobrien * The Regents of the University of California. All rights reserved. 459243Sobrien * 559243Sobrien * Redistribution and use in source and binary forms, with or without 659243Sobrien * modification, are permitted provided that the following conditions 759243Sobrien * are met: 859243Sobrien * 1. Redistributions of source code must retain the above copyright 959243Sobrien * notice, this list of conditions and the following disclaimer. 1059243Sobrien * 2. Redistributions in binary form must reproduce the above copyright 1159243Sobrien * notice, this list of conditions and the following disclaimer in the 1259243Sobrien * documentation and/or other materials provided with the distribution. 1359243Sobrien * 3. All advertising materials mentioning features or use of this software 1459243Sobrien * must display the following acknowledgement: 1559243Sobrien * This product includes software developed by the University of 1659243Sobrien * California, Berkeley and its contributors. 17100616Smp * 4. Neither the name of the University nor the names of its contributors 1859243Sobrien * may be used to endorse or promote products derived from this software 1959243Sobrien * without specific prior written permission. 2059243Sobrien * 2159243Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 2259243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2359243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2459243Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 2559243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2659243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2759243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2859243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2959243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3059243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3159243Sobrien * SUCH DAMAGE. 3259243Sobrien */ 3359243Sobrien 3459243Sobrien#ifndef lint 3559243Sobrienstatic const char copyright[] = 3659243Sobrien"@(#) Copyright (c) 1988, 1993, 1994\n\ 3759243Sobrien The Regents of the University of California. All rights reserved.\n"; 3859243Sobrien#endif /* not lint */ 3959243Sobrien 4059243Sobrien#ifndef lint 4159243Sobrien#if 0 42167465Smpstatic char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94"; 4359243Sobrien#endif 4459243Sobrienstatic const char rcsid[] = 4559243Sobrien "$FreeBSD: head/usr.bin/su/su.c 91745 2002-03-06 12:46:56Z des $"; 4659243Sobrien#endif /* not lint */ 4759243Sobrien 48145479Smp#include <sys/param.h> 49145479Smp#include <sys/time.h> 5059243Sobrien#include <sys/resource.h> 5159243Sobrien#include <sys/wait.h> 5259243Sobrien 5359243Sobrien#include <err.h> 5459243Sobrien#include <errno.h> 5559243Sobrien#include <grp.h> 5659243Sobrien#include <libutil.h> 5759243Sobrien#include <login_cap.h> 5859243Sobrien#include <paths.h> 5959243Sobrien#include <pwd.h> 6059243Sobrien#include <signal.h> 6159243Sobrien#include <stdio.h> 6259243Sobrien#include <stdlib.h> 6359243Sobrien#include <string.h> 6459243Sobrien#include <syslog.h> 6559243Sobrien#include <unistd.h> 6659243Sobrien 6759243Sobrien#include <security/pam_appl.h> 6859243Sobrien#include <security/openpam.h> 6959243Sobrien 7059243Sobrien#define PAM_END() do { \ 7159243Sobrien int local_ret; \ 7259243Sobrien if (pamh != NULL && creds_set) { \ 7359243Sobrien local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \ 7459243Sobrien if (local_ret != PAM_SUCCESS) \ 7559243Sobrien syslog(LOG_ERR, "pam_setcred: %s", \ 7659243Sobrien pam_strerror(pamh, local_ret)); \ 7759243Sobrien local_ret = pam_end(pamh, local_ret); \ 7859243Sobrien if (local_ret != PAM_SUCCESS) \ 7959243Sobrien syslog(LOG_ERR, "pam_end: %s", \ 8059243Sobrien pam_strerror(pamh, local_ret)); \ 8159243Sobrien } \ 8259243Sobrien} while (0) 8359243Sobrien 8459243Sobrien 85167465Smp#define PAM_SET_ITEM(what, item) do { \ 86167465Smp int local_ret; \ 8759243Sobrien local_ret = pam_set_item(pamh, what, item); \ 8859243Sobrien if (local_ret != PAM_SUCCESS) { \ 8959243Sobrien syslog(LOG_ERR, "pam_set_item(" #what "): %s", \ 9059243Sobrien pam_strerror(pamh, local_ret)); \ 9159243Sobrien errx(1, "pam_set_item(" #what "): %s", \ 9259243Sobrien pam_strerror(pamh, local_ret)); \ 93145479Smp } \ 9459243Sobrien} while (0) 9559243Sobrien 9659243Sobrienenum tristate { UNSET, YES, NO }; 9759243Sobrien 9859243Sobrienstatic pam_handle_t *pamh = NULL; 9959243Sobrienstatic int creds_set = 0; 10059243Sobrienstatic char **environ_pam; 10159243Sobrien 10259243Sobrienstatic char *ontty(void); 103167465Smpstatic int chshell(char *); 104145479Smpstatic void usage(void); 105145479Smpstatic int export_pam_environment(void); 106145479Smpstatic int ok_to_export(const char *); 107145479Smp 10859243Sobrienextern char **environ; 10959243Sobrien 110145479Smpint 11159243Sobrienmain(int argc, char *argv[]) 11269408Sache{ 11359243Sobrien struct passwd *pwd; 11459243Sobrien struct pam_conv conv = { openpam_ttyconv, NULL }; 11559243Sobrien enum tristate iscsh; 11659243Sobrien login_cap_t *lc; 11759243Sobrien union { 11859243Sobrien const char **a; 11959243Sobrien char * const *b; 12059243Sobrien } np; 12159243Sobrien uid_t ruid; 12259243Sobrien gid_t gid; 123100616Smp int asme, ch, asthem, fastlogin, prio, i, setwhat, retcode, 12459243Sobrien statusp, child_pid, child_pgrp, ret_pid; 125100616Smp char *username, *cleanenv, *class, shellbuf[MAXPATHLEN]; 126100616Smp const char *p, *user, *shell, *mytty, **nargv; 12759243Sobrien 12859243Sobrien shell = class = cleanenv = NULL; 12959243Sobrien asme = asthem = fastlogin = statusp = 0; 13059243Sobrien user = "root"; 13159243Sobrien iscsh = UNSET; 13259243Sobrien 13359243Sobrien while ((ch = getopt(argc, argv, "-flmc:")) != -1) 13459243Sobrien switch ((char)ch) { 13559243Sobrien case 'f': 136145479Smp fastlogin = 1; 13759243Sobrien break; 138167465Smp case '-': 13959243Sobrien case 'l': 140145479Smp asme = 0; 14159243Sobrien asthem = 1; 14259243Sobrien break; 14359243Sobrien case 'm': 144167465Smp asme = 1; 14583098Smp asthem = 0; 146167465Smp break; 14783098Smp case 'c': 148167465Smp class = optarg; 14983098Smp break; 150167465Smp case '?': 151167465Smp default: 15259243Sobrien usage(); 153167465Smp } 15459243Sobrien 155167465Smp if (optind < argc) 156167465Smp user = argv[optind++]; 157167465Smp 158167465Smp if (user == NULL) 15959243Sobrien usage(); 160167465Smp 16159243Sobrien if (strlen(user) > MAXLOGNAME - 1) 16259243Sobrien errx(1, "username too long"); 163167465Smp 16459243Sobrien nargv = malloc(sizeof(char *) * (argc + 4)); 165167465Smp if (nargv == NULL) 166167465Smp errx(1, "malloc failure"); 167167465Smp 168167465Smp nargv[argc + 3] = NULL; 169167465Smp for (i = argc; i >= optind; i--) 170167465Smp nargv[i + 3] = argv[i]; 171145479Smp np.a = &nargv[i + 3]; 17259243Sobrien 173145479Smp argv += optind; 17459243Sobrien 175145479Smp errno = 0; 176167465Smp prio = getpriority(PRIO_PROCESS, 0); 177145479Smp if (errno) 178131962Smp prio = 0; 179167465Smp 18059243Sobrien setpriority(PRIO_PROCESS, 0, -2); 18169408Sache openlog("su", LOG_CONS, LOG_AUTH); 18259243Sobrien 18369408Sache /* get current login name, real uid and shell */ 18459243Sobrien ruid = getuid(); 18559243Sobrien username = getlogin(); 18659243Sobrien pwd = getpwnam(username); 18759243Sobrien if (username == NULL || pwd == NULL || pwd->pw_uid != ruid) 18859243Sobrien pwd = getpwuid(ruid); 18959243Sobrien if (pwd == NULL) 19059243Sobrien errx(1, "who are you?"); 19159243Sobrien gid = pwd->pw_gid; 19259243Sobrien 19359243Sobrien username = strdup(pwd->pw_name); 19459243Sobrien if (username == NULL) 19559243Sobrien err(1, "strdup failure"); 19659243Sobrien 197167465Smp if (asme) { 198145479Smp if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { 199145479Smp /* must copy - pwd memory is recycled */ 20059243Sobrien shell = strncpy(shellbuf, pwd->pw_shell, 20159243Sobrien sizeof(shellbuf)); 20259243Sobrien shellbuf[sizeof(shellbuf) - 1] = '\0'; 20359243Sobrien } 20459243Sobrien else { 20559243Sobrien shell = _PATH_BSHELL; 20659243Sobrien iscsh = NO; 20759243Sobrien } 20859243Sobrien } 20959243Sobrien 21059243Sobrien /* Do the whole PAM startup thing */ 21159243Sobrien retcode = pam_start("su", user, &conv, &pamh); 21259243Sobrien if (retcode != PAM_SUCCESS) { 21359243Sobrien syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode)); 21459243Sobrien errx(1, "pam_start: %s", pam_strerror(pamh, retcode)); 21559243Sobrien } 21659243Sobrien 21759243Sobrien PAM_SET_ITEM(PAM_RUSER, getlogin()); 21859243Sobrien 21959243Sobrien mytty = ttyname(STDERR_FILENO); 22059243Sobrien if (!mytty) 22159243Sobrien mytty = "tty"; 22259243Sobrien PAM_SET_ITEM(PAM_TTY, mytty); 22359243Sobrien 22459243Sobrien retcode = pam_authenticate(pamh, 0); 225167465Smp if (retcode != PAM_SUCCESS) { 226167465Smp syslog(LOG_ERR, "pam_authenticate: %s", 22759243Sobrien pam_strerror(pamh, retcode)); 22859243Sobrien errx(1, "Sorry"); 229167465Smp } 23059243Sobrien retcode = pam_get_item(pamh, PAM_USER, (const void **)&p); 23159243Sobrien if (retcode == PAM_SUCCESS) 232131962Smp user = p; 233131962Smp else 234131962Smp syslog(LOG_ERR, "pam_get_item(PAM_USER): %s", 235131962Smp pam_strerror(pamh, retcode)); 23659243Sobrien 23759243Sobrien retcode = pam_acct_mgmt(pamh, 0); 23859243Sobrien if (retcode == PAM_NEW_AUTHTOK_REQD) { 23959243Sobrien retcode = pam_chauthtok(pamh, 24059243Sobrien PAM_CHANGE_EXPIRED_AUTHTOK); 24159243Sobrien if (retcode != PAM_SUCCESS) { 24259243Sobrien syslog(LOG_ERR, "pam_chauthtok: %s", 24369408Sache pam_strerror(pamh, retcode)); 24459243Sobrien errx(1, "Sorry"); 24559243Sobrien } 24659243Sobrien } 24759243Sobrien if (retcode != PAM_SUCCESS) { 24859243Sobrien syslog(LOG_ERR, "pam_acct_mgmt: %s", 24969408Sache pam_strerror(pamh, retcode)); 25059243Sobrien errx(1, "Sorry"); 25159243Sobrien } 25259243Sobrien 25359243Sobrien /* get target login information, default to root */ 25459243Sobrien pwd = getpwnam(user); 25559243Sobrien if (pwd == NULL) 25659243Sobrien errx(1, "unknown login: %s", user); 25759243Sobrien if (class == NULL) 25859243Sobrien lc = login_getpwclass(pwd); 25959243Sobrien else { 26059243Sobrien if (ruid != 0) 26159243Sobrien errx(1, "only root may use -c"); 26259243Sobrien lc = login_getclass(class); 26359243Sobrien if (lc == NULL) 26459243Sobrien errx(1, "unknown class: %s", class); 26559243Sobrien } 26659243Sobrien 26759243Sobrien /* if asme and non-standard target shell, must be root */ 26859243Sobrien if (asme) { 26959243Sobrien if (ruid != 0 && !chshell(pwd->pw_shell)) 27059243Sobrien errx(1, "permission denied (shell)."); 27159243Sobrien } 27259243Sobrien else if (pwd->pw_shell && *pwd->pw_shell) { 27359243Sobrien shell = pwd->pw_shell; 27459243Sobrien iscsh = UNSET; 27559243Sobrien } 27659243Sobrien else { 27759243Sobrien shell = _PATH_BSHELL; 27859243Sobrien iscsh = NO; 27959243Sobrien } 28059243Sobrien 281167465Smp /* if we're forking a csh, we want to slightly muck the args */ 28259243Sobrien if (iscsh == UNSET) { 28359243Sobrien p = strrchr(shell, '/'); 28459243Sobrien if (p) 28559243Sobrien ++p; 28659243Sobrien else 28759243Sobrien p = shell; 28859243Sobrien iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES; 28959243Sobrien } 29059243Sobrien setpriority(PRIO_PROCESS, 0, prio); 29159243Sobrien 29259243Sobrien /* 29359243Sobrien * PAM modules might add supplementary groups in pam_setcred(), so 29459243Sobrien * initialize them first. 29559243Sobrien */ 29659243Sobrien if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0) 29759243Sobrien err(1, "setusercontext"); 29859243Sobrien 29959243Sobrien retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); 30059243Sobrien if (retcode != PAM_SUCCESS) 30159243Sobrien syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s", 30259243Sobrien pam_strerror(pamh, retcode)); 30359243Sobrien else 30459243Sobrien creds_set = 1; 30559243Sobrien 30659243Sobrien /* 30759243Sobrien * We must fork() before setuid() because we need to call 308167465Smp * pam_setcred(pamh, PAM_DELETE_CRED) as root. 309167465Smp */ 31059243Sobrien 31159243Sobrien statusp = 1; 31259243Sobrien child_pid = fork(); 31359243Sobrien switch (child_pid) { 31459243Sobrien default: 31559243Sobrien while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) { 316167465Smp if (WIFSTOPPED(statusp)) { 317167465Smp child_pgrp = tcgetpgrp(1); 31859243Sobrien kill(getpid(), SIGSTOP); 31959243Sobrien tcsetpgrp(1, child_pgrp); 32059243Sobrien kill(child_pid, SIGCONT); 32159243Sobrien statusp = 1; 322167465Smp continue; 32359243Sobrien } 32459243Sobrien break; 32559243Sobrien } 32659243Sobrien if (ret_pid == -1) 32759243Sobrien err(1, "waitpid"); 32859243Sobrien PAM_END(); 32959243Sobrien exit(statusp); 33059243Sobrien case -1: 33159243Sobrien err(1, "fork"); 33259243Sobrien PAM_END(); 33359243Sobrien exit(1); 33459243Sobrien case 0: 33559243Sobrien /* 33659243Sobrien * Set all user context except for: Environmental variables 33759243Sobrien * Umask Login records (wtmp, etc) Path 33859243Sobrien */ 33959243Sobrien setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK | 34059243Sobrien LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP); 34159243Sobrien /* 34259243Sobrien * Don't touch resource/priority settings if -m has been used 34359243Sobrien * or -l and -c hasn't, and we're not su'ing to root. 34459243Sobrien */ 345167465Smp if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) 34659243Sobrien setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES); 347145479Smp if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0) 34859243Sobrien err(1, "setusercontext"); 34959243Sobrien 35059243Sobrien if (!asme) { 35159243Sobrien if (asthem) { 35259243Sobrien p = getenv("TERM"); 35359243Sobrien environ = &cleanenv; 35459243Sobrien 35559243Sobrien /* 35659243Sobrien * Add any environmental variables that the 35759243Sobrien * PAM modules may have set. 35859243Sobrien */ 35959243Sobrien environ_pam = pam_getenvlist(pamh); 36059243Sobrien if (environ_pam) 36159243Sobrien export_pam_environment(); 36259243Sobrien 36359243Sobrien /* set the su'd user's environment & umask */ 36459243Sobrien setusercontext(lc, pwd, pwd->pw_uid, 36559243Sobrien LOGIN_SETPATH | LOGIN_SETUMASK | 36659243Sobrien LOGIN_SETENV); 36759243Sobrien if (p) 36859243Sobrien setenv("TERM", p, 1); 36959243Sobrien if (chdir(pwd->pw_dir) < 0) 37059243Sobrien errx(1, "no directory"); 37159243Sobrien } 37259243Sobrien if (asthem || pwd->pw_uid) 37359243Sobrien setenv("USER", pwd->pw_name, 1); 37459243Sobrien setenv("HOME", pwd->pw_dir, 1); 37559243Sobrien setenv("SHELL", shell, 1); 37659243Sobrien } 37759243Sobrien login_close(lc); 37859243Sobrien 37959243Sobrien if (iscsh == YES) { 38059243Sobrien if (fastlogin) 38159243Sobrien *np.a-- = "-f"; 38259243Sobrien if (asme) 383167465Smp *np.a-- = "-m"; 384167465Smp } 385167465Smp /* csh strips the first character... */ 38659243Sobrien *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su"; 38759243Sobrien 38859243Sobrien if (ruid != 0) 38959243Sobrien syslog(LOG_NOTICE, "%s to %s%s", username, user, 39059243Sobrien ontty()); 39159243Sobrien 39259243Sobrien execv(shell, np.b); 39359243Sobrien err(1, "%s", shell); 39459243Sobrien } 39559243Sobrien} 39659243Sobrien 39759243Sobrienstatic int 39859243Sobrienexport_pam_environment(void) 39959243Sobrien{ 40059243Sobrien char **pp; 40159243Sobrien 40259243Sobrien for (pp = environ_pam; *pp != NULL; pp++) { 40359243Sobrien if (ok_to_export(*pp)) 40459243Sobrien putenv(*pp); 40559243Sobrien free(*pp); 40659243Sobrien } 40759243Sobrien return PAM_SUCCESS; 40859243Sobrien} 409167465Smp 41059243Sobrien/* 411167465Smp * Sanity checks on PAM environmental variables: 41259243Sobrien * - Make sure there is an '=' in the string. 41359243Sobrien * - Make sure the string doesn't run on too long. 414167465Smp * - Do not export certain variables. This list was taken from the 41559243Sobrien * Solaris pam_putenv(3) man page. 41659243Sobrien */ 41759243Sobrienstatic int 41859243Sobrienok_to_export(const char *s) 41959243Sobrien{ 42059243Sobrien static const char *noexport[] = { 42159243Sobrien "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH", 42259243Sobrien "IFS", "PATH", NULL 42359243Sobrien }; 42459243Sobrien const char **pp; 42559243Sobrien size_t n; 42659243Sobrien 42759243Sobrien if (strlen(s) > 1024 || strchr(s, '=') == NULL) 42859243Sobrien return 0; 42959243Sobrien if (strncmp(s, "LD_", 3) == 0) 43059243Sobrien return 0; 43159243Sobrien for (pp = noexport; *pp != NULL; pp++) { 43259243Sobrien n = strlen(*pp); 43359243Sobrien if (s[n] == '=' && strncmp(s, *pp, n) == 0) 43459243Sobrien return 0; 43559243Sobrien } 43659243Sobrien return 1; 43759243Sobrien} 43859243Sobrien 43959243Sobrienstatic void 44059243Sobrienusage(void) 44159243Sobrien{ 44259243Sobrien 44359243Sobrien fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n"); 44459243Sobrien exit(1); 44559243Sobrien} 44659243Sobrien 44759243Sobrienstatic int 448167465Smpchshell(char *sh) 44959243Sobrien{ 450167465Smp int r; 45159243Sobrien char *cp; 45259243Sobrien 45359243Sobrien r = 0; 454167465Smp setusershell(); 45559243Sobrien do { 45659243Sobrien cp = getusershell(); 45759243Sobrien r = strcmp(cp, sh); 45859243Sobrien } while (!r && cp != NULL); 45959243Sobrien endusershell(); 460172665Smp return r; 46159243Sobrien} 46259243Sobrien 46359243Sobrienstatic char * 46459243Sobrienontty(void) 465184072Sru{ 46659243Sobrien char *p; 46759243Sobrien static char buf[MAXPATHLEN + 4]; 468167465Smp 46959243Sobrien buf[0] = 0; 47059243Sobrien p = ttyname(STDERR_FILENO); 47159243Sobrien if (p) 47259243Sobrien snprintf(buf, sizeof(buf), " on %s", p); 47359243Sobrien return buf; 47459243Sobrien} 47559243Sobrien