Reimplement, hopefully with marginally fewer bugs. There is an

unfortunate amount of code duplication between the tty and non-tty
paths, but the alternative is greatly increased complexity.


git-svn-id: svn+ssh://svn.openpam.org/svn/openpam/trunk@687 185d5e19-27fe-0310-9dcf-9bff6b9f3609
This commit is contained in:
Dag-Erling Smørgrav 2013-07-11 16:37:25 +00:00
parent 3a53d5117b
commit 93d104bfd6
1 changed files with 239 additions and 89 deletions

View File

@ -40,9 +40,11 @@
#endif
#include <sys/types.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <errno.h>
#include <setjmp.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@ -56,108 +58,253 @@
int openpam_ttyconv_timeout = 0;
volatile sig_atomic_t caught_signal;
/*
* Handle incoming signals during tty conversation
*/
static void
timeout(int sig)
catch_signal(int signo)
{
(void)sig;
switch (signo) {
case SIGINT:
case SIGQUIT:
case SIGTERM:
caught_signal = signo;
break;
}
}
static char *
prompt(const char *msg)
/*
* Accept a response from the user on a tty
*/
static int
prompt_tty(int ifd, int ofd, const char *message, char *response, int echo)
{
char buf[PAM_MAX_RESP_SIZE];
struct sigaction action, saved_action;
sigset_t saved_sigset, the_sigset;
unsigned int saved_alarm;
int eof, error, fd;
size_t len;
char *retval;
struct sigaction action;
struct sigaction saction_sigint, saction_sigquit, saction_sigterm;
struct termios tcattr;
struct timeval now, target, remaining;
int remaining_ms;
tcflag_t slflag;
struct pollfd pfd;
int serrno;
int pos, ret;
char ch;
sigemptyset(&the_sigset);
sigaddset(&the_sigset, SIGINT);
sigaddset(&the_sigset, SIGTSTP);
sigprocmask(SIG_SETMASK, &the_sigset, &saved_sigset);
action.sa_handler = &timeout;
action.sa_flags = 0;
sigemptyset(&action.sa_mask);
sigaction(SIGALRM, &action, &saved_action);
fputs(msg, stdout);
fflush(stdout);
#ifdef HAVE_FPURGE
fpurge(stdin);
#endif
fd = fileno(stdin);
buf[0] = '\0';
eof = error = 0;
saved_alarm = 0;
if (openpam_ttyconv_timeout >= 0)
saved_alarm = alarm(openpam_ttyconv_timeout);
ch = '\0';
for (len = 0; ch != '\n' && !eof && !error; ++len) {
switch (read(fd, &ch, 1)) {
case 1:
if (len < PAM_MAX_RESP_SIZE - 1) {
buf[len + 1] = '\0';
buf[len] = ch;
}
break;
case 0:
eof = 1;
break;
default:
error = errno;
break;
/* write prompt */
if (write(ofd, message, strlen(message)) < 0) {
openpam_log(PAM_LOG_ERROR, "write(): %m");
return (-1);
}
/* turn echo off if requested */
slflag = 0; /* prevent bogus uninitialized variable warning */
if (!echo) {
if (tcgetattr(ifd, &tcattr) != 0) {
openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
return (-1);
}
slflag = tcattr.c_lflag;
tcattr.c_lflag &= ~ECHO;
if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) {
openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
return (-1);
}
}
if (openpam_ttyconv_timeout >= 0)
alarm(0);
sigaction(SIGALRM, &saved_action, NULL);
sigprocmask(SIG_SETMASK, &saved_sigset, NULL);
if (saved_alarm > 0)
alarm(saved_alarm);
if (error == EINTR)
fputs(" timeout!", stderr);
if (error || eof) {
fputs("\n", stderr);
memset(buf, 0, sizeof(buf));
return (NULL);
/* install signal handlers */
caught_signal = 0;
action.sa_handler = &catch_signal;
action.sa_flags = 0;
sigfillset(&action.sa_mask);
sigaction(SIGINT, &action, &saction_sigint);
sigaction(SIGQUIT, &action, &saction_sigquit);
sigaction(SIGTERM, &action, &saction_sigterm);
/* compute timeout */
if (openpam_ttyconv_timeout > 0) {
(void)gettimeofday(&now, NULL);
remaining.tv_sec = openpam_ttyconv_timeout;
remaining.tv_usec = 0;
timeradd(&now, &remaining, &target);
} else {
/* prevent bogus uninitialized variable warning */
now.tv_sec = now.tv_usec = 0;
remaining.tv_sec = remaining.tv_usec = 0;
target.tv_sec = target.tv_usec = 0;
}
/* trim trailing whitespace */
for (len = strlen(buf); len > 0; --len)
if (buf[len - 1] != '\r' && buf[len - 1] != '\n')
/* input loop */
pos = 0;
ret = -1;
serrno = 0;
while (!caught_signal) {
pfd.fd = ifd;
pfd.events = POLLIN;
pfd.revents = 0;
if (openpam_ttyconv_timeout > 0) {
gettimeofday(&now, NULL);
if (timercmp(&now, &target, >))
break;
timersub(&target, &now, &remaining);
remaining_ms = remaining.tv_sec * 1000 +
remaining.tv_usec / 1000;
} else {
remaining_ms = INFTIM;
}
if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
serrno = errno;
if (errno == EINTR)
continue;
openpam_log(PAM_LOG_ERROR, "poll(): %m");
break;
buf[len] = '\0';
retval = strdup(buf);
memset(buf, 0, sizeof(buf));
return (retval);
} else if (ret == 0) {
/* timeout */
write(ofd, " timed out", 10);
openpam_log(PAM_LOG_NOTICE, "timed out");
break;
}
if ((ret = read(ifd, &ch, 1)) < 0) {
serrno = errno;
openpam_log(PAM_LOG_ERROR, "read(): %m");
break;
} else if (ret == 0 || ch == '\n') {
response[pos] = '\0';
ret = pos;
break;
}
if (pos + 1 < PAM_MAX_RESP_SIZE)
response[pos++] = ch;
/* overflow is discarded */
}
/* restore tty state */
if (!echo) {
tcattr.c_lflag = slflag;
if (tcsetattr(ifd, 0, &tcattr) != 0) {
/* treat as non-fatal, since we have our answer */
openpam_log(PAM_LOG_NOTICE, "tcsetattr(): %m");
}
}
/* restore signal handlers and re-post caught signal*/
sigaction(SIGINT, &saction_sigint, NULL);
sigaction(SIGQUIT, &saction_sigquit, NULL);
sigaction(SIGTERM, &saction_sigterm, NULL);
if (caught_signal != 0) {
openpam_log(PAM_LOG_ERROR, "caught signal %d",
(int)caught_signal);
raise((int)caught_signal);
/* if raise() had no effect... */
serrno = EINTR;
ret = -1;
}
/* done */
write(ofd, "\n", 1);
errno = serrno;
return (ret);
}
static char *
prompt_echo_off(const char *msg)
/*
* Accept a response from the user on a non-tty stdin.
*/
static int
prompt_notty(const char *message, char *response)
{
struct termios tattr;
tcflag_t lflag;
char *ret;
int fd;
struct timeval now, target, remaining;
int remaining_ms;
struct pollfd pfd;
int ch, pos, ret;
fd = fileno(stdin);
if (tcgetattr(fd, &tattr) != 0) {
openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
return (NULL);
/* show prompt */
fputs(message, stdout);
fflush(stdout);
/* compute timeout */
if (openpam_ttyconv_timeout > 0) {
(void)gettimeofday(&now, NULL);
remaining.tv_sec = openpam_ttyconv_timeout;
remaining.tv_usec = 0;
timeradd(&now, &remaining, &target);
} else {
/* prevent bogus uninitialized variable warning */
now.tv_sec = now.tv_usec = 0;
remaining.tv_sec = remaining.tv_usec = 0;
target.tv_sec = target.tv_usec = 0;
}
lflag = tattr.c_lflag;
tattr.c_lflag &= ~ECHO;
if (tcsetattr(fd, TCSAFLUSH, &tattr) != 0) {
openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
return (NULL);
/* input loop */
pos = 0;
for (;;) {
pfd.fd = STDIN_FILENO;
pfd.events = POLLIN;
pfd.revents = 0;
if (openpam_ttyconv_timeout > 0) {
gettimeofday(&now, NULL);
if (timercmp(&now, &target, >))
break;
timersub(&target, &now, &remaining);
remaining_ms = remaining.tv_sec * 1000 +
remaining.tv_usec / 1000;
} else {
remaining_ms = INFTIM;
}
if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
/* interrupt is ok, everything else -> bail */
if (errno == EINTR)
continue;
perror("\nopenpam_ttyconv");
return (-1);
} else if (ret == 0) {
/* timeout */
break;
} else {
/* input */
if ((ch = getchar()) == EOF && ferror(stdin)) {
perror("\nopenpam_ttyconv");
return (-1);
}
if (ch == EOF || ch == '\n') {
response[pos] = '\0';
return (pos);
}
if (pos + 1 < PAM_MAX_RESP_SIZE)
response[pos++] = ch;
/* overflow is discarded */
}
}
ret = prompt(msg);
tattr.c_lflag = lflag;
(void)tcsetattr(fd, TCSANOW, &tattr);
if (ret != NULL)
fputs("\n", stdout);
fputs("\nopenpam_ttyconv: timeout\n", stderr);
return (-1);
}
/*
* Determine whether stdin is a tty; if not, try to open the tty; in
* either case, call the appropriate method.
*/
static int
prompt(const char *message, char *response, int echo)
{
int ifd, ofd, ret;
if (isatty(STDIN_FILENO)) {
fflush(stdout);
#ifdef HAVE_FPURGE
fpurge(stdin);
#endif
ifd = STDIN_FILENO;
ofd = STDOUT_FILENO;
} else {
if ((ifd = open("/dev/tty", O_RDWR)) < 0)
/* no way to prevent echo */
return (prompt_notty(message, response));
ofd = ifd;
}
ret = prompt_tty(ifd, ofd, message, response, echo);
if (ifd != STDIN_FILENO)
close(ifd);
return (ret);
}
@ -173,6 +320,7 @@ openpam_ttyconv(int n,
struct pam_response **resp,
void *data)
{
char respbuf[PAM_MAX_RESP_SIZE];
struct pam_response *aresp;
int i;
@ -187,13 +335,13 @@ openpam_ttyconv(int n,
aresp[i].resp = NULL;
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
aresp[i].resp = prompt_echo_off(msg[i]->msg);
if (aresp[i].resp == NULL)
if (prompt(msg[i]->msg, respbuf, 0) < 0 ||
(aresp[i].resp = strdup(respbuf)) == NULL)
goto fail;
break;
case PAM_PROMPT_ECHO_ON:
aresp[i].resp = prompt(msg[i]->msg);
if (aresp[i].resp == NULL)
if (prompt(msg[i]->msg, respbuf, 1) < 0 ||
(aresp[i].resp = strdup(respbuf)) == NULL)
goto fail;
break;
case PAM_ERROR_MSG:
@ -213,6 +361,7 @@ openpam_ttyconv(int n,
}
}
*resp = aresp;
memset(respbuf, 0, sizeof respbuf);
RETURNC(PAM_SUCCESS);
fail:
for (i = 0; i < n; ++i) {
@ -224,6 +373,7 @@ fail:
memset(aresp, 0, n * sizeof *aresp);
FREE(aresp);
*resp = NULL;
memset(respbuf, 0, sizeof respbuf);
RETURNC(PAM_CONV_ERR);
}