Fix the SSH protocol version exchange, which had a weird stack trash
[u/mdw/putty] / plink.c
CommitLineData
12dc4ec0 1/*
2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
3 */
4
4d331a77 5#ifndef AUTO_WINSOCK
12dc4ec0 6#include <winsock2.h>
4d331a77 7#endif
12dc4ec0 8#include <windows.h>
9#include <stdio.h>
49bad831 10#include <stdlib.h>
12dc4ec0 11#include <stdarg.h>
12
13#define PUTTY_DO_GLOBALS /* actually _define_ globals */
14#include "putty.h"
a9422f39 15#include "storage.h"
8df7a775 16#include "tree234.h"
12dc4ec0 17
18void fatalbox (char *p, ...) {
19 va_list ap;
49bad831 20 fprintf(stderr, "FATAL ERROR: ");
12dc4ec0 21 va_start(ap, p);
22 vfprintf(stderr, p, ap);
23 va_end(ap);
24 fputc('\n', stderr);
25 WSACleanup();
26 exit(1);
27}
8d5de777 28void connection_fatal (char *p, ...) {
29 va_list ap;
49bad831 30 fprintf(stderr, "FATAL ERROR: ");
8d5de777 31 va_start(ap, p);
32 vfprintf(stderr, p, ap);
33 va_end(ap);
34 fputc('\n', stderr);
35 WSACleanup();
36 exit(1);
37}
12dc4ec0 38
d8426c54 39static char *password = NULL;
40
a9422f39 41void logevent(char *string) { }
42
43void verify_ssh_host_key(char *host, int port, char *keytype,
44 char *keystr, char *fingerprint) {
45 int ret;
fcbb94d3 46 HANDLE hin;
47 DWORD savemode, i;
a9422f39 48
49 static const char absentmsg[] =
50 "The server's host key is not cached in the registry. You\n"
51 "have no guarantee that the server is the computer you\n"
52 "think it is.\n"
53 "The server's key fingerprint is:\n"
54 "%s\n"
55 "If you trust this host, enter \"y\" to add the key to\n"
56 "PuTTY's cache and carry on connecting.\n"
57 "If you do not trust this host, enter \"n\" to abandon the\n"
58 "connection.\n"
59 "Continue connecting? (y/n) ";
60
61 static const char wrongmsg[] =
62 "WARNING - POTENTIAL SECURITY BREACH!\n"
63 "The server's host key does not match the one PuTTY has\n"
64 "cached in the registry. This means that either the\n"
65 "server administrator has changed the host key, or you\n"
66 "have actually connected to another computer pretending\n"
67 "to be the server.\n"
68 "The new key fingerprint is:\n"
69 "%s\n"
70 "If you were expecting this change and trust the new key,\n"
71 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
72 "If you want to carry on connecting but without updating\n"
73 "the cache, enter \"n\".\n"
74 "If you want to abandon the connection completely, press\n"
75 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
76 "safe choice.\n"
77 "Update cached key? (y/n, Return cancels connection) ";
78
79 static const char abandoned[] = "Connection abandoned.\n";
80
81 char line[32];
82
83 /*
84 * Verify the key against the registry.
85 */
86 ret = verify_host_key(host, port, keytype, keystr);
87
88 if (ret == 0) /* success - key matched OK */
89 return;
fcbb94d3 90
91 if (ret == 2) /* key was different */
a9422f39 92 fprintf(stderr, wrongmsg, fingerprint);
fcbb94d3 93 if (ret == 1) /* key was absent */
94 fprintf(stderr, absentmsg, fingerprint);
95
96 hin = GetStdHandle(STD_INPUT_HANDLE);
97 GetConsoleMode(hin, &savemode);
98 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
99 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
100 ReadFile(hin, line, sizeof(line)-1, &i, NULL);
101 SetConsoleMode(hin, savemode);
102
103 if (ret == 2) { /* key was different */
104 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
a9422f39 105 if (line[0] == 'y' || line[0] == 'Y')
106 store_host_key(host, port, keytype, keystr);
107 } else {
108 fprintf(stderr, abandoned);
109 exit(0);
110 }
111 }
112 if (ret == 1) { /* key was absent */
fcbb94d3 113 if (line[0] == 'y' || line[0] == 'Y')
a9422f39 114 store_host_key(host, port, keytype, keystr);
115 else {
116 fprintf(stderr, abandoned);
117 exit(0);
118 }
119 }
120}
989b10e9 121
0965bee0 122HANDLE inhandle, outhandle, errhandle;
6f34e365 123DWORD orig_console_mode;
124
8df7a775 125WSAEVENT netevent;
126
fe50e814 127void from_backend(int is_stderr, char *data, int len) {
128 int pos;
12dc4ec0 129 DWORD ret;
fe50e814 130 HANDLE h = (is_stderr ? errhandle : outhandle);
131
132 pos = 0;
133 while (pos < len) {
134 if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
12dc4ec0 135 return; /* give up in panic */
fe50e814 136 pos += ret;
12dc4ec0 137 }
12dc4ec0 138}
139
0965bee0 140int term_ldisc(int mode) { return FALSE; }
141void ldisc_update(int echo, int edit) {
142 /* Update stdin read mode to reflect changes in line discipline. */
143 DWORD mode;
144
145 mode = ENABLE_PROCESSED_INPUT;
146 if (echo)
147 mode = mode | ENABLE_ECHO_INPUT;
148 else
149 mode = mode &~ ENABLE_ECHO_INPUT;
150 if (edit)
151 mode = mode | ENABLE_LINE_INPUT;
152 else
153 mode = mode &~ ENABLE_LINE_INPUT;
154 SetConsoleMode(inhandle, mode);
155}
156
12dc4ec0 157struct input_data {
158 DWORD len;
159 char buffer[4096];
8df7a775 160 HANDLE event, eventback;
12dc4ec0 161};
162
fa17a66e 163static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
67779be7 164{
165 HANDLE hin, hout;
fa17a66e 166 DWORD savemode, newmode, i;
67779be7 167
fa17a66e 168 if (is_pw && password) {
67779be7 169 static int tried_once = 0;
170
171 if (tried_once) {
172 return 0;
173 } else {
174 strncpy(str, password, maxlen);
175 str[maxlen-1] = '\0';
176 tried_once = 1;
177 return 1;
178 }
179 }
67779be7 180
181 hin = GetStdHandle(STD_INPUT_HANDLE);
182 hout = GetStdHandle(STD_OUTPUT_HANDLE);
183 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
184 fprintf(stderr, "Cannot get standard input/output handles");
185 return 0;
186 }
187
188 GetConsoleMode(hin, &savemode);
fa17a66e 189 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
190 if (is_pw)
191 newmode &= ~ENABLE_ECHO_INPUT;
192 else
193 newmode |= ENABLE_ECHO_INPUT;
194 SetConsoleMode(hin, newmode);
67779be7 195
196 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
197 ReadFile(hin, str, maxlen-1, &i, NULL);
198
199 SetConsoleMode(hin, savemode);
200
201 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
202 str[i] = '\0';
203
fa17a66e 204 if (is_pw)
205 WriteFile(hout, "\r\n", 2, &i, NULL);
67779be7 206
207 return 1;
208}
209
6d2d5e8d 210static DWORD WINAPI stdin_read_thread(void *param) {
12dc4ec0 211 struct input_data *idata = (struct input_data *)param;
212 HANDLE inhandle;
213
214 inhandle = GetStdHandle(STD_INPUT_HANDLE);
215
216 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
8df7a775 217 &idata->len, NULL) && idata->len > 0) {
12dc4ec0 218 SetEvent(idata->event);
8df7a775 219 WaitForSingleObject(idata->eventback, INFINITE);
12dc4ec0 220 }
221
222 idata->len = 0;
223 SetEvent(idata->event);
224
225 return 0;
226}
227
d8426c54 228/*
229 * Short description of parameters.
230 */
231static void usage(void)
232{
233 printf("PuTTY Link: command-line connection utility\n");
234 printf("%s\n", ver);
235 printf("Usage: plink [options] [user@]host [command]\n");
236 printf("Options:\n");
237 printf(" -v show verbose messages\n");
238 printf(" -ssh force use of ssh protocol\n");
239 printf(" -P port connect to specified port\n");
240 printf(" -pw passw login with specified password\n");
96621a84 241 printf(" -m file read remote command(s) from file\n");
d8426c54 242 exit(1);
243}
244
8df7a775 245char *do_select(SOCKET skt, int startup) {
246 int events;
247 if (startup) {
248 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
249 } else {
250 events = 0;
251 }
252 if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
253 switch (WSAGetLastError()) {
254 case WSAENETDOWN: return "Network is down";
255 default: return "WSAAsyncSelect(): unknown error";
256 }
257 }
258 return NULL;
259}
260
12dc4ec0 261int main(int argc, char **argv) {
262 WSADATA wsadata;
263 WORD winsock_ver;
8df7a775 264 WSAEVENT stdinevent;
12dc4ec0 265 HANDLE handles[2];
12dc4ec0 266 DWORD threadid;
267 struct input_data idata;
268 int sending;
d8426c54 269 int portnumber = -1;
8df7a775 270 SOCKET *sklist;
271 int skcount, sksize;
272 int connopen;
12dc4ec0 273
fa17a66e 274 ssh_get_line = get_line;
67779be7 275
8df7a775 276 sklist = NULL; skcount = sksize = 0;
277
67779be7 278 flags = FLAG_STDERR;
12dc4ec0 279 /*
280 * Process the command line.
281 */
a9422f39 282 do_defaults(NULL, &cfg);
e7a7383f 283 default_protocol = cfg.protocol;
284 default_port = cfg.port;
8cb9c947 285 {
286 /*
287 * Override the default protocol if PLINK_PROTOCOL is set.
288 */
289 char *p = getenv("PLINK_PROTOCOL");
290 int i;
291 if (p) {
292 for (i = 0; backends[i].backend != NULL; i++) {
293 if (!strcmp(backends[i].name, p)) {
294 default_protocol = cfg.protocol = backends[i].protocol;
295 default_port = cfg.port = backends[i].backend->default_port;
296 break;
297 }
298 }
299 }
300 }
12dc4ec0 301 while (--argc) {
302 char *p = *++argv;
303 if (*p == '-') {
304 if (!strcmp(p, "-ssh")) {
305 default_protocol = cfg.protocol = PROT_SSH;
306 default_port = cfg.port = 22;
9d33ebdd 307 } else if (!strcmp(p, "-telnet")) {
308 default_protocol = cfg.protocol = PROT_TELNET;
309 default_port = cfg.port = 23;
310 } else if (!strcmp(p, "-raw")) {
311 default_protocol = cfg.protocol = PROT_RAW;
67779be7 312 } else if (!strcmp(p, "-v")) {
313 flags |= FLAG_VERBOSE;
12dc4ec0 314 } else if (!strcmp(p, "-log")) {
315 logfile = "putty.log";
d8426c54 316 } else if (!strcmp(p, "-pw") && argc > 1) {
317 --argc, password = *++argv;
e7a7383f 318 } else if (!strcmp(p, "-l") && argc > 1) {
319 char *username;
320 --argc, username = *++argv;
321 strncpy(cfg.username, username, sizeof(cfg.username));
322 cfg.username[sizeof(cfg.username)-1] = '\0';
96621a84 323 } else if (!strcmp(p, "-m") && argc > 1) {
324 char *filename, *command;
325 int cmdlen, cmdsize;
326 FILE *fp;
327 int c, d;
328
329 --argc, filename = *++argv;
330
331 cmdlen = cmdsize = 0;
332 command = NULL;
333 fp = fopen(filename, "r");
334 if (!fp) {
335 fprintf(stderr, "plink: unable to open command "
336 "file \"%s\"\n", filename);
337 return 1;
338 }
339 do {
340 c = fgetc(fp);
341 d = c;
342 if (c == EOF)
343 d = 0;
344 if (cmdlen >= cmdsize) {
345 cmdsize = cmdlen + 512;
346 command = srealloc(command, cmdsize);
347 }
348 command[cmdlen++] = d;
349 } while (c != EOF);
350 cfg.remote_cmd_ptr = command;
351 cfg.nopty = TRUE; /* command => no terminal */
d8426c54 352 } else if (!strcmp(p, "-P") && argc > 1) {
353 --argc, portnumber = atoi(*++argv);
354 }
12dc4ec0 355 } else if (*p) {
356 if (!*cfg.host) {
357 char *q = p;
358 /*
359 * If the hostname starts with "telnet:", set the
360 * protocol to Telnet and process the string as a
361 * Telnet URL.
362 */
363 if (!strncmp(q, "telnet:", 7)) {
364 char c;
365
366 q += 7;
367 if (q[0] == '/' && q[1] == '/')
368 q += 2;
369 cfg.protocol = PROT_TELNET;
370 p = q;
371 while (*p && *p != ':' && *p != '/') p++;
372 c = *p;
373 if (*p)
374 *p++ = '\0';
375 if (c == ':')
376 cfg.port = atoi(p);
377 else
378 cfg.port = -1;
379 strncpy (cfg.host, q, sizeof(cfg.host)-1);
380 cfg.host[sizeof(cfg.host)-1] = '\0';
381 } else {
9d33ebdd 382 char *r;
383 /*
384 * Before we process the [user@]host string, we
385 * first check for the presence of a protocol
386 * prefix (a protocol name followed by ",").
387 */
388 r = strchr(p, ',');
389 if (r) {
390 int i, j;
391 for (i = 0; backends[i].backend != NULL; i++) {
392 j = strlen(backends[i].name);
393 if (j == r-p &&
394 !memcmp(backends[i].name, p, j)) {
395 default_protocol = cfg.protocol = backends[i].protocol;
396 portnumber = backends[i].backend->default_port;
397 p = r+1;
398 break;
399 }
400 }
401 }
402
12dc4ec0 403 /*
404 * Three cases. Either (a) there's a nonzero
405 * length string followed by an @, in which
406 * case that's user and the remainder is host.
407 * Or (b) there's only one string, not counting
408 * a potential initial @, and it exists in the
409 * saved-sessions database. Or (c) only one
410 * string and it _doesn't_ exist in the
411 * database.
412 */
9d33ebdd 413 r = strrchr(p, '@');
12dc4ec0 414 if (r == p) p++, r = NULL; /* discount initial @ */
415 if (r == NULL) {
416 /*
417 * One string.
418 */
9c41b8ea 419 Config cfg2;
420 do_defaults (p, &cfg2);
421 if (cfg2.host[0] == '\0') {
12dc4ec0 422 /* No settings for this host; use defaults */
423 strncpy(cfg.host, p, sizeof(cfg.host)-1);
424 cfg.host[sizeof(cfg.host)-1] = '\0';
6a32cafc 425 cfg.port = default_port;
9c41b8ea 426 } else
427 cfg = cfg2;
12dc4ec0 428 } else {
429 *r++ = '\0';
430 strncpy(cfg.username, p, sizeof(cfg.username)-1);
431 cfg.username[sizeof(cfg.username)-1] = '\0';
432 strncpy(cfg.host, r, sizeof(cfg.host)-1);
433 cfg.host[sizeof(cfg.host)-1] = '\0';
6a32cafc 434 cfg.port = default_port;
12dc4ec0 435 }
436 }
437 } else {
438 int len = sizeof(cfg.remote_cmd) - 1;
439 char *cp = cfg.remote_cmd;
440 int len2;
441
442 strncpy(cp, p, len); cp[len] = '\0';
443 len2 = strlen(cp); len -= len2; cp += len2;
444 while (--argc) {
445 if (len > 0)
446 len--, *cp++ = ' ';
447 strncpy(cp, *++argv, len); cp[len] = '\0';
448 len2 = strlen(cp); len -= len2; cp += len2;
449 }
450 cfg.nopty = TRUE; /* command => no terminal */
12dc4ec0 451 break; /* done with cmdline */
452 }
453 }
454 }
455
d8426c54 456 if (!*cfg.host) {
457 usage();
458 }
d8426c54 459
96621a84 460 if (!*cfg.remote_cmd_ptr)
67779be7 461 flags |= FLAG_INTERACTIVE;
462
12dc4ec0 463 /*
464 * Select protocol. This is farmed out into a table in a
465 * separate file to enable an ssh-free variant.
466 */
467 {
468 int i;
469 back = NULL;
470 for (i = 0; backends[i].backend != NULL; i++)
471 if (backends[i].protocol == cfg.protocol) {
472 back = backends[i].backend;
473 break;
474 }
475 if (back == NULL) {
476 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
477 return 1;
478 }
479 }
480
481 /*
8cb9c947 482 * Select port.
483 */
484 if (portnumber != -1)
485 cfg.port = portnumber;
486
487 /*
12dc4ec0 488 * Initialise WinSock.
489 */
490 winsock_ver = MAKEWORD(2, 0);
491 if (WSAStartup(winsock_ver, &wsadata)) {
492 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
493 MB_OK | MB_ICONEXCLAMATION);
494 return 1;
495 }
496 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
497 MessageBox(NULL, "WinSock version is incompatible with 2.0",
498 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
499 WSACleanup();
500 return 1;
501 }
8df7a775 502 sk_init();
12dc4ec0 503
504 /*
505 * Start up the connection.
506 */
8df7a775 507 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 508 {
509 char *error;
510 char *realhost;
511
8df7a775 512 error = back->init (cfg.host, cfg.port, &realhost);
12dc4ec0 513 if (error) {
514 fprintf(stderr, "Unable to open connection:\n%s", error);
515 return 1;
516 }
517 }
8df7a775 518 connopen = 1;
12dc4ec0 519
12dc4ec0 520 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
521
0965bee0 522 inhandle = GetStdHandle(STD_INPUT_HANDLE);
12dc4ec0 523 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 524 errhandle = GetStdHandle(STD_ERROR_HANDLE);
0965bee0 525 GetConsoleMode(inhandle, &orig_console_mode);
526 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
12dc4ec0 527
528 /*
12dc4ec0 529 * Turn off ECHO and LINE input modes. We don't care if this
530 * call fails, because we know we aren't necessarily running in
531 * a console.
532 */
12dc4ec0 533 handles[0] = netevent;
534 handles[1] = stdinevent;
535 sending = FALSE;
536 while (1) {
537 int n;
8cb9c947 538
539 if (!sending && back->sendok()) {
540 /*
541 * Create a separate thread to read from stdin. This is
542 * a total pain, but I can't find another way to do it:
543 *
544 * - an overlapped ReadFile or ReadFileEx just doesn't
545 * happen; we get failure from ReadFileEx, and
546 * ReadFile blocks despite being given an OVERLAPPED
547 * structure. Perhaps we can't do overlapped reads
548 * on consoles. WHY THE HELL NOT?
549 *
550 * - WaitForMultipleObjects(netevent, console) doesn't
551 * work, because it signals the console when
552 * _anything_ happens, including mouse motions and
553 * other things that don't cause data to be readable
554 * - so we're back to ReadFile blocking.
555 */
556 idata.event = stdinevent;
8df7a775 557 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
8cb9c947 558 if (!CreateThread(NULL, 0, stdin_read_thread,
559 &idata, 0, &threadid)) {
560 fprintf(stderr, "Unable to create second thread\n");
561 exit(1);
562 }
563 sending = TRUE;
564 }
565
12dc4ec0 566 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
567 if (n == 0) {
568 WSANETWORKEVENTS things;
8df7a775 569 enum234 e;
570 SOCKET socket;
571 extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
572 extern int select_result(WPARAM, LPARAM);
573 int i;
574
575 /*
576 * We must not call select_result() for any socket
577 * until we have finished enumerating within the tree.
578 * This is because select_result() may close the socket
579 * and modify the tree.
580 */
581 /* Count the active sockets. */
582 i = 0;
583 for (socket = first_socket(&e); socket != INVALID_SOCKET;
584 socket = next_socket(&e))
585 i++;
586
587 /* Expand the buffer if necessary. */
588 if (i > sksize) {
589 sksize = i+16;
590 sklist = srealloc(sklist, sksize * sizeof(*sklist));
591 }
592
593 /* Retrieve the sockets into sklist. */
594 skcount = 0;
595 for (socket = first_socket(&e); socket != INVALID_SOCKET;
596 socket = next_socket(&e)) {
597 sklist[skcount++] = socket;
12dc4ec0 598 }
8df7a775 599
600 /* Now we're done enumerating; go through the list. */
601 for (i = 0; i < skcount; i++) {
602 WPARAM wp;
603 socket = sklist[i];
604 wp = (WPARAM)socket;
ffb959c7 605 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
7d6ee6ff 606 noise_ultralight(socket);
607 noise_ultralight(things.lNetworkEvents);
8df7a775 608 if (things.lNetworkEvents & FD_READ)
609 connopen &= select_result(wp, (LPARAM)FD_READ);
610 if (things.lNetworkEvents & FD_CLOSE)
611 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
612 if (things.lNetworkEvents & FD_OOB)
613 connopen &= select_result(wp, (LPARAM)FD_OOB);
614 if (things.lNetworkEvents & FD_WRITE)
615 connopen &= select_result(wp, (LPARAM)FD_WRITE);
616 }
617 }
12dc4ec0 618 } else if (n == 1) {
7d6ee6ff 619 noise_ultralight(idata.len);
12dc4ec0 620 if (idata.len > 0) {
621 back->send(idata.buffer, idata.len);
622 } else {
623 back->special(TS_EOF);
624 }
8df7a775 625 SetEvent(idata.eventback);
12dc4ec0 626 }
8df7a775 627 if (!connopen || back->socket() == NULL)
cf6e59d6 628 break; /* we closed the connection */
12dc4ec0 629 }
630 WSACleanup();
631 return 0;
632}