File Coverage

secret_buffer_console.c
Criterion Covered Total %
statement 33 86 38.3
branch 21 54 38.8
condition n/a
subroutine n/a
pod n/a
total 54 140 38.5


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             #else /* not WIN32 */
10             int fd;
11             struct termios orig_state, cur_state;
12             #endif
13             bool auto_restore, own_fd;
14             } sb_console_state;
15              
16             /* These are the platform-dependent functions */
17              
18             /* init state struct, and also read the console/terminal state and return whether it could be read */
19             static bool sb_console_state_init(pTHX_ sb_console_state *state, PerlIO *stream);
20             /* return status of echo bit */
21             static bool sb_console_state_get_echo(sb_console_state *state);
22             /* write console state with echo bit enabled or disabled */
23             static bool sb_console_state_set_echo(sb_console_state *state, bool enable);
24             /* return status of "line input" bit (ICANON mode on Posix) */
25             static bool sb_console_state_get_line_input(sb_console_state *state);
26             /* write console state with new value for line-input */
27             static bool sb_console_state_set_line_input(sb_console_state *state, bool enable);
28             /* Make a copy of the file descriptor to guarantee that we can restore it later even if the
29             * user interfered with the original file descriptor */
30             static bool sb_console_state_dup_fd(sb_console_state *state);
31             /* Write console/terminal state to original value. Return whether operation succeeded. */
32             static bool sb_console_state_restore(sb_console_state *state);
33             /* Clean up state struct and maybe auto-restore the state */
34             static void sb_console_state_destroy(pTHX_ sb_console_state *state);
35              
36             /* Public API */
37              
38             /* secret_buffer_append_console_line
39             * Append one line of console input to a SecretBuffer.
40             * Returns number of bytes on success, 0 for EOF (or count==0) even if some bytes appended,
41             * or -1 for temporary error. croaks on fatal error.
42             */
43 22           int secret_buffer_append_console_line(secret_buffer *buf, PerlIO *stream) {
44             dTHX;
45             sb_console_state cstate;
46             /* If either step fails, assume its not a console and keep going */
47 22           bool is_console= sb_console_state_init(aTHX_ &cstate, stream);
48 22           bool console_changed= is_console
49 0 0         && sb_console_state_get_echo(&cstate)
50 22 50         && sb_console_state_set_echo(&cstate, false);
    0          
51             /* Read one character at a time, because once we find "\n" the rest needs to stay in the OS buffer.
52             * This is inefficient, but passwords are relatively short so it hardly matters.
53             */
54 22           int got= 0;
55             char *eol;
56             while (1) {
57 205           got= secret_buffer_append_read(buf, stream, 1);
58 205 100         if (got <= 0)
59 5           break;
60 200           eol= buf->data + buf->len - 1;
61 200 100         if (*eol == '\r' || *eol == '\n') {
    100          
62 17           --buf->len; /* back up one char */
63             /* in the event of reading a text file, try to consume the "\n" that follows a "\r".
64             * If we get anything else, push it back into Perl's buffer.
65             * There really ought to be a test for "is it a disk file", or make it a nonblocking
66             * read, but I don't know how to write that portably for Win32.
67             */
68 17 100         if (*eol == '\r' && !is_console) {
    50          
69 4 50         if (secret_buffer_append_read(buf, stream, 1) > 0) {
70 4           --buf->len;
71 4 50         if (*eol != '\n') {
72 0 0         if (PerlIO_ungetc(stream, *eol) == EOF)
73 0           warn("BUG: lost a character of the input stream");
74             }
75             }
76             }
77 17           *eol= 0;
78 17           break;
79             }
80             }
81             /* Restore echo if we disabled it */
82 22 50         if (console_changed)
83 0           sb_console_state_restore(&cstate);
84 22           sb_console_state_destroy(aTHX_ &cstate);
85              
86             /* Any EOF is returned as an EOF even if some data appended */
87 22           return got;
88             }
89              
90             /* Perl MAGIC for holding sb_console_state */
91              
92             static int secret_buffer_console_state_magic_free(pTHX_ SV *sv, MAGIC *mg);
93             #ifdef USE_ITHREADS
94             static int secret_buffer_console_state_magic_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param);
95             #else
96             #define secret_buffer_console_state_magic_dup 0
97             #endif
98              
99             static MGVTBL secret_buffer_console_state_magic_vtbl = {
100             NULL, NULL, NULL, NULL,
101             secret_buffer_console_state_magic_free,
102             NULL,
103             secret_buffer_console_state_magic_dup
104             #ifdef MGf_LOCAL
105             ,NULL
106             #endif
107             };
108              
109             /* callback to auto-create sb_console_state for MAGIC */
110 0           void* secret_buffer_console_state_auto_ctor(pTHX_ SV *owner) {
111 0           sb_console_state *state_p= NULL;
112 0           Newx(state_p, 1, sb_console_state);
113 0           sb_console_state_init(aTHX_ state_p, NULL);
114 0           return state_p;
115             }
116             /* get a sb_console_state struct from MAGIC on an object.
117             * Flags can AUTOCREATE it or request an exception if there isn't one.
118             * See secret_buffer_X_from_magic docs.
119             */
120 0           sb_console_state * secret_buffer_console_state_from_magic(SV *owner, int flags) {
121             dTHX;
122 0           return (sb_console_state *)
123 0           secret_buffer_X_from_magic(aTHX_ owner, flags, &secret_buffer_console_state_magic_vtbl,
124             "console_state", secret_buffer_console_state_auto_ctor);
125             }
126 0           int secret_buffer_console_state_magic_free(pTHX_ SV *sv, MAGIC *mg) {
127 0           sb_console_state *cs= (sb_console_state *) mg->mg_ptr;
128 0 0         if (cs)
129 0           sb_console_state_destroy(aTHX_ cs);
130 0           return 0;
131             }
132             #ifdef USE_ITHREADS
133             int secret_buffer_console_state_magic_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param) {
134             croak("Can't clone console_state objects (patches welcome)");
135             return 0;
136             }
137             #endif
138              
139             #ifdef WIN32
140              
141             static bool sb_console_state_init(pTHX_ sb_console_state *state, PerlIO *stream) {
142             int stream_fd;
143             HANDLE stream_hdl;
144              
145             Zero(state, 1, sb_console_state);
146             state->hdl= INVALID_HANDLE_VALUE;
147              
148             /* PerlIO may or may not be backed by a real OS file descriptor */
149             stream_fd= stream? PerlIO_fileno(stream) : -1;
150             if (stream_fd < 0)
151             return false;
152              
153             /* LibC holds Win32 HANDLEs for each fd */
154             stream_hdl= (HANDLE)_get_osfhandle(stream_fd);
155             if (stream_hdl == INVALID_HANDLE_VALUE || GetFileType(stream_hdl) != FILE_TYPE_CHAR)
156             return false;
157              
158             /* Capture current state */
159             if (!GetConsoleMode(stream_hdl, &state->orig_mode))
160             return false;
161              
162             state->hdl= stream_hdl;
163             state->mode= state->orig_mode;
164             return true;
165             }
166              
167             static bool sb_console_state_dup_fd(sb_console_state *state) {
168             /* Clone the handle so that we can reset it independent of anything the user does that might
169             rearrange file descriptors */
170             if (!state->own_fd) {
171             HANDLE tmp;
172             if (!DuplicateHandle(GetCurrentProcess(), state->hdl, GetCurrentProcess(), &tmp, 0, FALSE, DUPLICATE_SAME_ACCESS))
173             croak_with_syserror("DuplicateHandle failed", GetLastError());
174             state->hdl= tmp;
175             state->own_fd= true;
176             }
177             return true;
178             }
179              
180             static bool sb_console_state_get_echo(sb_console_state *state) {
181             return state->mode & ENABLE_ECHO_INPUT;
182             }
183              
184             static bool sb_console_state_set_echo(sb_console_state *state, bool enable) {
185             DWORD mode= enable? (state->mode | ENABLE_ECHO_INPUT)
186             : (state->mode & ~ENABLE_ECHO_INPUT);
187             if (SetConsoleMode(state->hdl, mode)) {
188             state->mode= mode;
189             return true;
190             }
191             return false;
192             }
193              
194             static bool sb_console_state_get_line_input(sb_console_state *state) {
195             return state->mode & ENABLE_LINE_INPUT;
196             }
197              
198             static bool sb_console_state_set_line_input(sb_console_state *state, bool enable) {
199             DWORD mode= enable? (state->mode | ENABLE_LINE_INPUT)
200             : (state->mode & ~ENABLE_LINE_INPUT);
201             if (SetConsoleMode(state->hdl, mode)) {
202             state->mode= mode;
203             return true;
204             }
205             return false;
206             }
207              
208             static bool sb_console_state_restore(sb_console_state *state) {
209             if (SetConsoleMode(state->hdl, state->orig_mode)) {
210             state->mode= state->orig_mode;
211             }
212             }
213              
214             static void sb_console_state_destroy(pTHX_ sb_console_state *state) {
215             if (state->hdl != INVALID_HANDLE_VALUE) {
216             if (state->auto_restore)
217             if (!sb_console_state_restore(state))
218             warn("failed to restore console state");
219             if (state->own_fd)
220             if (!CloseHandle(state->hdl))
221             warn("BUG: CloseHandle failed");
222             state->hdl= INVALID_HANDLE_VALUE;
223             }
224             }
225              
226             #else /* not WIN32 */
227              
228 44           static bool sb_console_state_init(pTHX_ sb_console_state *state, PerlIO *stream) {
229 44           Zero(state, 1, sb_console_state);
230              
231             /* PerlIO may or may not be backed by a real OS file descriptor */
232 44 50         state->fd= stream? PerlIO_fileno(stream) : -1;
233 44 100         if (state->fd < 0)
234 4           return false;
235              
236             /* Capture current state */
237 40 50         if (tcgetattr(state->fd, &state->orig_state) != 0)
238 40           return false;
239              
240 0           state->cur_state= state->orig_state;
241 0           return true;
242             }
243              
244 0           static bool sb_console_state_dup_fd(sb_console_state *state) {
245             /* Clone the handle so that we can reset it independent of anything the user does that might
246             rearrange file descriptors */
247 0 0         if (!state->own_fd) {
248 0           int new_fd= dup(state->fd);
249 0 0         if (new_fd < 0)
250 0           croak_with_syserror("dup() failed", errno);
251 0           state->fd= new_fd;
252 0           state->own_fd= true;
253             }
254 0           return true;
255             }
256              
257 0           static bool sb_console_state_get_echo(sb_console_state *state) {
258 0           return state->cur_state.c_lflag & ECHO;
259             }
260              
261 0           static bool sb_console_state_set_echo(sb_console_state *state, bool enable) {
262 0           struct termios new_st= state->cur_state;
263 0           new_st.c_lflag= enable? (new_st.c_lflag | ECHO)
264 0 0         : (new_st.c_lflag & ~ECHO);
265 0 0         if (tcsetattr(state->fd, TCSANOW, &new_st) == 0) {
266 0           state->cur_state= new_st;
267 0           return true;
268             }
269 0           else return false;
270             }
271              
272 0           static bool sb_console_state_get_line_input(sb_console_state *state) {
273 0           return state->cur_state.c_lflag & ICANON;
274             }
275              
276 0           static bool sb_console_state_set_line_input(sb_console_state *state, bool enable) {
277 0           struct termios new_st= state->cur_state;
278             /* to keep this similar to disabling *only* the line buffering of Win32,
279             enable ISIG so that ^C is still handled by the OS */
280 0           new_st.c_lflag= enable? ((new_st.c_lflag | ICANON) & ~(tcflag_t)ISIG)
281 0 0         : ((new_st.c_lflag & ~(tcflag_t)ICANON) | ISIG);
282 0 0         if (tcsetattr(state->fd, TCSANOW, &new_st) == 0) {
283 0           state->cur_state= new_st;
284 0           return true;
285             }
286 0           else return false;
287             }
288              
289 0           static bool sb_console_state_restore(sb_console_state *state) {
290 0           return tcsetattr(state->fd, TCSANOW, &state->orig_state) == 0;
291             }
292              
293 22           static void sb_console_state_destroy(pTHX_ sb_console_state *state) {
294 22 100         if (state->fd >= 0) {
295 20 50         if (state->auto_restore)
296 0 0         if (!sb_console_state_restore(state))
297 0           warn("failed to restore console state");
298 20 50         if (state->own_fd)
299 0 0         if (close(state->fd) < 0)
300 0           warn("BUG: close(tty_dup) failed");
301 20           state->fd= -1;
302             }
303 22           }
304              
305             #endif