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 * $FreeBSD: src/lib/libpam/modules/pam_exec/pam_exec.c,v 1.9 2012/04/12 14:02:59 dumbbell Exp $ 35 */ 36 37 #include <sys/types.h> 38 #include <sys/wait.h> 39 40 #include <errno.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 46 #include <security/pam_appl.h> 47 #include <security/pam_modules.h> 48 #include <security/openpam.h> 49 50 #define ENV_ITEM(n) { (n), #n } 51 static struct { 52 int item; 53 const char *name; 54 } env_items[] = { 55 ENV_ITEM(PAM_SERVICE), 56 ENV_ITEM(PAM_USER), 57 ENV_ITEM(PAM_TTY), 58 ENV_ITEM(PAM_RHOST), 59 ENV_ITEM(PAM_RUSER), 60 }; 61 62 struct pe_opts { 63 int return_prog_exit_status; 64 }; 65 66 #define PAM_RV_COUNT 24 67 68 static int 69 parse_options(const char *func, int *argc, const char **argv[], 70 struct pe_opts *options) 71 { 72 int i; 73 74 /* 75 * Parse options: 76 * return_prog_exit_status: 77 * use the program exit status as the return code of pam_exec 78 * --: 79 * stop options parsing; what follows is the command to execute 80 */ 81 options->return_prog_exit_status = 0; 82 83 for (i = 0; i < *argc; ++i) { 84 if (strcmp((*argv)[i], "return_prog_exit_status") == 0) { 85 openpam_log(PAM_LOG_DEBUG, 86 "%s: Option \"return_prog_exit_status\" enabled", 87 func); 88 options->return_prog_exit_status = 1; 89 } else { 90 if (strcmp((*argv)[i], "--") == 0) { 91 (*argc)--; 92 (*argv)++; 93 } 94 95 break; 96 } 97 } 98 99 (*argc) -= i; 100 (*argv) += i; 101 102 return (0); 103 } 104 105 static int 106 _pam_exec(pam_handle_t *pamh __unused, 107 const char *func, int flags __unused, int argc, const char *argv[], 108 struct pe_opts *options) 109 { 110 int envlen, i, nitems, pam_err, status; 111 int nitems_rv; 112 char **envlist, **tmp, *envstr; 113 volatile int childerr; 114 pid_t pid; 115 116 /* 117 * XXX For additional credit, divert child's stdin/stdout/stderr 118 * to the conversation function. 119 */ 120 121 /* Check there's a program name left after parsing options. */ 122 if (argc < 1) { 123 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting", 124 func); 125 return (PAM_SERVICE_ERR); 126 } 127 128 /* 129 * Set up the child's environment list. It consists of the PAM 130 * environment, plus a few hand-picked PAM items, the pam_sm_* 131 * function name calling it and, if return_prog_exit_status is 132 * set, the valid return codes numerical values. 133 */ 134 envlist = pam_getenvlist(pamh); 135 for (envlen = 0; envlist[envlen] != NULL; ++envlen) 136 /* nothing */ ; 137 nitems = sizeof(env_items) / sizeof(*env_items); 138 /* Count PAM return values put in the environment. */ 139 nitems_rv = options->return_prog_exit_status ? PAM_RV_COUNT : 0; 140 tmp = realloc(envlist, (envlen + nitems + 1 + nitems_rv + 1) * 141 sizeof(*envlist)); 142 if (tmp == NULL) { 143 openpam_free_envlist(envlist); 144 return (PAM_BUF_ERR); 145 } 146 envlist = tmp; 147 for (i = 0; i < nitems; ++i) { 148 const void *item; 149 150 pam_err = pam_get_item(pamh, env_items[i].item, &item); 151 if (pam_err != PAM_SUCCESS || item == NULL) 152 continue; 153 asprintf(&envstr, "%s=%s", env_items[i].name, 154 (const char *)item); 155 if (envstr == NULL) { 156 openpam_free_envlist(envlist); 157 return (PAM_BUF_ERR); 158 } 159 envlist[envlen++] = envstr; 160 envlist[envlen] = NULL; 161 } 162 163 /* Add the pam_sm_* function name to the environment. */ 164 asprintf(&envstr, "PAM_SM_FUNC=%s", func); 165 if (envstr == NULL) { 166 openpam_free_envlist(envlist); 167 return (PAM_BUF_ERR); 168 } 169 envlist[envlen++] = envstr; 170 171 /* Add the PAM return values to the environment. */ 172 if (options->return_prog_exit_status) { 173 #define ADD_PAM_RV_TO_ENV(name) \ 174 asprintf(&envstr, #name "=%d", name); \ 175 if (envstr == NULL) { \ 176 openpam_free_envlist(envlist); \ 177 return (PAM_BUF_ERR); \ 178 } \ 179 envlist[envlen++] = envstr 180 /* 181 * CAUTION: When adding/removing an item in the list 182 * below, be sure to update the value of PAM_RV_COUNT. 183 */ 184 ADD_PAM_RV_TO_ENV(PAM_ABORT); 185 ADD_PAM_RV_TO_ENV(PAM_ACCT_EXPIRED); 186 ADD_PAM_RV_TO_ENV(PAM_AUTHINFO_UNAVAIL); 187 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_DISABLE_AGING); 188 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_ERR); 189 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_LOCK_BUSY); 190 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_RECOVERY_ERR); 191 ADD_PAM_RV_TO_ENV(PAM_AUTH_ERR); 192 ADD_PAM_RV_TO_ENV(PAM_BUF_ERR); 193 ADD_PAM_RV_TO_ENV(PAM_CONV_ERR); 194 ADD_PAM_RV_TO_ENV(PAM_CRED_ERR); 195 ADD_PAM_RV_TO_ENV(PAM_CRED_EXPIRED); 196 ADD_PAM_RV_TO_ENV(PAM_CRED_INSUFFICIENT); 197 ADD_PAM_RV_TO_ENV(PAM_CRED_UNAVAIL); 198 ADD_PAM_RV_TO_ENV(PAM_IGNORE); 199 ADD_PAM_RV_TO_ENV(PAM_MAXTRIES); 200 ADD_PAM_RV_TO_ENV(PAM_NEW_AUTHTOK_REQD); 201 ADD_PAM_RV_TO_ENV(PAM_PERM_DENIED); 202 ADD_PAM_RV_TO_ENV(PAM_SERVICE_ERR); 203 ADD_PAM_RV_TO_ENV(PAM_SESSION_ERR); 204 ADD_PAM_RV_TO_ENV(PAM_SUCCESS); 205 ADD_PAM_RV_TO_ENV(PAM_SYSTEM_ERR); 206 ADD_PAM_RV_TO_ENV(PAM_TRY_AGAIN); 207 ADD_PAM_RV_TO_ENV(PAM_USER_UNKNOWN); 208 } 209 210 envlist[envlen] = NULL; 211 212 /* 213 * Fork and run the command. By using vfork() instead of fork(), 214 * we can distinguish between an execve() failure and a non-zero 215 * exit status from the command. 216 */ 217 childerr = 0; 218 if ((pid = vfork()) == 0) { 219 execve(argv[0], (char * const *)argv, (char * const *)envlist); 220 childerr = errno; 221 _exit(1); 222 } 223 openpam_free_envlist(envlist); 224 if (pid == -1) { 225 openpam_log(PAM_LOG_ERROR, "%s: vfork(): %m", func); 226 return (PAM_SYSTEM_ERR); 227 } 228 while (waitpid(pid, &status, 0) == -1) { 229 if (errno == EINTR) 230 continue; 231 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func); 232 return (PAM_SYSTEM_ERR); 233 } 234 if (childerr != 0) { 235 openpam_log(PAM_LOG_ERROR, "%s: execve(): %m", func); 236 return (PAM_SYSTEM_ERR); 237 } 238 if (WIFSIGNALED(status)) { 239 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s", 240 func, argv[0], WTERMSIG(status), 241 WCOREDUMP(status) ? " (core dumped)" : ""); 242 return (PAM_SERVICE_ERR); 243 } 244 if (!WIFEXITED(status)) { 245 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x", 246 func, status); 247 return (PAM_SERVICE_ERR); 248 } 249 250 if (options->return_prog_exit_status) { 251 openpam_log(PAM_LOG_DEBUG, 252 "%s: Use program exit status as return value: %d", 253 func, WEXITSTATUS(status)); 254 return (WEXITSTATUS(status)); 255 } else { 256 return (WEXITSTATUS(status) == 0 ? 257 PAM_SUCCESS : PAM_PERM_DENIED); 258 } 259 } 260 261 PAM_EXTERN int 262 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 263 { 264 int ret; 265 struct pe_opts options; 266 267 ret = parse_options(__func__, &argc, &argv, &options); 268 if (ret != 0) 269 return (PAM_SERVICE_ERR); 270 271 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 272 273 /* 274 * We must check that the program returned a valid code for this 275 * function. 276 */ 277 switch (ret) { 278 case PAM_SUCCESS: 279 case PAM_ABORT: 280 case PAM_AUTHINFO_UNAVAIL: 281 case PAM_AUTH_ERR: 282 case PAM_BUF_ERR: 283 case PAM_CONV_ERR: 284 case PAM_CRED_INSUFFICIENT: 285 case PAM_IGNORE: 286 case PAM_MAXTRIES: 287 case PAM_PERM_DENIED: 288 case PAM_SERVICE_ERR: 289 case PAM_SYSTEM_ERR: 290 case PAM_USER_UNKNOWN: 291 break; 292 default: 293 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 294 argv[0], ret); 295 ret = PAM_SERVICE_ERR; 296 } 297 298 return (ret); 299 } 300 301 PAM_EXTERN int 302 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 303 { 304 int ret; 305 struct pe_opts options; 306 307 ret = parse_options(__func__, &argc, &argv, &options); 308 if (ret != 0) 309 return (PAM_SERVICE_ERR); 310 311 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 312 313 /* 314 * We must check that the program returned a valid code for this 315 * function. 316 */ 317 switch (ret) { 318 case PAM_SUCCESS: 319 case PAM_ABORT: 320 case PAM_BUF_ERR: 321 case PAM_CONV_ERR: 322 case PAM_CRED_ERR: 323 case PAM_CRED_EXPIRED: 324 case PAM_CRED_UNAVAIL: 325 case PAM_IGNORE: 326 case PAM_PERM_DENIED: 327 case PAM_SERVICE_ERR: 328 case PAM_SYSTEM_ERR: 329 case PAM_USER_UNKNOWN: 330 break; 331 default: 332 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 333 argv[0], ret); 334 ret = PAM_SERVICE_ERR; 335 } 336 337 return (ret); 338 } 339 340 PAM_EXTERN int 341 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 342 { 343 int ret; 344 struct pe_opts options; 345 346 ret = parse_options(__func__, &argc, &argv, &options); 347 if (ret != 0) 348 return (PAM_SERVICE_ERR); 349 350 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 351 352 /* 353 * We must check that the program returned a valid code for this 354 * function. 355 */ 356 switch (ret) { 357 case PAM_SUCCESS: 358 case PAM_ABORT: 359 case PAM_ACCT_EXPIRED: 360 case PAM_AUTH_ERR: 361 case PAM_BUF_ERR: 362 case PAM_CONV_ERR: 363 case PAM_IGNORE: 364 case PAM_NEW_AUTHTOK_REQD: 365 case PAM_PERM_DENIED: 366 case PAM_SERVICE_ERR: 367 case PAM_SYSTEM_ERR: 368 case PAM_USER_UNKNOWN: 369 break; 370 default: 371 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 372 argv[0], ret); 373 ret = PAM_SERVICE_ERR; 374 } 375 376 return (ret); 377 } 378 379 PAM_EXTERN int 380 pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 381 { 382 int ret; 383 struct pe_opts options; 384 385 ret = parse_options(__func__, &argc, &argv, &options); 386 if (ret != 0) 387 return (PAM_SERVICE_ERR); 388 389 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 390 391 /* 392 * We must check that the program returned a valid code for this 393 * function. 394 */ 395 switch (ret) { 396 case PAM_SUCCESS: 397 case PAM_ABORT: 398 case PAM_BUF_ERR: 399 case PAM_CONV_ERR: 400 case PAM_IGNORE: 401 case PAM_PERM_DENIED: 402 case PAM_SERVICE_ERR: 403 case PAM_SESSION_ERR: 404 case PAM_SYSTEM_ERR: 405 break; 406 default: 407 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 408 argv[0], ret); 409 ret = PAM_SERVICE_ERR; 410 } 411 412 return (ret); 413 } 414 415 PAM_EXTERN int 416 pam_sm_close_session(pam_handle_t *pamh, int flags, 417 int argc, const char *argv[]) 418 { 419 int ret; 420 struct pe_opts options; 421 422 ret = parse_options(__func__, &argc, &argv, &options); 423 if (ret != 0) 424 return (PAM_SERVICE_ERR); 425 426 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 427 428 /* 429 * We must check that the program returned a valid code for this 430 * function. 431 */ 432 switch (ret) { 433 case PAM_SUCCESS: 434 case PAM_ABORT: 435 case PAM_BUF_ERR: 436 case PAM_CONV_ERR: 437 case PAM_IGNORE: 438 case PAM_PERM_DENIED: 439 case PAM_SERVICE_ERR: 440 case PAM_SESSION_ERR: 441 case PAM_SYSTEM_ERR: 442 break; 443 default: 444 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 445 argv[0], ret); 446 ret = PAM_SERVICE_ERR; 447 } 448 449 return (ret); 450 } 451 452 PAM_EXTERN int 453 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 454 { 455 int ret; 456 struct pe_opts options; 457 458 ret = parse_options(__func__, &argc, &argv, &options); 459 if (ret != 0) 460 return (PAM_SERVICE_ERR); 461 462 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options); 463 464 /* 465 * We must check that the program returned a valid code for this 466 * function. 467 */ 468 switch (ret) { 469 case PAM_SUCCESS: 470 case PAM_ABORT: 471 case PAM_AUTHTOK_DISABLE_AGING: 472 case PAM_AUTHTOK_ERR: 473 case PAM_AUTHTOK_LOCK_BUSY: 474 case PAM_AUTHTOK_RECOVERY_ERR: 475 case PAM_BUF_ERR: 476 case PAM_CONV_ERR: 477 case PAM_IGNORE: 478 case PAM_PERM_DENIED: 479 case PAM_SERVICE_ERR: 480 case PAM_SYSTEM_ERR: 481 case PAM_TRY_AGAIN: 482 break; 483 default: 484 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 485 argv[0], ret); 486 ret = PAM_SERVICE_ERR; 487 } 488 489 return (ret); 490 } 491 492 PAM_MODULE_ENTRY("pam_exec"); 493