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 #endif
#include <sys/types.h> #include <sys/types.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <errno.h> #include <errno.h>
#include <setjmp.h> #include <fcntl.h>
#include <signal.h> #include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -56,108 +58,253 @@
int openpam_ttyconv_timeout = 0; int openpam_ttyconv_timeout = 0;
volatile sig_atomic_t caught_signal;
/*
* Handle incoming signals during tty conversation
*/
static void 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;
struct sigaction action, saved_action; struct sigaction saction_sigint, saction_sigquit, saction_sigterm;
sigset_t saved_sigset, the_sigset; struct termios tcattr;
unsigned int saved_alarm; struct timeval now, target, remaining;
int eof, error, fd; int remaining_ms;
size_t len; tcflag_t slflag;
char *retval; struct pollfd pfd;
int serrno;
int pos, ret;
char ch; char ch;
sigemptyset(&the_sigset); /* write prompt */
sigaddset(&the_sigset, SIGINT); if (write(ofd, message, strlen(message)) < 0) {
sigaddset(&the_sigset, SIGTSTP); openpam_log(PAM_LOG_ERROR, "write(): %m");
sigprocmask(SIG_SETMASK, &the_sigset, &saved_sigset); return (-1);
action.sa_handler = &timeout; }
action.sa_flags = 0;
sigemptyset(&action.sa_mask); /* turn echo off if requested */
sigaction(SIGALRM, &action, &saved_action); slflag = 0; /* prevent bogus uninitialized variable warning */
fputs(msg, stdout); if (!echo) {
fflush(stdout); if (tcgetattr(ifd, &tcattr) != 0) {
#ifdef HAVE_FPURGE openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
fpurge(stdin); return (-1);
#endif }
fd = fileno(stdin); slflag = tcattr.c_lflag;
buf[0] = '\0'; tcattr.c_lflag &= ~ECHO;
eof = error = 0; if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) {
saved_alarm = 0; openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
if (openpam_ttyconv_timeout >= 0) return (-1);
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;
} }
} }
if (openpam_ttyconv_timeout >= 0)
alarm(0); /* install signal handlers */
sigaction(SIGALRM, &saved_action, NULL); caught_signal = 0;
sigprocmask(SIG_SETMASK, &saved_sigset, NULL); action.sa_handler = &catch_signal;
if (saved_alarm > 0) action.sa_flags = 0;
alarm(saved_alarm); sigfillset(&action.sa_mask);
if (error == EINTR) sigaction(SIGINT, &action, &saction_sigint);
fputs(" timeout!", stderr); sigaction(SIGQUIT, &action, &saction_sigquit);
if (error || eof) { sigaction(SIGTERM, &action, &saction_sigterm);
fputs("\n", stderr);
memset(buf, 0, sizeof(buf)); /* compute timeout */
return (NULL); 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) /* input loop */
if (buf[len - 1] != '\r' && buf[len - 1] != '\n') 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; break;
buf[len] = '\0'; } else if (ret == 0) {
retval = strdup(buf); /* timeout */
memset(buf, 0, sizeof(buf)); write(ofd, " timed out", 10);
return (retval); 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; struct timeval now, target, remaining;
tcflag_t lflag; int remaining_ms;
char *ret; struct pollfd pfd;
int fd; int ch, pos, ret;
fd = fileno(stdin); /* show prompt */
if (tcgetattr(fd, &tattr) != 0) { fputs(message, stdout);
openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m"); fflush(stdout);
return (NULL);
/* 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; /* input loop */
if (tcsetattr(fd, TCSAFLUSH, &tattr) != 0) { pos = 0;
openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m"); for (;;) {
return (NULL); 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); fputs("\nopenpam_ttyconv: timeout\n", stderr);
tattr.c_lflag = lflag; return (-1);
(void)tcsetattr(fd, TCSANOW, &tattr); }
if (ret != NULL)
fputs("\n", stdout); /*
* 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); return (ret);
} }
@ -173,6 +320,7 @@ openpam_ttyconv(int n,
struct pam_response **resp, struct pam_response **resp,
void *data) void *data)
{ {
char respbuf[PAM_MAX_RESP_SIZE];
struct pam_response *aresp; struct pam_response *aresp;
int i; int i;
@ -187,13 +335,13 @@ openpam_ttyconv(int n,
aresp[i].resp = NULL; aresp[i].resp = NULL;
switch (msg[i]->msg_style) { switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_OFF:
aresp[i].resp = prompt_echo_off(msg[i]->msg); if (prompt(msg[i]->msg, respbuf, 0) < 0 ||
if (aresp[i].resp == NULL) (aresp[i].resp = strdup(respbuf)) == NULL)
goto fail; goto fail;
break; break;
case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_ON:
aresp[i].resp = prompt(msg[i]->msg); if (prompt(msg[i]->msg, respbuf, 1) < 0 ||
if (aresp[i].resp == NULL) (aresp[i].resp = strdup(respbuf)) == NULL)
goto fail; goto fail;
break; break;
case PAM_ERROR_MSG: case PAM_ERROR_MSG:
@ -213,6 +361,7 @@ openpam_ttyconv(int n,
} }
} }
*resp = aresp; *resp = aresp;
memset(respbuf, 0, sizeof respbuf);
RETURNC(PAM_SUCCESS); RETURNC(PAM_SUCCESS);
fail: fail:
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
@ -224,6 +373,7 @@ fail:
memset(aresp, 0, n * sizeof *aresp); memset(aresp, 0, n * sizeof *aresp);
FREE(aresp); FREE(aresp);
*resp = NULL; *resp = NULL;
memset(respbuf, 0, sizeof respbuf);
RETURNC(PAM_CONV_ERR); RETURNC(PAM_CONV_ERR);
} }