xref: /freebsd/crypto/krb5/src/lib/krb5/os/prompter.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "k5-int.h"
3 #include "os-proto.h"
4 #if !defined(_WIN32) || (defined(_WIN32) && defined(__CYGWIN32__))
5 #include <stdio.h>
6 #include <errno.h>
7 #include <signal.h>
8 #include <limits.h>
9 /* Is vxworks broken w.r.t. termios? --tlyu */
10 #ifdef __vxworks
11 #define ECHO_PASSWORD
12 #endif
13 
14 #include <termios.h>
15 
16 #ifdef POSIX_SIGNALS
17 typedef struct sigaction osiginfo;
18 #else
19 typedef struct void (*osiginfo)();
20 #endif
21 
22 static void     catch_signals(osiginfo *);
23 static void     restore_signals(osiginfo *);
24 static void     intrfunc(int sig);
25 
26 static krb5_error_code  setup_tty(FILE*, int, struct termios *, osiginfo *);
27 static krb5_error_code  restore_tty(FILE*, struct termios *, osiginfo *);
28 
29 static volatile int got_int;    /* should be sig_atomic_t */
30 
31 krb5_error_code KRB5_CALLCONV
krb5_prompter_posix(krb5_context context,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt prompts[])32 krb5_prompter_posix(
33     krb5_context        context,
34     void                *data,
35     const char          *name,
36     const char          *banner,
37     int                 num_prompts,
38     krb5_prompt         prompts[])
39 {
40     int         fd, i, scratchchar;
41     FILE        *fp;
42     char        *retp;
43     krb5_error_code     errcode;
44     struct termios saveparm;
45     osiginfo osigint;
46 
47     errcode = KRB5_LIBOS_CANTREADPWD;
48 
49     if (name) {
50         fputs(name, stdout);
51         fputs("\n", stdout);
52     }
53     if (banner) {
54         fputs(banner, stdout);
55         fputs("\n", stdout);
56     }
57 
58     /*
59      * Get a non-buffered stream on stdin.
60      */
61     fp = NULL;
62     fd = dup(STDIN_FILENO);
63     if (fd < 0)
64         return KRB5_LIBOS_CANTREADPWD;
65     set_cloexec_fd(fd);
66     fp = fdopen(fd, "r");
67     if (fp == NULL)
68         goto cleanup;
69     if (setvbuf(fp, NULL, _IONBF, 0))
70         goto cleanup;
71 
72     for (i = 0; i < num_prompts; i++) {
73         errcode = KRB5_LIBOS_CANTREADPWD;
74         /* fgets() takes int, but krb5_data.length is unsigned. */
75         if (prompts[i].reply->length > INT_MAX)
76             goto cleanup;
77 
78         errcode = setup_tty(fp, prompts[i].hidden, &saveparm, &osigint);
79         if (errcode)
80             break;
81 
82         /* put out the prompt */
83         (void)fputs(prompts[i].prompt, stdout);
84         (void)fputs(": ", stdout);
85         (void)fflush(stdout);
86         (void)memset(prompts[i].reply->data, 0, prompts[i].reply->length);
87 
88         got_int = 0;
89         retp = fgets(prompts[i].reply->data, (int)prompts[i].reply->length,
90                      fp);
91         if (prompts[i].hidden)
92             putchar('\n');
93         if (retp == NULL) {
94             if (got_int)
95                 errcode = KRB5_LIBOS_PWDINTR;
96             else
97                 errcode = KRB5_LIBOS_CANTREADPWD;
98             restore_tty(fp, &saveparm, &osigint);
99             break;
100         }
101 
102         /* replace newline with null */
103         retp = strchr(prompts[i].reply->data, '\n');
104         if (retp != NULL)
105             *retp = '\0';
106         else {
107             /* flush rest of input line */
108             do {
109                 scratchchar = getc(fp);
110             } while (scratchchar != EOF && scratchchar != '\n');
111         }
112 
113         errcode = restore_tty(fp, &saveparm, &osigint);
114         if (errcode)
115             break;
116         prompts[i].reply->length = strlen(prompts[i].reply->data);
117     }
118 cleanup:
119     if (fp != NULL)
120         fclose(fp);
121     else if (fd >= 0)
122         close(fd);
123 
124     return errcode;
125 }
126 
127 static void
intrfunc(int sig)128 intrfunc(int sig)
129 {
130     got_int = 1;
131 }
132 
133 static void
catch_signals(osiginfo * osigint)134 catch_signals(osiginfo *osigint)
135 {
136 #ifdef POSIX_SIGNALS
137     struct sigaction sa;
138 
139     sigemptyset(&sa.sa_mask);
140     sa.sa_flags = 0;
141     sa.sa_handler = intrfunc;
142     sigaction(SIGINT, &sa, osigint);
143 #else
144     *osigint = signal(SIGINT, intrfunc);
145 #endif
146 }
147 
148 static void
restore_signals(osiginfo * osigint)149 restore_signals(osiginfo *osigint)
150 {
151 #ifdef POSIX_SIGNALS
152     sigaction(SIGINT, osigint, NULL);
153 #else
154     signal(SIGINT, *osigint);
155 #endif
156 }
157 
158 static krb5_error_code
setup_tty(FILE * fp,int hidden,struct termios * saveparm,osiginfo * osigint)159 setup_tty(FILE *fp, int hidden, struct termios *saveparm, osiginfo *osigint)
160 {
161     krb5_error_code     ret;
162     int                 fd;
163     struct termios      tparm;
164 
165     ret = KRB5_LIBOS_CANTREADPWD;
166     catch_signals(osigint);
167     fd = fileno(fp);
168     do {
169         if (!isatty(fd)) {
170             ret = 0;
171             break;
172         }
173         if (tcgetattr(fd, &tparm) < 0)
174             break;
175         *saveparm = tparm;
176 #ifndef ECHO_PASSWORD
177         if (hidden)
178             tparm.c_lflag &= ~(ECHO|ECHONL);
179 #endif
180         tparm.c_lflag |= ISIG|ICANON;
181         if (tcsetattr(STDIN_FILENO, TCSANOW, &tparm) < 0)
182             break;
183         ret = 0;
184     } while (0);
185     /* If we're losing, restore signal handlers. */
186     if (ret)
187         restore_signals(osigint);
188     return ret;
189 }
190 
191 static krb5_error_code
restore_tty(FILE * fp,struct termios * saveparm,osiginfo * osigint)192 restore_tty(FILE* fp, struct termios *saveparm, osiginfo *osigint)
193 {
194     int ret, fd;
195 
196     ret = 0;
197     fd = fileno(fp);
198     if (isatty(fd)) {
199         ret = tcsetattr(fd, TCSANOW, saveparm);
200         if (ret < 0)
201             ret = KRB5_LIBOS_CANTREADPWD;
202         else
203             ret = 0;
204     }
205     restore_signals(osigint);
206     return ret;
207 }
208 
209 #else /* non-Cygwin Windows, or Mac */
210 
211 #if defined(_WIN32)
212 
213 #include <io.h>
214 
215 krb5_error_code KRB5_CALLCONV
krb5_prompter_posix(krb5_context context,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt prompts[])216 krb5_prompter_posix(krb5_context context,
217                     void *data,
218                     const char *name,
219                     const char *banner,
220                     int num_prompts,
221                     krb5_prompt prompts[])
222 {
223     HANDLE              handle;
224     DWORD               old_mode, new_mode;
225     char                *ptr;
226     int                 scratchchar;
227     krb5_error_code     errcode = 0;
228     int                 i;
229 
230     handle = GetStdHandle(STD_INPUT_HANDLE);
231     if (handle == INVALID_HANDLE_VALUE)
232         return ENOTTY;
233     if (!GetConsoleMode(handle, &old_mode))
234         return ENOTTY;
235 
236     new_mode = old_mode;
237     new_mode |=  ( ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT );
238     new_mode &= ~( ENABLE_ECHO_INPUT );
239 
240     if (!SetConsoleMode(handle, new_mode))
241         return ENOTTY;
242 
243     if (!SetConsoleMode(handle, old_mode))
244         return ENOTTY;
245 
246     if (name) {
247         fputs(name, stdout);
248         fputs("\n", stdout);
249     }
250 
251     if (banner) {
252         fputs(banner, stdout);
253         fputs("\n", stdout);
254     }
255 
256     for (i = 0; i < num_prompts; i++) {
257         if (prompts[i].hidden) {
258             if (!SetConsoleMode(handle, new_mode)) {
259                 errcode = ENOTTY;
260                 goto cleanup;
261             }
262         }
263 
264         fputs(prompts[i].prompt,stdout);
265         fputs(": ", stdout);
266         fflush(stdout);
267         memset(prompts[i].reply->data, 0, prompts[i].reply->length);
268 
269         if (fgets(prompts[i].reply->data, prompts[i].reply->length, stdin)
270             == NULL) {
271             if (prompts[i].hidden)
272                 putchar('\n');
273             errcode = KRB5_LIBOS_CANTREADPWD;
274             goto cleanup;
275         }
276         if (prompts[i].hidden)
277             putchar('\n');
278         /* fgets always null-terminates the returned string */
279 
280         /* replace newline with null */
281         if ((ptr = strchr(prompts[i].reply->data, '\n')))
282             *ptr = '\0';
283         else /* flush rest of input line */
284             do {
285                 scratchchar = getchar();
286             } while (scratchchar != EOF && scratchchar != '\n');
287 
288         prompts[i].reply->length = strlen(prompts[i].reply->data);
289 
290         if (!SetConsoleMode(handle, old_mode)) {
291             errcode = ENOTTY;
292             goto cleanup;
293         }
294     }
295 
296 cleanup:
297     if (errcode) {
298         for (i = 0; i < num_prompts; i++) {
299             memset(prompts[i].reply->data, 0, prompts[i].reply->length);
300         }
301     }
302     return errcode;
303 }
304 
305 #else /* !_WIN32 */
306 
307 krb5_error_code KRB5_CALLCONV
krb5_prompter_posix(krb5_context context,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt prompts[])308 krb5_prompter_posix(krb5_context context,
309                     void *data,
310                     const char *name,
311                     const char *banner,
312                     int num_prompts,
313                     krb5_prompt prompts[])
314 {
315     return(EINVAL);
316 }
317 #endif /* !_WIN32 */
318 #endif /* Windows or Mac */
319 
320 void
k5_set_prompt_types(krb5_context context,krb5_prompt_type * types)321 k5_set_prompt_types(krb5_context context, krb5_prompt_type *types)
322 {
323     context->prompt_types = types;
324 }
325 
326 krb5_prompt_type*
327 KRB5_CALLCONV
krb5_get_prompt_types(krb5_context context)328 krb5_get_prompt_types(krb5_context context)
329 {
330     return context->prompt_types;
331 }
332