File Coverage

secret_buffer_console.c
Criterion Covered Total %
statement 33 88 37.5
branch 21 54 38.8
condition n/a
subroutine n/a
pod n/a
total 54 142 38.0


line stmt bran cond sub pod time code
1             /*
2             * Cross-platform implementation of disablig console echo to read a password.
3             */
4              
5             typedef struct {
6             #ifdef WIN32
7             HANDLE hdl;
8             DWORD orig_mode, mode;
9             int pending_high_surrogate;
10             #else /* not WIN32 */
11             int fd;
12             struct termios orig_state, cur_state;
13             #endif
14             bool auto_restore, own_fd;
15             } sb_console_state;
16              
17             /* These are the platform-dependent functions */
18              
19             /* init state struct, and also read the console/terminal state and return whether it could be read */
20             static bool sb_console_state_init(pTHX_ sb_console_state *state, PerlIO *stream);
21             /* return status of echo bit */
22             static bool sb_console_state_get_echo(sb_console_state *state);
23             /* write console state with echo bit enabled or disabled */
24             static bool sb_console_state_set_echo(sb_console_state *state, bool enable);
25             /* wait until a character is available to be read from the console/tty */
26             static bool sb_console_state_wait_char_readable(pTHX_ sb_console_state *state, SV *timeout_sv);
27             /* return status of "line input" bit (ICANON mode on Posix) */
28             static bool sb_console_state_get_line_input(sb_console_state *state);
29             /* write console state with new value for line-input */
30             static bool sb_console_state_set_line_input(sb_console_state *state, bool enable);
31             /* Make a copy of the file descriptor to guarantee that we can restore it later even if the
32             * user interfered with the original file descriptor */
33             static bool sb_console_state_dup_fd(sb_console_state *state);
34             /* Write console/terminal state to original value. Return whether operation succeeded. */
35             static bool sb_console_state_restore(sb_console_state *state);
36             /* Clean up state struct and maybe auto-restore the state */
37             static void sb_console_state_destroy(pTHX_ sb_console_state *state);
38              
39             /* Public API */
40              
41             /* secret_buffer_append_console_line
42             * Append one line of console input to a SecretBuffer.
43             * Returns number of bytes on success, 0 for EOF (or count==0) even if some bytes appended,
44             * or -1 for temporary error. croaks on fatal error.
45             */
46 40           int secret_buffer_append_console_line(secret_buffer *buf, PerlIO *stream) {
47             dTHX;
48             sb_console_state cstate;
49             /* If either step fails, assume its not a console and keep going */
50 40           bool is_console= sb_console_state_init(aTHX_ &cstate, stream);
51 40           bool console_changed= is_console
52 0 0         && sb_console_state_get_echo(&cstate)
53 40 50         && sb_console_state_set_echo(&cstate, false);
    0          
54             /* Read one character at a time, because once we find "\n" the rest needs to stay in the OS buffer.
55             * This is inefficient, but passwords are relatively short so it hardly matters.
56             */
57 40           int got= 0;
58             char *eol;
59             while (1) {
60 413           got= secret_buffer_append_read(buf, stream, 1);
61 413 100         if (got <= 0)
62 9           break;
63 404           eol= buf->data + buf->len - 1;
64 404 100         if (*eol == '\r' || *eol == '\n') {
    100          
65 31           --buf->len; /* back up one char */
66             /* in the event of reading a text file, try to consume the "\n" that follows a "\r".
67             * If we get anything else, push it back into Perl's buffer.
68             * There really ought to be a test for "is it a disk file", or make it a nonblocking
69             * read, but I don't know how to write that portably for Win32.
70             */
71 31 100         if (*eol == '\r' && !is_console) {
    50          
72 8 50         if (secret_buffer_append_read(buf, stream, 1) > 0) {
73 8           --buf->len;
74 8 50         if (*eol != '\n') {
75 0 0         if (PerlIO_ungetc(stream, *eol) == EOF)
76 0           warn("BUG: lost a character of the input stream");
77             }
78             }
79             }
80 31           *eol= 0;
81 31           break;
82             }
83             }
84             /* Restore echo if we disabled it */
85 40 50         if (console_changed)
86 0           sb_console_state_restore(&cstate);
87 40           sb_console_state_destroy(aTHX_ &cstate);
88              
89             /* Any EOF is returned as an EOF even if some data appended */
90 40           return got;
91             }
92              
93             /* Perl MAGIC for holding sb_console_state */
94              
95             static int secret_buffer_console_state_magic_free(pTHX_ SV *sv, MAGIC *mg);
96             #ifdef USE_ITHREADS
97             static int secret_buffer_console_state_magic_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param);
98             #else
99             #define secret_buffer_console_state_magic_dup 0
100             #endif
101              
102             static MGVTBL secret_buffer_console_state_magic_vtbl = {
103             NULL, NULL, NULL, NULL,
104             secret_buffer_console_state_magic_free,
105             NULL,
106             secret_buffer_console_state_magic_dup
107             #ifdef MGf_LOCAL
108             ,NULL
109             #endif
110             };
111              
112             /* callback to auto-create sb_console_state for MAGIC */
113 0           void* secret_buffer_console_state_auto_ctor(pTHX_ SV *owner) {
114 0           sb_console_state *state_p= NULL;
115 0           Newx(state_p, 1, sb_console_state);
116 0           sb_console_state_init(aTHX_ state_p, NULL);
117 0           return state_p;
118             }
119             /* get a sb_console_state struct from MAGIC on an object.
120             * Flags can AUTOCREATE it or request an exception if there isn't one.
121             * See secret_buffer_X_from_magic docs.
122             */
123 0           sb_console_state * secret_buffer_console_state_from_magic(SV *owner, int flags) {
124             dTHX;
125 0           return (sb_console_state *)
126 0           secret_buffer_X_from_magic(aTHX_ owner, flags, &secret_buffer_console_state_magic_vtbl,
127             "console_state", secret_buffer_console_state_auto_ctor);
128             }
129 0           int secret_buffer_console_state_magic_free(pTHX_ SV *sv, MAGIC *mg) {
130 0           sb_console_state *cs= (sb_console_state *) mg->mg_ptr;
131 0 0         if (cs)
132 0           sb_console_state_destroy(aTHX_ cs);
133 0           return 0;
134             }
135             #ifdef USE_ITHREADS
136             int secret_buffer_console_state_magic_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param) {
137             croak("Can't clone console_state objects (patches welcome)");
138             return 0;
139             }
140             #endif
141              
142             #ifdef WIN32
143              
144             static bool sb_console_state_init(pTHX_ sb_console_state *state, PerlIO *stream) {
145             int stream_fd;
146             HANDLE stream_hdl;
147              
148             Zero(state, 1, sb_console_state);
149             state->hdl= INVALID_HANDLE_VALUE;
150              
151             /* PerlIO may or may not be backed by a real OS file descriptor */
152             stream_fd= stream? PerlIO_fileno(stream) : -1;
153             if (stream_fd < 0)
154             return false;
155              
156             /* LibC holds Win32 HANDLEs for each fd */
157             stream_hdl= (HANDLE)_get_osfhandle(stream_fd);
158             if (stream_hdl == INVALID_HANDLE_VALUE || GetFileType(stream_hdl) != FILE_TYPE_CHAR)
159             return false;
160              
161             /* Capture current state */
162             if (!GetConsoleMode(stream_hdl, &state->orig_mode))
163             return false;
164              
165             state->hdl= stream_hdl;
166             state->mode= state->orig_mode;
167             return true;
168             }
169              
170             static bool sb_console_state_dup_fd(sb_console_state *state) {
171             /* Clone the handle so that we can reset it independent of anything the user does that might
172             rearrange file descriptors */
173             if (!state->own_fd) {
174             HANDLE tmp;
175             if (!DuplicateHandle(GetCurrentProcess(), state->hdl, GetCurrentProcess(), &tmp, 0, FALSE, DUPLICATE_SAME_ACCESS))
176             croak_with_syserror("DuplicateHandle failed", GetLastError());
177             state->hdl= tmp;
178             state->own_fd= true;
179             }
180             return true;
181             }
182              
183             static bool sb_console_state_get_echo(sb_console_state *state) {
184             return state->mode & ENABLE_ECHO_INPUT;
185             }
186              
187             static bool sb_console_state_set_echo(sb_console_state *state, bool enable) {
188             DWORD mode= enable? (state->mode | ENABLE_ECHO_INPUT)
189             : (state->mode & ~ENABLE_ECHO_INPUT);
190             if (SetConsoleMode(state->hdl, mode)) {
191             state->mode= mode;
192             return true;
193             }
194             return false;
195             }
196              
197             static bool sb_console_state_get_line_input(sb_console_state *state) {
198             return state->mode & ENABLE_LINE_INPUT;
199             }
200              
201             static bool sb_console_state_set_line_input(sb_console_state *state, bool enable) {
202             DWORD mode= enable? (state->mode | ENABLE_LINE_INPUT)
203             : (state->mode & ~ENABLE_LINE_INPUT);
204             if (SetConsoleMode(state->hdl, mode)) {
205             state->mode= mode;
206             return true;
207             }
208             return false;
209             }
210              
211             static bool sb_console_state_restore(sb_console_state *state) {
212             if (SetConsoleMode(state->hdl, state->orig_mode)) {
213             state->mode= state->orig_mode;
214             return true;
215             }
216             return false;
217             }
218              
219             static void sb_console_state_destroy(pTHX_ sb_console_state *state) {
220             if (state->hdl != INVALID_HANDLE_VALUE) {
221             if (state->auto_restore)
222             if (!sb_console_state_restore(state))
223             warn("failed to restore console state");
224             if (state->own_fd)
225             if (!CloseHandle(state->hdl))
226             warn("BUG: CloseHandle failed");
227             state->hdl= INVALID_HANDLE_VALUE;
228             }
229             state->pending_high_surrogate= 0;
230             }
231              
232             static bool
233             sb_console_state_wait_char_readable(pTHX_ sb_console_state *state, SV *timeout_sv) {
234             return sb_wait_win32_handle_readable(aTHX_ state->hdl, timeout_sv);
235             }
236              
237             /* Windows API provides unicode key characters when you use ReadConsoleInputW.
238             * SecretBuffer is UTF-8 by default, so this function translates them.
239             * Returns true if it added a character, and false if no character is available yet,
240             * and croaks on OS errors or invalid surrogate pairs.
241             * It only appends whole characters (encoded as UTF-8) and may store half a
242             * surrogate pair into `state` to be combined with a subsequent call.
243             */
244             static bool
245             sb_append_console_char(pTHX_ sb_console_state *state, secret_buffer *dest) {
246             INPUT_RECORD in_rec[16];
247             const char *error= NULL;
248             DWORD err_code= 0;
249             int cp= -1;
250             /* Inspect pending console events until we find a real character-producing key event,
251             * including looking for surrogate pair if half a character is received.
252             * Discard non-character events.
253             */
254             while (cp < 0 && !error) {
255             DWORD i, nread;
256             if (!PeekConsoleInputW(state->hdl, in_rec, sizeof(in_rec)/sizeof(*in_rec), &nread)) {
257             error= "PeekConsoleInputW failed";
258             err_code= GetLastError();
259             translate_to_errno();
260             break;
261             }
262             if (!nread)
263             break;
264             for (i = 0; i < nread && cp < 0 && !error; i++) {
265             if (in_rec[i].EventType == KEY_EVENT
266             && in_rec[i].Event.KeyEvent.bKeyDown
267             && in_rec[i].Event.KeyEvent.uChar.UnicodeChar != 0
268             ) {
269             cp= (int) in_rec[i].Event.KeyEvent.uChar.UnicodeChar;
270             if (state->pending_high_surrogate) {
271             /* this needs to be the low surrogate, or die */
272             if (cp >= 0xDC00 && cp <= 0xDFFF) {
273             /* combine them */
274             cp = 0x10000 + ((state->pending_high_surrogate - 0xD800) << 10) + (cp - 0xDC00);
275             state->pending_high_surrogate= 0;
276             } else {
277             /* croak, but also clear the surrogate so the next read can succeed */
278             state->pending_high_surrogate= 0;
279             error= "received unpaired high UTF-16 surrogate";
280             break; /* stop before i++ so that this event is available later */
281             }
282             }
283             /* new high surrogate? */
284             else if (cp >= 0xD800 && cp <= 0xDBFF) {
285             state->pending_high_surrogate= cp;
286             cp= -1; /* keep looping since the low surrogate is probably in a following event */
287             }
288             /* low surrogate without high surrogate */
289             else if (cp >= 0xDC00 && cp <= 0xDFFF) {
290             error= "received unpaired low UTF-16 surrogate";
291             }
292             }
293             }
294             /* discard consumed events */
295             if (i > 0) {
296             DWORD nread2;
297             if (!ReadConsoleInputW(state->hdl, in_rec, i, &nread2)) {
298             error= "ReadConsoleInputW failed";
299             err_code= GetLastError();
300             translate_to_errno();
301             }
302             else if (nread2 != i)
303             error= "ReadConsoleInputW returned short count";
304             }
305             }
306             secret_buffer_wipe((char*) in_rec, sizeof(in_rec));
307             if (error) {
308             if (err_code)
309             croak_with_syserror(error, err_code);
310             else
311             croak("%s", error);
312             }
313             /* append character */
314             if (cp >= 0) {
315             secret_buffer_parse_rw out;
316             size_t old_len = dest->len;
317             int need = sizeof_codepoint_encoding(cp, SECRET_BUFFER_ENCODING_UTF8);
318             if (need < 0) /* probably impossible */
319             croak("utf-8 encode failed");
320             secret_buffer_alloc_at_least(dest, old_len + need);
321              
322             out.pos = (U8 *) dest->data + old_len;
323             out.lim = out.pos + need;
324             out.error = NULL;
325             out.encoding = SECRET_BUFFER_ENCODING_UTF8;
326             out.pos_bit = out.lim_bit = 0;
327             out.sbuf = dest;
328              
329             if (!sb_parse_encode_codepoint(&out, cp)) /* probably impossible */
330             croak("utf-8 encode failed");
331              
332             secret_buffer_set_len(dest, (size_t)(out.pos - (U8 *) dest->data));
333             return true;
334             }
335             else {
336             /* either no characters available, or read the high half of a surrogate pair */
337             return false;
338             }
339             }
340              
341             #else /* not WIN32 */
342              
343 87           static bool sb_console_state_init(pTHX_ sb_console_state *state, PerlIO *stream) {
344 87           Zero(state, 1, sb_console_state);
345              
346             /* PerlIO may or may not be backed by a real OS file descriptor */
347 87 50         state->fd= stream? PerlIO_fileno(stream) : -1;
348 87 100         if (state->fd < 0)
349 8           return false;
350              
351             /* Capture current state */
352 79 50         if (tcgetattr(state->fd, &state->orig_state) != 0)
353 79           return false;
354              
355 0           state->cur_state= state->orig_state;
356 0           return true;
357             }
358              
359 0           static bool sb_console_state_dup_fd(sb_console_state *state) {
360             /* Clone the handle so that we can reset it independent of anything the user does that might
361             rearrange file descriptors */
362 0 0         if (!state->own_fd) {
363 0           int new_fd= dup(state->fd);
364 0 0         if (new_fd < 0)
365 0           croak_with_syserror("dup() failed", errno);
366 0           state->fd= new_fd;
367 0           state->own_fd= true;
368             }
369 0           return true;
370             }
371              
372 0           static bool sb_console_state_get_echo(sb_console_state *state) {
373 0           return state->cur_state.c_lflag & ECHO;
374             }
375              
376 0           static bool sb_console_state_set_echo(sb_console_state *state, bool enable) {
377 0           struct termios new_st= state->cur_state;
378 0           new_st.c_lflag= enable? (new_st.c_lflag | ECHO)
379 0 0         : (new_st.c_lflag & ~ECHO);
380 0 0         if (tcsetattr(state->fd, TCSANOW, &new_st) == 0) {
381 0           state->cur_state= new_st;
382 0           return true;
383             }
384 0           else return false;
385             }
386              
387 0           static bool sb_console_state_get_line_input(sb_console_state *state) {
388 0           return state->cur_state.c_lflag & ICANON;
389             }
390              
391 0           static bool sb_console_state_set_line_input(sb_console_state *state, bool enable) {
392 0           struct termios new_st= state->cur_state;
393             /* to keep this similar to disabling *only* the line buffering of Win32,
394             enable ISIG so that ^C is still handled by the OS */
395 0           new_st.c_lflag= enable? ((new_st.c_lflag | ICANON) & ~(tcflag_t)ISIG)
396 0 0         : ((new_st.c_lflag & ~(tcflag_t)ICANON) | ISIG);
397 0 0         if (tcsetattr(state->fd, TCSANOW, &new_st) == 0) {
398 0           state->cur_state= new_st;
399 0           return true;
400             }
401 0           else return false;
402             }
403              
404 0           static bool sb_console_state_restore(sb_console_state *state) {
405 0           return tcsetattr(state->fd, TCSANOW, &state->orig_state) == 0;
406             }
407              
408 40           static void sb_console_state_destroy(pTHX_ sb_console_state *state) {
409 40 100         if (state->fd >= 0) {
410 36 50         if (state->auto_restore)
411 0 0         if (!sb_console_state_restore(state))
412 0           warn("failed to restore console state");
413 36 50         if (state->own_fd)
414 0 0         if (close(state->fd) < 0)
415 0           warn("BUG: close(tty_dup) failed");
416 36           state->fd= -1;
417             }
418 40           }
419              
420             static bool
421 0           sb_console_state_wait_char_readable(pTHX_ sb_console_state *state, SV *timeout_sv) {
422 0           return sb_wait_fd_readable(aTHX_ state->fd, timeout_sv);
423             }
424              
425             #endif