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