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