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