1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 2001,2003 Networks Associates Technology, Inc. 5 * All rights reserved. 6 * 7 * This software was developed for the FreeBSD Project by ThinkSec AS and 8 * NAI Labs, the Security Research Division of Network Associates, Inc. 9 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 10 * DARPA CHATS research program. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. The name of the author may not be used to endorse or promote 21 * products derived from this software without specific prior written 22 * permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 * 36 * $FreeBSD: head/lib/libpam/modules/pam_exec/pam_exec.c 315164 2017-03-12 17:41:51Z pfg $ 37 */ 38 39 #include <sys/types.h> 40 #include <sys/wait.h> 41 42 #include <errno.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <unistd.h> 47 48 #include <security/pam_appl.h> 49 #include <security/pam_modules.h> 50 #include <security/openpam.h> 51 52 #define ENV_ITEM(n) { (n), #n } 53 static struct { 54 int item; 55 const char *name; 56 } env_items[] = { 57 ENV_ITEM(PAM_SERVICE), 58 ENV_ITEM(PAM_USER), 59 ENV_ITEM(PAM_TTY), 60 ENV_ITEM(PAM_RHOST), 61 ENV_ITEM(PAM_RUSER), 62 }; 63 64 struct pe_opts { 65 int return_prog_exit_status; 66 }; 67 68 #define PAM_RV_COUNT 24 69 70 static int 71 parse_options(const char *func, int *argc, const char **argv[], 72 struct pe_opts *options) 73 { 74 int i; 75 76 /* 77 * Parse options: 78 * return_prog_exit_status: 79 * use the program exit status as the return code of pam_exec 80 * --: 81 * stop options parsing; what follows is the command to execute 82 */ 83 options->return_prog_exit_status = 0; 84 85 for (i = 0; i < *argc; ++i) { 86 if (strcmp((*argv)[i], "return_prog_exit_status") == 0) { 87 openpam_log(PAM_LOG_DEBUG, 88 "%s: Option \"return_prog_exit_status\" enabled", 89 func); 90 options->return_prog_exit_status = 1; 91 } else { 92 if (strcmp((*argv)[i], "--") == 0) { 93 (*argc)--; 94 (*argv)++; 95 } 96 97 break; 98 } 99 } 100 101 (*argc) -= i; 102 (*argv) += i; 103 104 return (0); 105 } 106 107 static int 108 _pam_exec(pam_handle_t *pamh __unused, 109 const char *func, int flags __unused, int argc, const char *argv[], 110 struct pe_opts *options) 111 { 112 int envlen, i, nitems, pam_err, status; 113 int nitems_rv; 114 char **envlist, **tmp, *envstr; 115 volatile int childerr; 116 pid_t pid; 117 118 /* 119 * XXX For additional credit, divert child's stdin/stdout/stderr 120 * to the conversation function. 121 */ 122 123 /* Check there's a program name left after parsing options. */ 124 if (argc < 1) { 125 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting", 126 func); 127 return (PAM_SERVICE_ERR); 128 } 129 130 /* 131 * Set up the child's environment list. It consists of the PAM 132 * environment, plus a few hand-picked PAM items, the pam_sm_* 133 * function name calling it and, if return_prog_exit_status is 134 * set, the valid return codes numerical values. 135 */ 136 envlist = pam_getenvlist(pamh); 137 for (envlen = 0; envlist[envlen] != NULL; ++envlen) 138 /* nothing */ ; 139 nitems = sizeof(env_items) / sizeof(*env_items); 140 /* Count PAM return values put in the environment. */ 141 nitems_rv = options->return_prog_exit_status ? PAM_RV_COUNT : 0; 142 tmp = realloc(envlist, (envlen + nitems + 1 + nitems_rv + 1) * 143 sizeof(*envlist)); 144 if (tmp == NULL) { 145 openpam_free_envlist(envlist); 146 return (PAM_BUF_ERR); 147 } 148 envlist = tmp; 149 for (i = 0; i < nitems; ++i) { 150 const void *item; 151 152 pam_err = pam_get_item(pamh, env_items[i].item, &item); 153 if (pam_err != PAM_SUCCESS || item == NULL) 154 continue; 155 asprintf(&envstr, "%s=%s", env_items[i].name, 156 (const char *)item); 157 if (envstr == NULL) { 158 openpam_free_envlist(envlist); 159 return (PAM_BUF_ERR); 160 } 161 envlist[envlen++] = envstr; 162 envlist[envlen] = NULL; 163 } 164 165 /* Add the pam_sm_* function name to the environment. */ 166 asprintf(&envstr, "PAM_SM_FUNC=%s", func); 167 if (envstr == NULL) { 168 openpam_free_envlist(envlist); 169 return (PAM_BUF_ERR); 170 } 171 envlist[envlen++] = envstr; 172 173 /* Add the PAM return values to the environment. */ 174 if (options->return_prog_exit_status) { 175 #define ADD_PAM_RV_TO_ENV(name) \ 176 asprintf(&envstr, #name "=%d", name); \ 177 if (envstr == NULL) { \ 178 openpam_free_envlist(envlist); \ 179 return (PAM_BUF_ERR); \ 180 } \ 181 envlist[envlen++] = envstr 182 /* 183 * CAUTION: When adding/removing an item in the list 184 * below, be sure to update the value of PAM_RV_COUNT. 185 */ 186 ADD_PAM_RV_TO_ENV(PAM_ABORT); 187 ADD_PAM_RV_TO_ENV(PAM_ACCT_EXPIRED); 188 ADD_PAM_RV_TO_ENV(PAM_AUTHINFO_UNAVAIL); 189 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_DISABLE_AGING); 190 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_ERR); 191 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_LOCK_BUSY); 192 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_RECOVERY_ERR); 193 ADD_PAM_RV_TO_ENV(PAM_AUTH_ERR); 194 ADD_PAM_RV_TO_ENV(PAM_BUF_ERR); 195 ADD_PAM_RV_TO_ENV(PAM_CONV_ERR); 196 ADD_PAM_RV_TO_ENV(PAM_CRED_ERR); 197 ADD_PAM_RV_TO_ENV(PAM_CRED_EXPIRED); 198 ADD_PAM_RV_TO_ENV(PAM_CRED_INSUFFICIENT); 199 ADD_PAM_RV_TO_ENV(PAM_CRED_UNAVAIL); 200 ADD_PAM_RV_TO_ENV(PAM_IGNORE); 201 ADD_PAM_RV_TO_ENV(PAM_MAXTRIES); 202 ADD_PAM_RV_TO_ENV(PAM_NEW_AUTHTOK_REQD); 203 ADD_PAM_RV_TO_ENV(PAM_PERM_DENIED); 204 ADD_PAM_RV_TO_ENV(PAM_SERVICE_ERR); 205 ADD_PAM_RV_TO_ENV(PAM_SESSION_ERR); 206 ADD_PAM_RV_TO_ENV(PAM_SUCCESS); 207 ADD_PAM_RV_TO_ENV(PAM_SYSTEM_ERR); 208 ADD_PAM_RV_TO_ENV(PAM_TRY_AGAIN); 209 ADD_PAM_RV_TO_ENV(PAM_USER_UNKNOWN); 210 } 211 212 envlist[envlen] = NULL; 213 214 /* 215 * Fork and run the command. By using vfork() instead of fork(), 216 * we can distinguish between an execve() failure and a non-zero 217 * exit status from the command. 218 */ 219 childerr = 0; 220 if ((pid = vfork()) == 0) { 221 execve(argv[0], (char * const *)argv, (char * const *)envlist); 222 childerr = errno; 223 _exit(1); 224 } 225 openpam_free_envlist(envlist); 226 if (pid == -1) { 227 openpam_log(PAM_LOG_ERROR, "%s: vfork(): %m", func); 228 return (PAM_SYSTEM_ERR); 229 } 230 while (waitpid(pid, &status, 0) == -1) { 231 if (errno == EINTR) 232 continue; 233 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func); 234 return (PAM_SYSTEM_ERR); 235 } 236 if (childerr != 0) { 237 openpam_log(PAM_LOG_ERROR, "%s: execve(): %m", func); 238 return (PAM_SYSTEM_ERR); 239 } 240 if (WIFSIGNALED(status)) { 241 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s", 242 func, argv[0], WTERMSIG(status), 243 WCOREDUMP(status) ? " (core dumped)" : ""); 244 return (PAM_SERVICE_ERR); 245 } 246 if (!WIFEXITED(status)) { 247 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x", 248 func, status); 249 return (PAM_SERVICE_ERR); 250 } 251 252 if (options->return_prog_exit_status) { 253 openpam_log(PAM_LOG_DEBUG, 254 "%s: Use program exit status as return value: %d", 255 func, WEXITSTATUS(status)); 256 return (WEXITSTATUS(status)); 257 } else { 258 return (WEXITSTATUS(status) == 0 ? 259 PAM_SUCCESS : PAM_PERM_DENIED); 260 } 261 } 262 263 PAM_EXTERN int 264 pam_sm_authenticate(pam_handle_t *pamh, int flags, 265 int argc, const char *argv[]) 266 { 267 int ret; 268 struct pe_opts options; 269 270 ret = parse_options(__func__, &argc, &argv, &options); 271 if (ret != 0) 272 return (PAM_SERVICE_ERR); 273 274 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 275 276 /* 277 * We must check that the program returned a valid code for this 278 * function. 279 */ 280 switch (ret) { 281 case PAM_SUCCESS: 282 case PAM_ABORT: 283 case PAM_AUTHINFO_UNAVAIL: 284 case PAM_AUTH_ERR: 285 case PAM_BUF_ERR: 286 case PAM_CONV_ERR: 287 case PAM_CRED_INSUFFICIENT: 288 case PAM_IGNORE: 289 case PAM_MAXTRIES: 290 case PAM_PERM_DENIED: 291 case PAM_SERVICE_ERR: 292 case PAM_SYSTEM_ERR: 293 case PAM_USER_UNKNOWN: 294 break; 295 default: 296 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 297 argv[0], ret); 298 ret = PAM_SERVICE_ERR; 299 } 300 301 return (ret); 302 } 303 304 PAM_EXTERN int 305 pam_sm_setcred(pam_handle_t *pamh, int flags, 306 int argc, const char *argv[]) 307 { 308 int ret; 309 struct pe_opts options; 310 311 ret = parse_options(__func__, &argc, &argv, &options); 312 if (ret != 0) 313 return (PAM_SERVICE_ERR); 314 315 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 316 317 /* 318 * We must check that the program returned a valid code for this 319 * function. 320 */ 321 switch (ret) { 322 case PAM_SUCCESS: 323 case PAM_ABORT: 324 case PAM_BUF_ERR: 325 case PAM_CONV_ERR: 326 case PAM_CRED_ERR: 327 case PAM_CRED_EXPIRED: 328 case PAM_CRED_UNAVAIL: 329 case PAM_IGNORE: 330 case PAM_PERM_DENIED: 331 case PAM_SERVICE_ERR: 332 case PAM_SYSTEM_ERR: 333 case PAM_USER_UNKNOWN: 334 break; 335 default: 336 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 337 argv[0], ret); 338 ret = PAM_SERVICE_ERR; 339 } 340 341 return (ret); 342 } 343 344 PAM_EXTERN int 345 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, 346 int argc, const char *argv[]) 347 { 348 int ret; 349 struct pe_opts options; 350 351 ret = parse_options(__func__, &argc, &argv, &options); 352 if (ret != 0) 353 return (PAM_SERVICE_ERR); 354 355 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 356 357 /* 358 * We must check that the program returned a valid code for this 359 * function. 360 */ 361 switch (ret) { 362 case PAM_SUCCESS: 363 case PAM_ABORT: 364 case PAM_ACCT_EXPIRED: 365 case PAM_AUTH_ERR: 366 case PAM_BUF_ERR: 367 case PAM_CONV_ERR: 368 case PAM_IGNORE: 369 case PAM_NEW_AUTHTOK_REQD: 370 case PAM_PERM_DENIED: 371 case PAM_SERVICE_ERR: 372 case PAM_SYSTEM_ERR: 373 case PAM_USER_UNKNOWN: 374 break; 375 default: 376 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 377 argv[0], ret); 378 ret = PAM_SERVICE_ERR; 379 } 380 381 return (ret); 382 } 383 384 PAM_EXTERN int 385 pam_sm_open_session(pam_handle_t *pamh, int flags, 386 int argc, const char *argv[]) 387 { 388 int ret; 389 struct pe_opts options; 390 391 ret = parse_options(__func__, &argc, &argv, &options); 392 if (ret != 0) 393 return (PAM_SERVICE_ERR); 394 395 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 396 397 /* 398 * We must check that the program returned a valid code for this 399 * function. 400 */ 401 switch (ret) { 402 case PAM_SUCCESS: 403 case PAM_ABORT: 404 case PAM_BUF_ERR: 405 case PAM_CONV_ERR: 406 case PAM_IGNORE: 407 case PAM_PERM_DENIED: 408 case PAM_SERVICE_ERR: 409 case PAM_SESSION_ERR: 410 case PAM_SYSTEM_ERR: 411 break; 412 default: 413 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 414 argv[0], ret); 415 ret = PAM_SERVICE_ERR; 416 } 417 418 return (ret); 419 } 420 421 PAM_EXTERN int 422 pam_sm_close_session(pam_handle_t *pamh, int flags, 423 int argc, const char *argv[]) 424 { 425 int ret; 426 struct pe_opts options; 427 428 ret = parse_options(__func__, &argc, &argv, &options); 429 if (ret != 0) 430 return (PAM_SERVICE_ERR); 431 432 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 433 434 /* 435 * We must check that the program returned a valid code for this 436 * function. 437 */ 438 switch (ret) { 439 case PAM_SUCCESS: 440 case PAM_ABORT: 441 case PAM_BUF_ERR: 442 case PAM_CONV_ERR: 443 case PAM_IGNORE: 444 case PAM_PERM_DENIED: 445 case PAM_SERVICE_ERR: 446 case PAM_SESSION_ERR: 447 case PAM_SYSTEM_ERR: 448 break; 449 default: 450 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 451 argv[0], ret); 452 ret = PAM_SERVICE_ERR; 453 } 454 455 return (ret); 456 } 457 458 PAM_EXTERN int 459 pam_sm_chauthtok(pam_handle_t *pamh, int flags, 460 int argc, const char *argv[]) 461 { 462 int ret; 463 struct pe_opts options; 464 465 ret = parse_options(__func__, &argc, &argv, &options); 466 if (ret != 0) 467 return (PAM_SERVICE_ERR); 468 469 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 470 471 /* 472 * We must check that the program returned a valid code for this 473 * function. 474 */ 475 switch (ret) { 476 case PAM_SUCCESS: 477 case PAM_ABORT: 478 case PAM_AUTHTOK_DISABLE_AGING: 479 case PAM_AUTHTOK_ERR: 480 case PAM_AUTHTOK_LOCK_BUSY: 481 case PAM_AUTHTOK_RECOVERY_ERR: 482 case PAM_BUF_ERR: 483 case PAM_CONV_ERR: 484 case PAM_IGNORE: 485 case PAM_PERM_DENIED: 486 case PAM_SERVICE_ERR: 487 case PAM_SYSTEM_ERR: 488 case PAM_TRY_AGAIN: 489 break; 490 default: 491 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 492 argv[0], ret); 493 ret = PAM_SERVICE_ERR; 494 } 495 496 return (ret); 497 } 498 499 PAM_MODULE_ENTRY("pam_exec"); 500