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