2 * SPDX-License-Identifier: BSD-3-Clause
4 * Copyright (c) 2001,2003 Networks Associates Technology, Inc.
5 * Copyright (c) 2017-2019 Dag-Erling Smørgrav
6 * Copyright (c) 2018 Thomas Munro
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.
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
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
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
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
42 #include <sys/types.h>
44 #include <sys/procdesc.h>
54 #include <security/pam_appl.h>
55 #include <security/pam_modules.h>
56 #include <security/openpam.h>
58 #define PAM_ITEM_ENV(n) { (n), #n }
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),
69 #define NUM_PAM_ITEM_ENV (sizeof(pam_item_env) / sizeof(pam_item_env[0]))
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),
106 #define NUM_PAM_ERR_ENV (sizeof(pam_err_env) / sizeof(pam_err_env[0]))
109 int return_prog_exit_status;
117 parse_options(const char *func, int *argc, const char **argv[],
118 struct pe_opts *options)
124 * return_prog_exit_status:
125 * use the program exit status as the return code of pam_exec
127 * stop options parsing; what follows is the command to execute
129 memset(options, 0, sizeof(*options));
131 for (i = 0; i < *argc; ++i) {
132 if (strcmp((*argv)[i], "debug") == 0 ||
133 strcmp((*argv)[i], "no_warn") == 0) {
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;
146 if (strcmp((*argv)[i], "--") == 0) {
152 openpam_log(PAM_LOG_DEBUG, "%s: option \"%s\" enabled",
163 _pam_exec(pam_handle_t *pamh,
164 const char *func, int flags __unused, int argc, const char *argv[],
165 struct pe_opts *options)
167 char buf[PAM_MAX_MSG_SIZE];
168 struct pollfd pfd[4];
170 char **envlist, *envstr, *resp, **tmp;
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;
183 chin[0] = chin[1] = chout[0] = chout[1] = cherr[0] = cherr[1] = -1;
186 #define OUT(ret) do { pam_err = (ret); goto out; } while (0)
188 /* Check there's a program name left after parsing options. */
190 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting",
192 OUT(PAM_SERVICE_ERR);
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.
202 /* compute the final size of the environment. */
203 envlist = pam_getenvlist(pamh);
204 for (envlen = 0; envlist[envlen] != NULL; ++envlen)
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);
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)
222 if (asprintf(&envstr, "%s=%s", pam_item_env[i].name, item) < 0)
224 envlist[envlen++] = envstr;
225 envlist[envlen] = NULL;
226 openpam_log(PAM_LOG_DEBUG, "setenv %s", envstr);
229 /* add the name of the service function to the environment */
230 if (asprintf(&envstr, "PAM_SM_FUNC=%s", func) < 0)
232 envlist[envlen++] = envstr;
233 envlist[envlen] = NULL;
235 /* add the PAM error codes to the environment. */
236 if (options->return_prog_exit_status) {
237 for (i = 0; i < (int)NUM_PAM_ERR_ENV; ++i) {
238 if ((envstr = strdup(pam_err_env[i])) == NULL)
240 envlist[envlen++] = envstr;
241 envlist[envlen] = NULL;
245 openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d envlist = %p",
246 envlen, extralen, envlist);
248 /* set up pipe and get authtok if requested */
249 if (options->expose_authtok) {
250 if (pipe(chin) != 0) {
251 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
254 if (fcntl(chin[1], F_SETFL, O_NONBLOCK)) {
255 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
258 if (options->use_first_pass ||
259 strcmp(func, "pam_sm_setcred") == 0) {
260 /* don't prompt, only expose existing token */
261 rc = pam_get_item(pamh, PAM_AUTHTOK, &item);
264 rc = pam_get_authtok(pamh, PAM_AUTHTOK, &authtok, NULL);
266 if (rc == PAM_SUCCESS) {
267 /* We include the trailing null terminator. */
268 authtok_size = strlen(authtok) + 1;
270 openpam_log(PAM_LOG_ERROR, "%s: pam_get_authtok(): %s",
271 func, pam_strerror(pamh, rc));
275 /* set up pipes if capture was requested */
276 if (options->capture_stdout) {
277 if (pipe(chout) != 0) {
278 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
281 if (fcntl(chout[0], F_SETFL, O_NONBLOCK) != 0) {
282 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
286 if ((chout[1] = open("/dev/null", O_RDWR)) < 0) {
287 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
291 if (options->capture_stderr) {
292 if (pipe(cherr) != 0) {
293 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
296 if (fcntl(cherr[0], F_SETFL, O_NONBLOCK) != 0) {
297 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
301 if ((cherr[1] = open("/dev/null", O_RDWR)) < 0) {
302 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
307 if ((pid = pdfork(&pd, 0)) == 0) {
309 if ((chin[1] >= 0 && close(chin[1]) != 0) ||
310 (chout[0] >= 0 && close(chout[0]) != 0) ||
311 (cherr[0] >= 0 && close(cherr[0]) != 0)) {
312 openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func);
313 } else if (chin[0] >= 0 &&
314 dup2(chin[0], STDIN_FILENO) != STDIN_FILENO) {
315 openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
316 } else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO ||
317 dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) {
318 openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
320 execve(argv[0], (char * const *)argv,
321 (char * const *)envlist);
322 openpam_log(PAM_LOG_ERROR, "%s: execve(%s): %m",
329 openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func);
332 /* use poll() to watch the process and stdin / stdout / stderr */
339 memset(pfd, 0, sizeof pfd);
341 pfd[0].events = POLLHUP;
344 if (options->capture_stdout) {
345 pfd[nfds].fd = chout[0];
346 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
350 if (options->capture_stderr) {
351 pfd[nfds].fd = cherr[0];
352 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
356 if (options->expose_authtok) {
357 pfd[nfds].fd = chin[1];
358 pfd[nfds].events = POLLOUT|POLLERR|POLLHUP;
362 /* loop until the process exits */
364 if (poll(pfd, nfds, INFTIM) < 0) {
365 openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func);
368 /* are the stderr / stdout pipes ready for reading? */
369 for (i = 1; i < 1 + nreadfds; ++i) {
370 if ((pfd[i].revents & POLLIN) == 0)
372 if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) {
373 openpam_log(PAM_LOG_ERROR, "%s: read(): %m",
376 } else if (rlen == 0) {
380 (void)pam_prompt(pamh, pfd[i].fd == chout[0] ?
381 PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf);
383 /* is the stdin pipe ready for writing? */
384 if (options->expose_authtok && authtok_size > 0 &&
385 (pfd[nfds - 1].revents & POLLOUT) != 0) {
386 if ((wlen = write(chin[1], authtok, authtok_size)) < 0) {
389 openpam_log(PAM_LOG_ERROR, "%s: write(): %m",
394 authtok_size -= wlen;
395 if (authtok_size == 0) {
396 /* finished writing; close and forget the pipe */
403 } while (pfd[0].revents == 0);
405 /* the child process has exited */
406 while (waitpid(pid, &status, 0) == -1) {
409 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
413 /* check exit code */
414 if (WIFSIGNALED(status)) {
415 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
416 func, argv[0], WTERMSIG(status),
417 WCOREDUMP(status) ? " (core dumped)" : "");
418 OUT(PAM_SERVICE_ERR);
420 if (!WIFEXITED(status)) {
421 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
423 OUT(PAM_SERVICE_ERR);
426 if (options->return_prog_exit_status) {
427 openpam_log(PAM_LOG_DEBUG,
428 "%s: Use program exit status as return value: %d",
429 func, WEXITSTATUS(status));
430 OUT(WEXITSTATUS(status));
432 OUT(WEXITSTATUS(status) == 0 ? PAM_SUCCESS : PAM_PERM_DENIED);
452 openpam_free_envlist(envlist);
458 pam_sm_authenticate(pam_handle_t *pamh, int flags,
459 int argc, const char *argv[])
462 struct pe_opts options;
464 ret = parse_options(__func__, &argc, &argv, &options);
466 return (PAM_SERVICE_ERR);
468 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
471 * We must check that the program returned a valid code for this
477 case PAM_AUTHINFO_UNAVAIL:
481 case PAM_CRED_INSUFFICIENT:
484 case PAM_PERM_DENIED:
485 case PAM_SERVICE_ERR:
487 case PAM_USER_UNKNOWN:
490 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
492 ret = PAM_SERVICE_ERR;
499 pam_sm_setcred(pam_handle_t *pamh, int flags,
500 int argc, const char *argv[])
503 struct pe_opts options;
505 ret = parse_options(__func__, &argc, &argv, &options);
507 return (PAM_SERVICE_ERR);
509 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
512 * We must check that the program returned a valid code for this
521 case PAM_CRED_EXPIRED:
522 case PAM_CRED_UNAVAIL:
524 case PAM_PERM_DENIED:
525 case PAM_SERVICE_ERR:
527 case PAM_USER_UNKNOWN:
530 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
532 ret = PAM_SERVICE_ERR;
539 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
540 int argc, const char *argv[])
543 struct pe_opts options;
545 ret = parse_options(__func__, &argc, &argv, &options);
547 return (PAM_SERVICE_ERR);
549 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
552 * We must check that the program returned a valid code for this
558 case PAM_ACCT_EXPIRED:
563 case PAM_NEW_AUTHTOK_REQD:
564 case PAM_PERM_DENIED:
565 case PAM_SERVICE_ERR:
567 case PAM_USER_UNKNOWN:
570 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
572 ret = PAM_SERVICE_ERR;
579 pam_sm_open_session(pam_handle_t *pamh, int flags,
580 int argc, const char *argv[])
583 struct pe_opts options;
585 ret = parse_options(__func__, &argc, &argv, &options);
587 return (PAM_SERVICE_ERR);
589 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
592 * We must check that the program returned a valid code for this
601 case PAM_PERM_DENIED:
602 case PAM_SERVICE_ERR:
603 case PAM_SESSION_ERR:
607 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
609 ret = PAM_SERVICE_ERR;
616 pam_sm_close_session(pam_handle_t *pamh, int flags,
617 int argc, const char *argv[])
620 struct pe_opts options;
622 ret = parse_options(__func__, &argc, &argv, &options);
624 return (PAM_SERVICE_ERR);
626 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
629 * We must check that the program returned a valid code for this
638 case PAM_PERM_DENIED:
639 case PAM_SERVICE_ERR:
640 case PAM_SESSION_ERR:
644 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
646 ret = PAM_SERVICE_ERR;
653 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
654 int argc, const char *argv[])
657 struct pe_opts options;
659 ret = parse_options(__func__, &argc, &argv, &options);
661 return (PAM_SERVICE_ERR);
663 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
666 * We must check that the program returned a valid code for this
672 case PAM_AUTHTOK_DISABLE_AGING:
673 case PAM_AUTHTOK_ERR:
674 case PAM_AUTHTOK_LOCK_BUSY:
675 case PAM_AUTHTOK_RECOVERY_ERR:
679 case PAM_PERM_DENIED:
680 case PAM_SERVICE_ERR:
685 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
687 ret = PAM_SERVICE_ERR;
693 PAM_MODULE_ENTRY("pam_exec");