1 /* $NetBSD: su_pam.c,v 1.11 2005/12/15 14:01:31 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1988 The Regents of the University of California. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 __COPYRIGHT( 35 "@(#) Copyright (c) 1988 The Regents of the University of California.\n\ 36 All rights reserved.\n"); 37 #endif /* not lint */ 38 39 #ifndef lint 40 #if 0 41 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";*/ 42 #else 43 __RCSID("$NetBSD: su_pam.c,v 1.11 2005/12/15 14:01:31 christos Exp $"); 44 #endif 45 #endif /* not lint */ 46 47 #include <sys/param.h> 48 #include <sys/time.h> 49 #include <sys/resource.h> 50 #include <sys/wait.h> 51 #include <err.h> 52 #include <errno.h> 53 #include <grp.h> 54 #include <paths.h> 55 #include <pwd.h> 56 #include <signal.h> 57 #include <stdio.h> 58 #include <stdlib.h> 59 #include <string.h> 60 #include <syslog.h> 61 #include <time.h> 62 #include <tzfile.h> 63 #include <unistd.h> 64 #include <login_cap.h> 65 66 #include <security/pam_appl.h> 67 #include <security/openpam.h> /* for openpam_ttyconv() */ 68 69 static const struct pam_conv pamc = { &openpam_ttyconv, NULL }; 70 71 static void logit(const char *, ...); 72 static int chshell(const char *); 73 static char *ontty(void); 74 75 int main(int, char **); 76 77 #define ARGSTRX "-dflm" 78 79 #ifdef LOGIN_CAP 80 #define ARGSTR ARGSTRX "c:" 81 #else 82 #define ARGSTR ARGSTRX 83 #endif 84 85 int 86 main(int argc, char **argv) 87 { 88 extern char **environ; 89 struct passwd *pwd, pwres; 90 char *p; 91 uid_t ruid; 92 int asme, ch, asthem, fastlogin, prio, gohome, setwhat; 93 enum { UNSET, YES, NO } iscsh = UNSET; 94 char *user, *shell, *avshell, *username, **np; 95 char *class; 96 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN]; 97 int pam_err; 98 char hostname[MAXHOSTNAMELEN]; 99 char *tty; 100 const char *func; 101 const void *newuser; 102 login_cap_t *lc; 103 pam_handle_t *pamh = NULL; 104 char pwbuf[1024]; 105 #ifdef PAM_DEBUG 106 extern int _openpam_debug; 107 108 _openpam_debug = 1; 109 #endif 110 111 asme = asthem = fastlogin = 0; 112 gohome = 1; 113 shell = class = NULL; 114 while ((ch = getopt(argc, argv, ARGSTR)) != -1) 115 switch((char)ch) { 116 case 'c': 117 class = optarg; 118 break; 119 case 'd': 120 asme = 0; 121 asthem = 1; 122 gohome = 0; 123 break; 124 case 'f': 125 fastlogin = 1; 126 break; 127 case '-': 128 case 'l': 129 asme = 0; 130 asthem = 1; 131 break; 132 case 'm': 133 asme = 1; 134 asthem = 0; 135 break; 136 case '?': 137 default: 138 (void)fprintf(stderr, 139 "Usage: %s [%s] [login [shell arguments]]\n", 140 getprogname(), ARGSTR); 141 exit(EXIT_FAILURE); 142 } 143 argv += optind; 144 145 /* Lower the priority so su runs faster */ 146 errno = 0; 147 prio = getpriority(PRIO_PROCESS, 0); 148 if (errno) 149 prio = 0; 150 if (prio > -2) 151 (void)setpriority(PRIO_PROCESS, 0, -2); 152 openlog("su", 0, LOG_AUTH); 153 154 /* get current login name and shell */ 155 ruid = getuid(); 156 username = getlogin(); 157 if (username == NULL || 158 getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 || 159 pwd == NULL || pwd->pw_uid != ruid) { 160 if (getpwuid_r(ruid, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0) 161 pwd = NULL; 162 } 163 if (pwd == NULL) 164 errx(EXIT_FAILURE, "who are you?"); 165 if ((username = strdup(pwd->pw_name)) == NULL) 166 err(EXIT_FAILURE, "strdup"); 167 168 if (asme) { 169 if (pwd->pw_shell && *pwd->pw_shell) { 170 strlcpy(shellbuf, pwd->pw_shell, sizeof(shellbuf)); 171 shell = shellbuf; 172 } else { 173 shell = _PATH_BSHELL; 174 iscsh = NO; 175 } 176 } 177 /* get target login information, default to root */ 178 user = *argv ? *argv : "root"; 179 np = *argv ? argv : argv - 1; 180 181 if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 || 182 pwd == NULL) 183 errx(EXIT_FAILURE, "unknown login %s", user); 184 185 /* 186 * PAM initialization 187 */ 188 #define PAM_END(msg) do { func = msg; goto done; } while (/*CONSTCOND*/0) 189 190 if ((pam_err = pam_start("su", user, &pamc, &pamh)) != PAM_SUCCESS) { 191 if (pamh != NULL) 192 PAM_END("pam_start"); 193 /* Things went really bad... */ 194 syslog(LOG_ERR, "pam_start failed: %s", 195 pam_strerror(pamh, pam_err)); 196 errx(EXIT_FAILURE, "pam_start failed"); 197 } 198 199 #define PAM_END_ITEM(item) PAM_END("pam_set_item(" # item ")") 200 #define PAM_SET_ITEM(item, var) \ 201 if ((pam_err = pam_set_item(pamh, (item), (var))) != PAM_SUCCESS) \ 202 PAM_END_ITEM(item) 203 204 /* 205 * Fill hostname, username and tty 206 */ 207 PAM_SET_ITEM(PAM_RUSER, username); 208 if (gethostname(hostname, sizeof(hostname)) != -1) 209 PAM_SET_ITEM(PAM_RHOST, hostname); 210 211 if ((tty = ttyname(STDERR_FILENO)) != NULL) 212 PAM_SET_ITEM(PAM_TTY, tty); 213 214 /* 215 * Authentication 216 */ 217 if ((pam_err = pam_authenticate(pamh, 0)) != PAM_SUCCESS) { 218 syslog(LOG_WARNING, "BAD SU %s to %s%s: %s", 219 username, user, ontty(), pam_strerror(pamh, pam_err)); 220 pam_end(pamh, pam_err); 221 errx(EXIT_FAILURE, "Sorry: %s", pam_strerror(pamh, pam_err)); 222 } 223 224 /* 225 * Authorization 226 */ 227 switch(pam_err = pam_acct_mgmt(pamh, 0)) { 228 case PAM_NEW_AUTHTOK_REQD: 229 pam_err = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); 230 if (pam_err != PAM_SUCCESS) 231 PAM_END("pam_chauthok"); 232 break; 233 case PAM_SUCCESS: 234 break; 235 default: 236 PAM_END("pam_acct_mgmt"); 237 break; 238 } 239 240 /* 241 * pam_authenticate might have changed the target user. 242 * refresh pwd and user 243 */ 244 pam_err = pam_get_item(pamh, PAM_USER, &newuser); 245 if (pam_err != PAM_SUCCESS) { 246 syslog(LOG_WARNING, 247 "pam_get_item(PAM_USER): %s", pam_strerror(pamh, pam_err)); 248 } else { 249 user = (char *)newuser; 250 if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 || 251 pwd == NULL) { 252 pam_end(pamh, pam_err); 253 syslog(LOG_ERR, "unknown login: %s", username); 254 errx(EXIT_FAILURE, "unknown login: %s", username); 255 } 256 } 257 258 #define ERRX_PAM_END(args) do { \ 259 pam_end(pamh, pam_err); \ 260 errx args; \ 261 } while (/* CONSTOCOND */0) 262 263 #define ERR_PAM_END(args) do { \ 264 pam_end(pamh, pam_err); \ 265 err args; \ 266 } while (/* CONSTOCOND */0) 267 268 /* force the usage of specified class */ 269 if (class) { 270 if (ruid) 271 ERRX_PAM_END((EXIT_FAILURE, "Only root may use -c")); 272 273 pwd->pw_class = class; 274 } 275 276 if ((lc = login_getclass(pwd->pw_class)) == NULL) 277 ERRX_PAM_END((EXIT_FAILURE, 278 "Unknown class %s\n", pwd->pw_class)); 279 280 if (asme) { 281 /* if asme and non-standard target shell, must be root */ 282 if (!chshell(pwd->pw_shell) && ruid) 283 ERRX_PAM_END((EXIT_FAILURE, 284 "permission denied (shell).")); 285 } else if (pwd->pw_shell && *pwd->pw_shell) { 286 shell = pwd->pw_shell; 287 iscsh = UNSET; 288 } else { 289 shell = _PATH_BSHELL; 290 iscsh = NO; 291 } 292 293 if ((p = strrchr(shell, '/')) != NULL) 294 avshell = p + 1; 295 else 296 avshell = shell; 297 298 /* if we're forking a csh, we want to slightly muck the args */ 299 if (iscsh == UNSET) 300 iscsh = strstr(avshell, "csh") ? YES : NO; 301 302 /* 303 * Initialize the supplemental groups before pam gets to them, 304 * so that other pam modules get a chance to add more when 305 * we do setcred. Note, we don't relinguish our set-userid yet 306 */ 307 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0) 308 ERR_PAM_END((EXIT_FAILURE, "setting user context")); 309 310 if ((pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) 311 PAM_END("pam_setcred"); 312 313 /* 314 * Manage session. 315 */ 316 if (asthem) { 317 pid_t pid, xpid; 318 int status = 1; 319 struct sigaction sa, sa_int, sa_pipe, sa_quit; 320 int fds[2]; 321 322 if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS) 323 PAM_END("pam_open_session"); 324 325 /* 326 * In order to call pam_close_session after the 327 * command terminates, we need to fork. 328 */ 329 sa.sa_flags = SA_RESTART; 330 sa.sa_handler = SIG_IGN; 331 sigemptyset(&sa.sa_mask); 332 (void)sigaction(SIGINT, &sa, &sa_int); 333 (void)sigaction(SIGQUIT, &sa, &sa_quit); 334 (void)sigaction(SIGPIPE, &sa, &sa_pipe); 335 sa.sa_handler = SIG_DFL; 336 (void)sigaction(SIGTSTP, &sa, NULL); 337 /* 338 * Use a pipe to guarantee the order of execution of 339 * the parent and the child. 340 */ 341 if (pipe(fds) == -1) { 342 warn("pipe failed"); 343 goto out; 344 } 345 346 switch (pid = fork()) { 347 case -1: 348 logit("fork failed (%s)", strerror(errno)); 349 goto out; 350 351 case 0: /* Child */ 352 (void)close(fds[1]); 353 (void)read(fds[0], &status, 1); 354 (void)close(fds[0]); 355 (void)sigaction(SIGINT, &sa_int, NULL); 356 (void)sigaction(SIGQUIT, &sa_quit, NULL); 357 (void)sigaction(SIGPIPE, &sa_pipe, NULL); 358 break; 359 360 default: 361 sa.sa_handler = SIG_IGN; 362 (void)sigaction(SIGTTOU, &sa, NULL); 363 (void)close(fds[0]); 364 (void)setpgid(pid, pid); 365 (void)tcsetpgrp(STDERR_FILENO, pid); 366 (void)close(fds[1]); 367 (void)sigaction(SIGPIPE, &sa_pipe, NULL); 368 /* 369 * Parent: wait for the child to terminate 370 * and call pam_close_session. 371 */ 372 while ((xpid = waitpid(pid, &status, WUNTRACED)) 373 == pid) { 374 if (WIFSTOPPED(status)) { 375 (void)kill(getpid(), SIGSTOP); 376 (void)tcsetpgrp(STDERR_FILENO, 377 getpgid(pid)); 378 (void)kill(pid, SIGCONT); 379 status = 1; 380 continue; 381 } 382 break; 383 } 384 385 (void)tcsetpgrp(STDERR_FILENO, getpgid(0)); 386 387 if (xpid == -1) { 388 logit("Error waiting for pid %d (%s)", pid, 389 strerror(errno)); 390 } else if (xpid != pid) { 391 /* Can't happen. */ 392 logit("Wrong PID: %d != %d", pid, xpid); 393 } 394 out: 395 pam_err = pam_setcred(pamh, PAM_DELETE_CRED); 396 if (pam_err != PAM_SUCCESS) 397 logit("pam_setcred: %s", 398 pam_strerror(pamh, pam_err)); 399 pam_err = pam_close_session(pamh, 0); 400 if (pam_err != PAM_SUCCESS) 401 logit("pam_close_session: %s", 402 pam_strerror(pamh, pam_err)); 403 pam_end(pamh, pam_err); 404 exit(WEXITSTATUS(status)); 405 break; 406 } 407 } 408 409 /* 410 * The child: starting here, we don't have to care about 411 * handling PAM issues if we exit, the parent will do the 412 * job when we exit. 413 */ 414 #undef PAM_END 415 #undef ERR_PAM_END 416 #undef ERRX_PAM_END 417 418 if (!asme) { 419 if (asthem) { 420 char **pamenv; 421 422 p = getenv("TERM"); 423 /* 424 * Create an empty environment 425 */ 426 if ((environ = malloc(sizeof(char *))) == NULL) 427 err(EXIT_FAILURE, NULL); 428 environ[0] = NULL; 429 430 /* 431 * Add PAM environement, before the LOGIN_CAP stuff: 432 * if the login class is unspecified, we'll get the 433 * same data from PAM, if -c was used, the specified 434 * class must override PAM. 435 */ 436 if ((pamenv = pam_getenvlist(pamh)) != NULL) { 437 char **envitem; 438 439 /* 440 * XXX Here FreeBSD filters out 441 * SHELL, LOGNAME, MAIL, CDPATH, IFS, PATH 442 * how could we get untrusted data here? 443 */ 444 for (envitem = pamenv; *envitem; envitem++) { 445 putenv(*envitem); 446 free(*envitem); 447 } 448 449 free(pamenv); 450 } 451 452 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH| 453 LOGIN_SETENV|LOGIN_SETUMASK)) 454 err(EXIT_FAILURE, "setting user context"); 455 if (p) 456 (void)setenv("TERM", p, 1); 457 if (gohome && chdir(pwd->pw_dir) < 0) 458 errx(EXIT_FAILURE, "no directory"); 459 } 460 461 if (asthem || pwd->pw_uid) { 462 (void)setenv("LOGNAME", pwd->pw_name, 1); 463 (void)setenv("USER", pwd->pw_name, 1); 464 } 465 (void)setenv("HOME", pwd->pw_dir, 1); 466 (void)setenv("SHELL", shell, 1); 467 } 468 (void)setenv("SU_FROM", username, 1); 469 470 if (iscsh == YES) { 471 if (fastlogin) 472 *np-- = "-f"; 473 if (asme) 474 *np-- = "-m"; 475 } else { 476 if (fastlogin) 477 unsetenv("ENV"); 478 } 479 480 if (asthem) { 481 avshellbuf[0] = '-'; 482 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 483 avshell = avshellbuf; 484 } else if (iscsh == YES) { 485 /* csh strips the first character... */ 486 avshellbuf[0] = '_'; 487 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 488 avshell = avshellbuf; 489 } 490 *np = avshell; 491 492 if (ruid != 0) 493 syslog(LOG_NOTICE, "%s to %s%s", 494 username, pwd->pw_name, ontty()); 495 496 /* 497 * Set user context, except for umask, and the stuff 498 * we have done before. 499 */ 500 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV|LOGIN_SETUMASK| 501 LOGIN_SETLOGIN|LOGIN_SETPATH|LOGIN_SETGROUP); 502 503 /* 504 * Don't touch resource/priority settings if -m has been used 505 * or -l and -c hasn't, and we're not su'ing to root. 506 */ 507 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) 508 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES); 509 510 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) == -1) 511 err(EXIT_FAILURE, "setusercontext"); 512 513 (void)execv(shell, np); 514 err(EXIT_FAILURE, "%s", shell); 515 done: 516 logit("%s: %s", func, pam_strerror(pamh, pam_err)); 517 pam_end(pamh, pam_err); 518 return EXIT_FAILURE; 519 } 520 521 static void 522 logit(const char *fmt, ...) 523 { 524 va_list ap; 525 526 va_start(ap, fmt); 527 vwarnx(fmt, ap); 528 vsyslog(LOG_ERR, fmt, ap); 529 va_end(ap); 530 } 531 532 533 static int 534 chshell(const char *sh) 535 { 536 const char *cp; 537 538 setusershell(); 539 while ((cp = getusershell()) != NULL) 540 if (!strcmp(cp, sh)) 541 return 1; 542 return 0; 543 } 544 545 static char * 546 ontty(void) 547 { 548 char *p; 549 static char buf[MAXPATHLEN + 4]; 550 551 buf[0] = 0; 552 if ((p = ttyname(STDERR_FILENO)) != NULL) 553 (void)snprintf(buf, sizeof buf, " on %s", p); 554 return buf; 555 } 556