123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #include <signal.h>
- #include <stdio.h>
- #include <string.h>
- #include <termios.h>
- #include <unistd.h>
- #include "insecure_memzero.h"
- #include "warnp.h"
- #include "readpass.h"
- #define MAXPASSLEN 2048
- /* Signals we need to block. */
- static const int badsigs[] = {
- SIGALRM, SIGHUP, SIGINT,
- SIGPIPE, SIGQUIT, SIGTERM,
- SIGTSTP, SIGTTIN, SIGTTOU
- };
- #define NSIGS sizeof(badsigs)/sizeof(badsigs[0])
- /* Highest signal number we care about. */
- #define MAX2(a, b) ((a) > (b) ? (a) : (b))
- #define MAX4(a, b, c, d) MAX2(MAX2(a, b), MAX2(c, d))
- #define MAX8(a, b, c, d, e, f, g, h) MAX2(MAX4(a, b, c, d), MAX4(e, f, g, h))
- #define MAXBADSIG MAX2(SIGALRM, MAX8(SIGHUP, SIGINT, SIGPIPE, SIGQUIT, \
- SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU))
- /* Has a signal of this type been received? */
- static volatile sig_atomic_t gotsig[MAXBADSIG + 1];
- /* Signal handler. */
- static void
- handle(int sig)
- {
- gotsig[sig] = 1;
- }
- /* Restore old signals and re-issue intercepted signals. */
- static void
- resetsigs(struct sigaction savedsa[NSIGS])
- {
- size_t i;
- /* Restore old signals. */
- for (i = 0; i < NSIGS; i++)
- sigaction(badsigs[i], &savedsa[i], NULL);
- /* If we intercepted a signal, re-issue it. */
- for (i = 0; i < NSIGS; i++) {
- if (gotsig[badsigs[i]])
- raise(badsigs[i]);
- }
- }
- /**
- * readpass(passwd, prompt, confirmprompt, devtty):
- * If ${devtty} is 0, read a password from stdin. If ${devtty} is 1, read a
- * password from /dev/tty if possible; if not, read from stdin. If ${devtty}
- * is 2, read a password from /dev/tty if possible; if not, exit with an error.
- * If reading from a tty (either /dev/tty or stdin), disable echo and prompt
- * the user by printing ${prompt} to stderr. If ${confirmprompt} is non-NULL,
- * read a second password (prompting if a terminal is being used) and repeat
- * until the user enters the same password twice. Return the password as a
- * malloced NUL-terminated string via ${passwd}.
- */
- int
- readpass(char ** passwd, const char * prompt,
- const char * confirmprompt, int devtty)
- {
- FILE * readfrom;
- char passbuf[MAXPASSLEN];
- char confpassbuf[MAXPASSLEN];
- struct sigaction sa, savedsa[NSIGS];
- struct termios term, term_old;
- size_t i;
- int usingtty;
- /* Where should we read the password from? */
- switch (devtty) {
- case 0:
- /* Read directly from stdin. */
- readfrom = stdin;
- break;
- case 1:
- /* Try to open /dev/tty; if that fails, read from stdin. */
- if ((readfrom = fopen("/dev/tty", "r")) == NULL)
- readfrom = stdin;
- break;
- case 2:
- /* Try to open /dev/tty; if that fails, bail. */
- if ((readfrom = fopen("/dev/tty", "r")) == NULL) {
- warnp("fopen(/dev/tty)");
- goto err1;
- }
- break;
- default:
- warn0("readpass does not support devtty=%d", devtty);
- goto err1;
- }
- /* We have not received any signals yet. */
- for (i = 0; i <= MAXBADSIG; i++)
- gotsig[i] = 0;
- /*
- * If we receive a signal while we're reading the password, we might
- * end up with echo disabled; to prevent this, we catch the signals
- * here, and we'll re-send them to ourselves later after we re-enable
- * terminal echo.
- */
- sa.sa_handler = handle;
- sa.sa_flags = 0;
- sigemptyset(&sa.sa_mask);
- for (i = 0; i < NSIGS; i++)
- sigaction(badsigs[i], &sa, &savedsa[i]);
- /* If we're reading from a terminal, try to disable echo. */
- if ((usingtty = isatty(fileno(readfrom))) != 0) {
- if (tcgetattr(fileno(readfrom), &term_old)) {
- warnp("Cannot read terminal settings");
- goto err2;
- }
- memcpy(&term, &term_old, sizeof(struct termios));
- term.c_lflag = (term.c_lflag & ~((tcflag_t)ECHO)) | ECHONL;
- if (tcsetattr(fileno(readfrom), TCSANOW, &term)) {
- warnp("Cannot set terminal settings");
- goto err2;
- }
- }
- retry:
- /* If we have a terminal, prompt the user to enter the password. */
- if (usingtty)
- fprintf(stderr, "%s: ", prompt);
- /* Read the password. */
- if (fgets(passbuf, MAXPASSLEN, readfrom) == NULL) {
- if (feof(readfrom))
- warn0("EOF reading password");
- else
- warnp("Cannot read password");
- goto err3;
- }
- /* Confirm the password if necessary. */
- if (confirmprompt != NULL) {
- if (usingtty)
- fprintf(stderr, "%s: ", confirmprompt);
- if (fgets(confpassbuf, MAXPASSLEN, readfrom) == NULL) {
- if (feof(readfrom))
- warn0("EOF reading password");
- else
- warnp("Cannot read password");
- goto err3;
- }
- if (strcmp(passbuf, confpassbuf)) {
- fprintf(stderr,
- "Passwords mismatch, please try again\n");
- goto retry;
- }
- }
- /* Terminate the string at the first "\r" or "\n" (if any). */
- passbuf[strcspn(passbuf, "\r\n")] = '\0';
- /* If we changed terminal settings, reset them. */
- if (usingtty)
- tcsetattr(fileno(readfrom), TCSANOW, &term_old);
- /* Restore old signals and re-issue intercepted signals. */
- resetsigs(savedsa);
- /* Close /dev/tty if we opened it. */
- if ((readfrom != stdin) && fclose(readfrom))
- warnp("fclose");
- /* Copy the password out. */
- if ((*passwd = strdup(passbuf)) == NULL) {
- warnp("Cannot allocate memory");
- goto err1;
- }
- /*
- * Zero any stored passwords. This is not guaranteed to work, since a
- * "sufficiently intelligent" compiler can optimize these out due to
- * the values not being accessed again; and even if we outwitted the
- * compiler, all we can do is ensure that *a* buffer is zeroed but
- * not that it is the only buffer containing the data in question.
- * Unfortunately the C standard does not provide any way to mark data
- * as "sensitive" in order to prevent extra copies being sprinkled
- * around the implementation address space.
- */
- insecure_memzero(passbuf, MAXPASSLEN);
- insecure_memzero(confpassbuf, MAXPASSLEN);
- /* Success! */
- return (0);
- err3:
- /* Reset terminal settings if necessary. */
- if (usingtty)
- tcsetattr(fileno(readfrom), TCSAFLUSH, &term_old);
- err2:
- /* Close /dev/tty if we opened it. */
- if ((readfrom != stdin) && fclose(readfrom))
- warnp("fclose");
- /* Restore old signals and re-issue intercepted signals. */
- resetsigs(savedsa);
- err1:
- /* Zero any stored passwords. */
- insecure_memzero(passbuf, MAXPASSLEN);
- insecure_memzero(confpassbuf, MAXPASSLEN);
- /* Failure! */
- return (-1);
- }
|