2 * SPDX-License-Identifier: BSD-3-Clause
4 * Copyright (c) 2001,2003 Networks Associates Technology, Inc.
5 * Copyright (c) 2017 Dag-Erling Smørgrav
8 * This software was developed for the FreeBSD Project by ThinkSec AS and
9 * NAI Labs, the Security Research Division of Network Associates, Inc.
10 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
11 * DARPA CHATS research program.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. The name of the author may not be used to endorse or promote
22 * products derived from this software without specific prior written
25 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #include <sys/cdefs.h>
39 __FBSDID("$FreeBSD$");
41 #include <sys/types.h>
43 #include <sys/procdesc.h>
53 #include <security/pam_appl.h>
54 #include <security/pam_modules.h>
55 #include <security/openpam.h>
57 #define PAM_ITEM_ENV(n) { (n), #n }
62 PAM_ITEM_ENV(PAM_SERVICE),
63 PAM_ITEM_ENV(PAM_USER),
64 PAM_ITEM_ENV(PAM_TTY),
65 PAM_ITEM_ENV(PAM_RHOST),
66 PAM_ITEM_ENV(PAM_RUSER),
68 #define NUM_PAM_ITEM_ENV (sizeof(pam_item_env) / sizeof(pam_item_env[0]))
70 #define PAM_ERR_ENV_X(str, num) str "=" #num
71 #define PAM_ERR_ENV(pam_err) PAM_ERR_ENV_X(#pam_err, pam_err)
72 static const char *pam_err_env[] = {
73 PAM_ERR_ENV(PAM_SUCCESS),
74 PAM_ERR_ENV(PAM_OPEN_ERR),
75 PAM_ERR_ENV(PAM_SYMBOL_ERR),
76 PAM_ERR_ENV(PAM_SERVICE_ERR),
77 PAM_ERR_ENV(PAM_SYSTEM_ERR),
78 PAM_ERR_ENV(PAM_BUF_ERR),
79 PAM_ERR_ENV(PAM_CONV_ERR),
80 PAM_ERR_ENV(PAM_PERM_DENIED),
81 PAM_ERR_ENV(PAM_MAXTRIES),
82 PAM_ERR_ENV(PAM_AUTH_ERR),
83 PAM_ERR_ENV(PAM_NEW_AUTHTOK_REQD),
84 PAM_ERR_ENV(PAM_CRED_INSUFFICIENT),
85 PAM_ERR_ENV(PAM_AUTHINFO_UNAVAIL),
86 PAM_ERR_ENV(PAM_USER_UNKNOWN),
87 PAM_ERR_ENV(PAM_CRED_UNAVAIL),
88 PAM_ERR_ENV(PAM_CRED_EXPIRED),
89 PAM_ERR_ENV(PAM_CRED_ERR),
90 PAM_ERR_ENV(PAM_ACCT_EXPIRED),
91 PAM_ERR_ENV(PAM_AUTHTOK_EXPIRED),
92 PAM_ERR_ENV(PAM_SESSION_ERR),
93 PAM_ERR_ENV(PAM_AUTHTOK_ERR),
94 PAM_ERR_ENV(PAM_AUTHTOK_RECOVERY_ERR),
95 PAM_ERR_ENV(PAM_AUTHTOK_LOCK_BUSY),
96 PAM_ERR_ENV(PAM_AUTHTOK_DISABLE_AGING),
97 PAM_ERR_ENV(PAM_NO_MODULE_DATA),
98 PAM_ERR_ENV(PAM_IGNORE),
99 PAM_ERR_ENV(PAM_ABORT),
100 PAM_ERR_ENV(PAM_TRY_AGAIN),
101 PAM_ERR_ENV(PAM_MODULE_UNKNOWN),
102 PAM_ERR_ENV(PAM_DOMAIN_UNKNOWN),
103 PAM_ERR_ENV(PAM_NUM_ERR),
105 #define NUM_PAM_ERR_ENV (sizeof(pam_err_env) / sizeof(pam_err_env[0]))
108 int return_prog_exit_status;
114 parse_options(const char *func, int *argc, const char **argv[],
115 struct pe_opts *options)
121 * return_prog_exit_status:
122 * use the program exit status as the return code of pam_exec
124 * stop options parsing; what follows is the command to execute
126 memset(options, 0, sizeof(*options));
128 for (i = 0; i < *argc; ++i) {
129 if (strcmp((*argv)[i], "debug") == 0 ||
130 strcmp((*argv)[i], "no_warn") == 0) {
132 } else if (strcmp((*argv)[i], "capture_stdout") == 0) {
133 options->capture_stdout = 1;
134 } else if (strcmp((*argv)[i], "capture_stderr") == 0) {
135 options->capture_stderr = 1;
136 } else if (strcmp((*argv)[i], "return_prog_exit_status") == 0) {
137 options->return_prog_exit_status = 1;
139 if (strcmp((*argv)[i], "--") == 0) {
145 openpam_log(PAM_LOG_DEBUG, "%s: option \"%s\" enabled",
156 _pam_exec(pam_handle_t *pamh,
157 const char *func, int flags __unused, int argc, const char *argv[],
158 struct pe_opts *options)
160 char buf[PAM_MAX_MSG_SIZE];
161 struct pollfd pfd[3];
163 char **envlist, *envstr, *resp, **tmp;
165 int envlen, extralen, i;
166 int pam_err, serrno, status;
167 int chout[2], cherr[2], pd;
173 chout[0] = chout[1] = cherr[0] = cherr[1] = -1;
176 #define OUT(ret) do { pam_err = (ret); goto out; } while (0)
178 /* Check there's a program name left after parsing options. */
180 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting",
182 OUT(PAM_SERVICE_ERR);
186 * Set up the child's environment list. It consists of the PAM
187 * environment, a few hand-picked PAM items, the name of the
188 * service function, and if return_prog_exit_status is set, the
189 * numerical values of all PAM error codes.
192 /* compute the final size of the environment. */
193 envlist = pam_getenvlist(pamh);
194 for (envlen = 0; envlist[envlen] != NULL; ++envlen)
196 extralen = NUM_PAM_ITEM_ENV + 1;
197 if (options->return_prog_exit_status)
198 extralen += NUM_PAM_ERR_ENV;
199 tmp = reallocarray(envlist, envlen + extralen + 1, sizeof(*envlist));
200 openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d tmp = %p",
201 envlen, extralen, tmp);
207 /* copy selected PAM items to the environment */
208 for (i = 0; i < NUM_PAM_ITEM_ENV; ++i) {
209 pam_err = pam_get_item(pamh, pam_item_env[i].item, &item);
210 if (pam_err != PAM_SUCCESS || item == NULL)
212 if (asprintf(&envstr, "%s=%s", pam_item_env[i].name, item) < 0)
214 envlist[envlen++] = envstr;
215 envlist[envlen] = NULL;
216 openpam_log(PAM_LOG_DEBUG, "setenv %s", envstr);
219 /* add the name of the service function to the environment */
220 if (asprintf(&envstr, "PAM_SM_FUNC=%s", func) < 0)
222 envlist[envlen++] = envstr;
223 envlist[envlen] = NULL;
225 /* add the PAM error codes to the environment. */
226 if (options->return_prog_exit_status) {
227 for (i = 0; i < (int)NUM_PAM_ERR_ENV; ++i) {
228 if ((envstr = strdup(pam_err_env[i])) == NULL)
230 envlist[envlen++] = envstr;
231 envlist[envlen] = NULL;
235 openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d envlist = %p",
236 envlen, extralen, envlist);
238 /* set up pipes if capture was requested */
239 if (options->capture_stdout) {
240 if (pipe(chout) != 0) {
241 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
244 if (fcntl(chout[0], F_SETFL, O_NONBLOCK) != 0) {
245 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
249 if ((chout[1] = open("/dev/null", O_RDWR)) < 0) {
250 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
254 if (options->capture_stderr) {
255 if (pipe(cherr) != 0) {
256 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
259 if (fcntl(cherr[0], F_SETFL, O_NONBLOCK) != 0) {
260 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
264 if ((cherr[1] = open("/dev/null", O_RDWR)) < 0) {
265 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
270 if ((pid = pdfork(&pd, 0)) == 0) {
272 if ((chout[0] >= 0 && close(chout[0]) != 0) ||
273 (cherr[0] >= 0 && close(cherr[0]) != 0)) {
274 openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func);
275 } else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO ||
276 dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) {
277 openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
279 execve(argv[0], (char * const *)argv,
280 (char * const *)envlist);
281 openpam_log(PAM_LOG_ERROR, "%s: execve(%s): %m",
288 openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func);
291 /* use poll() to watch the process and stdout / stderr */
296 memset(pfd, 0, sizeof pfd);
298 pfd[0].events = POLLHUP;
300 if (options->capture_stdout) {
301 pfd[nfds].fd = chout[0];
302 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
305 if (options->capture_stderr) {
306 pfd[nfds].fd = cherr[0];
307 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
311 /* loop until the process exits */
313 if (poll(pfd, nfds, INFTIM) < 0) {
314 openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func);
317 for (i = 1; i < nfds; ++i) {
318 if ((pfd[i].revents & POLLIN) == 0)
320 if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) {
321 openpam_log(PAM_LOG_ERROR, "%s: read(): %m",
324 } else if (rlen == 0) {
328 (void)pam_prompt(pamh, pfd[i].fd == chout[0] ?
329 PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf);
331 } while (pfd[0].revents == 0);
333 /* the child process has exited */
334 while (waitpid(pid, &status, 0) == -1) {
337 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
341 /* check exit code */
342 if (WIFSIGNALED(status)) {
343 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
344 func, argv[0], WTERMSIG(status),
345 WCOREDUMP(status) ? " (core dumped)" : "");
346 OUT(PAM_SERVICE_ERR);
348 if (!WIFEXITED(status)) {
349 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
351 OUT(PAM_SERVICE_ERR);
354 if (options->return_prog_exit_status) {
355 openpam_log(PAM_LOG_DEBUG,
356 "%s: Use program exit status as return value: %d",
357 func, WEXITSTATUS(status));
358 OUT(WEXITSTATUS(status));
360 OUT(WEXITSTATUS(status) == 0 ? PAM_SUCCESS : PAM_PERM_DENIED);
376 openpam_free_envlist(envlist);
382 pam_sm_authenticate(pam_handle_t *pamh, int flags,
383 int argc, const char *argv[])
386 struct pe_opts options;
388 ret = parse_options(__func__, &argc, &argv, &options);
390 return (PAM_SERVICE_ERR);
392 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
395 * We must check that the program returned a valid code for this
401 case PAM_AUTHINFO_UNAVAIL:
405 case PAM_CRED_INSUFFICIENT:
408 case PAM_PERM_DENIED:
409 case PAM_SERVICE_ERR:
411 case PAM_USER_UNKNOWN:
414 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
416 ret = PAM_SERVICE_ERR;
423 pam_sm_setcred(pam_handle_t *pamh, int flags,
424 int argc, const char *argv[])
427 struct pe_opts options;
429 ret = parse_options(__func__, &argc, &argv, &options);
431 return (PAM_SERVICE_ERR);
433 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
436 * We must check that the program returned a valid code for this
445 case PAM_CRED_EXPIRED:
446 case PAM_CRED_UNAVAIL:
448 case PAM_PERM_DENIED:
449 case PAM_SERVICE_ERR:
451 case PAM_USER_UNKNOWN:
454 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
456 ret = PAM_SERVICE_ERR;
463 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
464 int argc, const char *argv[])
467 struct pe_opts options;
469 ret = parse_options(__func__, &argc, &argv, &options);
471 return (PAM_SERVICE_ERR);
473 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
476 * We must check that the program returned a valid code for this
482 case PAM_ACCT_EXPIRED:
487 case PAM_NEW_AUTHTOK_REQD:
488 case PAM_PERM_DENIED:
489 case PAM_SERVICE_ERR:
491 case PAM_USER_UNKNOWN:
494 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
496 ret = PAM_SERVICE_ERR;
503 pam_sm_open_session(pam_handle_t *pamh, int flags,
504 int argc, const char *argv[])
507 struct pe_opts options;
509 ret = parse_options(__func__, &argc, &argv, &options);
511 return (PAM_SERVICE_ERR);
513 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
516 * We must check that the program returned a valid code for this
525 case PAM_PERM_DENIED:
526 case PAM_SERVICE_ERR:
527 case PAM_SESSION_ERR:
531 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
533 ret = PAM_SERVICE_ERR;
540 pam_sm_close_session(pam_handle_t *pamh, int flags,
541 int argc, const char *argv[])
544 struct pe_opts options;
546 ret = parse_options(__func__, &argc, &argv, &options);
548 return (PAM_SERVICE_ERR);
550 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
553 * We must check that the program returned a valid code for this
562 case PAM_PERM_DENIED:
563 case PAM_SERVICE_ERR:
564 case PAM_SESSION_ERR:
568 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
570 ret = PAM_SERVICE_ERR;
577 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
578 int argc, const char *argv[])
581 struct pe_opts options;
583 ret = parse_options(__func__, &argc, &argv, &options);
585 return (PAM_SERVICE_ERR);
587 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
590 * We must check that the program returned a valid code for this
596 case PAM_AUTHTOK_DISABLE_AGING:
597 case PAM_AUTHTOK_ERR:
598 case PAM_AUTHTOK_LOCK_BUSY:
599 case PAM_AUTHTOK_RECOVERY_ERR:
603 case PAM_PERM_DENIED:
604 case PAM_SERVICE_ERR:
609 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
611 ret = PAM_SERVICE_ERR;
617 PAM_MODULE_ENTRY("pam_exec");