| 1 | #include <signal.h> |
| 2 | #include <stdio.h> |
| 3 | #include <string.h> |
| 4 | #include <termios.h> |
| 5 | #include <unistd.h> |
| 6 | |
| 7 | #include "insecure_memzero.h" |
| 8 | #include "warnp.h" |
| 9 | |
| 10 | #include "readpass.h" |
| 11 | |
| 12 | #define MAXPASSLEN 2048 |
| 13 | |
| 14 | /* Signals we need to block. */ |
| 15 | static const int badsigs[] = { |
| 16 | SIGALRM, SIGHUP, SIGINT, |
| 17 | SIGPIPE, SIGQUIT, SIGTERM, |
| 18 | SIGTSTP, SIGTTIN, SIGTTOU |
| 19 | }; |
| 20 | #define NSIGS sizeof(badsigs)/sizeof(badsigs[0]) |
| 21 | |
| 22 | /* Highest signal number we care about. */ |
| 23 | #define MAX2(a, b) ((a) > (b) ? (a) : (b)) |
| 24 | #define MAX4(a, b, c, d) MAX2(MAX2(a, b), MAX2(c, d)) |
| 25 | #define MAX8(a, b, c, d, e, f, g, h) MAX2(MAX4(a, b, c, d), MAX4(e, f, g, h)) |
| 26 | #define MAXBADSIG MAX2(SIGALRM, MAX8(SIGHUP, SIGINT, SIGPIPE, SIGQUIT, \ |
| 27 | SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU)) |
| 28 | |
| 29 | /* Has a signal of this type been received? */ |
| 30 | static volatile sig_atomic_t gotsig[MAXBADSIG + 1]; |
| 31 | |
| 32 | /* Signal handler. */ |
| 33 | static void |
| 34 | handle(int sig) |
| 35 | { |
| 36 | |
| 37 | gotsig[sig] = 1; |
| 38 | } |
| 39 | |
| 40 | /* Restore old signals and re-issue intercepted signals. */ |
| 41 | static void |
| 42 | resetsigs(struct sigaction savedsa[NSIGS]) |
| 43 | { |
| 44 | size_t i; |
| 45 | |
| 46 | /* Restore old signals. */ |
| 47 | for (i = 0; i < NSIGS; i++) |
| 48 | sigaction(badsigs[i], &savedsa[i], NULL); |
| 49 | |
| 50 | /* If we intercepted a signal, re-issue it. */ |
| 51 | for (i = 0; i < NSIGS; i++) { |
| 52 | if (gotsig[badsigs[i]]) |
| 53 | raise(badsigs[i]); |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * readpass(passwd, prompt, confirmprompt, devtty) |
| 59 | * If ${devtty} is non-zero, read a password from /dev/tty if possible; if |
| 60 | * not, read from stdin. If reading from a tty (either /dev/tty or stdin), |
| 61 | * disable echo and prompt the user by printing ${prompt} to stderr. If |
| 62 | * ${confirmprompt} is non-NULL, read a second password (prompting if a |
| 63 | * terminal is being used) and repeat until the user enters the same password |
| 64 | * twice. Return the password as a malloced NUL-terminated string via |
| 65 | * ${passwd}. |
| 66 | */ |
| 67 | int |
| 68 | readpass(char ** passwd, const char * prompt, |
| 69 | const char * confirmprompt, int devtty) |
| 70 | { |
| 71 | FILE * readfrom; |
| 72 | char passbuf[MAXPASSLEN]; |
| 73 | char confpassbuf[MAXPASSLEN]; |
| 74 | struct sigaction sa, savedsa[NSIGS]; |
| 75 | struct termios term, term_old; |
| 76 | size_t i; |
| 77 | int usingtty; |
| 78 | |
| 79 | /* |
| 80 | * If devtty != 0, try to open /dev/tty; if that fails, or if devtty |
| 81 | * is zero, we'll read the password from stdin instead. |
| 82 | */ |
| 83 | if ((devtty == 0) || ((readfrom = fopen("/dev/tty", "r")) == NULL)) |
| 84 | readfrom = stdin; |
| 85 | |
| 86 | /* We have not received any signals yet. */ |
| 87 | for (i = 0; i <= MAXBADSIG; i++) |
| 88 | gotsig[i] = 0; |
| 89 | |
| 90 | /* |
| 91 | * If we receive a signal while we're reading the password, we might |
| 92 | * end up with echo disabled; to prevent this, we catch the signals |
| 93 | * here, and we'll re-send them to ourselves later after we re-enable |
| 94 | * terminal echo. |
| 95 | */ |
| 96 | sa.sa_handler = handle; |
| 97 | sa.sa_flags = 0; |
| 98 | sigemptyset(&sa.sa_mask); |
| 99 | for (i = 0; i < NSIGS; i++) |
| 100 | sigaction(badsigs[i], &sa, &savedsa[i]); |
| 101 | |
| 102 | /* If we're reading from a terminal, try to disable echo. */ |
| 103 | if ((usingtty = isatty(fileno(readfrom))) != 0) { |
| 104 | if (tcgetattr(fileno(readfrom), &term_old)) { |
| 105 | warnp("Cannot read terminal settings"); |
| 106 | goto err2; |
| 107 | } |
| 108 | memcpy(&term, &term_old, sizeof(struct termios)); |
| 109 | term.c_lflag = (term.c_lflag & ~((tcflag_t)ECHO)) | ECHONL; |
| 110 | if (tcsetattr(fileno(readfrom), TCSANOW, &term)) { |
| 111 | warnp("Cannot set terminal settings"); |
| 112 | goto err2; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | retry: |
| 117 | /* If we have a terminal, prompt the user to enter the password. */ |
| 118 | if (usingtty) |
| 119 | fprintf(stderr, "%s: ", prompt); |
| 120 | |
| 121 | /* Read the password. */ |
| 122 | if (fgets(passbuf, MAXPASSLEN, readfrom) == NULL) { |
| 123 | if (feof(readfrom)) |
| 124 | warn0("EOF reading password"); |
| 125 | else |
| 126 | warnp("Cannot read password"); |
| 127 | goto err3; |
| 128 | } |
| 129 | |
| 130 | /* Confirm the password if necessary. */ |
| 131 | if (confirmprompt != NULL) { |
| 132 | if (usingtty) |
| 133 | fprintf(stderr, "%s: ", confirmprompt); |
| 134 | if (fgets(confpassbuf, MAXPASSLEN, readfrom) == NULL) { |
| 135 | if (feof(readfrom)) |
| 136 | warn0("EOF reading password"); |
| 137 | else |
| 138 | warnp("Cannot read password"); |
| 139 | goto err3; |
| 140 | } |
| 141 | if (strcmp(passbuf, confpassbuf)) { |
| 142 | fprintf(stderr, |
| 143 | "Passwords mismatch, please try again\n"); |
| 144 | goto retry; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /* Terminate the string at the first "\r" or "\n" (if any). */ |
| 149 | passbuf[strcspn(passbuf, "\r\n")] = '\0'; |
| 150 | |
| 151 | /* If we changed terminal settings, reset them. */ |
| 152 | if (usingtty) |
| 153 | tcsetattr(fileno(readfrom), TCSANOW, &term_old); |
| 154 | |
| 155 | /* Restore old signals and re-issue intercepted signals. */ |
| 156 | resetsigs(savedsa); |
| 157 | |
| 158 | /* Close /dev/tty if we opened it. */ |
| 159 | if (readfrom != stdin) |
| 160 | fclose(readfrom); |
| 161 | |
| 162 | /* Copy the password out. */ |
| 163 | if ((*passwd = strdup(passbuf)) == NULL) { |
| 164 | warnp("Cannot allocate memory"); |
| 165 | goto err1; |
| 166 | } |
| 167 | |
| 168 | /* |
| 169 | * Zero any stored passwords. This is not guaranteed to work, since a |
| 170 | * "sufficiently intelligent" compiler can optimize these out due to |
| 171 | * the values not being accessed again; and even if we outwitted the |
| 172 | * compiler, all we can do is ensure that *a* buffer is zeroed but |
| 173 | * not that it is the only buffer containing the data in question. |
| 174 | * Unfortunately the C standard does not provide any way to mark data |
| 175 | * as "sensitive" in order to prevent extra copies being sprinkled |
| 176 | * around the implementation address space. |
| 177 | */ |
| 178 | insecure_memzero(passbuf, MAXPASSLEN); |
| 179 | insecure_memzero(confpassbuf, MAXPASSLEN); |
| 180 | |
| 181 | /* Success! */ |
| 182 | return (0); |
| 183 | |
| 184 | err3: |
| 185 | /* Reset terminal settings if necessary. */ |
| 186 | if (usingtty) |
| 187 | tcsetattr(fileno(readfrom), TCSAFLUSH, &term_old); |
| 188 | err2: |
| 189 | /* Close /dev/tty if we opened it. */ |
| 190 | if (readfrom != stdin) |
| 191 | fclose(readfrom); |
| 192 | |
| 193 | /* Restore old signals and re-issue intercepted signals. */ |
| 194 | resetsigs(savedsa); |
| 195 | err1: |
| 196 | /* Zero any stored passwords. */ |
| 197 | insecure_memzero(passbuf, MAXPASSLEN); |
| 198 | insecure_memzero(confpassbuf, MAXPASSLEN); |
| 199 | |
| 200 | /* Failure! */ |
| 201 | return (-1); |
| 202 | } |