Replace PuTTY's 2-3-4 tree implementation with the shiny new counted
[u/mdw/putty] / plink.c
1 /*
2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
3 */
4
5 #ifndef AUTO_WINSOCK
6 #include <winsock2.h>
7 #endif
8 #include <windows.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <stdarg.h>
12
13 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
14 #include "putty.h"
15 #include "storage.h"
16 #include "tree234.h"
17
18 void fatalbox (char *p, ...) {
19 va_list ap;
20 fprintf(stderr, "FATAL ERROR: ");
21 va_start(ap, p);
22 vfprintf(stderr, p, ap);
23 va_end(ap);
24 fputc('\n', stderr);
25 WSACleanup();
26 exit(1);
27 }
28 void connection_fatal (char *p, ...) {
29 va_list ap;
30 fprintf(stderr, "FATAL ERROR: ");
31 va_start(ap, p);
32 vfprintf(stderr, p, ap);
33 va_end(ap);
34 fputc('\n', stderr);
35 WSACleanup();
36 exit(1);
37 }
38
39 static char *password = NULL;
40
41 void logevent(char *string) { }
42
43 void verify_ssh_host_key(char *host, int port, char *keytype,
44 char *keystr, char *fingerprint) {
45 int ret;
46 HANDLE hin;
47 DWORD savemode, i;
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;
90
91 if (ret == 2) /* key was different */
92 fprintf(stderr, wrongmsg, fingerprint);
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') {
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 */
113 if (line[0] == 'y' || line[0] == 'Y')
114 store_host_key(host, port, keytype, keystr);
115 else {
116 fprintf(stderr, abandoned);
117 exit(0);
118 }
119 }
120 }
121
122 HANDLE inhandle, outhandle, errhandle;
123 DWORD orig_console_mode;
124
125 WSAEVENT netevent;
126
127 void from_backend(int is_stderr, char *data, int len) {
128 int pos;
129 DWORD ret;
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))
135 return; /* give up in panic */
136 pos += ret;
137 }
138 }
139
140 int term_ldisc(int mode) { return FALSE; }
141 void 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
157 struct input_data {
158 DWORD len;
159 char buffer[4096];
160 HANDLE event, eventback;
161 };
162
163 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
164 {
165 HANDLE hin, hout;
166 DWORD savemode, newmode, i;
167
168 if (is_pw && password) {
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 }
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);
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);
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
204 if (is_pw)
205 WriteFile(hout, "\r\n", 2, &i, NULL);
206
207 return 1;
208 }
209
210 static DWORD WINAPI stdin_read_thread(void *param) {
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),
217 &idata->len, NULL) && idata->len > 0) {
218 SetEvent(idata->event);
219 WaitForSingleObject(idata->eventback, INFINITE);
220 }
221
222 idata->len = 0;
223 SetEvent(idata->event);
224
225 return 0;
226 }
227
228 /*
229 * Short description of parameters.
230 */
231 static 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");
241 printf(" -m file read remote command(s) from file\n");
242 exit(1);
243 }
244
245 char *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
261 int main(int argc, char **argv) {
262 WSADATA wsadata;
263 WORD winsock_ver;
264 WSAEVENT stdinevent;
265 HANDLE handles[2];
266 DWORD threadid;
267 struct input_data idata;
268 int sending;
269 int portnumber = -1;
270 SOCKET *sklist;
271 int skcount, sksize;
272 int connopen;
273
274 ssh_get_line = get_line;
275
276 sklist = NULL; skcount = sksize = 0;
277
278 flags = FLAG_STDERR;
279 /*
280 * Process the command line.
281 */
282 do_defaults(NULL, &cfg);
283 default_protocol = cfg.protocol;
284 default_port = cfg.port;
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 }
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;
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;
312 } else if (!strcmp(p, "-v")) {
313 flags |= FLAG_VERBOSE;
314 } else if (!strcmp(p, "-log")) {
315 logfile = "putty.log";
316 } else if (!strcmp(p, "-pw") && argc > 1) {
317 --argc, password = *++argv;
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';
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 */
352 } else if (!strcmp(p, "-P") && argc > 1) {
353 --argc, portnumber = atoi(*++argv);
354 }
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 {
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
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 */
413 r = strrchr(p, '@');
414 if (r == p) p++, r = NULL; /* discount initial @ */
415 if (r == NULL) {
416 /*
417 * One string.
418 */
419 Config cfg2;
420 do_defaults (p, &cfg2);
421 if (cfg2.host[0] == '\0') {
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';
425 cfg.port = default_port;
426 } else {
427 cfg = cfg2;
428 cfg.remote_cmd_ptr = cfg.remote_cmd;
429 }
430 } else {
431 *r++ = '\0';
432 strncpy(cfg.username, p, sizeof(cfg.username)-1);
433 cfg.username[sizeof(cfg.username)-1] = '\0';
434 strncpy(cfg.host, r, sizeof(cfg.host)-1);
435 cfg.host[sizeof(cfg.host)-1] = '\0';
436 cfg.port = default_port;
437 }
438 }
439 } else {
440 int len = sizeof(cfg.remote_cmd) - 1;
441 char *cp = cfg.remote_cmd;
442 int len2;
443
444 strncpy(cp, p, len); cp[len] = '\0';
445 len2 = strlen(cp); len -= len2; cp += len2;
446 while (--argc) {
447 if (len > 0)
448 len--, *cp++ = ' ';
449 strncpy(cp, *++argv, len); cp[len] = '\0';
450 len2 = strlen(cp); len -= len2; cp += len2;
451 }
452 cfg.nopty = TRUE; /* command => no terminal */
453 break; /* done with cmdline */
454 }
455 }
456 }
457
458 if (!*cfg.host) {
459 usage();
460 }
461
462 if (!*cfg.remote_cmd_ptr)
463 flags |= FLAG_INTERACTIVE;
464
465 /*
466 * Select protocol. This is farmed out into a table in a
467 * separate file to enable an ssh-free variant.
468 */
469 {
470 int i;
471 back = NULL;
472 for (i = 0; backends[i].backend != NULL; i++)
473 if (backends[i].protocol == cfg.protocol) {
474 back = backends[i].backend;
475 break;
476 }
477 if (back == NULL) {
478 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
479 return 1;
480 }
481 }
482
483 /*
484 * Select port.
485 */
486 if (portnumber != -1)
487 cfg.port = portnumber;
488
489 /*
490 * Initialise WinSock.
491 */
492 winsock_ver = MAKEWORD(2, 0);
493 if (WSAStartup(winsock_ver, &wsadata)) {
494 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
495 MB_OK | MB_ICONEXCLAMATION);
496 return 1;
497 }
498 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
499 MessageBox(NULL, "WinSock version is incompatible with 2.0",
500 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
501 WSACleanup();
502 return 1;
503 }
504 sk_init();
505
506 /*
507 * Start up the connection.
508 */
509 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
510 {
511 char *error;
512 char *realhost;
513
514 error = back->init (cfg.host, cfg.port, &realhost);
515 if (error) {
516 fprintf(stderr, "Unable to open connection:\n%s", error);
517 return 1;
518 }
519 }
520 connopen = 1;
521
522 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
523
524 inhandle = GetStdHandle(STD_INPUT_HANDLE);
525 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
526 errhandle = GetStdHandle(STD_ERROR_HANDLE);
527 GetConsoleMode(inhandle, &orig_console_mode);
528 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
529
530 /*
531 * Turn off ECHO and LINE input modes. We don't care if this
532 * call fails, because we know we aren't necessarily running in
533 * a console.
534 */
535 handles[0] = netevent;
536 handles[1] = stdinevent;
537 sending = FALSE;
538 while (1) {
539 int n;
540
541 if (!sending && back->sendok()) {
542 /*
543 * Create a separate thread to read from stdin. This is
544 * a total pain, but I can't find another way to do it:
545 *
546 * - an overlapped ReadFile or ReadFileEx just doesn't
547 * happen; we get failure from ReadFileEx, and
548 * ReadFile blocks despite being given an OVERLAPPED
549 * structure. Perhaps we can't do overlapped reads
550 * on consoles. WHY THE HELL NOT?
551 *
552 * - WaitForMultipleObjects(netevent, console) doesn't
553 * work, because it signals the console when
554 * _anything_ happens, including mouse motions and
555 * other things that don't cause data to be readable
556 * - so we're back to ReadFile blocking.
557 */
558 idata.event = stdinevent;
559 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
560 if (!CreateThread(NULL, 0, stdin_read_thread,
561 &idata, 0, &threadid)) {
562 fprintf(stderr, "Unable to create second thread\n");
563 exit(1);
564 }
565 sending = TRUE;
566 }
567
568 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
569 if (n == 0) {
570 WSANETWORKEVENTS things;
571 SOCKET socket;
572 extern SOCKET first_socket(int *), next_socket(int *);
573 extern int select_result(WPARAM, LPARAM);
574 int i, socketstate;
575
576 /*
577 * We must not call select_result() for any socket
578 * until we have finished enumerating within the tree.
579 * This is because select_result() may close the socket
580 * and modify the tree.
581 */
582 /* Count the active sockets. */
583 i = 0;
584 for (socket = first_socket(&socketstate); socket != INVALID_SOCKET;
585 socket = next_socket(&socketstate))
586 i++;
587
588 /* Expand the buffer if necessary. */
589 if (i > sksize) {
590 sksize = i+16;
591 sklist = srealloc(sklist, sksize * sizeof(*sklist));
592 }
593
594 /* Retrieve the sockets into sklist. */
595 skcount = 0;
596 for (socket = first_socket(&socketstate); socket != INVALID_SOCKET;
597 socket = next_socket(&socketstate)) {
598 sklist[skcount++] = socket;
599 }
600
601 /* Now we're done enumerating; go through the list. */
602 for (i = 0; i < skcount; i++) {
603 WPARAM wp;
604 socket = sklist[i];
605 wp = (WPARAM)socket;
606 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
607 noise_ultralight(socket);
608 noise_ultralight(things.lNetworkEvents);
609 if (things.lNetworkEvents & FD_READ)
610 connopen &= select_result(wp, (LPARAM)FD_READ);
611 if (things.lNetworkEvents & FD_CLOSE)
612 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
613 if (things.lNetworkEvents & FD_OOB)
614 connopen &= select_result(wp, (LPARAM)FD_OOB);
615 if (things.lNetworkEvents & FD_WRITE)
616 connopen &= select_result(wp, (LPARAM)FD_WRITE);
617 }
618 }
619 } else if (n == 1) {
620 noise_ultralight(idata.len);
621 if (idata.len > 0) {
622 back->send(idata.buffer, idata.len);
623 } else {
624 back->special(TS_EOF);
625 }
626 SetEvent(idata.eventback);
627 }
628 if (!connopen || back->socket() == NULL)
629 break; /* we closed the connection */
630 }
631 WSACleanup();
632 return 0;
633 }