]>
Commit | Line | Data |
---|---|---|
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 | } |