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