xref: /freebsd/lib/libpam/modules/pam_exec/pam_exec.c (revision 4fbb9c43aa44d9145151bb5f77d302ba01fb7551)
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 #include <sys/types.h>
41 #include <sys/poll.h>
42 #include <sys/procdesc.h>
43 #include <sys/wait.h>
44 
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 #include <security/pam_appl.h>
53 #include <security/pam_modules.h>
54 #include <security/openpam.h>
55 
56 #define PAM_ITEM_ENV(n) { (n), #n }
57 static struct {
58 	int item;
59 	const char *name;
60 } pam_item_env[] = {
61 	PAM_ITEM_ENV(PAM_SERVICE),
62 	PAM_ITEM_ENV(PAM_USER),
63 	PAM_ITEM_ENV(PAM_TTY),
64 	PAM_ITEM_ENV(PAM_RHOST),
65 	PAM_ITEM_ENV(PAM_RUSER),
66 };
67 #define NUM_PAM_ITEM_ENV (sizeof(pam_item_env) / sizeof(pam_item_env[0]))
68 
69 #define PAM_ERR_ENV_X(str, num) str "=" #num
70 #define PAM_ERR_ENV(pam_err) PAM_ERR_ENV_X(#pam_err, pam_err)
71 static const char *pam_err_env[] = {
72 	PAM_ERR_ENV(PAM_SUCCESS),
73 	PAM_ERR_ENV(PAM_OPEN_ERR),
74 	PAM_ERR_ENV(PAM_SYMBOL_ERR),
75 	PAM_ERR_ENV(PAM_SERVICE_ERR),
76 	PAM_ERR_ENV(PAM_SYSTEM_ERR),
77 	PAM_ERR_ENV(PAM_BUF_ERR),
78 	PAM_ERR_ENV(PAM_CONV_ERR),
79 	PAM_ERR_ENV(PAM_PERM_DENIED),
80 	PAM_ERR_ENV(PAM_MAXTRIES),
81 	PAM_ERR_ENV(PAM_AUTH_ERR),
82 	PAM_ERR_ENV(PAM_NEW_AUTHTOK_REQD),
83 	PAM_ERR_ENV(PAM_CRED_INSUFFICIENT),
84 	PAM_ERR_ENV(PAM_AUTHINFO_UNAVAIL),
85 	PAM_ERR_ENV(PAM_USER_UNKNOWN),
86 	PAM_ERR_ENV(PAM_CRED_UNAVAIL),
87 	PAM_ERR_ENV(PAM_CRED_EXPIRED),
88 	PAM_ERR_ENV(PAM_CRED_ERR),
89 	PAM_ERR_ENV(PAM_ACCT_EXPIRED),
90 	PAM_ERR_ENV(PAM_AUTHTOK_EXPIRED),
91 	PAM_ERR_ENV(PAM_SESSION_ERR),
92 	PAM_ERR_ENV(PAM_AUTHTOK_ERR),
93 	PAM_ERR_ENV(PAM_AUTHTOK_RECOVERY_ERR),
94 	PAM_ERR_ENV(PAM_AUTHTOK_LOCK_BUSY),
95 	PAM_ERR_ENV(PAM_AUTHTOK_DISABLE_AGING),
96 	PAM_ERR_ENV(PAM_NO_MODULE_DATA),
97 	PAM_ERR_ENV(PAM_IGNORE),
98 	PAM_ERR_ENV(PAM_ABORT),
99 	PAM_ERR_ENV(PAM_TRY_AGAIN),
100 	PAM_ERR_ENV(PAM_MODULE_UNKNOWN),
101 	PAM_ERR_ENV(PAM_DOMAIN_UNKNOWN),
102 	PAM_ERR_ENV(PAM_NUM_ERR),
103 };
104 #define NUM_PAM_ERR_ENV (sizeof(pam_err_env) / sizeof(pam_err_env[0]))
105 
106 struct pe_opts {
107 	int	return_prog_exit_status;
108 	int	capture_stdout;
109 	int	capture_stderr;
110 	int	expose_authtok;
111 	int	use_first_pass;
112 };
113 
114 static int
115 parse_options(const char *func, int *argc, const char **argv[],
116     struct pe_opts *options)
117 {
118 	int i;
119 
120 	/*
121 	 * Parse options:
122 	 *   return_prog_exit_status:
123 	 *     use the program exit status as the return code of pam_exec
124 	 *   --:
125 	 *     stop options parsing; what follows is the command to execute
126 	 */
127 	memset(options, 0, sizeof(*options));
128 
129 	for (i = 0; i < *argc; ++i) {
130 		if (strcmp((*argv)[i], "debug") == 0 ||
131 		    strcmp((*argv)[i], "no_warn") == 0) {
132 			/* ignore */
133 		} else if (strcmp((*argv)[i], "capture_stdout") == 0) {
134 			options->capture_stdout = 1;
135 		} else if (strcmp((*argv)[i], "capture_stderr") == 0) {
136 			options->capture_stderr = 1;
137 		} else if (strcmp((*argv)[i], "return_prog_exit_status") == 0) {
138 			options->return_prog_exit_status = 1;
139 		} else if (strcmp((*argv)[i], "expose_authtok") == 0) {
140 			options->expose_authtok = 1;
141 		} else if (strcmp((*argv)[i], "use_first_pass") == 0) {
142 			options->use_first_pass = 1;
143 		} else {
144 			if (strcmp((*argv)[i], "--") == 0) {
145 				(*argc)--;
146 				(*argv)++;
147 			}
148 			break;
149 		}
150 		openpam_log(PAM_LOG_DEBUG, "%s: option \"%s\" enabled",
151 		    func, (*argv)[i]);
152 	}
153 
154 	(*argc) -= i;
155 	(*argv) += i;
156 
157 	return (0);
158 }
159 
160 static int
161 _pam_exec(pam_handle_t *pamh,
162     const char *func, int flags __unused, int argc, const char *argv[],
163     struct pe_opts *options)
164 {
165 	char buf[PAM_MAX_MSG_SIZE];
166 	struct pollfd pfd[4];
167 	const void *item;
168 	char **envlist, *envstr, *resp, **tmp;
169 	ssize_t rlen, wlen;
170 	int envlen, extralen, i;
171 	int pam_err, serrno, status;
172 	int chin[2], chout[2], cherr[2], pd;
173 	nfds_t nfds, nreadfds;
174 	pid_t pid;
175 	const char *authtok;
176 	size_t authtok_size;
177 	int rc;
178 
179 	pd = -1;
180 	pid = 0;
181 	chin[0] = chin[1] = chout[0] = chout[1] = cherr[0] = cherr[1] = -1;
182 	envlist = NULL;
183 
184 #define OUT(ret) do { pam_err = (ret); goto out; } while (0)
185 
186 	/* Check there's a program name left after parsing options. */
187 	if (argc < 1) {
188 		openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting",
189 		    func);
190 		OUT(PAM_SERVICE_ERR);
191 	}
192 
193 	/*
194 	 * Set up the child's environment list.  It consists of the PAM
195 	 * environment, a few hand-picked PAM items, the name of the
196 	 * service function, and if return_prog_exit_status is set, the
197 	 * numerical values of all PAM error codes.
198 	 */
199 
200 	/* compute the final size of the environment. */
201 	envlist = pam_getenvlist(pamh);
202 	for (envlen = 0; envlist[envlen] != NULL; ++envlen)
203 		/* nothing */ ;
204 	extralen = NUM_PAM_ITEM_ENV + 1;
205 	if (options->return_prog_exit_status)
206 		extralen += NUM_PAM_ERR_ENV;
207 	tmp = reallocarray(envlist, envlen + extralen + 1, sizeof(*envlist));
208 	openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d tmp = %p",
209 	    envlen, extralen, tmp);
210 	if (tmp == NULL)
211 		OUT(PAM_BUF_ERR);
212 	envlist = tmp;
213 	extralen += envlen;
214 
215 	/* copy selected PAM items to the environment */
216 	for (i = 0; i < NUM_PAM_ITEM_ENV; ++i) {
217 		pam_err = pam_get_item(pamh, pam_item_env[i].item, &item);
218 		if (pam_err != PAM_SUCCESS || item == NULL)
219 			continue;
220 		if (asprintf(&envstr, "%s=%s", pam_item_env[i].name,
221 		    (const char *)item) < 0)
222 			OUT(PAM_BUF_ERR);
223 		envlist[envlen++] = envstr;
224 		envlist[envlen] = NULL;
225 		openpam_log(PAM_LOG_DEBUG, "setenv %s", envstr);
226 	}
227 
228 	/* add the name of the service function to the environment */
229 	if (asprintf(&envstr, "PAM_SM_FUNC=%s", func) < 0)
230 		OUT(PAM_BUF_ERR);
231 	envlist[envlen++] = envstr;
232 	envlist[envlen] = NULL;
233 
234 	/* add the PAM error codes to the environment. */
235 	if (options->return_prog_exit_status) {
236 		for (i = 0; i < (int)NUM_PAM_ERR_ENV; ++i) {
237 			if ((envstr = strdup(pam_err_env[i])) == NULL)
238 				OUT(PAM_BUF_ERR);
239 			envlist[envlen++] = envstr;
240 			envlist[envlen] = NULL;
241 		}
242 	}
243 
244 	openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d envlist = %p",
245 	    envlen, extralen, envlist);
246 
247 	/* set up pipe and get authtok if requested */
248 	if (options->expose_authtok) {
249 		if (pipe(chin) != 0) {
250 			openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
251 			OUT(PAM_SYSTEM_ERR);
252 		}
253 		if (fcntl(chin[1], F_SETFL, O_NONBLOCK)) {
254 			openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
255 			OUT(PAM_SYSTEM_ERR);
256 		}
257 		if (options->use_first_pass ||
258 		    strcmp(func, "pam_sm_setcred") == 0) {
259 			/* don't prompt, only expose existing token */
260 			rc = pam_get_item(pamh, PAM_AUTHTOK, &item);
261 			authtok = item;
262 			if (authtok == NULL && rc == PAM_SUCCESS) {
263 				openpam_log(PAM_LOG_ERROR,
264 				    "%s: pam_get_authtok(): %s",
265 				    func, "authentication token not available");
266 				OUT(PAM_SYSTEM_ERR);
267 			}
268 
269 		} else {
270 			rc = pam_get_authtok(pamh, PAM_AUTHTOK, &authtok, NULL);
271 		}
272 		if (rc == PAM_SUCCESS) {
273 			/* We include the trailing null terminator. */
274 			authtok_size = strlen(authtok) + 1;
275 		} else {
276 			openpam_log(PAM_LOG_ERROR, "%s: pam_get_authtok(): %s",
277 			    func, pam_strerror(pamh, rc));
278 			OUT(PAM_SYSTEM_ERR);
279 		}
280 	}
281 	/* set up pipes if capture was requested */
282 	if (options->capture_stdout) {
283 		if (pipe(chout) != 0) {
284 			openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
285 			OUT(PAM_SYSTEM_ERR);
286 		}
287 		if (fcntl(chout[0], F_SETFL, O_NONBLOCK) != 0) {
288 			openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
289 			OUT(PAM_SYSTEM_ERR);
290 		}
291 	} else {
292 		if ((chout[1] = open("/dev/null", O_RDWR)) < 0) {
293 			openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
294 			OUT(PAM_SYSTEM_ERR);
295 		}
296 	}
297 	if (options->capture_stderr) {
298 		if (pipe(cherr) != 0) {
299 			openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
300 			OUT(PAM_SYSTEM_ERR);
301 		}
302 		if (fcntl(cherr[0], F_SETFL, O_NONBLOCK) != 0) {
303 			openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
304 			OUT(PAM_SYSTEM_ERR);
305 		}
306 	} else {
307 		if ((cherr[1] = open("/dev/null", O_RDWR)) < 0) {
308 			openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
309 			OUT(PAM_SYSTEM_ERR);
310 		}
311 	}
312 
313 	if ((pid = pdfork(&pd, 0)) == 0) {
314 		/* child */
315 		if ((chin[1] >= 0 && close(chin[1]) != 0) ||
316 			(chout[0] >= 0 && close(chout[0]) != 0) ||
317 		    (cherr[0] >= 0 && close(cherr[0]) != 0)) {
318 			openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func);
319 		} else if (chin[0] >= 0 &&
320 			dup2(chin[0], STDIN_FILENO) != STDIN_FILENO) {
321 			openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
322 		} else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO ||
323 		    dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) {
324 			openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
325 		} else {
326 			execve(argv[0], (char * const *)argv,
327 			    (char * const *)envlist);
328 			openpam_log(PAM_LOG_ERROR, "%s: execve(%s): %m",
329 			    func, argv[0]);
330 		}
331 		_exit(1);
332 	}
333 	/* parent */
334 	if (pid == -1) {
335 		openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func);
336 		OUT(PAM_SYSTEM_ERR);
337 	}
338 	/* use poll() to watch the process and stdin / stdout / stderr */
339 	if (chin[0] >= 0)
340 		close(chin[0]);
341 	if (chout[1] >= 0)
342 		close(chout[1]);
343 	if (cherr[1] >= 0)
344 		close(cherr[1]);
345 	memset(pfd, 0, sizeof pfd);
346 	pfd[0].fd = pd;
347 	pfd[0].events = POLLHUP;
348 	nfds = 1;
349 	nreadfds = 0;
350 	if (options->capture_stdout) {
351 		pfd[nfds].fd = chout[0];
352 		pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
353 		nfds++;
354 		nreadfds++;
355 	}
356 	if (options->capture_stderr) {
357 		pfd[nfds].fd = cherr[0];
358 		pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
359 		nfds++;
360 		nreadfds++;
361 	}
362 	if (options->expose_authtok) {
363 		pfd[nfds].fd = chin[1];
364 		pfd[nfds].events = POLLOUT|POLLERR|POLLHUP;
365 		nfds++;
366 	}
367 
368 	/* loop until the process exits */
369 	do {
370 		if (poll(pfd, nfds, INFTIM) < 0) {
371 			openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func);
372 			OUT(PAM_SYSTEM_ERR);
373 		}
374 		/* are the stderr / stdout pipes ready for reading? */
375 		for (i = 1; i < 1 + nreadfds; ++i) {
376 			if ((pfd[i].revents & POLLIN) == 0)
377 				continue;
378 			if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) {
379 				openpam_log(PAM_LOG_ERROR, "%s: read(): %m",
380 				    func);
381 				OUT(PAM_SYSTEM_ERR);
382 			} else if (rlen == 0) {
383 				continue;
384 			}
385 			buf[rlen] = '\0';
386 			(void)pam_prompt(pamh, pfd[i].fd == chout[0] ?
387 			    PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf);
388 		}
389 		/* is the stdin pipe ready for writing? */
390 		if (options->expose_authtok && authtok_size > 0 &&
391 			(pfd[nfds - 1].revents & POLLOUT) != 0) {
392 			if ((wlen = write(chin[1], authtok, authtok_size)) < 0) {
393 				if (errno == EAGAIN)
394 					continue;
395 				openpam_log(PAM_LOG_ERROR, "%s: write(): %m",
396 				    func);
397 				OUT(PAM_SYSTEM_ERR);
398 			} else {
399 				authtok += wlen;
400 				authtok_size -= wlen;
401 				if (authtok_size == 0) {
402 					/* finished writing; close and forget the pipe */
403 					close(chin[1]);
404 					chin[1] = -1;
405 					nfds--;
406 				}
407 			}
408 		}
409 	} while (pfd[0].revents == 0);
410 
411 	/* the child process has exited */
412 	while (waitpid(pid, &status, 0) == -1) {
413 		if (errno == EINTR)
414 			continue;
415 		openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
416 		OUT(PAM_SYSTEM_ERR);
417 	}
418 
419 	/* check exit code */
420 	if (WIFSIGNALED(status)) {
421 		openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
422 		    func, argv[0], WTERMSIG(status),
423 		    WCOREDUMP(status) ? " (core dumped)" : "");
424 		OUT(PAM_SERVICE_ERR);
425 	}
426 	if (!WIFEXITED(status)) {
427 		openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
428 		    func, status);
429 		OUT(PAM_SERVICE_ERR);
430 	}
431 
432 	if (options->return_prog_exit_status) {
433 		openpam_log(PAM_LOG_DEBUG,
434 		    "%s: Use program exit status as return value: %d",
435 		    func, WEXITSTATUS(status));
436 		OUT(WEXITSTATUS(status));
437 	} else {
438 		OUT(WEXITSTATUS(status) == 0 ? PAM_SUCCESS : PAM_PERM_DENIED);
439 	}
440 	/* unreachable */
441 out:
442 	serrno = errno;
443 	if (pd >= 0)
444 		close(pd);
445 	if (chin[0] >= 0)
446 		close(chin[0]);
447 	if (chin[1] >= 0)
448 		close(chin[1]);
449 	if (chout[0] >= 0)
450 		close(chout[0]);
451 	if (chout[1] >= 0)
452 		close(chout[1]);
453 	if (cherr[0] >= 0)
454 		close(cherr[0]);
455 	if (cherr[0] >= 0)
456 		close(cherr[1]);
457 	if (envlist != NULL)
458 		openpam_free_envlist(envlist);
459 	errno = serrno;
460 	return (pam_err);
461 }
462 
463 PAM_EXTERN int
464 pam_sm_authenticate(pam_handle_t *pamh, int flags,
465     int argc, const char *argv[])
466 {
467 	int ret;
468 	struct pe_opts options;
469 
470 	ret = parse_options(__func__, &argc, &argv, &options);
471 	if (ret != 0)
472 		return (PAM_SERVICE_ERR);
473 
474 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
475 
476 	/*
477 	 * We must check that the program returned a valid code for this
478 	 * function.
479 	 */
480 	switch (ret) {
481 	case PAM_SUCCESS:
482 	case PAM_ABORT:
483 	case PAM_AUTHINFO_UNAVAIL:
484 	case PAM_AUTH_ERR:
485 	case PAM_BUF_ERR:
486 	case PAM_CONV_ERR:
487 	case PAM_CRED_INSUFFICIENT:
488 	case PAM_IGNORE:
489 	case PAM_MAXTRIES:
490 	case PAM_PERM_DENIED:
491 	case PAM_SERVICE_ERR:
492 	case PAM_SYSTEM_ERR:
493 	case PAM_USER_UNKNOWN:
494 		break;
495 	default:
496 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
497 		    argv[0], ret);
498 		ret = PAM_SERVICE_ERR;
499 	}
500 
501 	return (ret);
502 }
503 
504 PAM_EXTERN int
505 pam_sm_setcred(pam_handle_t *pamh, int flags,
506     int argc, const char *argv[])
507 {
508 	int ret;
509 	struct pe_opts options;
510 
511 	ret = parse_options(__func__, &argc, &argv, &options);
512 	if (ret != 0)
513 		return (PAM_SERVICE_ERR);
514 
515 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
516 
517 	/*
518 	 * We must check that the program returned a valid code for this
519 	 * function.
520 	 */
521 	switch (ret) {
522 	case PAM_SUCCESS:
523 	case PAM_ABORT:
524 	case PAM_BUF_ERR:
525 	case PAM_CONV_ERR:
526 	case PAM_CRED_ERR:
527 	case PAM_CRED_EXPIRED:
528 	case PAM_CRED_UNAVAIL:
529 	case PAM_IGNORE:
530 	case PAM_PERM_DENIED:
531 	case PAM_SERVICE_ERR:
532 	case PAM_SYSTEM_ERR:
533 	case PAM_USER_UNKNOWN:
534 		break;
535 	default:
536 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
537 		    argv[0], ret);
538 		ret = PAM_SERVICE_ERR;
539 	}
540 
541 	return (ret);
542 }
543 
544 PAM_EXTERN int
545 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
546     int argc, const char *argv[])
547 {
548 	int ret;
549 	struct pe_opts options;
550 
551 	ret = parse_options(__func__, &argc, &argv, &options);
552 	if (ret != 0)
553 		return (PAM_SERVICE_ERR);
554 
555 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
556 
557 	/*
558 	 * We must check that the program returned a valid code for this
559 	 * function.
560 	 */
561 	switch (ret) {
562 	case PAM_SUCCESS:
563 	case PAM_ABORT:
564 	case PAM_ACCT_EXPIRED:
565 	case PAM_AUTH_ERR:
566 	case PAM_BUF_ERR:
567 	case PAM_CONV_ERR:
568 	case PAM_IGNORE:
569 	case PAM_NEW_AUTHTOK_REQD:
570 	case PAM_PERM_DENIED:
571 	case PAM_SERVICE_ERR:
572 	case PAM_SYSTEM_ERR:
573 	case PAM_USER_UNKNOWN:
574 		break;
575 	default:
576 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
577 		    argv[0], ret);
578 		ret = PAM_SERVICE_ERR;
579 	}
580 
581 	return (ret);
582 }
583 
584 PAM_EXTERN int
585 pam_sm_open_session(pam_handle_t *pamh, int flags,
586     int argc, const char *argv[])
587 {
588 	int ret;
589 	struct pe_opts options;
590 
591 	ret = parse_options(__func__, &argc, &argv, &options);
592 	if (ret != 0)
593 		return (PAM_SERVICE_ERR);
594 
595 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
596 
597 	/*
598 	 * We must check that the program returned a valid code for this
599 	 * function.
600 	 */
601 	switch (ret) {
602 	case PAM_SUCCESS:
603 	case PAM_ABORT:
604 	case PAM_BUF_ERR:
605 	case PAM_CONV_ERR:
606 	case PAM_IGNORE:
607 	case PAM_PERM_DENIED:
608 	case PAM_SERVICE_ERR:
609 	case PAM_SESSION_ERR:
610 	case PAM_SYSTEM_ERR:
611 		break;
612 	default:
613 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
614 		    argv[0], ret);
615 		ret = PAM_SERVICE_ERR;
616 	}
617 
618 	return (ret);
619 }
620 
621 PAM_EXTERN int
622 pam_sm_close_session(pam_handle_t *pamh, int flags,
623     int argc, const char *argv[])
624 {
625 	int ret;
626 	struct pe_opts options;
627 
628 	ret = parse_options(__func__, &argc, &argv, &options);
629 	if (ret != 0)
630 		return (PAM_SERVICE_ERR);
631 
632 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
633 
634 	/*
635 	 * We must check that the program returned a valid code for this
636 	 * function.
637 	 */
638 	switch (ret) {
639 	case PAM_SUCCESS:
640 	case PAM_ABORT:
641 	case PAM_BUF_ERR:
642 	case PAM_CONV_ERR:
643 	case PAM_IGNORE:
644 	case PAM_PERM_DENIED:
645 	case PAM_SERVICE_ERR:
646 	case PAM_SESSION_ERR:
647 	case PAM_SYSTEM_ERR:
648 		break;
649 	default:
650 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
651 		    argv[0], ret);
652 		ret = PAM_SERVICE_ERR;
653 	}
654 
655 	return (ret);
656 }
657 
658 PAM_EXTERN int
659 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
660     int argc, const char *argv[])
661 {
662 	int ret;
663 	struct pe_opts options;
664 
665 	ret = parse_options(__func__, &argc, &argv, &options);
666 	if (ret != 0)
667 		return (PAM_SERVICE_ERR);
668 
669 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
670 
671 	/*
672 	 * We must check that the program returned a valid code for this
673 	 * function.
674 	 */
675 	switch (ret) {
676 	case PAM_SUCCESS:
677 	case PAM_ABORT:
678 	case PAM_AUTHTOK_DISABLE_AGING:
679 	case PAM_AUTHTOK_ERR:
680 	case PAM_AUTHTOK_LOCK_BUSY:
681 	case PAM_AUTHTOK_RECOVERY_ERR:
682 	case PAM_BUF_ERR:
683 	case PAM_CONV_ERR:
684 	case PAM_IGNORE:
685 	case PAM_PERM_DENIED:
686 	case PAM_SERVICE_ERR:
687 	case PAM_SYSTEM_ERR:
688 	case PAM_TRY_AGAIN:
689 		break;
690 	default:
691 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
692 		    argv[0], ret);
693 		ret = PAM_SERVICE_ERR;
694 	}
695 
696 	return (ret);
697 }
698 
699 PAM_MODULE_ENTRY("pam_exec");
700