su.c revision 83209
1/* 2 * Copyright (c) 1988, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 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. 20 * 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 31 * SUCH DAMAGE. 32 */ 33 34#ifndef lint 35static const char copyright[] = 36"@(#) Copyright (c) 1988, 1993, 1994\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38#endif /* not lint */ 39 40#ifndef lint 41#if 0 42static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94"; 43#endif 44static const char rcsid[] = 45 "$FreeBSD: head/usr.bin/su/su.c 83209 2001-09-07 16:20:38Z markm $"; 46#endif /* not lint */ 47 48#include <sys/param.h> 49#include <sys/time.h> 50#include <sys/resource.h> 51#include <sys/wait.h> 52 53#include <err.h> 54#include <errno.h> 55#include <grp.h> 56#include <libutil.h> 57#include <login_cap.h> 58#include <paths.h> 59#include <pwd.h> 60#include <signal.h> 61#include <stdio.h> 62#include <stdlib.h> 63#include <string.h> 64#include <syslog.h> 65#include <unistd.h> 66 67#include <security/pam_appl.h> 68#include <security/pam_misc.h> 69 70#define PAM_END() do { \ 71 int local_ret; \ 72 if (pamh != NULL && creds_set) { \ 73 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \ 74 if (local_ret != PAM_SUCCESS) \ 75 syslog(LOG_ERR, "pam_setcred: %s", \ 76 pam_strerror(pamh, local_ret)); \ 77 local_ret = pam_end(pamh, local_ret); \ 78 if (local_ret != PAM_SUCCESS) \ 79 syslog(LOG_ERR, "pam_end: %s", \ 80 pam_strerror(pamh, local_ret)); \ 81 } \ 82} while (0) 83 84 85#define PAM_SET_ITEM(what, item) do { \ 86 int local_ret; \ 87 local_ret = pam_set_item(pamh, what, item); \ 88 if (local_ret != PAM_SUCCESS) { \ 89 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \ 90 pam_strerror(pamh, local_ret)); \ 91 errx(1, "pam_set_item(" #what "): %s", \ 92 pam_strerror(pamh, local_ret)); \ 93 } \ 94} while (0) 95 96enum tristate { UNSET, YES, NO }; 97 98static pam_handle_t *pamh = NULL; 99static int creds_set = 0; 100static char **environ_pam; 101 102static char *ontty(void); 103static int chshell(char *); 104static void usage(void); 105static int export_pam_environment(void); 106static int ok_to_export(const char *); 107 108extern char **environ; 109 110int 111main(int argc, char *argv[]) 112{ 113 struct passwd *pwd; 114 struct pam_conv conv = {misc_conv, NULL}; 115 enum tristate iscsh; 116 login_cap_t *lc; 117 uid_t ruid; 118 gid_t gid; 119 int asme, ch, asthem, fastlogin, prio, i, setwhat, retcode, 120 statusp, child_pid, child_pgrp, ret_pid; 121 char *username, *cleanenv, *class, shellbuf[MAXPATHLEN], 122 myhost[MAXHOSTNAMELEN + 1]; 123 const char *p, *user, *shell, *mytty, **nargv, **np; 124 125 shell = class = cleanenv = NULL; 126 asme = asthem = fastlogin = statusp = 0; 127 user = "root"; 128 iscsh = UNSET; 129 130 while ((ch = getopt(argc, argv, "-flmc:")) != -1) 131 switch ((char)ch) { 132 case 'f': 133 fastlogin = 1; 134 break; 135 case '-': 136 case 'l': 137 asme = 0; 138 asthem = 1; 139 break; 140 case 'm': 141 asme = 1; 142 asthem = 0; 143 break; 144 case 'c': 145 class = optarg; 146 break; 147 case '?': 148 default: 149 usage(); 150 } 151 152 if (optind < argc) 153 user = argv[optind++]; 154 155 if (user == NULL) 156 usage(); 157 158 if (strlen(user) > MAXLOGNAME - 1) 159 errx(1, "username too long"); 160 161 nargv = malloc(sizeof(char *) * (argc + 4)); 162 if (nargv == NULL) 163 errx(1, "malloc failure"); 164 165 nargv[argc + 3] = NULL; 166 for (i = argc; i >= optind; i--) 167 nargv[i + 3] = argv[i]; 168 np = &nargv[i + 3]; 169 170 argv += optind; 171 172 errno = 0; 173 prio = getpriority(PRIO_PROCESS, 0); 174 if (errno) 175 prio = 0; 176 177 setpriority(PRIO_PROCESS, 0, -2); 178 openlog("su", LOG_CONS, LOG_AUTH); 179 180 /* get current login name, real uid and shell */ 181 ruid = getuid(); 182 username = getlogin(); 183 pwd = getpwnam(username); 184 if (username == NULL || pwd == NULL || pwd->pw_uid != ruid) 185 pwd = getpwuid(ruid); 186 if (pwd == NULL) 187 errx(1, "who are you?"); 188 gid = pwd->pw_gid; 189 190 username = strdup(pwd->pw_name); 191 if (username == NULL) 192 err(1, "strdup failure"); 193 194 if (asme) { 195 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { 196 /* must copy - pwd memory is recycled */ 197 shell = strncpy(shellbuf, pwd->pw_shell, 198 sizeof(shellbuf)); 199 shellbuf[sizeof(shellbuf) - 1] = '\0'; 200 } 201 else { 202 shell = _PATH_BSHELL; 203 iscsh = NO; 204 } 205 } 206 207 /* Do the whole PAM startup thing */ 208 retcode = pam_start("su", user, &conv, &pamh); 209 if (retcode != PAM_SUCCESS) { 210 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode)); 211 errx(1, "pam_start: %s", pam_strerror(pamh, retcode)); 212 } 213 214 PAM_SET_ITEM(PAM_RUSER, getlogin()); 215 216 gethostname(myhost, sizeof(myhost)); 217 PAM_SET_ITEM(PAM_RHOST, myhost); 218 219 mytty = ttyname(STDERR_FILENO); 220 if (!mytty) 221 mytty = "tty"; 222 PAM_SET_ITEM(PAM_TTY, mytty); 223 224 retcode = pam_authenticate(pamh, 0); 225 if (retcode != PAM_SUCCESS) { 226 syslog(LOG_ERR, "pam_authenticate: %s", 227 pam_strerror(pamh, retcode)); 228 errx(1, "Sorry"); 229 } 230 retcode = pam_get_item(pamh, PAM_USER, (const void **)&p); 231 if (retcode == PAM_SUCCESS) 232 user = p; 233 else 234 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s", 235 pam_strerror(pamh, retcode)); 236 237 retcode = pam_acct_mgmt(pamh, 0); 238 if (retcode == PAM_NEW_AUTHTOK_REQD) { 239 retcode = pam_chauthtok(pamh, 240 PAM_CHANGE_EXPIRED_AUTHTOK); 241 if (retcode != PAM_SUCCESS) { 242 syslog(LOG_ERR, "pam_chauthtok: %s", 243 pam_strerror(pamh, retcode)); 244 errx(1, "Sorry"); 245 } 246 } 247 if (retcode != PAM_SUCCESS) { 248 syslog(LOG_ERR, "pam_acct_mgmt: %s", 249 pam_strerror(pamh, retcode)); 250 errx(1, "Sorry"); 251 } 252 253 /* get target login information, default to root */ 254 pwd = getpwnam(user); 255 if (pwd == NULL) 256 errx(1, "unknown login: %s", user); 257 if (class == NULL) 258 lc = login_getpwclass(pwd); 259 else { 260 if (ruid != 0) 261 errx(1, "only root may use -c"); 262 lc = login_getclass(class); 263 if (lc == NULL) 264 errx(1, "unknown class: %s", class); 265 } 266 267 /* if asme and non-standard target shell, must be root */ 268 if (asme) { 269 if (ruid != 0 && !chshell(pwd->pw_shell)) 270 errx(1, "permission denied (shell)."); 271 } 272 else if (pwd->pw_shell && *pwd->pw_shell) { 273 shell = pwd->pw_shell; 274 iscsh = UNSET; 275 } 276 else { 277 shell = _PATH_BSHELL; 278 iscsh = NO; 279 } 280 281 /* if we're forking a csh, we want to slightly muck the args */ 282 if (iscsh == UNSET) { 283 p = strrchr(shell, '/'); 284 if (p) 285 ++p; 286 else 287 p = shell; 288 iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES; 289 } 290 setpriority(PRIO_PROCESS, 0, prio); 291 292 /* 293 * PAM modules might add supplementary groups in pam_setcred(), so 294 * initialize them first. 295 */ 296 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0) 297 err(1, "setusercontext"); 298 299 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); 300 if (retcode != PAM_SUCCESS) 301 syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s", 302 pam_strerror(pamh, retcode)); 303 else 304 creds_set = 1; 305 306 /* 307 * We must fork() before setuid() because we need to call 308 * pam_setcred(pamh, PAM_DELETE_CRED) as root. 309 */ 310 311 statusp = 1; 312 child_pid = fork(); 313 switch (child_pid) { 314 default: 315 while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) { 316 if (WIFSTOPPED(statusp)) { 317 child_pgrp = tcgetpgrp(1); 318 kill(getpid(), SIGSTOP); 319 tcsetpgrp(1, child_pgrp); 320 kill(child_pid, SIGCONT); 321 statusp = 1; 322 continue; 323 } 324 break; 325 } 326 if (ret_pid == -1) 327 err(1, "waitpid"); 328 PAM_END(); 329 exit(statusp); 330 case -1: 331 err(1, "fork"); 332 PAM_END(); 333 exit(1); 334 case 0: 335 /* 336 * Set all user context except for: Environmental variables 337 * Umask Login records (wtmp, etc) Path 338 */ 339 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK | 340 LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP); 341 /* 342 * Don't touch resource/priority settings if -m has been used 343 * or -l and -c hasn't, and we're not su'ing to root. 344 */ 345 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) 346 setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES); 347 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0) 348 err(1, "setusercontext"); 349 350 if (!asme) { 351 if (asthem) { 352 p = getenv("TERM"); 353 environ = &cleanenv; 354 355 /* 356 * Add any environmental variables that the 357 * PAM modules may have set. 358 */ 359 environ_pam = pam_getenvlist(pamh); 360 if (environ_pam) 361 export_pam_environment(); 362 363 /* set the su'd user's environment & umask */ 364 setusercontext(lc, pwd, pwd->pw_uid, 365 LOGIN_SETPATH | LOGIN_SETUMASK | 366 LOGIN_SETENV); 367 if (p) 368 setenv("TERM", p, 1); 369 if (chdir(pwd->pw_dir) < 0) 370 errx(1, "no directory"); 371 } 372 if (asthem || pwd->pw_uid) 373 setenv("USER", pwd->pw_name, 1); 374 setenv("HOME", pwd->pw_dir, 1); 375 setenv("SHELL", shell, 1); 376 } 377 login_close(lc); 378 379 if (iscsh == YES) { 380 if (fastlogin) 381 *np-- = "-f"; 382 if (asme) 383 *np-- = "-m"; 384 } 385 /* csh strips the first character... */ 386 *np = asthem ? "-su" : iscsh == YES ? "_su" : "su"; 387 388 if (ruid != 0) 389 syslog(LOG_NOTICE, "%s to %s%s", username, user, 390 ontty()); 391 392 execv(shell, np); 393 err(1, "%s", shell); 394 } 395} 396 397static int 398export_pam_environment(void) 399{ 400 char **pp; 401 402 for (pp = environ_pam; *pp != NULL; pp++) { 403 if (ok_to_export(*pp)) 404 putenv(*pp); 405 free(*pp); 406 } 407 return PAM_SUCCESS; 408} 409 410/* 411 * Sanity checks on PAM environmental variables: 412 * - Make sure there is an '=' in the string. 413 * - Make sure the string doesn't run on too long. 414 * - Do not export certain variables. This list was taken from the 415 * Solaris pam_putenv(3) man page. 416 */ 417static int 418ok_to_export(const char *s) 419{ 420 static const char *noexport[] = { 421 "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH", 422 "IFS", "PATH", NULL 423 }; 424 const char **pp; 425 size_t n; 426 427 if (strlen(s) > 1024 || strchr(s, '=') == NULL) 428 return 0; 429 if (strncmp(s, "LD_", 3) == 0) 430 return 0; 431 for (pp = noexport; *pp != NULL; pp++) { 432 n = strlen(*pp); 433 if (s[n] == '=' && strncmp(s, *pp, n) == 0) 434 return 0; 435 } 436 return 1; 437} 438 439static void 440usage(void) 441{ 442 443 fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n"); 444 exit(1); 445} 446 447static int 448chshell(char *sh) 449{ 450 int r; 451 char *cp; 452 453 r = 0; 454 setusershell(); 455 do { 456 cp = getusershell(); 457 r = strcmp(cp, sh); 458 } while (!r && cp != NULL); 459 endusershell(); 460 return r; 461} 462 463static char * 464ontty(void) 465{ 466 char *p; 467 static char buf[MAXPATHLEN + 4]; 468 469 buf[0] = 0; 470 p = ttyname(STDERR_FILENO); 471 if (p) 472 snprintf(buf, sizeof(buf), " on %s", p); 473 return buf; 474} 475