2 * Copyright (c) 2001,2003 Networks Associates Technology, Inc.
3 * Copyright (c) 2017 Dag-Erling Smørgrav
6 * This software was developed for the FreeBSD Project by ThinkSec AS and
7 * NAI Labs, the Security Research Division of Network Associates, Inc.
8 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
9 * DARPA CHATS research program.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. The name of the author may not be used to endorse or promote
20 * products derived from this software without specific prior written
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 #include <sys/cdefs.h>
37 __FBSDID("$FreeBSD$");
39 #include <sys/types.h>
41 #include <sys/procdesc.h>
51 #include <security/pam_appl.h>
52 #include <security/pam_modules.h>
53 #include <security/openpam.h>
55 #define PAM_ITEM_ENV(n) { (n), #n }
60 PAM_ITEM_ENV(PAM_SERVICE),
61 PAM_ITEM_ENV(PAM_USER),
62 PAM_ITEM_ENV(PAM_TTY),
63 PAM_ITEM_ENV(PAM_RHOST),
64 PAM_ITEM_ENV(PAM_RUSER),
66 #define NUM_PAM_ITEM_ENV (sizeof(pam_item_env) / sizeof(pam_item_env[0]))
68 #define PAM_ERR_ENV_X(str, num) str "=" #num
69 #define PAM_ERR_ENV(pam_err) PAM_ERR_ENV_X(#pam_err, pam_err)
70 static const char *pam_err_env[] = {
71 PAM_ERR_ENV(PAM_SUCCESS),
72 PAM_ERR_ENV(PAM_OPEN_ERR),
73 PAM_ERR_ENV(PAM_SYMBOL_ERR),
74 PAM_ERR_ENV(PAM_SERVICE_ERR),
75 PAM_ERR_ENV(PAM_SYSTEM_ERR),
76 PAM_ERR_ENV(PAM_BUF_ERR),
77 PAM_ERR_ENV(PAM_CONV_ERR),
78 PAM_ERR_ENV(PAM_PERM_DENIED),
79 PAM_ERR_ENV(PAM_MAXTRIES),
80 PAM_ERR_ENV(PAM_AUTH_ERR),
81 PAM_ERR_ENV(PAM_NEW_AUTHTOK_REQD),
82 PAM_ERR_ENV(PAM_CRED_INSUFFICIENT),
83 PAM_ERR_ENV(PAM_AUTHINFO_UNAVAIL),
84 PAM_ERR_ENV(PAM_USER_UNKNOWN),
85 PAM_ERR_ENV(PAM_CRED_UNAVAIL),
86 PAM_ERR_ENV(PAM_CRED_EXPIRED),
87 PAM_ERR_ENV(PAM_CRED_ERR),
88 PAM_ERR_ENV(PAM_ACCT_EXPIRED),
89 PAM_ERR_ENV(PAM_AUTHTOK_EXPIRED),
90 PAM_ERR_ENV(PAM_SESSION_ERR),
91 PAM_ERR_ENV(PAM_AUTHTOK_ERR),
92 PAM_ERR_ENV(PAM_AUTHTOK_RECOVERY_ERR),
93 PAM_ERR_ENV(PAM_AUTHTOK_LOCK_BUSY),
94 PAM_ERR_ENV(PAM_AUTHTOK_DISABLE_AGING),
95 PAM_ERR_ENV(PAM_NO_MODULE_DATA),
96 PAM_ERR_ENV(PAM_IGNORE),
97 PAM_ERR_ENV(PAM_ABORT),
98 PAM_ERR_ENV(PAM_TRY_AGAIN),
99 PAM_ERR_ENV(PAM_MODULE_UNKNOWN),
100 PAM_ERR_ENV(PAM_DOMAIN_UNKNOWN),
101 PAM_ERR_ENV(PAM_NUM_ERR),
103 #define NUM_PAM_ERR_ENV (sizeof(pam_err_env) / sizeof(pam_err_env[0]))
106 int return_prog_exit_status;
112 parse_options(const char *func, int *argc, const char **argv[],
113 struct pe_opts *options)
119 * return_prog_exit_status:
120 * use the program exit status as the return code of pam_exec
122 * stop options parsing; what follows is the command to execute
124 memset(options, 0, sizeof(*options));
126 for (i = 0; i < *argc; ++i) {
127 if (strcmp((*argv)[i], "debug") == 0 ||
128 strcmp((*argv)[i], "no_warn") == 0) {
130 } else if (strcmp((*argv)[i], "capture_stdout") == 0) {
131 options->capture_stdout = 1;
132 } else if (strcmp((*argv)[i], "capture_stderr") == 0) {
133 options->capture_stderr = 1;
134 } else if (strcmp((*argv)[i], "return_prog_exit_status") == 0) {
135 options->return_prog_exit_status = 1;
137 if (strcmp((*argv)[i], "--") == 0) {
143 openpam_log(PAM_LOG_DEBUG, "%s: option \"%s\" enabled",
154 _pam_exec(pam_handle_t *pamh,
155 const char *func, int flags __unused, int argc, const char *argv[],
156 struct pe_opts *options)
158 char buf[PAM_MAX_MSG_SIZE];
159 struct pollfd pfd[3];
161 char **envlist, *envstr, *resp, **tmp;
163 int envlen, extralen, i;
164 int pam_err, serrno, status;
165 int chout[2], cherr[2], pd;
171 chout[0] = chout[1] = cherr[0] = cherr[1] = -1;
174 #define OUT(ret) do { pam_err = (ret); goto out; } while (0)
176 /* Check there's a program name left after parsing options. */
178 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting",
180 OUT(PAM_SERVICE_ERR);
184 * Set up the child's environment list. It consists of the PAM
185 * environment, a few hand-picked PAM items, the name of the
186 * service function, and if return_prog_exit_status is set, the
187 * numerical values of all PAM error codes.
190 /* compute the final size of the environment. */
191 envlist = pam_getenvlist(pamh);
192 for (envlen = 0; envlist[envlen] != NULL; ++envlen)
194 extralen = NUM_PAM_ITEM_ENV + 1;
195 if (options->return_prog_exit_status)
196 extralen += NUM_PAM_ERR_ENV;
197 tmp = reallocarray(envlist, envlen + extralen + 1, sizeof(*envlist));
198 openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d tmp = %p",
199 envlen, extralen, tmp);
205 /* copy selected PAM items to the environment */
206 for (i = 0; i < NUM_PAM_ITEM_ENV; ++i) {
207 pam_err = pam_get_item(pamh, pam_item_env[i].item, &item);
208 if (pam_err != PAM_SUCCESS || item == NULL)
210 if (asprintf(&envstr, "%s=%s", pam_item_env[i].name, item) < 0)
212 envlist[envlen++] = envstr;
213 envlist[envlen] = NULL;
214 openpam_log(PAM_LOG_DEBUG, "setenv %s", envstr);
217 /* add the name of the service function to the environment */
218 if (asprintf(&envstr, "PAM_SM_FUNC=%s", func) < 0)
220 envlist[envlen++] = envstr;
221 envlist[envlen] = NULL;
223 /* add the PAM error codes to the environment. */
224 if (options->return_prog_exit_status) {
225 for (i = 0; i < (int)NUM_PAM_ERR_ENV; ++i) {
226 if ((envstr = strdup(pam_err_env[i])) == NULL)
228 envlist[envlen++] = envstr;
229 envlist[envlen] = NULL;
233 openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d envlist = %p",
234 envlen, extralen, envlist);
236 /* set up pipes if capture was requested */
237 if (options->capture_stdout) {
238 if (pipe(chout) != 0) {
239 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
242 if (fcntl(chout[0], F_SETFL, O_NONBLOCK) != 0) {
243 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
247 if ((chout[1] = open("/dev/null", O_RDWR)) < 0) {
248 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
252 if (options->capture_stderr) {
253 if (pipe(cherr) != 0) {
254 openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
257 if (fcntl(cherr[0], F_SETFL, O_NONBLOCK) != 0) {
258 openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
262 if ((cherr[1] = open("/dev/null", O_RDWR)) < 0) {
263 openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
268 if ((pid = pdfork(&pd, 0)) == 0) {
270 if ((chout[0] >= 0 && close(chout[0]) != 0) ||
271 (cherr[0] >= 0 && close(cherr[0]) != 0)) {
272 openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func);
273 } else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO ||
274 dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) {
275 openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
277 execve(argv[0], (char * const *)argv,
278 (char * const *)envlist);
279 openpam_log(PAM_LOG_ERROR, "%s: execve(%s): %m",
286 openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func);
289 /* use poll() to watch the process and stdout / stderr */
294 memset(pfd, 0, sizeof pfd);
296 pfd[0].events = POLLHUP;
298 if (options->capture_stdout) {
299 pfd[nfds].fd = chout[0];
300 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
303 if (options->capture_stderr) {
304 pfd[nfds].fd = cherr[0];
305 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
309 /* loop until the process exits */
311 if (poll(pfd, nfds, INFTIM) < 0) {
312 openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func);
315 for (i = 1; i < nfds; ++i) {
316 if ((pfd[i].revents & POLLIN) == 0)
318 if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) {
319 openpam_log(PAM_LOG_ERROR, "%s: read(): %m",
322 } else if (rlen == 0) {
326 (void)pam_prompt(pamh, pfd[i].fd == chout[0] ?
327 PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf);
329 } while (pfd[0].revents == 0);
331 /* the child process has exited */
332 while (waitpid(pid, &status, 0) == -1) {
335 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
339 /* check exit code */
340 if (WIFSIGNALED(status)) {
341 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
342 func, argv[0], WTERMSIG(status),
343 WCOREDUMP(status) ? " (core dumped)" : "");
344 OUT(PAM_SERVICE_ERR);
346 if (!WIFEXITED(status)) {
347 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
349 OUT(PAM_SERVICE_ERR);
352 if (options->return_prog_exit_status) {
353 openpam_log(PAM_LOG_DEBUG,
354 "%s: Use program exit status as return value: %d",
355 func, WEXITSTATUS(status));
356 OUT(WEXITSTATUS(status));
358 OUT(WEXITSTATUS(status) == 0 ? PAM_SUCCESS : PAM_PERM_DENIED);
374 openpam_free_envlist(envlist);
380 pam_sm_authenticate(pam_handle_t *pamh, int flags,
381 int argc, const char *argv[])
384 struct pe_opts options;
386 ret = parse_options(__func__, &argc, &argv, &options);
388 return (PAM_SERVICE_ERR);
390 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
393 * We must check that the program returned a valid code for this
399 case PAM_AUTHINFO_UNAVAIL:
403 case PAM_CRED_INSUFFICIENT:
406 case PAM_PERM_DENIED:
407 case PAM_SERVICE_ERR:
409 case PAM_USER_UNKNOWN:
412 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
414 ret = PAM_SERVICE_ERR;
421 pam_sm_setcred(pam_handle_t *pamh, int flags,
422 int argc, const char *argv[])
425 struct pe_opts options;
427 ret = parse_options(__func__, &argc, &argv, &options);
429 return (PAM_SERVICE_ERR);
431 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
434 * We must check that the program returned a valid code for this
443 case PAM_CRED_EXPIRED:
444 case PAM_CRED_UNAVAIL:
446 case PAM_PERM_DENIED:
447 case PAM_SERVICE_ERR:
449 case PAM_USER_UNKNOWN:
452 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
454 ret = PAM_SERVICE_ERR;
461 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
462 int argc, const char *argv[])
465 struct pe_opts options;
467 ret = parse_options(__func__, &argc, &argv, &options);
469 return (PAM_SERVICE_ERR);
471 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
474 * We must check that the program returned a valid code for this
480 case PAM_ACCT_EXPIRED:
485 case PAM_NEW_AUTHTOK_REQD:
486 case PAM_PERM_DENIED:
487 case PAM_SERVICE_ERR:
489 case PAM_USER_UNKNOWN:
492 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
494 ret = PAM_SERVICE_ERR;
501 pam_sm_open_session(pam_handle_t *pamh, int flags,
502 int argc, const char *argv[])
505 struct pe_opts options;
507 ret = parse_options(__func__, &argc, &argv, &options);
509 return (PAM_SERVICE_ERR);
511 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
514 * We must check that the program returned a valid code for this
523 case PAM_PERM_DENIED:
524 case PAM_SERVICE_ERR:
525 case PAM_SESSION_ERR:
529 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
531 ret = PAM_SERVICE_ERR;
538 pam_sm_close_session(pam_handle_t *pamh, int flags,
539 int argc, const char *argv[])
542 struct pe_opts options;
544 ret = parse_options(__func__, &argc, &argv, &options);
546 return (PAM_SERVICE_ERR);
548 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
551 * We must check that the program returned a valid code for this
560 case PAM_PERM_DENIED:
561 case PAM_SERVICE_ERR:
562 case PAM_SESSION_ERR:
566 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
568 ret = PAM_SERVICE_ERR;
575 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
576 int argc, const char *argv[])
579 struct pe_opts options;
581 ret = parse_options(__func__, &argc, &argv, &options);
583 return (PAM_SERVICE_ERR);
585 ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
588 * We must check that the program returned a valid code for this
594 case PAM_AUTHTOK_DISABLE_AGING:
595 case PAM_AUTHTOK_ERR:
596 case PAM_AUTHTOK_LOCK_BUSY:
597 case PAM_AUTHTOK_RECOVERY_ERR:
601 case PAM_PERM_DENIED:
602 case PAM_SERVICE_ERR:
607 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
609 ret = PAM_SERVICE_ERR;
615 PAM_MODULE_ENTRY("pam_exec");