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             return true;
212             }
213             return false;
214             }
215              
216             static void sb_console_state_destroy(pTHX_ sb_console_state *state) {
217             if (state->hdl != INVALID_HANDLE_VALUE) {
218             if (state->auto_restore)
219             if (!sb_console_state_restore(state))
220             warn("failed to restore console state");
221             if (state->own_fd)
222             if (!CloseHandle(state->hdl))
223             warn("BUG: CloseHandle failed");
224             state->hdl= INVALID_HANDLE_VALUE;
225             }
226             }
227              
228             #else /* not WIN32 */
229              
230 44           static bool sb_console_state_init(pTHX_ sb_console_state *state, PerlIO *stream) {
231 44           Zero(state, 1, sb_console_state);
232              
233             /* PerlIO may or may not be backed by a real OS file descriptor */
234 44 50         state->fd= stream? PerlIO_fileno(stream) : -1;
235 44 100         if (state->fd < 0)
236 4           return false;
237              
238             /* Capture current state */
239 40 50         if (tcgetattr(state->fd, &state->orig_state) != 0)
240 40           return false;
241              
242 0           state->cur_state= state->orig_state;
243 0           return true;
244             }
245              
246 0           static bool sb_console_state_dup_fd(sb_console_state *state) {
247             /* Clone the handle so that we can reset it independent of anything the user does that might
248             rearrange file descriptors */
249 0 0         if (!state->own_fd) {
250 0           int new_fd= dup(state->fd);
251 0 0         if (new_fd < 0)
252 0           croak_with_syserror("dup() failed", errno);
253 0           state->fd= new_fd;
254 0           state->own_fd= true;
255             }
256 0           return true;
257             }
258              
259 0           static bool sb_console_state_get_echo(sb_console_state *state) {
260 0           return state->cur_state.c_lflag & ECHO;
261             }
262              
263 0           static bool sb_console_state_set_echo(sb_console_state *state, bool enable) {
264 0           struct termios new_st= state->cur_state;
265 0           new_st.c_lflag= enable? (new_st.c_lflag | ECHO)
266 0 0         : (new_st.c_lflag & ~ECHO);
267 0 0         if (tcsetattr(state->fd, TCSANOW, &new_st) == 0) {
268 0           state->cur_state= new_st;
269 0           return true;
270             }
271 0           else return false;
272             }
273              
274 0           static bool sb_console_state_get_line_input(sb_console_state *state) {
275 0           return state->cur_state.c_lflag & ICANON;
276             }
277              
278 0           static bool sb_console_state_set_line_input(sb_console_state *state, bool enable) {
279 0           struct termios new_st= state->cur_state;
280             /* to keep this similar to disabling *only* the line buffering of Win32,
281             enable ISIG so that ^C is still handled by the OS */
282 0           new_st.c_lflag= enable? ((new_st.c_lflag | ICANON) & ~(tcflag_t)ISIG)
283 0 0         : ((new_st.c_lflag & ~(tcflag_t)ICANON) | ISIG);
284 0 0         if (tcsetattr(state->fd, TCSANOW, &new_st) == 0) {
285 0           state->cur_state= new_st;
286 0           return true;
287             }
288 0           else return false;
289             }
290              
291 0           static bool sb_console_state_restore(sb_console_state *state) {
292 0           return tcsetattr(state->fd, TCSANOW, &state->orig_state) == 0;
293             }
294              
295 22           static void sb_console_state_destroy(pTHX_ sb_console_state *state) {
296 22 100         if (state->fd >= 0) {
297 20 50         if (state->auto_restore)
298 0 0         if (!sb_console_state_restore(state))
299 0           warn("failed to restore console state");
300 20 50         if (state->own_fd)
301 0 0         if (close(state->fd) < 0)
302 0           warn("BUG: close(tty_dup) failed");
303 20           state->fd= -1;
304             }
305 22           }
306              
307             #endif