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/openpam.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 = { openpam_ttyconv, 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 const char *p, *user, *shell, *mytty, **nargv; 127 128 shell = class = cleanenv = NULL; 129 asme = asthem = fastlogin = statusp = 0; 130 user = "root"; 131 iscsh = UNSET; 132 133 while ((ch = getopt(argc, argv, "-flmc:")) != -1) 134 switch ((char)ch) { 135 case 'f': 136 fastlogin = 1; 137 break; 138 case '-': 139 case 'l': 140 asme = 0; 141 asthem = 1; 142 break; 143 case 'm': 144 asme = 1; 145 asthem = 0; 146 break; 147 case 'c': 148 class = optarg; 149 break; 150 case '?': 151 default: 152 usage(); 153 } 154 155 if (optind < argc) 156 user = argv[optind++]; 157 158 if (user == NULL) 159 usage(); 160 161 if (strlen(user) > MAXLOGNAME - 1) 162 errx(1, "username too long"); 163 164 nargv = malloc(sizeof(char *) * (argc + 4)); 165 if (nargv == NULL) 166 errx(1, "malloc failure"); 167 168 nargv[argc + 3] = NULL; 169 for (i = argc; i >= optind; i--) 170 nargv[i + 3] = argv[i]; 171 np.a = &nargv[i + 3]; 172 173 argv += optind; 174 175 errno = 0; 176 prio = getpriority(PRIO_PROCESS, 0); 177 if (errno) 178 prio = 0; 179 180 setpriority(PRIO_PROCESS, 0, -2); 181 openlog("su", LOG_CONS, LOG_AUTH); 182 183 /* get current login name, real uid and shell */ 184 ruid = getuid(); 185 username = getlogin(); 186 pwd = getpwnam(username); 187 if (username == NULL || pwd == NULL || pwd->pw_uid != ruid) 188 pwd = getpwuid(ruid); 189 if (pwd == NULL) 190 errx(1, "who are you?"); 191 gid = pwd->pw_gid; 192 193 username = strdup(pwd->pw_name); 194 if (username == NULL) 195 err(1, "strdup failure"); 196 197 if (asme) { 198 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { 199 /* must copy - pwd memory is recycled */ 200 shell = strncpy(shellbuf, pwd->pw_shell, 201 sizeof(shellbuf)); 202 shellbuf[sizeof(shellbuf) - 1] = '\0'; 203 } 204 else { 205 shell = _PATH_BSHELL; 206 iscsh = NO; 207 } 208 } 209 210 /* Do the whole PAM startup thing */ 211 retcode = pam_start("su", user, &conv, &pamh); 212 if (retcode != PAM_SUCCESS) { 213 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode)); 214 errx(1, "pam_start: %s", pam_strerror(pamh, retcode)); 215 } 216 217 PAM_SET_ITEM(PAM_RUSER, getlogin()); 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.a-- = "-f"; 382 if (asme) 383 *np.a-- = "-m"; 384 } 385 /* csh strips the first character... */ 386 *np.a = 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.b); 393 err(1, "%s", shell); 394 } 395 } 396 397 static int 398 export_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 */ 417 static int 418 ok_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 439 static void 440 usage(void) 441 { 442 443 fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n"); 444 exit(1); 445 } 446 447 static int 448 chshell(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 463 static char * 464 ontty(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