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