xref: /freebsd/lib/libpam/modules/pam_exec/pam_exec.c (revision 9768746b)
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 			if (authtok == NULL && rc == PAM_SUCCESS) {
265 				openpam_log(PAM_LOG_ERROR,
266 				    "%s: pam_get_authtok(): %s",
267 				    func, "authentication token not available");
268 				OUT(PAM_SYSTEM_ERR);
269 			}
270 
271 		} else {
272 			rc = pam_get_authtok(pamh, PAM_AUTHTOK, &authtok, NULL);
273 		}
274 		if (rc == PAM_SUCCESS) {
275 			/* We include the trailing null terminator. */
276 			authtok_size = strlen(authtok) + 1;
277 		} else {
278 			openpam_log(PAM_LOG_ERROR, "%s: pam_get_authtok(): %s",
279 			    func, pam_strerror(pamh, rc));
280 			OUT(PAM_SYSTEM_ERR);
281 		}
282 	}
283 	/* set up pipes if capture was requested */
284 	if (options->capture_stdout) {
285 		if (pipe(chout) != 0) {
286 			openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
287 			OUT(PAM_SYSTEM_ERR);
288 		}
289 		if (fcntl(chout[0], F_SETFL, O_NONBLOCK) != 0) {
290 			openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
291 			OUT(PAM_SYSTEM_ERR);
292 		}
293 	} else {
294 		if ((chout[1] = open("/dev/null", O_RDWR)) < 0) {
295 			openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
296 			OUT(PAM_SYSTEM_ERR);
297 		}
298 	}
299 	if (options->capture_stderr) {
300 		if (pipe(cherr) != 0) {
301 			openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
302 			OUT(PAM_SYSTEM_ERR);
303 		}
304 		if (fcntl(cherr[0], F_SETFL, O_NONBLOCK) != 0) {
305 			openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
306 			OUT(PAM_SYSTEM_ERR);
307 		}
308 	} else {
309 		if ((cherr[1] = open("/dev/null", O_RDWR)) < 0) {
310 			openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
311 			OUT(PAM_SYSTEM_ERR);
312 		}
313 	}
314 
315 	if ((pid = pdfork(&pd, 0)) == 0) {
316 		/* child */
317 		if ((chin[1] >= 0 && close(chin[1]) != 0) ||
318 			(chout[0] >= 0 && close(chout[0]) != 0) ||
319 		    (cherr[0] >= 0 && close(cherr[0]) != 0)) {
320 			openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func);
321 		} else if (chin[0] >= 0 &&
322 			dup2(chin[0], STDIN_FILENO) != STDIN_FILENO) {
323 			openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
324 		} else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO ||
325 		    dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) {
326 			openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
327 		} else {
328 			execve(argv[0], (char * const *)argv,
329 			    (char * const *)envlist);
330 			openpam_log(PAM_LOG_ERROR, "%s: execve(%s): %m",
331 			    func, argv[0]);
332 		}
333 		_exit(1);
334 	}
335 	/* parent */
336 	if (pid == -1) {
337 		openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func);
338 		OUT(PAM_SYSTEM_ERR);
339 	}
340 	/* use poll() to watch the process and stdin / stdout / stderr */
341 	if (chin[0] >= 0)
342 		close(chin[0]);
343 	if (chout[1] >= 0)
344 		close(chout[1]);
345 	if (cherr[1] >= 0)
346 		close(cherr[1]);
347 	memset(pfd, 0, sizeof pfd);
348 	pfd[0].fd = pd;
349 	pfd[0].events = POLLHUP;
350 	nfds = 1;
351 	nreadfds = 0;
352 	if (options->capture_stdout) {
353 		pfd[nfds].fd = chout[0];
354 		pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
355 		nfds++;
356 		nreadfds++;
357 	}
358 	if (options->capture_stderr) {
359 		pfd[nfds].fd = cherr[0];
360 		pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
361 		nfds++;
362 		nreadfds++;
363 	}
364 	if (options->expose_authtok) {
365 		pfd[nfds].fd = chin[1];
366 		pfd[nfds].events = POLLOUT|POLLERR|POLLHUP;
367 		nfds++;
368 	}
369 
370 	/* loop until the process exits */
371 	do {
372 		if (poll(pfd, nfds, INFTIM) < 0) {
373 			openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func);
374 			OUT(PAM_SYSTEM_ERR);
375 		}
376 		/* are the stderr / stdout pipes ready for reading? */
377 		for (i = 1; i < 1 + nreadfds; ++i) {
378 			if ((pfd[i].revents & POLLIN) == 0)
379 				continue;
380 			if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) {
381 				openpam_log(PAM_LOG_ERROR, "%s: read(): %m",
382 				    func);
383 				OUT(PAM_SYSTEM_ERR);
384 			} else if (rlen == 0) {
385 				continue;
386 			}
387 			buf[rlen] = '\0';
388 			(void)pam_prompt(pamh, pfd[i].fd == chout[0] ?
389 			    PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf);
390 		}
391 		/* is the stdin pipe ready for writing? */
392 		if (options->expose_authtok && authtok_size > 0 &&
393 			(pfd[nfds - 1].revents & POLLOUT) != 0) {
394 			if ((wlen = write(chin[1], authtok, authtok_size)) < 0) {
395 				if (errno == EAGAIN)
396 					continue;
397 				openpam_log(PAM_LOG_ERROR, "%s: write(): %m",
398 				    func);
399 				OUT(PAM_SYSTEM_ERR);
400 			} else {
401 				authtok += wlen;
402 				authtok_size -= wlen;
403 				if (authtok_size == 0) {
404 					/* finished writing; close and forget the pipe */
405 					close(chin[1]);
406 					chin[1] = -1;
407 					nfds--;
408 				}
409 			}
410 		}
411 	} while (pfd[0].revents == 0);
412 
413 	/* the child process has exited */
414 	while (waitpid(pid, &status, 0) == -1) {
415 		if (errno == EINTR)
416 			continue;
417 		openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
418 		OUT(PAM_SYSTEM_ERR);
419 	}
420 
421 	/* check exit code */
422 	if (WIFSIGNALED(status)) {
423 		openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
424 		    func, argv[0], WTERMSIG(status),
425 		    WCOREDUMP(status) ? " (core dumped)" : "");
426 		OUT(PAM_SERVICE_ERR);
427 	}
428 	if (!WIFEXITED(status)) {
429 		openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
430 		    func, status);
431 		OUT(PAM_SERVICE_ERR);
432 	}
433 
434 	if (options->return_prog_exit_status) {
435 		openpam_log(PAM_LOG_DEBUG,
436 		    "%s: Use program exit status as return value: %d",
437 		    func, WEXITSTATUS(status));
438 		OUT(WEXITSTATUS(status));
439 	} else {
440 		OUT(WEXITSTATUS(status) == 0 ? PAM_SUCCESS : PAM_PERM_DENIED);
441 	}
442 	/* unreachable */
443 out:
444 	serrno = errno;
445 	if (pd >= 0)
446 		close(pd);
447 	if (chin[0] >= 0)
448 		close(chin[0]);
449 	if (chin[1] >= 0)
450 		close(chin[1]);
451 	if (chout[0] >= 0)
452 		close(chout[0]);
453 	if (chout[1] >= 0)
454 		close(chout[1]);
455 	if (cherr[0] >= 0)
456 		close(cherr[0]);
457 	if (cherr[0] >= 0)
458 		close(cherr[1]);
459 	if (envlist != NULL)
460 		openpam_free_envlist(envlist);
461 	errno = serrno;
462 	return (pam_err);
463 }
464 
465 PAM_EXTERN int
466 pam_sm_authenticate(pam_handle_t *pamh, int flags,
467     int argc, const char *argv[])
468 {
469 	int ret;
470 	struct pe_opts options;
471 
472 	ret = parse_options(__func__, &argc, &argv, &options);
473 	if (ret != 0)
474 		return (PAM_SERVICE_ERR);
475 
476 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
477 
478 	/*
479 	 * We must check that the program returned a valid code for this
480 	 * function.
481 	 */
482 	switch (ret) {
483 	case PAM_SUCCESS:
484 	case PAM_ABORT:
485 	case PAM_AUTHINFO_UNAVAIL:
486 	case PAM_AUTH_ERR:
487 	case PAM_BUF_ERR:
488 	case PAM_CONV_ERR:
489 	case PAM_CRED_INSUFFICIENT:
490 	case PAM_IGNORE:
491 	case PAM_MAXTRIES:
492 	case PAM_PERM_DENIED:
493 	case PAM_SERVICE_ERR:
494 	case PAM_SYSTEM_ERR:
495 	case PAM_USER_UNKNOWN:
496 		break;
497 	default:
498 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
499 		    argv[0], ret);
500 		ret = PAM_SERVICE_ERR;
501 	}
502 
503 	return (ret);
504 }
505 
506 PAM_EXTERN int
507 pam_sm_setcred(pam_handle_t *pamh, int flags,
508     int argc, const char *argv[])
509 {
510 	int ret;
511 	struct pe_opts options;
512 
513 	ret = parse_options(__func__, &argc, &argv, &options);
514 	if (ret != 0)
515 		return (PAM_SERVICE_ERR);
516 
517 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
518 
519 	/*
520 	 * We must check that the program returned a valid code for this
521 	 * function.
522 	 */
523 	switch (ret) {
524 	case PAM_SUCCESS:
525 	case PAM_ABORT:
526 	case PAM_BUF_ERR:
527 	case PAM_CONV_ERR:
528 	case PAM_CRED_ERR:
529 	case PAM_CRED_EXPIRED:
530 	case PAM_CRED_UNAVAIL:
531 	case PAM_IGNORE:
532 	case PAM_PERM_DENIED:
533 	case PAM_SERVICE_ERR:
534 	case PAM_SYSTEM_ERR:
535 	case PAM_USER_UNKNOWN:
536 		break;
537 	default:
538 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
539 		    argv[0], ret);
540 		ret = PAM_SERVICE_ERR;
541 	}
542 
543 	return (ret);
544 }
545 
546 PAM_EXTERN int
547 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
548     int argc, const char *argv[])
549 {
550 	int ret;
551 	struct pe_opts options;
552 
553 	ret = parse_options(__func__, &argc, &argv, &options);
554 	if (ret != 0)
555 		return (PAM_SERVICE_ERR);
556 
557 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
558 
559 	/*
560 	 * We must check that the program returned a valid code for this
561 	 * function.
562 	 */
563 	switch (ret) {
564 	case PAM_SUCCESS:
565 	case PAM_ABORT:
566 	case PAM_ACCT_EXPIRED:
567 	case PAM_AUTH_ERR:
568 	case PAM_BUF_ERR:
569 	case PAM_CONV_ERR:
570 	case PAM_IGNORE:
571 	case PAM_NEW_AUTHTOK_REQD:
572 	case PAM_PERM_DENIED:
573 	case PAM_SERVICE_ERR:
574 	case PAM_SYSTEM_ERR:
575 	case PAM_USER_UNKNOWN:
576 		break;
577 	default:
578 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
579 		    argv[0], ret);
580 		ret = PAM_SERVICE_ERR;
581 	}
582 
583 	return (ret);
584 }
585 
586 PAM_EXTERN int
587 pam_sm_open_session(pam_handle_t *pamh, int flags,
588     int argc, const char *argv[])
589 {
590 	int ret;
591 	struct pe_opts options;
592 
593 	ret = parse_options(__func__, &argc, &argv, &options);
594 	if (ret != 0)
595 		return (PAM_SERVICE_ERR);
596 
597 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
598 
599 	/*
600 	 * We must check that the program returned a valid code for this
601 	 * function.
602 	 */
603 	switch (ret) {
604 	case PAM_SUCCESS:
605 	case PAM_ABORT:
606 	case PAM_BUF_ERR:
607 	case PAM_CONV_ERR:
608 	case PAM_IGNORE:
609 	case PAM_PERM_DENIED:
610 	case PAM_SERVICE_ERR:
611 	case PAM_SESSION_ERR:
612 	case PAM_SYSTEM_ERR:
613 		break;
614 	default:
615 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
616 		    argv[0], ret);
617 		ret = PAM_SERVICE_ERR;
618 	}
619 
620 	return (ret);
621 }
622 
623 PAM_EXTERN int
624 pam_sm_close_session(pam_handle_t *pamh, int flags,
625     int argc, const char *argv[])
626 {
627 	int ret;
628 	struct pe_opts options;
629 
630 	ret = parse_options(__func__, &argc, &argv, &options);
631 	if (ret != 0)
632 		return (PAM_SERVICE_ERR);
633 
634 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
635 
636 	/*
637 	 * We must check that the program returned a valid code for this
638 	 * function.
639 	 */
640 	switch (ret) {
641 	case PAM_SUCCESS:
642 	case PAM_ABORT:
643 	case PAM_BUF_ERR:
644 	case PAM_CONV_ERR:
645 	case PAM_IGNORE:
646 	case PAM_PERM_DENIED:
647 	case PAM_SERVICE_ERR:
648 	case PAM_SESSION_ERR:
649 	case PAM_SYSTEM_ERR:
650 		break;
651 	default:
652 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
653 		    argv[0], ret);
654 		ret = PAM_SERVICE_ERR;
655 	}
656 
657 	return (ret);
658 }
659 
660 PAM_EXTERN int
661 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
662     int argc, const char *argv[])
663 {
664 	int ret;
665 	struct pe_opts options;
666 
667 	ret = parse_options(__func__, &argc, &argv, &options);
668 	if (ret != 0)
669 		return (PAM_SERVICE_ERR);
670 
671 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
672 
673 	/*
674 	 * We must check that the program returned a valid code for this
675 	 * function.
676 	 */
677 	switch (ret) {
678 	case PAM_SUCCESS:
679 	case PAM_ABORT:
680 	case PAM_AUTHTOK_DISABLE_AGING:
681 	case PAM_AUTHTOK_ERR:
682 	case PAM_AUTHTOK_LOCK_BUSY:
683 	case PAM_AUTHTOK_RECOVERY_ERR:
684 	case PAM_BUF_ERR:
685 	case PAM_CONV_ERR:
686 	case PAM_IGNORE:
687 	case PAM_PERM_DENIED:
688 	case PAM_SERVICE_ERR:
689 	case PAM_SYSTEM_ERR:
690 	case PAM_TRY_AGAIN:
691 		break;
692 	default:
693 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
694 		    argv[0], ret);
695 		ret = PAM_SERVICE_ERR;
696 	}
697 
698 	return (ret);
699 }
700 
701 PAM_MODULE_ENTRY("pam_exec");
702