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