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