1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 2001,2003 Networks Associates Technology, Inc. 5 * Copyright (c) 2017 Dag-Erling Smørgrav 6 * All rights reserved. 7 * 8 * This software was developed for the FreeBSD Project by ThinkSec AS and 9 * NAI Labs, the Security Research Division of Network Associates, Inc. 10 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 11 * DARPA CHATS research program. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. The name of the author may not be used to endorse or promote 22 * products derived from this software without specific prior written 23 * permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 38 #include <sys/cdefs.h> 39 __FBSDID("$FreeBSD$"); 40 41 #include <sys/types.h> 42 #include <sys/poll.h> 43 #include <sys/procdesc.h> 44 #include <sys/wait.h> 45 46 #include <errno.h> 47 #include <fcntl.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <unistd.h> 52 53 #include <security/pam_appl.h> 54 #include <security/pam_modules.h> 55 #include <security/openpam.h> 56 57 #define PAM_ITEM_ENV(n) { (n), #n } 58 static struct { 59 int item; 60 const char *name; 61 } pam_item_env[] = { 62 PAM_ITEM_ENV(PAM_SERVICE), 63 PAM_ITEM_ENV(PAM_USER), 64 PAM_ITEM_ENV(PAM_TTY), 65 PAM_ITEM_ENV(PAM_RHOST), 66 PAM_ITEM_ENV(PAM_RUSER), 67 }; 68 #define NUM_PAM_ITEM_ENV (sizeof(pam_item_env) / sizeof(pam_item_env[0])) 69 70 #define PAM_ERR_ENV_X(str, num) str "=" #num 71 #define PAM_ERR_ENV(pam_err) PAM_ERR_ENV_X(#pam_err, pam_err) 72 static const char *pam_err_env[] = { 73 PAM_ERR_ENV(PAM_SUCCESS), 74 PAM_ERR_ENV(PAM_OPEN_ERR), 75 PAM_ERR_ENV(PAM_SYMBOL_ERR), 76 PAM_ERR_ENV(PAM_SERVICE_ERR), 77 PAM_ERR_ENV(PAM_SYSTEM_ERR), 78 PAM_ERR_ENV(PAM_BUF_ERR), 79 PAM_ERR_ENV(PAM_CONV_ERR), 80 PAM_ERR_ENV(PAM_PERM_DENIED), 81 PAM_ERR_ENV(PAM_MAXTRIES), 82 PAM_ERR_ENV(PAM_AUTH_ERR), 83 PAM_ERR_ENV(PAM_NEW_AUTHTOK_REQD), 84 PAM_ERR_ENV(PAM_CRED_INSUFFICIENT), 85 PAM_ERR_ENV(PAM_AUTHINFO_UNAVAIL), 86 PAM_ERR_ENV(PAM_USER_UNKNOWN), 87 PAM_ERR_ENV(PAM_CRED_UNAVAIL), 88 PAM_ERR_ENV(PAM_CRED_EXPIRED), 89 PAM_ERR_ENV(PAM_CRED_ERR), 90 PAM_ERR_ENV(PAM_ACCT_EXPIRED), 91 PAM_ERR_ENV(PAM_AUTHTOK_EXPIRED), 92 PAM_ERR_ENV(PAM_SESSION_ERR), 93 PAM_ERR_ENV(PAM_AUTHTOK_ERR), 94 PAM_ERR_ENV(PAM_AUTHTOK_RECOVERY_ERR), 95 PAM_ERR_ENV(PAM_AUTHTOK_LOCK_BUSY), 96 PAM_ERR_ENV(PAM_AUTHTOK_DISABLE_AGING), 97 PAM_ERR_ENV(PAM_NO_MODULE_DATA), 98 PAM_ERR_ENV(PAM_IGNORE), 99 PAM_ERR_ENV(PAM_ABORT), 100 PAM_ERR_ENV(PAM_TRY_AGAIN), 101 PAM_ERR_ENV(PAM_MODULE_UNKNOWN), 102 PAM_ERR_ENV(PAM_DOMAIN_UNKNOWN), 103 PAM_ERR_ENV(PAM_NUM_ERR), 104 }; 105 #define NUM_PAM_ERR_ENV (sizeof(pam_err_env) / sizeof(pam_err_env[0])) 106 107 struct pe_opts { 108 int return_prog_exit_status; 109 int capture_stdout; 110 int capture_stderr; 111 }; 112 113 static int 114 parse_options(const char *func, int *argc, const char **argv[], 115 struct pe_opts *options) 116 { 117 int i; 118 119 /* 120 * Parse options: 121 * return_prog_exit_status: 122 * use the program exit status as the return code of pam_exec 123 * --: 124 * stop options parsing; what follows is the command to execute 125 */ 126 memset(options, 0, sizeof(*options)); 127 128 for (i = 0; i < *argc; ++i) { 129 if (strcmp((*argv)[i], "debug") == 0 || 130 strcmp((*argv)[i], "no_warn") == 0) { 131 /* ignore */ 132 } else if (strcmp((*argv)[i], "capture_stdout") == 0) { 133 options->capture_stdout = 1; 134 } else if (strcmp((*argv)[i], "capture_stderr") == 0) { 135 options->capture_stderr = 1; 136 } else if (strcmp((*argv)[i], "return_prog_exit_status") == 0) { 137 options->return_prog_exit_status = 1; 138 } else { 139 if (strcmp((*argv)[i], "--") == 0) { 140 (*argc)--; 141 (*argv)++; 142 } 143 break; 144 } 145 openpam_log(PAM_LOG_DEBUG, "%s: option \"%s\" enabled", 146 func, (*argv)[i]); 147 } 148 149 (*argc) -= i; 150 (*argv) += i; 151 152 return (0); 153 } 154 155 static int 156 _pam_exec(pam_handle_t *pamh, 157 const char *func, int flags __unused, int argc, const char *argv[], 158 struct pe_opts *options) 159 { 160 char buf[PAM_MAX_MSG_SIZE]; 161 struct pollfd pfd[3]; 162 const void *item; 163 char **envlist, *envstr, *resp, **tmp; 164 ssize_t rlen; 165 int envlen, extralen, i; 166 int pam_err, serrno, status; 167 int chout[2], cherr[2], pd; 168 nfds_t nfds; 169 pid_t pid; 170 171 pd = -1; 172 pid = 0; 173 chout[0] = chout[1] = cherr[0] = cherr[1] = -1; 174 envlist = NULL; 175 176 #define OUT(ret) do { pam_err = (ret); goto out; } while (0) 177 178 /* Check there's a program name left after parsing options. */ 179 if (argc < 1) { 180 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting", 181 func); 182 OUT(PAM_SERVICE_ERR); 183 } 184 185 /* 186 * Set up the child's environment list. It consists of the PAM 187 * environment, a few hand-picked PAM items, the name of the 188 * service function, and if return_prog_exit_status is set, the 189 * numerical values of all PAM error codes. 190 */ 191 192 /* compute the final size of the environment. */ 193 envlist = pam_getenvlist(pamh); 194 for (envlen = 0; envlist[envlen] != NULL; ++envlen) 195 /* nothing */ ; 196 extralen = NUM_PAM_ITEM_ENV + 1; 197 if (options->return_prog_exit_status) 198 extralen += NUM_PAM_ERR_ENV; 199 tmp = reallocarray(envlist, envlen + extralen + 1, sizeof(*envlist)); 200 openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d tmp = %p", 201 envlen, extralen, tmp); 202 if (tmp == NULL) 203 OUT(PAM_BUF_ERR); 204 envlist = tmp; 205 extralen += envlen; 206 207 /* copy selected PAM items to the environment */ 208 for (i = 0; i < NUM_PAM_ITEM_ENV; ++i) { 209 pam_err = pam_get_item(pamh, pam_item_env[i].item, &item); 210 if (pam_err != PAM_SUCCESS || item == NULL) 211 continue; 212 if (asprintf(&envstr, "%s=%s", pam_item_env[i].name, item) < 0) 213 OUT(PAM_BUF_ERR); 214 envlist[envlen++] = envstr; 215 envlist[envlen] = NULL; 216 openpam_log(PAM_LOG_DEBUG, "setenv %s", envstr); 217 } 218 219 /* add the name of the service function to the environment */ 220 if (asprintf(&envstr, "PAM_SM_FUNC=%s", func) < 0) 221 OUT(PAM_BUF_ERR); 222 envlist[envlen++] = envstr; 223 envlist[envlen] = NULL; 224 225 /* add the PAM error codes to the environment. */ 226 if (options->return_prog_exit_status) { 227 for (i = 0; i < (int)NUM_PAM_ERR_ENV; ++i) { 228 if ((envstr = strdup(pam_err_env[i])) == NULL) 229 OUT(PAM_BUF_ERR); 230 envlist[envlen++] = envstr; 231 envlist[envlen] = NULL; 232 } 233 } 234 235 openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d envlist = %p", 236 envlen, extralen, envlist); 237 238 /* set up pipes if capture was requested */ 239 if (options->capture_stdout) { 240 if (pipe(chout) != 0) { 241 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func); 242 OUT(PAM_SYSTEM_ERR); 243 } 244 if (fcntl(chout[0], F_SETFL, O_NONBLOCK) != 0) { 245 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func); 246 OUT(PAM_SYSTEM_ERR); 247 } 248 } else { 249 if ((chout[1] = open("/dev/null", O_RDWR)) < 0) { 250 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func); 251 OUT(PAM_SYSTEM_ERR); 252 } 253 } 254 if (options->capture_stderr) { 255 if (pipe(cherr) != 0) { 256 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func); 257 OUT(PAM_SYSTEM_ERR); 258 } 259 if (fcntl(cherr[0], F_SETFL, O_NONBLOCK) != 0) { 260 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func); 261 OUT(PAM_SYSTEM_ERR); 262 } 263 } else { 264 if ((cherr[1] = open("/dev/null", O_RDWR)) < 0) { 265 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func); 266 OUT(PAM_SYSTEM_ERR); 267 } 268 } 269 270 if ((pid = pdfork(&pd, 0)) == 0) { 271 /* child */ 272 if ((chout[0] >= 0 && close(chout[0]) != 0) || 273 (cherr[0] >= 0 && close(cherr[0]) != 0)) { 274 openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func); 275 } else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO || 276 dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) { 277 openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func); 278 } else { 279 execve(argv[0], (char * const *)argv, 280 (char * const *)envlist); 281 openpam_log(PAM_LOG_ERROR, "%s: execve(%s): %m", 282 func, argv[0]); 283 } 284 _exit(1); 285 } 286 /* parent */ 287 if (pid == -1) { 288 openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func); 289 OUT(PAM_SYSTEM_ERR); 290 } 291 /* use poll() to watch the process and stdout / stderr */ 292 if (chout[1] >= 0) 293 close(chout[1]); 294 if (cherr[1] >= 0) 295 close(cherr[1]); 296 memset(pfd, 0, sizeof pfd); 297 pfd[0].fd = pd; 298 pfd[0].events = POLLHUP; 299 nfds = 1; 300 if (options->capture_stdout) { 301 pfd[nfds].fd = chout[0]; 302 pfd[nfds].events = POLLIN|POLLERR|POLLHUP; 303 nfds++; 304 } 305 if (options->capture_stderr) { 306 pfd[nfds].fd = cherr[0]; 307 pfd[nfds].events = POLLIN|POLLERR|POLLHUP; 308 nfds++; 309 } 310 311 /* loop until the process exits */ 312 do { 313 if (poll(pfd, nfds, INFTIM) < 0) { 314 openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func); 315 OUT(PAM_SYSTEM_ERR); 316 } 317 for (i = 1; i < nfds; ++i) { 318 if ((pfd[i].revents & POLLIN) == 0) 319 continue; 320 if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) { 321 openpam_log(PAM_LOG_ERROR, "%s: read(): %m", 322 func); 323 OUT(PAM_SYSTEM_ERR); 324 } else if (rlen == 0) { 325 continue; 326 } 327 buf[rlen] = '\0'; 328 (void)pam_prompt(pamh, pfd[i].fd == chout[0] ? 329 PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf); 330 } 331 } while (pfd[0].revents == 0); 332 333 /* the child process has exited */ 334 while (waitpid(pid, &status, 0) == -1) { 335 if (errno == EINTR) 336 continue; 337 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func); 338 OUT(PAM_SYSTEM_ERR); 339 } 340 341 /* check exit code */ 342 if (WIFSIGNALED(status)) { 343 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s", 344 func, argv[0], WTERMSIG(status), 345 WCOREDUMP(status) ? " (core dumped)" : ""); 346 OUT(PAM_SERVICE_ERR); 347 } 348 if (!WIFEXITED(status)) { 349 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x", 350 func, status); 351 OUT(PAM_SERVICE_ERR); 352 } 353 354 if (options->return_prog_exit_status) { 355 openpam_log(PAM_LOG_DEBUG, 356 "%s: Use program exit status as return value: %d", 357 func, WEXITSTATUS(status)); 358 OUT(WEXITSTATUS(status)); 359 } else { 360 OUT(WEXITSTATUS(status) == 0 ? PAM_SUCCESS : PAM_PERM_DENIED); 361 } 362 /* unreachable */ 363 out: 364 serrno = errno; 365 if (pd >= 0) 366 close(pd); 367 if (chout[0] >= 0) 368 close(chout[0]); 369 if (chout[1] >= 0) 370 close(chout[1]); 371 if (cherr[0] >= 0) 372 close(cherr[0]); 373 if (cherr[0] >= 0) 374 close(cherr[1]); 375 if (envlist != NULL) 376 openpam_free_envlist(envlist); 377 errno = serrno; 378 return (pam_err); 379 } 380 381 PAM_EXTERN int 382 pam_sm_authenticate(pam_handle_t *pamh, int flags, 383 int argc, const char *argv[]) 384 { 385 int ret; 386 struct pe_opts options; 387 388 ret = parse_options(__func__, &argc, &argv, &options); 389 if (ret != 0) 390 return (PAM_SERVICE_ERR); 391 392 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 393 394 /* 395 * We must check that the program returned a valid code for this 396 * function. 397 */ 398 switch (ret) { 399 case PAM_SUCCESS: 400 case PAM_ABORT: 401 case PAM_AUTHINFO_UNAVAIL: 402 case PAM_AUTH_ERR: 403 case PAM_BUF_ERR: 404 case PAM_CONV_ERR: 405 case PAM_CRED_INSUFFICIENT: 406 case PAM_IGNORE: 407 case PAM_MAXTRIES: 408 case PAM_PERM_DENIED: 409 case PAM_SERVICE_ERR: 410 case PAM_SYSTEM_ERR: 411 case PAM_USER_UNKNOWN: 412 break; 413 default: 414 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 415 argv[0], ret); 416 ret = PAM_SERVICE_ERR; 417 } 418 419 return (ret); 420 } 421 422 PAM_EXTERN int 423 pam_sm_setcred(pam_handle_t *pamh, int flags, 424 int argc, const char *argv[]) 425 { 426 int ret; 427 struct pe_opts options; 428 429 ret = parse_options(__func__, &argc, &argv, &options); 430 if (ret != 0) 431 return (PAM_SERVICE_ERR); 432 433 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 434 435 /* 436 * We must check that the program returned a valid code for this 437 * function. 438 */ 439 switch (ret) { 440 case PAM_SUCCESS: 441 case PAM_ABORT: 442 case PAM_BUF_ERR: 443 case PAM_CONV_ERR: 444 case PAM_CRED_ERR: 445 case PAM_CRED_EXPIRED: 446 case PAM_CRED_UNAVAIL: 447 case PAM_IGNORE: 448 case PAM_PERM_DENIED: 449 case PAM_SERVICE_ERR: 450 case PAM_SYSTEM_ERR: 451 case PAM_USER_UNKNOWN: 452 break; 453 default: 454 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 455 argv[0], ret); 456 ret = PAM_SERVICE_ERR; 457 } 458 459 return (ret); 460 } 461 462 PAM_EXTERN int 463 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, 464 int argc, const char *argv[]) 465 { 466 int ret; 467 struct pe_opts options; 468 469 ret = parse_options(__func__, &argc, &argv, &options); 470 if (ret != 0) 471 return (PAM_SERVICE_ERR); 472 473 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 474 475 /* 476 * We must check that the program returned a valid code for this 477 * function. 478 */ 479 switch (ret) { 480 case PAM_SUCCESS: 481 case PAM_ABORT: 482 case PAM_ACCT_EXPIRED: 483 case PAM_AUTH_ERR: 484 case PAM_BUF_ERR: 485 case PAM_CONV_ERR: 486 case PAM_IGNORE: 487 case PAM_NEW_AUTHTOK_REQD: 488 case PAM_PERM_DENIED: 489 case PAM_SERVICE_ERR: 490 case PAM_SYSTEM_ERR: 491 case PAM_USER_UNKNOWN: 492 break; 493 default: 494 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 495 argv[0], ret); 496 ret = PAM_SERVICE_ERR; 497 } 498 499 return (ret); 500 } 501 502 PAM_EXTERN int 503 pam_sm_open_session(pam_handle_t *pamh, int flags, 504 int argc, const char *argv[]) 505 { 506 int ret; 507 struct pe_opts options; 508 509 ret = parse_options(__func__, &argc, &argv, &options); 510 if (ret != 0) 511 return (PAM_SERVICE_ERR); 512 513 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 514 515 /* 516 * We must check that the program returned a valid code for this 517 * function. 518 */ 519 switch (ret) { 520 case PAM_SUCCESS: 521 case PAM_ABORT: 522 case PAM_BUF_ERR: 523 case PAM_CONV_ERR: 524 case PAM_IGNORE: 525 case PAM_PERM_DENIED: 526 case PAM_SERVICE_ERR: 527 case PAM_SESSION_ERR: 528 case PAM_SYSTEM_ERR: 529 break; 530 default: 531 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 532 argv[0], ret); 533 ret = PAM_SERVICE_ERR; 534 } 535 536 return (ret); 537 } 538 539 PAM_EXTERN int 540 pam_sm_close_session(pam_handle_t *pamh, int flags, 541 int argc, const char *argv[]) 542 { 543 int ret; 544 struct pe_opts options; 545 546 ret = parse_options(__func__, &argc, &argv, &options); 547 if (ret != 0) 548 return (PAM_SERVICE_ERR); 549 550 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 551 552 /* 553 * We must check that the program returned a valid code for this 554 * function. 555 */ 556 switch (ret) { 557 case PAM_SUCCESS: 558 case PAM_ABORT: 559 case PAM_BUF_ERR: 560 case PAM_CONV_ERR: 561 case PAM_IGNORE: 562 case PAM_PERM_DENIED: 563 case PAM_SERVICE_ERR: 564 case PAM_SESSION_ERR: 565 case PAM_SYSTEM_ERR: 566 break; 567 default: 568 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 569 argv[0], ret); 570 ret = PAM_SERVICE_ERR; 571 } 572 573 return (ret); 574 } 575 576 PAM_EXTERN int 577 pam_sm_chauthtok(pam_handle_t *pamh, int flags, 578 int argc, const char *argv[]) 579 { 580 int ret; 581 struct pe_opts options; 582 583 ret = parse_options(__func__, &argc, &argv, &options); 584 if (ret != 0) 585 return (PAM_SERVICE_ERR); 586 587 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 588 589 /* 590 * We must check that the program returned a valid code for this 591 * function. 592 */ 593 switch (ret) { 594 case PAM_SUCCESS: 595 case PAM_ABORT: 596 case PAM_AUTHTOK_DISABLE_AGING: 597 case PAM_AUTHTOK_ERR: 598 case PAM_AUTHTOK_LOCK_BUSY: 599 case PAM_AUTHTOK_RECOVERY_ERR: 600 case PAM_BUF_ERR: 601 case PAM_CONV_ERR: 602 case PAM_IGNORE: 603 case PAM_PERM_DENIED: 604 case PAM_SERVICE_ERR: 605 case PAM_SYSTEM_ERR: 606 case PAM_TRY_AGAIN: 607 break; 608 default: 609 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 610 argv[0], ret); 611 ret = PAM_SERVICE_ERR; 612 } 613 614 return (ret); 615 } 616 617 PAM_MODULE_ENTRY("pam_exec"); 618