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 #define PAM_RV_COUNT 24 64 65 static int 66 _pam_exec(pam_handle_t *pamh __unused, 67 const char *func, int flags __unused, int argc, const char *argv[]) 68 { 69 int envlen, i, nitems, pam_err, status, return_prog_exit_status; 70 int nitems_rv; 71 char *env, **envlist, **tmp, *envstr; 72 volatile int childerr; 73 pid_t pid; 74 75 /* 76 * XXX For additional credit, divert child's stdin/stdout/stderr 77 * to the conversation function. 78 */ 79 80 /* 81 * Parse options: 82 * return_prog_exit_status: 83 * use the program exit status as the return code of pam_exec 84 * --: 85 * stop options parsing; what follows is the command to execute 86 */ 87 return_prog_exit_status = 0; 88 for (i = 0; i < argc; ++i) { 89 if (strcmp(argv[i], "return_prog_exit_status") == 0) { 90 openpam_log(PAM_LOG_DEBUG, 91 "%s: Option \"return_prog_exit_status\" enabled", 92 func); 93 return_prog_exit_status = 1; 94 } else { 95 if (strcmp(argv[i], "--") == 0) { 96 argc--; 97 argv++; 98 } 99 100 break; 101 } 102 } 103 104 argc -= i; 105 argv += i; 106 107 /* Check there's a program name left after parsing options. */ 108 if (argc < 1) { 109 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting", 110 func); 111 return (PAM_SERVICE_ERR); 112 } 113 114 /* 115 * Set up the child's environment list. It consists of the PAM 116 * environment, plus a few hand-picked PAM items, the pam_sm_* 117 * function name calling it and, if return_prog_exit_status is 118 * set, the valid return codes numerical values. 119 */ 120 envlist = pam_getenvlist(pamh); 121 for (envlen = 0; envlist[envlen] != NULL; ++envlen) 122 /* nothing */ ; 123 nitems = sizeof(env_items) / sizeof(*env_items); 124 /* Count PAM return values put in the environment. */ 125 nitems_rv = return_prog_exit_status ? PAM_RV_COUNT : 0; 126 tmp = realloc(envlist, (envlen + nitems + 1 + nitems_rv + 1) * 127 sizeof(*envlist)); 128 if (tmp == NULL) { 129 openpam_free_envlist(envlist); 130 return (PAM_BUF_ERR); 131 } 132 envlist = tmp; 133 for (i = 0; i < nitems; ++i) { 134 const void *item; 135 136 pam_err = pam_get_item(pamh, env_items[i].item, &item); 137 if (pam_err != PAM_SUCCESS || item == NULL) 138 continue; 139 asprintf(&envstr, "%s=%s", env_items[i].name, item); 140 if (envstr == NULL) { 141 openpam_free_envlist(envlist); 142 return (PAM_BUF_ERR); 143 } 144 envlist[envlen++] = envstr; 145 envlist[envlen] = NULL; 146 } 147 148 /* Add the pam_sm_* function name to the environment. */ 149 asprintf(&envstr, "PAM_SM_FUNC=%s", func); 150 if (envstr == NULL) { 151 openpam_free_envlist(envlist); 152 return (PAM_BUF_ERR); 153 } 154 envlist[envlen++] = envstr; 155 156 /* Add the PAM return values to the environment. */ 157 if (return_prog_exit_status) { 158 #define ADD_PAM_RV_TO_ENV(name) \ 159 asprintf(&envstr, #name "=%d", name); \ 160 if (envstr == NULL) { \ 161 openpam_free_envlist(envlist); \ 162 return (PAM_BUF_ERR); \ 163 } \ 164 envlist[envlen++] = envstr 165 /* 166 * CAUTION: When adding/removing an item in the list 167 * below, be sure to update the value of PAM_RV_COUNT. 168 */ 169 ADD_PAM_RV_TO_ENV(PAM_ABORT); 170 ADD_PAM_RV_TO_ENV(PAM_ACCT_EXPIRED); 171 ADD_PAM_RV_TO_ENV(PAM_AUTHINFO_UNAVAIL); 172 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_DISABLE_AGING); 173 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_ERR); 174 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_LOCK_BUSY); 175 ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_RECOVERY_ERR); 176 ADD_PAM_RV_TO_ENV(PAM_AUTH_ERR); 177 ADD_PAM_RV_TO_ENV(PAM_BUF_ERR); 178 ADD_PAM_RV_TO_ENV(PAM_CONV_ERR); 179 ADD_PAM_RV_TO_ENV(PAM_CRED_ERR); 180 ADD_PAM_RV_TO_ENV(PAM_CRED_EXPIRED); 181 ADD_PAM_RV_TO_ENV(PAM_CRED_INSUFFICIENT); 182 ADD_PAM_RV_TO_ENV(PAM_CRED_UNAVAIL); 183 ADD_PAM_RV_TO_ENV(PAM_IGNORE); 184 ADD_PAM_RV_TO_ENV(PAM_MAXTRIES); 185 ADD_PAM_RV_TO_ENV(PAM_NEW_AUTHTOK_REQD); 186 ADD_PAM_RV_TO_ENV(PAM_PERM_DENIED); 187 ADD_PAM_RV_TO_ENV(PAM_SERVICE_ERR); 188 ADD_PAM_RV_TO_ENV(PAM_SESSION_ERR); 189 ADD_PAM_RV_TO_ENV(PAM_SUCCESS); 190 ADD_PAM_RV_TO_ENV(PAM_SYSTEM_ERR); 191 ADD_PAM_RV_TO_ENV(PAM_TRY_AGAIN); 192 ADD_PAM_RV_TO_ENV(PAM_USER_UNKNOWN); 193 } 194 195 envlist[envlen] = NULL; 196 197 /* 198 * Fork and run the command. By using vfork() instead of fork(), 199 * we can distinguish between an execve() failure and a non-zero 200 * exit status from the command. 201 */ 202 childerr = 0; 203 if ((pid = vfork()) == 0) { 204 execve(argv[0], (char * const *)argv, (char * const *)envlist); 205 childerr = errno; 206 _exit(1); 207 } 208 openpam_free_envlist(envlist); 209 if (pid == -1) { 210 openpam_log(PAM_LOG_ERROR, "%s: vfork(): %m", func); 211 return (PAM_SYSTEM_ERR); 212 } 213 while (waitpid(pid, &status, 0) == -1) { 214 if (errno == EINTR) 215 continue; 216 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func); 217 return (PAM_SYSTEM_ERR); 218 } 219 if (childerr != 0) { 220 openpam_log(PAM_LOG_ERROR, "%s: execve(): %m", func); 221 return (PAM_SYSTEM_ERR); 222 } 223 if (WIFSIGNALED(status)) { 224 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s", 225 func, argv[0], WTERMSIG(status), 226 WCOREDUMP(status) ? " (core dumped)" : ""); 227 return (PAM_SERVICE_ERR); 228 } 229 if (!WIFEXITED(status)) { 230 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x", 231 func, status); 232 return (PAM_SERVICE_ERR); 233 } 234 235 if (return_prog_exit_status) { 236 openpam_log(PAM_LOG_DEBUG, 237 "%s: Use program exit status as return value: %d", 238 func, WEXITSTATUS(status)); 239 return (WEXITSTATUS(status)); 240 } else { 241 return (WEXITSTATUS(status) == 0 ? 242 PAM_SUCCESS : PAM_PERM_DENIED); 243 } 244 } 245 246 PAM_EXTERN int 247 pam_sm_authenticate(pam_handle_t *pamh, int flags, 248 int argc, const char *argv[]) 249 { 250 int ret; 251 252 ret = _pam_exec(pamh, __func__, flags, argc, argv); 253 254 /* 255 * We must check that the program returned a valid code for this 256 * function. 257 */ 258 switch (ret) { 259 case PAM_SUCCESS: 260 case PAM_ABORT: 261 case PAM_AUTHINFO_UNAVAIL: 262 case PAM_AUTH_ERR: 263 case PAM_BUF_ERR: 264 case PAM_CONV_ERR: 265 case PAM_CRED_INSUFFICIENT: 266 case PAM_IGNORE: 267 case PAM_MAXTRIES: 268 case PAM_PERM_DENIED: 269 case PAM_SERVICE_ERR: 270 case PAM_SYSTEM_ERR: 271 case PAM_USER_UNKNOWN: 272 break; 273 default: 274 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 275 argv[0], ret); 276 ret = PAM_SERVICE_ERR; 277 } 278 279 return (ret); 280 } 281 282 PAM_EXTERN int 283 pam_sm_setcred(pam_handle_t *pamh, int flags, 284 int argc, const char *argv[]) 285 { 286 int ret; 287 288 ret = _pam_exec(pamh, __func__, flags, argc, argv); 289 290 /* 291 * We must check that the program returned a valid code for this 292 * function. 293 */ 294 switch (ret) { 295 case PAM_SUCCESS: 296 case PAM_ABORT: 297 case PAM_BUF_ERR: 298 case PAM_CONV_ERR: 299 case PAM_CRED_ERR: 300 case PAM_CRED_EXPIRED: 301 case PAM_CRED_UNAVAIL: 302 case PAM_IGNORE: 303 case PAM_PERM_DENIED: 304 case PAM_SERVICE_ERR: 305 case PAM_SYSTEM_ERR: 306 case PAM_USER_UNKNOWN: 307 break; 308 default: 309 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 310 argv[0], ret); 311 ret = PAM_SERVICE_ERR; 312 } 313 314 return (ret); 315 } 316 317 PAM_EXTERN int 318 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, 319 int argc, const char *argv[]) 320 { 321 int ret; 322 323 ret = _pam_exec(pamh, __func__, flags, argc, argv); 324 325 /* 326 * We must check that the program returned a valid code for this 327 * function. 328 */ 329 switch (ret) { 330 case PAM_SUCCESS: 331 case PAM_ABORT: 332 case PAM_ACCT_EXPIRED: 333 case PAM_AUTH_ERR: 334 case PAM_BUF_ERR: 335 case PAM_CONV_ERR: 336 case PAM_IGNORE: 337 case PAM_NEW_AUTHTOK_REQD: 338 case PAM_PERM_DENIED: 339 case PAM_SERVICE_ERR: 340 case PAM_SYSTEM_ERR: 341 case PAM_USER_UNKNOWN: 342 break; 343 default: 344 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 345 argv[0], ret); 346 ret = PAM_SERVICE_ERR; 347 } 348 349 return (ret); 350 } 351 352 PAM_EXTERN int 353 pam_sm_open_session(pam_handle_t *pamh, int flags, 354 int argc, const char *argv[]) 355 { 356 int ret; 357 358 ret = _pam_exec(pamh, __func__, flags, argc, argv); 359 360 /* 361 * We must check that the program returned a valid code for this 362 * function. 363 */ 364 switch (ret) { 365 case PAM_SUCCESS: 366 case PAM_ABORT: 367 case PAM_BUF_ERR: 368 case PAM_CONV_ERR: 369 case PAM_IGNORE: 370 case PAM_PERM_DENIED: 371 case PAM_SERVICE_ERR: 372 case PAM_SESSION_ERR: 373 case PAM_SYSTEM_ERR: 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_close_session(pam_handle_t *pamh, int flags, 386 int argc, const char *argv[]) 387 { 388 int ret; 389 390 ret = _pam_exec(pamh, __func__, flags, argc, argv); 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_BUF_ERR: 400 case PAM_CONV_ERR: 401 case PAM_IGNORE: 402 case PAM_PERM_DENIED: 403 case PAM_SERVICE_ERR: 404 case PAM_SESSION_ERR: 405 case PAM_SYSTEM_ERR: 406 break; 407 default: 408 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d", 409 argv[0], ret); 410 ret = PAM_SERVICE_ERR; 411 } 412 413 return (ret); 414 } 415 416 PAM_EXTERN int 417 pam_sm_chauthtok(pam_handle_t *pamh, int flags, 418 int argc, const char *argv[]) 419 { 420 int ret; 421 422 ret = _pam_exec(pamh, __func__, flags, argc, argv); 423 424 /* 425 * We must check that the program returned a valid code for this 426 * function. 427 */ 428 switch (ret) { 429 case PAM_SUCCESS: 430 case PAM_ABORT: 431 case PAM_AUTHTOK_DISABLE_AGING: 432 case PAM_AUTHTOK_ERR: 433 case PAM_AUTHTOK_LOCK_BUSY: 434 case PAM_AUTHTOK_RECOVERY_ERR: 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_SYSTEM_ERR: 441 case PAM_TRY_AGAIN: 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_MODULE_ENTRY("pam_exec"); 453