#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
__RCSID("$NetBSD: getpass.c,v 1.30 2016/01/31 23:41:38 christos Exp $");
#endif
#include "namespace.h"
#include <assert.h>
#ifdef TEST
#include <stdio.h>
#endif
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <paths.h>
#include <stdbool.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#ifdef __weak_alias
__weak_alias(getpassfd,_getpassfd)
__weak_alias(getpass_r,_getpass_r)
__weak_alias(getpass,_getpass)
#endif
char *
getpassfd(const char *prompt, char *buf, size_t len, int *fd, int flags,
int tout)
{
struct termios gt;
char c;
int sig;
bool lnext, havetty, allocated, opentty, good;
int fdc[3];
_DIAGASSERT(prompt != NULL);
if (buf != NULL && len == 0) {
errno = EINVAL;
return NULL;
}
good = false;
opentty = false;
if (fd == NULL) {
fd = fdc;
if ((fd[0] = fd[1] = fd[2] = open(_PATH_TTY,
O_RDWR | O_CLOEXEC)) == -1) {
fd[0] = STDIN_FILENO;
fd[1] = fd[2] = STDERR_FILENO;
} else
opentty = true;
}
sig = 0;
allocated = buf == NULL;
if (tcgetattr(fd[0], >) == -1) {
havetty = false;
if (flags & GETPASS_NEED_TTY)
goto out;
memset(>, -1, sizeof(gt));
} else
havetty = true;
if (havetty) {
struct termios st = gt;
st.c_lflag &= ~(ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL|ISIG|ICANON);
st.c_cc[VMIN] = 1;
st.c_cc[VTIME] = 0;
if (tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, &st) == -1)
goto out;
}
if (prompt != NULL) {
size_t plen = strlen(prompt);
(void)write(fd[1], prompt, plen);
}
if (allocated) {
len = 1024;
if ((buf = malloc(len)) == NULL)
goto restore;
}
c = '\1';
lnext = false;
for (size_t l = 0; c != '\0'; ) {
if (tout) {
struct pollfd pfd;
pfd.fd = fd[0];
pfd.events = POLLIN|POLLRDNORM;
pfd.revents = 0;
switch (poll(&pfd, 1, tout * 1000)) {
case 0:
errno = ETIMEDOUT;
case -1:
goto restore;
default:
break;
}
}
if (read(fd[0], &c, 1) != 1)
goto restore;
#define beep() \
do \
if (flags & GETPASS_NO_BEEP) \
(void)write(fd[2], "\a", 1); \
while ( 0)
#define erase() (void)write(fd[1], "\b \b", 3)
#define C(a, b) ((gt.c_cc[(a)] == _POSIX_VDISABLE || gt.c_cc[(a)] == '\0') ? \
(b) : gt.c_cc[(a)])
if (lnext) {
lnext = false;
goto add;
}
if (c == C(VREPRINT, CTRL('r')) || c == C(VSTART, CTRL('q')) ||
c == C(VSTOP, CTRL('s')) || c == C(VSTATUS, CTRL('t')) ||
c == C(VDISCARD, CTRL('o')))
continue;
if (c == C(VLNEXT, CTRL('v'))) {
lnext = true;
continue;
}
if (c == C(VKILL, CTRL('u')) || c == C(VWERASE, CTRL('w'))) {
if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR)) {
while (l--)
erase();
}
l = 0;
continue;
}
if (c == C(VERASE, CTRL('h'))) {
if (l == 0)
beep();
else {
l--;
if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR))
erase();
}
continue;
}
if (c == C(VINTR, CTRL('c'))) {
sig = SIGINT;
goto out;
}
if (c == C(VQUIT, CTRL('\\'))) {
sig = SIGQUIT;
goto out;
}
if (c == C(VSUSP, CTRL('z')) || c == C(VDSUSP, CTRL('y'))) {
sig = SIGTSTP;
goto out;
}
if (c == C(VEOF, CTRL('d'))) {
if (flags & GETPASS_FAIL_EOF) {
errno = ENODATA;
goto out;
} else {
c = '\0';
goto add;
}
}
if (c == C(VEOL, CTRL('j')) || c == C(VEOL2, CTRL('m')))
c = '\0';
add:
if (l >= len) {
if (allocated) {
size_t nlen = len + 1024;
char *nbuf = realloc(buf, nlen);
if (nbuf == NULL)
goto restore;
buf = nbuf;
len = nlen;
} else {
if (flags & GETPASS_BUF_LIMIT) {
beep();
continue;
}
if (c == '\0' && l > 0)
l--;
else
continue;
}
}
if (flags & GETPASS_7BIT)
c &= 0x7f;
if ((flags & GETPASS_FORCE_LOWER) && isupper((unsigned char)c))
c = tolower((unsigned char)c);
if ((flags & GETPASS_FORCE_UPPER) && islower((unsigned char)c))
c = toupper((unsigned char)c);
buf[l++] = c;
if (c) {
if (flags & GETPASS_ECHO_STAR)
(void)write(fd[1], "*", 1);
else if (flags & GETPASS_ECHO)
(void)write(fd[1], isprint((unsigned char)c) ?
&c : "?", 1);
}
}
good = true;
restore:
out:
if (havetty) {
c = errno;
(void)tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, >);
errno = c;
}
if (good && (flags & GETPASS_ECHO_NL))
(void)write(fd[1], "\n", 1);
if (opentty) {
c = errno;
(void)close(fd[0]);
errno = c;
}
if (good)
return buf;
if (sig) {
if ((flags & GETPASS_NO_SIGNAL) == 0)
(void)raise(sig);
errno = EINTR;
}
memset(buf, 0, len);
if (allocated)
free(buf);
return NULL;
}
char *
getpass_r(const char *prompt, char *buf, size_t len)
{
return getpassfd(prompt, buf, len, NULL, GETPASS_ECHO_NL, 0);
}
char *
getpass(const char *prompt)
{
static char e[] = "";
static char *buf;
static long bufsiz;
char *rv;
if (buf == NULL) {
if ((bufsiz = sysconf(_SC_PASS_MAX)) == -1)
return e;
if ((buf = malloc((size_t)bufsiz)) == NULL)
return e;
}
if ((rv = getpass_r(prompt, buf, (size_t)bufsiz)) == NULL)
return e;
return rv;
}
#ifdef TEST
int
main(int argc, char *argv[])
{
char buf[28];
printf("[%s]\n", getpassfd("foo>", buf, sizeof(buf), NULL,
GETPASS_ECHO_STAR|GETPASS_ECHO_NL, 2));
return 0;
}
#endif