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