Index: head/lib/libpam/modules/pam_exec/pam_exec.8 =================================================================== --- head/lib/libpam/modules/pam_exec/pam_exec.8 +++ head/lib/libpam/modules/pam_exec/pam_exec.8 @@ -1,5 +1,6 @@ .\" Copyright (c) 2001,2003 Networks Associates Technology, Inc. .\" Copyright (c) 2017 Dag-Erling Smørgrav +.\" Copyright (c) 2018 Thomas Munro .\" All rights reserved. .\" .\" Portions of this software were developed for the FreeBSD Project by @@ -33,7 +34,7 @@ .\" .\" $FreeBSD$ .\" -.Dd March 22, 2017 +.Dd August 14, 2018 .Dt PAM_EXEC 8 .Os .Sh NAME @@ -72,6 +73,8 @@ .It Cm return_prog_exit_status Use the program exit status as the return code of the pam_sm_* function. It must be a valid return value for this function. +.It Cm expose_authtok +Write the authentication token to the program's standard input stream. .It Cm -- Stop options parsing; program and its arguments follow. Index: head/lib/libpam/modules/pam_exec/pam_exec.c =================================================================== --- head/lib/libpam/modules/pam_exec/pam_exec.c +++ head/lib/libpam/modules/pam_exec/pam_exec.c @@ -3,6 +3,7 @@ * * Copyright (c) 2001,2003 Networks Associates Technology, Inc. * Copyright (c) 2017 Dag-Erling Smørgrav + * Copyright (c) 2018 Thomas Munro * All rights reserved. * * This software was developed for the FreeBSD Project by ThinkSec AS and @@ -108,6 +109,7 @@ int return_prog_exit_status; int capture_stdout; int capture_stderr; + int expose_authtok; }; static int @@ -135,6 +137,8 @@ options->capture_stderr = 1; } else if (strcmp((*argv)[i], "return_prog_exit_status") == 0) { options->return_prog_exit_status = 1; + } else if (strcmp((*argv)[i], "expose_authtok") == 0) { + options->expose_authtok = 1; } else { if (strcmp((*argv)[i], "--") == 0) { (*argc)--; @@ -158,19 +162,22 @@ struct pe_opts *options) { char buf[PAM_MAX_MSG_SIZE]; - struct pollfd pfd[3]; + struct pollfd pfd[4]; const void *item; char **envlist, *envstr, *resp, **tmp; - ssize_t rlen; + ssize_t rlen, wlen; int envlen, extralen, i; int pam_err, serrno, status; - int chout[2], cherr[2], pd; - nfds_t nfds; + int chin[2], chout[2], cherr[2], pd; + nfds_t nfds, nreadfds; pid_t pid; + const char *authtok; + size_t authtok_size; + int rc; pd = -1; pid = 0; - chout[0] = chout[1] = cherr[0] = cherr[1] = -1; + chin[0] = chin[1] = chout[0] = chout[1] = cherr[0] = cherr[1] = -1; envlist = NULL; #define OUT(ret) do { pam_err = (ret); goto out; } while (0) @@ -235,6 +242,25 @@ openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d envlist = %p", envlen, extralen, envlist); + /* set up pipe and get authtok if requested */ + if (options->expose_authtok) { + if (pipe(chin) != 0) { + openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func); + OUT(PAM_SYSTEM_ERR); + } + if (fcntl(chin[1], F_SETFL, O_NONBLOCK)) { + openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func); + OUT(PAM_SYSTEM_ERR); + } + rc = pam_get_authtok(pamh, PAM_AUTHTOK, &authtok, NULL); + if (rc == PAM_SUCCESS) { + authtok_size = strlen(authtok); + } else { + openpam_log(PAM_LOG_ERROR, "%s: pam_get_authtok(): %s", func, + pam_strerror(pamh, rc)); + OUT(PAM_SYSTEM_ERR); + } + } /* set up pipes if capture was requested */ if (options->capture_stdout) { if (pipe(chout) != 0) { @@ -269,9 +295,13 @@ if ((pid = pdfork(&pd, 0)) == 0) { /* child */ - if ((chout[0] >= 0 && close(chout[0]) != 0) || + if ((chin[1] >= 0 && close(chin[1]) != 0) || + (chout[0] >= 0 && close(chout[0]) != 0) || (cherr[0] >= 0 && close(cherr[0]) != 0)) { openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func); + } else if (chin[0] >= 0 && + dup2(chin[0], STDIN_FILENO) != STDIN_FILENO) { + openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func); } else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO || dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) { openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func); @@ -288,7 +318,9 @@ openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func); OUT(PAM_SYSTEM_ERR); } - /* use poll() to watch the process and stdout / stderr */ + /* use poll() to watch the process and stdin / stdout / stderr */ + if (chin[0] >= 0) + close(chin[0]); if (chout[1] >= 0) close(chout[1]); if (cherr[1] >= 0) @@ -297,16 +329,24 @@ pfd[0].fd = pd; pfd[0].events = POLLHUP; nfds = 1; + nreadfds = 0; if (options->capture_stdout) { pfd[nfds].fd = chout[0]; pfd[nfds].events = POLLIN|POLLERR|POLLHUP; nfds++; + nreadfds++; } if (options->capture_stderr) { pfd[nfds].fd = cherr[0]; pfd[nfds].events = POLLIN|POLLERR|POLLHUP; nfds++; + nreadfds++; } + if (options->expose_authtok) { + pfd[nfds].fd = chin[1]; + pfd[nfds].events = POLLOUT|POLLERR|POLLHUP; + nfds++; + } /* loop until the process exits */ do { @@ -314,7 +354,8 @@ openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func); OUT(PAM_SYSTEM_ERR); } - for (i = 1; i < nfds; ++i) { + /* are the stderr / stdout pipes ready for reading? */ + for (i = 1; i < 1 + nreadfds; ++i) { if ((pfd[i].revents & POLLIN) == 0) continue; if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) { @@ -328,6 +369,26 @@ (void)pam_prompt(pamh, pfd[i].fd == chout[0] ? PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf); } + /* is the stdin pipe ready for writing? */ + if (options->expose_authtok && authtok_size > 0 && + (pfd[nfds - 1].revents & POLLOUT) != 0) { + if ((wlen = write(chin[1], authtok, authtok_size)) < 0) { + if (errno == EAGAIN) + continue; + openpam_log(PAM_LOG_ERROR, "%s: write(): %m", + func); + OUT(PAM_SYSTEM_ERR); + } else { + authtok += wlen; + authtok_size -= wlen; + if (authtok_size == 0) { + /* finished writing; close and forget the pipe */ + close(chin[1]); + chin[1] = -1; + nfds--; + } + } + } } while (pfd[0].revents == 0); /* the child process has exited */ @@ -364,6 +425,10 @@ serrno = errno; if (pd >= 0) close(pd); + if (chin[0] >= 0) + close(chin[0]); + if (chin[1] >= 0) + close(chin[1]); if (chout[0] >= 0) close(chout[0]); if (chout[1] >= 0)