]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxcons.c
f2a25f3fed70f90c17a9968ca000fdf9d02a9a3e
[PuTTY.git] / unix / uxcons.c
1 /*
2  * uxcons.c: various interactive-prompt routines shared between the
3  * Unix console PuTTY tools
4  */
5
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <stdarg.h>
9 #include <assert.h>
10 #include <errno.h>
11
12 #include <termios.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <sys/select.h>
16
17 #include "putty.h"
18 #include "storage.h"
19 #include "ssh.h"
20
21 int console_batch_mode = FALSE;
22
23 static void *console_logctx = NULL;
24
25 static struct termios orig_termios_stderr;
26 static int stderr_is_a_tty;
27
28 void stderr_tty_init()
29 {
30     /* Ensure that if stderr is a tty, we can get it back to a sane state. */
31     if ((flags & FLAG_STDERR_TTY) && isatty(STDERR_FILENO)) {
32         stderr_is_a_tty = TRUE;
33         tcgetattr(STDERR_FILENO, &orig_termios_stderr);
34     }
35 }
36
37 void premsg(struct termios *cf)
38 {
39     if (stderr_is_a_tty) {
40         tcgetattr(STDERR_FILENO, cf);
41         tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr);
42     }
43 }
44 void postmsg(struct termios *cf)
45 {
46     if (stderr_is_a_tty)
47         tcsetattr(STDERR_FILENO, TCSADRAIN, cf);
48 }
49
50 /*
51  * Clean up and exit.
52  */
53 void cleanup_exit(int code)
54 {
55     /*
56      * Clean up.
57      */
58     sk_cleanup();
59     random_save_seed();
60     exit(code);
61 }
62
63 void set_busy_status(void *frontend, int status)
64 {
65 }
66
67 void update_specials_menu(void *frontend)
68 {
69 }
70
71 void notify_remote_exit(void *frontend)
72 {
73 }
74
75 void timer_change_notify(unsigned long next)
76 {
77 }
78
79 /*
80  * Wrapper around Unix read(2), suitable for use on a file descriptor
81  * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK
82  * by means of doing a one-fd select and then trying again; all other
83  * errors (including errors from select) are returned to the caller.
84  */
85 static int block_and_read(int fd, void *buf, size_t len)
86 {
87     int ret;
88
89     while ((ret = read(fd, buf, len)) < 0 && (
90 #ifdef EAGAIN
91                (errno == EAGAIN) ||
92 #endif
93 #ifdef EWOULDBLOCK
94                (errno == EWOULDBLOCK) ||
95 #endif
96                0)) {
97
98         fd_set rfds;
99         FD_ZERO(&rfds);
100         FD_SET(fd, &rfds);
101         ret = select(fd+1, &rfds, NULL, NULL, NULL);
102         assert(ret != 0);
103         if (ret < 0)
104             return ret;
105         assert(FD_ISSET(fd, &rfds));
106     }
107
108     return ret;
109 }
110
111 int verify_ssh_host_key(void *frontend, char *host, int port,
112                         const char *keytype, char *keystr, char *fingerprint,
113                         void (*callback)(void *ctx, int result), void *ctx)
114 {
115     int ret;
116
117     static const char absentmsg_batch[] =
118         "The server's host key is not cached. You have no guarantee\n"
119         "that the server is the computer you think it is.\n"
120         "The server's %s key fingerprint is:\n"
121         "%s\n"
122         "Connection abandoned.\n";
123     static const char absentmsg[] =
124         "The server's host key is not cached. You have no guarantee\n"
125         "that the server is the computer you think it is.\n"
126         "The server's %s key fingerprint is:\n"
127         "%s\n"
128         "If you trust this host, enter \"y\" to add the key to\n"
129         "PuTTY's cache and carry on connecting.\n"
130         "If you want to carry on connecting just once, without\n"
131         "adding the key to the cache, enter \"n\".\n"
132         "If you do not trust this host, press Return to abandon the\n"
133         "connection.\n"
134         "Store key in cache? (y/n) ";
135
136     static const char wrongmsg_batch[] =
137         "WARNING - POTENTIAL SECURITY BREACH!\n"
138         "The server's host key does not match the one PuTTY has\n"
139         "cached. This means that either the server administrator\n"
140         "has changed the host key, or you have actually connected\n"
141         "to another computer pretending to be the server.\n"
142         "The new %s key fingerprint is:\n"
143         "%s\n"
144         "Connection abandoned.\n";
145     static const char wrongmsg[] =
146         "WARNING - POTENTIAL SECURITY BREACH!\n"
147         "The server's host key does not match the one PuTTY has\n"
148         "cached. This means that either the server administrator\n"
149         "has changed the host key, or you have actually connected\n"
150         "to another computer pretending to be the server.\n"
151         "The new %s key fingerprint is:\n"
152         "%s\n"
153         "If you were expecting this change and trust the new key,\n"
154         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
155         "If you want to carry on connecting but without updating\n"
156         "the cache, enter \"n\".\n"
157         "If you want to abandon the connection completely, press\n"
158         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
159         "safe choice.\n"
160         "Update cached key? (y/n, Return cancels connection) ";
161
162     static const char abandoned[] = "Connection abandoned.\n";
163
164     char line[32];
165     struct termios cf;
166
167     /*
168      * Verify the key.
169      */
170     ret = verify_host_key(host, port, keytype, keystr);
171
172     if (ret == 0)                      /* success - key matched OK */
173         return 1;
174
175     premsg(&cf);
176     if (ret == 2) {                    /* key was different */
177         if (console_batch_mode) {
178             fprintf(stderr, wrongmsg_batch, keytype, fingerprint);
179             return 0;
180         }
181         fprintf(stderr, wrongmsg, keytype, fingerprint);
182         fflush(stderr);
183     }
184     if (ret == 1) {                    /* key was absent */
185         if (console_batch_mode) {
186             fprintf(stderr, absentmsg_batch, keytype, fingerprint);
187             return 0;
188         }
189         fprintf(stderr, absentmsg, keytype, fingerprint);
190         fflush(stderr);
191     }
192
193     {
194         struct termios oldmode, newmode;
195         tcgetattr(0, &oldmode);
196         newmode = oldmode;
197         newmode.c_lflag |= ECHO | ISIG | ICANON;
198         tcsetattr(0, TCSANOW, &newmode);
199         line[0] = '\0';
200         if (block_and_read(0, line, sizeof(line) - 1) <= 0)
201             /* handled below */;
202         tcsetattr(0, TCSANOW, &oldmode);
203     }
204
205     if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
206         if (line[0] == 'y' || line[0] == 'Y')
207             store_host_key(host, port, keytype, keystr);
208         postmsg(&cf);
209         return 1;
210     } else {
211         fprintf(stderr, abandoned);
212         postmsg(&cf);
213         return 0;
214     }
215 }
216
217 /*
218  * Ask whether the selected algorithm is acceptable (since it was
219  * below the configured 'warn' threshold).
220  */
221 int askalg(void *frontend, const char *algtype, const char *algname,
222            void (*callback)(void *ctx, int result), void *ctx)
223 {
224     static const char msg[] =
225         "The first %s supported by the server is\n"
226         "%s, which is below the configured warning threshold.\n"
227         "Continue with connection? (y/n) ";
228     static const char msg_batch[] =
229         "The first %s supported by the server is\n"
230         "%s, which is below the configured warning threshold.\n"
231         "Connection abandoned.\n";
232     static const char abandoned[] = "Connection abandoned.\n";
233
234     char line[32];
235     struct termios cf;
236
237     premsg(&cf);
238     if (console_batch_mode) {
239         fprintf(stderr, msg_batch, algtype, algname);
240         return 0;
241     }
242
243     fprintf(stderr, msg, algtype, algname);
244     fflush(stderr);
245
246     {
247         struct termios oldmode, newmode;
248         tcgetattr(0, &oldmode);
249         newmode = oldmode;
250         newmode.c_lflag |= ECHO | ISIG | ICANON;
251         tcsetattr(0, TCSANOW, &newmode);
252         line[0] = '\0';
253         if (block_and_read(0, line, sizeof(line) - 1) <= 0)
254             /* handled below */;
255         tcsetattr(0, TCSANOW, &oldmode);
256     }
257
258     if (line[0] == 'y' || line[0] == 'Y') {
259         postmsg(&cf);
260         return 1;
261     } else {
262         fprintf(stderr, abandoned);
263         postmsg(&cf);
264         return 0;
265     }
266 }
267
268 int askhk(void *frontend, const char *algname, const char *betteralgs,
269           void (*callback)(void *ctx, int result), void *ctx)
270 {
271     static const char msg[] =
272         "The first host key type we have stored for this server\n"
273         "is %s, which is below the configured warning threshold.\n"
274         "The server also provides the following types of host key\n"
275         "above the threshold, which we do not have stored:\n"
276         "%s\n"
277         "Continue with connection? (y/n) ";
278     static const char msg_batch[] =
279         "The first host key type we have stored for this server\n"
280         "is %s, which is below the configured warning threshold.\n"
281         "The server also provides the following types of host key\n"
282         "above the threshold, which we do not have stored:\n"
283         "%s\n"
284         "Connection abandoned.\n";
285     static const char abandoned[] = "Connection abandoned.\n";
286
287     char line[32];
288     struct termios cf;
289
290     premsg(&cf);
291     if (console_batch_mode) {
292         fprintf(stderr, msg_batch, algname, betteralgs);
293         return 0;
294     }
295
296     fprintf(stderr, msg, algname, betteralgs);
297     fflush(stderr);
298
299     {
300         struct termios oldmode, newmode;
301         tcgetattr(0, &oldmode);
302         newmode = oldmode;
303         newmode.c_lflag |= ECHO | ISIG | ICANON;
304         tcsetattr(0, TCSANOW, &newmode);
305         line[0] = '\0';
306         if (block_and_read(0, line, sizeof(line) - 1) <= 0)
307             /* handled below */;
308         tcsetattr(0, TCSANOW, &oldmode);
309     }
310
311     if (line[0] == 'y' || line[0] == 'Y') {
312         postmsg(&cf);
313         return 1;
314     } else {
315         fprintf(stderr, abandoned);
316         postmsg(&cf);
317         return 0;
318     }
319 }
320
321 /*
322  * Ask whether to wipe a session log file before writing to it.
323  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
324  */
325 int askappend(void *frontend, Filename *filename,
326               void (*callback)(void *ctx, int result), void *ctx)
327 {
328     static const char msgtemplate[] =
329         "The session log file \"%.*s\" already exists.\n"
330         "You can overwrite it with a new session log,\n"
331         "append your session log to the end of it,\n"
332         "or disable session logging for this session.\n"
333         "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
334         "or just press Return to disable logging.\n"
335         "Wipe the log file? (y/n, Return cancels logging) ";
336
337     static const char msgtemplate_batch[] =
338         "The session log file \"%.*s\" already exists.\n"
339         "Logging will not be enabled.\n";
340
341     char line[32];
342     struct termios cf;
343
344     premsg(&cf);
345     if (console_batch_mode) {
346         fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path);
347         fflush(stderr);
348         return 0;
349     }
350     fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path);
351     fflush(stderr);
352
353     {
354         struct termios oldmode, newmode;
355         tcgetattr(0, &oldmode);
356         newmode = oldmode;
357         newmode.c_lflag |= ECHO | ISIG | ICANON;
358         tcsetattr(0, TCSANOW, &newmode);
359         line[0] = '\0';
360         if (block_and_read(0, line, sizeof(line) - 1) <= 0)
361             /* handled below */;
362         tcsetattr(0, TCSANOW, &oldmode);
363     }
364
365     postmsg(&cf);
366     if (line[0] == 'y' || line[0] == 'Y')
367         return 2;
368     else if (line[0] == 'n' || line[0] == 'N')
369         return 1;
370     else
371         return 0;
372 }
373
374 /*
375  * Warn about the obsolescent key file format.
376  * 
377  * Uniquely among these functions, this one does _not_ expect a
378  * frontend handle. This means that if PuTTY is ported to a
379  * platform which requires frontend handles, this function will be
380  * an anomaly. Fortunately, the problem it addresses will not have
381  * been present on that platform, so it can plausibly be
382  * implemented as an empty function.
383  */
384 void old_keyfile_warning(void)
385 {
386     static const char message[] =
387         "You are loading an SSH-2 private key which has an\n"
388         "old version of the file format. This means your key\n"
389         "file is not fully tamperproof. Future versions of\n"
390         "PuTTY may stop supporting this private key format,\n"
391         "so we recommend you convert your key to the new\n"
392         "format.\n"
393         "\n"
394         "Once the key is loaded into PuTTYgen, you can perform\n"
395         "this conversion simply by saving it again.\n";
396
397     struct termios cf;
398     premsg(&cf);
399     fputs(message, stderr);
400     postmsg(&cf);
401 }
402
403 void console_provide_logctx(void *logctx)
404 {
405     console_logctx = logctx;
406 }
407
408 void logevent(void *frontend, const char *string)
409 {
410     struct termios cf;
411     if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE))
412         premsg(&cf);
413     if (console_logctx)
414         log_eventlog(console_logctx, string);
415     if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE))
416         postmsg(&cf);
417 }
418
419 /*
420  * Special functions to read and print to the console for password
421  * prompts and the like. Uses /dev/tty or stdin/stderr, in that order
422  * of preference; also sanitises escape sequences out of the text, on
423  * the basis that it might have been sent by a hostile SSH server
424  * doing malicious keyboard-interactive.
425  */
426 static void console_open(FILE **outfp, int *infd)
427 {
428     int fd;
429
430     if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
431         *infd = fd;
432         *outfp = fdopen(*infd, "w");
433     } else {
434         *infd = 0;
435         *outfp = stderr;
436     }
437 }
438 static void console_close(FILE *outfp, int infd)
439 {
440     if (outfp != stderr)
441         fclose(outfp);             /* will automatically close infd too */
442 }
443
444 static void console_prompt_text(FILE *outfp, const char *data, int len)
445 {
446     int i;
447
448     for (i = 0; i < len; i++)
449         if ((data[i] & 0x60) || (data[i] == '\n'))
450             fputc(data[i], outfp);
451     fflush(outfp);
452 }
453
454 int console_get_userpass_input(prompts_t *p, const unsigned char *in,
455                                int inlen)
456 {
457     size_t curr_prompt;
458     FILE *outfp = NULL;
459     int infd;
460
461     /*
462      * Zero all the results, in case we abort half-way through.
463      */
464     {
465         int i;
466         for (i = 0; i < p->n_prompts; i++)
467             prompt_set_result(p->prompts[i], "");
468     }
469
470     if (p->n_prompts && console_batch_mode)
471         return 0;
472
473     console_open(&outfp, &infd);
474
475     /*
476      * Preamble.
477      */
478     /* We only print the `name' caption if we have to... */
479     if (p->name_reqd && p->name) {
480         size_t l = strlen(p->name);
481         console_prompt_text(outfp, p->name, l);
482         if (p->name[l-1] != '\n')
483             console_prompt_text(outfp, "\n", 1);
484     }
485     /* ...but we always print any `instruction'. */
486     if (p->instruction) {
487         size_t l = strlen(p->instruction);
488         console_prompt_text(outfp, p->instruction, l);
489         if (p->instruction[l-1] != '\n')
490             console_prompt_text(outfp, "\n", 1);
491     }
492
493     for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
494
495         struct termios oldmode, newmode;
496         int len;
497         prompt_t *pr = p->prompts[curr_prompt];
498
499         tcgetattr(infd, &oldmode);
500         newmode = oldmode;
501         newmode.c_lflag |= ISIG | ICANON;
502         if (!pr->echo)
503             newmode.c_lflag &= ~ECHO;
504         else
505             newmode.c_lflag |= ECHO;
506         tcsetattr(infd, TCSANOW, &newmode);
507
508         console_prompt_text(outfp, pr->prompt, strlen(pr->prompt));
509
510         len = 0;
511         while (1) {
512             int ret;
513
514             prompt_ensure_result_size(pr, len * 5 / 4 + 512);
515             ret = read(infd, pr->result + len, pr->resultsize - len - 1);
516             if (ret <= 0) {
517                 len = -1;
518                 break;
519             }
520             len += ret;
521             if (pr->result[len - 1] == '\n') {
522                 len--;
523                 break;
524             }
525         }
526
527         tcsetattr(infd, TCSANOW, &oldmode);
528
529         if (!pr->echo)
530             console_prompt_text(outfp, "\n", 1);
531
532         if (len < 0) {
533             console_close(outfp, infd);
534             return 0;                  /* failure due to read error */
535         }
536
537         pr->result[len] = '\0';
538     }
539
540     console_close(outfp, infd);
541
542     return 1; /* success */
543 }
544
545 void frontend_keypress(void *handle)
546 {
547     /*
548      * This is nothing but a stub, in console code.
549      */
550     return;
551 }
552
553 int is_interactive(void)
554 {
555     return isatty(0);
556 }
557
558 /*
559  * X11-forwarding-related things suitable for console.
560  */
561
562 char *platform_get_x_display(void) {
563     return dupstr(getenv("DISPLAY"));
564 }