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