6c2115035bbab346d6d32b459dd2f979d3b82dbb
[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 #define MAX_STDIN_BACKLOG 4096
19
20 void fatalbox(char *p, ...)
21 {
22 va_list ap;
23 fprintf(stderr, "FATAL ERROR: ");
24 va_start(ap, p);
25 vfprintf(stderr, p, ap);
26 va_end(ap);
27 fputc('\n', stderr);
28 WSACleanup();
29 exit(1);
30 }
31 void connection_fatal(char *p, ...)
32 {
33 va_list ap;
34 fprintf(stderr, "FATAL ERROR: ");
35 va_start(ap, p);
36 vfprintf(stderr, p, ap);
37 va_end(ap);
38 fputc('\n', stderr);
39 WSACleanup();
40 exit(1);
41 }
42
43 static char *password = NULL;
44
45 void logevent(char *string)
46 {
47 }
48
49 void verify_ssh_host_key(char *host, int port, char *keytype,
50 char *keystr, char *fingerprint)
51 {
52 int ret;
53 HANDLE hin;
54 DWORD savemode, i;
55
56 static const char absentmsg[] =
57 "The server's host key is not cached in the registry. You\n"
58 "have no guarantee that the server is the computer you\n"
59 "think it is.\n"
60 "The server's key fingerprint is:\n"
61 "%s\n"
62 "If you trust this host, enter \"y\" to add the key to\n"
63 "PuTTY's cache and carry on connecting.\n"
64 "If you want to carry on connecting just once, without\n"
65 "adding the key to the cache, enter \"n\".\n"
66 "If you do not trust this host, press Return to abandon the\n"
67 "connection.\n"
68 "Store key in cache? (y/n) ";
69
70 static const char wrongmsg[] =
71 "WARNING - POTENTIAL SECURITY BREACH!\n"
72 "The server's host key does not match the one PuTTY has\n"
73 "cached in the registry. This means that either the\n"
74 "server administrator has changed the host key, or you\n"
75 "have actually connected to another computer pretending\n"
76 "to be the server.\n"
77 "The new key fingerprint is:\n"
78 "%s\n"
79 "If you were expecting this change and trust the new key,\n"
80 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
81 "If you want to carry on connecting but without updating\n"
82 "the cache, enter \"n\".\n"
83 "If you want to abandon the connection completely, press\n"
84 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
85 "safe choice.\n"
86 "Update cached key? (y/n, Return cancels connection) ";
87
88 static const char abandoned[] = "Connection abandoned.\n";
89
90 char line[32];
91
92 /*
93 * Verify the key against the registry.
94 */
95 ret = verify_host_key(host, port, keytype, keystr);
96
97 if (ret == 0) /* success - key matched OK */
98 return;
99
100 if (ret == 2) { /* key was different */
101 fprintf(stderr, wrongmsg, fingerprint);
102 fflush(stderr);
103 }
104 if (ret == 1) { /* key was absent */
105 fprintf(stderr, absentmsg, fingerprint);
106 fflush(stderr);
107 }
108
109 hin = GetStdHandle(STD_INPUT_HANDLE);
110 GetConsoleMode(hin, &savemode);
111 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
112 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
113 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
114 SetConsoleMode(hin, savemode);
115
116 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
117 if (line[0] == 'y' || line[0] == 'Y')
118 store_host_key(host, port, keytype, keystr);
119 } else {
120 fprintf(stderr, abandoned);
121 exit(0);
122 }
123 }
124
125 /*
126 * Ask whether the selected cipher is acceptable (since it was
127 * below the configured 'warn' threshold).
128 * cs: 0 = both ways, 1 = client->server, 2 = server->client
129 */
130 void askcipher(char *ciphername, int cs)
131 {
132 HANDLE hin;
133 DWORD savemode, i;
134
135 static const char msg[] =
136 "The first %scipher supported by the server is\n"
137 "%s, which is below the configured warning threshold.\n"
138 "Continue with connection? (y/n) ";
139 static const char abandoned[] = "Connection abandoned.\n";
140
141 char line[32];
142
143 fprintf(stderr, msg,
144 (cs == 0) ? "" :
145 (cs == 1) ? "client-to-server " :
146 "server-to-client ",
147 ciphername);
148 fflush(stderr);
149
150 hin = GetStdHandle(STD_INPUT_HANDLE);
151 GetConsoleMode(hin, &savemode);
152 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
153 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
154 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
155 SetConsoleMode(hin, savemode);
156
157 if (line[0] == 'y' || line[0] == 'Y') {
158 return;
159 } else {
160 fprintf(stderr, abandoned);
161 exit(0);
162 }
163 }
164
165 /*
166 * Ask whether to wipe a session log file before writing to it.
167 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
168 */
169 int askappend(char *filename)
170 {
171 HANDLE hin;
172 DWORD savemode, i;
173
174 static const char msgtemplate[] =
175 "The session log file \"%.*s\" already exists.\n"
176 "You can overwrite it with a new session log,\n"
177 "append your session log to the end of it,\n"
178 "or disable session logging for this session.\n"
179 "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
180 "or just press Return to disable logging.\n"
181 "Wipe the log file? (y/n, Return cancels logging) ";
182
183 char line[32];
184
185 fprintf(stderr, msgtemplate, FILENAME_MAX, filename);
186 fflush(stderr);
187
188 hin = GetStdHandle(STD_INPUT_HANDLE);
189 GetConsoleMode(hin, &savemode);
190 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
191 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
192 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
193 SetConsoleMode(hin, savemode);
194
195 if (line[0] == 'y' || line[0] == 'Y')
196 return 2;
197 else if (line[0] == 'n' || line[0] == 'N')
198 return 1;
199 else
200 return 0;
201 }
202
203 /*
204 * Warn about the obsolescent key file format.
205 */
206 void old_keyfile_warning(void)
207 {
208 static const char message[] =
209 "You are loading an SSH 2 private key which has an\n"
210 "old version of the file format. This means your key\n"
211 "file is not fully tamperproof. Future versions of\n"
212 "PuTTY may stop supporting this private key format,\n"
213 "so we recommend you convert your key to the new\n"
214 "format.\n"
215 "\n"
216 "Once the key is loaded into PuTTYgen, you can perform\n"
217 "this conversion simply by saving it again.\n";
218
219 fputs(message, stderr);
220 }
221
222 HANDLE inhandle, outhandle, errhandle;
223 DWORD orig_console_mode;
224
225 WSAEVENT netevent;
226
227 int term_ldisc(int mode)
228 {
229 return FALSE;
230 }
231 void ldisc_update(int echo, int edit)
232 {
233 /* Update stdin read mode to reflect changes in line discipline. */
234 DWORD mode;
235
236 mode = ENABLE_PROCESSED_INPUT;
237 if (echo)
238 mode = mode | ENABLE_ECHO_INPUT;
239 else
240 mode = mode & ~ENABLE_ECHO_INPUT;
241 if (edit)
242 mode = mode | ENABLE_LINE_INPUT;
243 else
244 mode = mode & ~ENABLE_LINE_INPUT;
245 SetConsoleMode(inhandle, mode);
246 }
247
248 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
249 {
250 HANDLE hin, hout;
251 DWORD savemode, newmode, i;
252
253 if (is_pw && password) {
254 static int tried_once = 0;
255
256 if (tried_once) {
257 return 0;
258 } else {
259 strncpy(str, password, maxlen);
260 str[maxlen - 1] = '\0';
261 tried_once = 1;
262 return 1;
263 }
264 }
265
266 hin = GetStdHandle(STD_INPUT_HANDLE);
267 hout = GetStdHandle(STD_OUTPUT_HANDLE);
268 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
269 fprintf(stderr, "Cannot get standard input/output handles");
270 return 0;
271 }
272
273 GetConsoleMode(hin, &savemode);
274 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
275 if (is_pw)
276 newmode &= ~ENABLE_ECHO_INPUT;
277 else
278 newmode |= ENABLE_ECHO_INPUT;
279 SetConsoleMode(hin, newmode);
280
281 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
282 ReadFile(hin, str, maxlen - 1, &i, NULL);
283
284 SetConsoleMode(hin, savemode);
285
286 if ((int) i > maxlen)
287 i = maxlen - 1;
288 else
289 i = i - 2;
290 str[i] = '\0';
291
292 if (is_pw)
293 WriteFile(hout, "\r\n", 2, &i, NULL);
294
295 return 1;
296 }
297
298 struct input_data {
299 DWORD len;
300 char buffer[4096];
301 HANDLE event, eventback;
302 };
303
304 static DWORD WINAPI stdin_read_thread(void *param)
305 {
306 struct input_data *idata = (struct input_data *) param;
307 HANDLE inhandle;
308
309 inhandle = GetStdHandle(STD_INPUT_HANDLE);
310
311 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
312 &idata->len, NULL) && idata->len > 0) {
313 SetEvent(idata->event);
314 WaitForSingleObject(idata->eventback, INFINITE);
315 }
316
317 idata->len = 0;
318 SetEvent(idata->event);
319
320 return 0;
321 }
322
323 struct output_data {
324 DWORD len, lenwritten;
325 int writeret;
326 char *buffer;
327 int is_stderr, done;
328 HANDLE event, eventback;
329 int busy;
330 };
331
332 static DWORD WINAPI stdout_write_thread(void *param)
333 {
334 struct output_data *odata = (struct output_data *) param;
335 HANDLE outhandle, errhandle;
336
337 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
338 errhandle = GetStdHandle(STD_ERROR_HANDLE);
339
340 while (1) {
341 WaitForSingleObject(odata->eventback, INFINITE);
342 if (odata->done)
343 break;
344 odata->writeret =
345 WriteFile(odata->is_stderr ? errhandle : outhandle,
346 odata->buffer, odata->len, &odata->lenwritten, NULL);
347 SetEvent(odata->event);
348 }
349
350 return 0;
351 }
352
353 bufchain stdout_data, stderr_data;
354 struct output_data odata, edata;
355
356 void try_output(int is_stderr)
357 {
358 struct output_data *data = (is_stderr ? &edata : &odata);
359 void *senddata;
360 int sendlen;
361
362 if (!data->busy) {
363 bufchain_prefix(is_stderr ? &stderr_data : &stdout_data,
364 &senddata, &sendlen);
365 data->buffer = senddata;
366 data->len = sendlen;
367 SetEvent(data->eventback);
368 data->busy = 1;
369 }
370 }
371
372 int from_backend(int is_stderr, char *data, int len)
373 {
374 HANDLE h = (is_stderr ? errhandle : outhandle);
375 int osize, esize;
376
377 if (is_stderr) {
378 bufchain_add(&stderr_data, data, len);
379 try_output(1);
380 } else {
381 bufchain_add(&stdout_data, data, len);
382 try_output(0);
383 }
384
385 osize = bufchain_size(&stdout_data);
386 esize = bufchain_size(&stderr_data);
387
388 return osize + esize;
389 }
390
391 /*
392 * Short description of parameters.
393 */
394 static void usage(void)
395 {
396 printf("PuTTY Link: command-line connection utility\n");
397 printf("%s\n", ver);
398 printf("Usage: plink [options] [user@]host [command]\n");
399 printf(" (\"host\" can also be a PuTTY saved session name)\n");
400 printf("Options:\n");
401 printf(" -v show verbose messages\n");
402 printf(" -ssh force use of ssh protocol\n");
403 printf(" -P port connect to specified port\n");
404 printf(" -pw passw login with specified password\n");
405 printf(" -m file read remote command(s) from file\n");
406 printf(" -L listen-port:host:port Forward local port to "
407 "remote address\n");
408 printf(" -R listen-port:host:port Forward remote port to"
409 " local address\n");
410 exit(1);
411 }
412
413 char *do_select(SOCKET skt, int startup)
414 {
415 int events;
416 if (startup) {
417 events = (FD_CONNECT | FD_READ | FD_WRITE |
418 FD_OOB | FD_CLOSE | FD_ACCEPT);
419 } else {
420 events = 0;
421 }
422 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
423 switch (WSAGetLastError()) {
424 case WSAENETDOWN:
425 return "Network is down";
426 default:
427 return "WSAAsyncSelect(): unknown error";
428 }
429 }
430 return NULL;
431 }
432
433 int main(int argc, char **argv)
434 {
435 WSADATA wsadata;
436 WORD winsock_ver;
437 WSAEVENT stdinevent, stdoutevent, stderrevent;
438 HANDLE handles[4];
439 DWORD in_threadid, out_threadid, err_threadid;
440 struct input_data idata;
441 int reading;
442 int sending;
443 int portnumber = -1;
444 SOCKET *sklist;
445 int skcount, sksize;
446 int connopen;
447 char extra_portfwd[sizeof(cfg.portfwd)];
448
449 ssh_get_line = get_line;
450
451 sklist = NULL;
452 skcount = sksize = 0;
453 /*
454 * Initialise port and protocol to sensible defaults. (These
455 * will be overridden by more or less anything.)
456 */
457 default_protocol = PROT_SSH;
458 default_port = 22;
459
460 flags = FLAG_STDERR;
461 /*
462 * Process the command line.
463 */
464 do_defaults(NULL, &cfg);
465 default_protocol = cfg.protocol;
466 default_port = cfg.port;
467 {
468 /*
469 * Override the default protocol if PLINK_PROTOCOL is set.
470 */
471 char *p = getenv("PLINK_PROTOCOL");
472 int i;
473 if (p) {
474 for (i = 0; backends[i].backend != NULL; i++) {
475 if (!strcmp(backends[i].name, p)) {
476 default_protocol = cfg.protocol = backends[i].protocol;
477 default_port = cfg.port =
478 backends[i].backend->default_port;
479 break;
480 }
481 }
482 }
483 }
484 while (--argc) {
485 char *p = *++argv;
486 if (*p == '-') {
487 if (!strcmp(p, "-ssh")) {
488 default_protocol = cfg.protocol = PROT_SSH;
489 default_port = cfg.port = 22;
490 } else if (!strcmp(p, "-telnet")) {
491 default_protocol = cfg.protocol = PROT_TELNET;
492 default_port = cfg.port = 23;
493 } else if (!strcmp(p, "-rlogin")) {
494 default_protocol = cfg.protocol = PROT_RLOGIN;
495 default_port = cfg.port = 513;
496 } else if (!strcmp(p, "-raw")) {
497 default_protocol = cfg.protocol = PROT_RAW;
498 } else if (!strcmp(p, "-v")) {
499 flags |= FLAG_VERBOSE;
500 } else if (!strcmp(p, "-log")) {
501 logfile = "putty.log";
502 } else if (!strcmp(p, "-pw") && argc > 1) {
503 --argc, password = *++argv;
504 } else if (!strcmp(p, "-l") && argc > 1) {
505 char *username;
506 --argc, username = *++argv;
507 strncpy(cfg.username, username, sizeof(cfg.username));
508 cfg.username[sizeof(cfg.username) - 1] = '\0';
509 } else if ((!strcmp(p, "-L") || !strcmp(p, "-R")) && argc > 1) {
510 char *fwd, *ptr, *q;
511 int i=0;
512 --argc, fwd = *++argv;
513 ptr = extra_portfwd;
514 /* if multiple forwards, find end of list */
515 if (ptr[0]=='R' || ptr[0]=='L') {
516 for (i = 0; i < sizeof(extra_portfwd) - 2; i++)
517 if (ptr[i]=='\000' && ptr[i+1]=='\000')
518 break;
519 ptr = ptr + i + 1; /* point to next forward slot */
520 }
521 ptr[0] = p[1]; /* insert a 'L' or 'R' at the start */
522 strncpy(ptr+1, fwd, sizeof(extra_portfwd) - i);
523 q = strchr(ptr, ':');
524 if (q) *q = '\t'; /* replace first : with \t */
525 ptr[strlen(ptr)+1] = '\000'; /* append two '\000' */
526 extra_portfwd[sizeof(extra_portfwd) - 1] = '\0';
527 } else if (!strcmp(p, "-m") && argc > 1) {
528 char *filename, *command;
529 int cmdlen, cmdsize;
530 FILE *fp;
531 int c, d;
532
533 --argc, filename = *++argv;
534
535 cmdlen = cmdsize = 0;
536 command = NULL;
537 fp = fopen(filename, "r");
538 if (!fp) {
539 fprintf(stderr, "plink: unable to open command "
540 "file \"%s\"\n", filename);
541 return 1;
542 }
543 do {
544 c = fgetc(fp);
545 d = c;
546 if (c == EOF)
547 d = 0;
548 if (cmdlen >= cmdsize) {
549 cmdsize = cmdlen + 512;
550 command = srealloc(command, cmdsize);
551 }
552 command[cmdlen++] = d;
553 } while (c != EOF);
554 cfg.remote_cmd_ptr = command;
555 cfg.remote_cmd_ptr2 = NULL;
556 cfg.nopty = TRUE; /* command => no terminal */
557 } else if (!strcmp(p, "-P") && argc > 1) {
558 --argc, portnumber = atoi(*++argv);
559 }
560 } else if (*p) {
561 if (!*cfg.host) {
562 char *q = p;
563 /*
564 * If the hostname starts with "telnet:", set the
565 * protocol to Telnet and process the string as a
566 * Telnet URL.
567 */
568 if (!strncmp(q, "telnet:", 7)) {
569 char c;
570
571 q += 7;
572 if (q[0] == '/' && q[1] == '/')
573 q += 2;
574 cfg.protocol = PROT_TELNET;
575 p = q;
576 while (*p && *p != ':' && *p != '/')
577 p++;
578 c = *p;
579 if (*p)
580 *p++ = '\0';
581 if (c == ':')
582 cfg.port = atoi(p);
583 else
584 cfg.port = -1;
585 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
586 cfg.host[sizeof(cfg.host) - 1] = '\0';
587 } else {
588 char *r;
589 /*
590 * Before we process the [user@]host string, we
591 * first check for the presence of a protocol
592 * prefix (a protocol name followed by ",").
593 */
594 r = strchr(p, ',');
595 if (r) {
596 int i, j;
597 for (i = 0; backends[i].backend != NULL; i++) {
598 j = strlen(backends[i].name);
599 if (j == r - p &&
600 !memcmp(backends[i].name, p, j)) {
601 default_protocol = cfg.protocol =
602 backends[i].protocol;
603 portnumber =
604 backends[i].backend->default_port;
605 p = r + 1;
606 break;
607 }
608 }
609 }
610
611 /*
612 * Three cases. Either (a) there's a nonzero
613 * length string followed by an @, in which
614 * case that's user and the remainder is host.
615 * Or (b) there's only one string, not counting
616 * a potential initial @, and it exists in the
617 * saved-sessions database. Or (c) only one
618 * string and it _doesn't_ exist in the
619 * database.
620 */
621 r = strrchr(p, '@');
622 if (r == p)
623 p++, r = NULL; /* discount initial @ */
624 if (r == NULL) {
625 /*
626 * One string.
627 */
628 Config cfg2;
629 do_defaults(p, &cfg2);
630 if (cfg2.host[0] == '\0') {
631 /* No settings for this host; use defaults */
632 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
633 cfg.host[sizeof(cfg.host) - 1] = '\0';
634 cfg.port = default_port;
635 } else {
636 cfg = cfg2;
637 cfg.remote_cmd_ptr = cfg.remote_cmd;
638 }
639 } else {
640 *r++ = '\0';
641 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
642 cfg.username[sizeof(cfg.username) - 1] = '\0';
643 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
644 cfg.host[sizeof(cfg.host) - 1] = '\0';
645 cfg.port = default_port;
646 }
647 }
648 } else {
649 int len = sizeof(cfg.remote_cmd) - 1;
650 char *cp = cfg.remote_cmd;
651 int len2;
652
653 strncpy(cp, p, len);
654 cp[len] = '\0';
655 len2 = strlen(cp);
656 len -= len2;
657 cp += len2;
658 while (--argc) {
659 if (len > 0)
660 len--, *cp++ = ' ';
661 strncpy(cp, *++argv, len);
662 cp[len] = '\0';
663 len2 = strlen(cp);
664 len -= len2;
665 cp += len2;
666 }
667 cfg.nopty = TRUE; /* command => no terminal */
668 break; /* done with cmdline */
669 }
670 }
671 }
672
673 if (!*cfg.host) {
674 usage();
675 }
676
677 /*
678 * Trim leading whitespace off the hostname if it's there.
679 */
680 {
681 int space = strspn(cfg.host, " \t");
682 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
683 }
684
685 /* See if host is of the form user@host */
686 if (cfg.host[0] != '\0') {
687 char *atsign = strchr(cfg.host, '@');
688 /* Make sure we're not overflowing the user field */
689 if (atsign) {
690 if (atsign - cfg.host < sizeof cfg.username) {
691 strncpy(cfg.username, cfg.host, atsign - cfg.host);
692 cfg.username[atsign - cfg.host] = '\0';
693 }
694 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
695 }
696 }
697
698 /*
699 * Trim a colon suffix off the hostname if it's there.
700 */
701 cfg.host[strcspn(cfg.host, ":")] = '\0';
702
703 if (!*cfg.remote_cmd_ptr)
704 flags |= FLAG_INTERACTIVE;
705
706 /*
707 * Select protocol. This is farmed out into a table in a
708 * separate file to enable an ssh-free variant.
709 */
710 {
711 int i;
712 back = NULL;
713 for (i = 0; backends[i].backend != NULL; i++)
714 if (backends[i].protocol == cfg.protocol) {
715 back = backends[i].backend;
716 break;
717 }
718 if (back == NULL) {
719 fprintf(stderr,
720 "Internal fault: Unsupported protocol found\n");
721 return 1;
722 }
723 }
724
725 /*
726 * Add extra port forwardings (accumulated on command line) to
727 * cfg.
728 */
729 {
730 int i;
731 char *p;
732 p = extra_portfwd;
733 i = 0;
734 while (cfg.portfwd[i])
735 i += strlen(cfg.portfwd+i) + 1;
736 while (*p) {
737 if (strlen(p)+2 > sizeof(cfg.portfwd)-i) {
738 fprintf(stderr, "Internal fault: not enough space for all"
739 " port forwardings\n");
740 break;
741 }
742 strncpy(cfg.portfwd+i, p, sizeof(cfg.portfwd)-i-1);
743 i += strlen(cfg.portfwd+i) + 1;
744 cfg.portfwd[i] = '\0';
745 p += strlen(p)+1;
746 }
747 }
748
749 /*
750 * Select port.
751 */
752 if (portnumber != -1)
753 cfg.port = portnumber;
754
755 /*
756 * Initialise WinSock.
757 */
758 winsock_ver = MAKEWORD(2, 0);
759 if (WSAStartup(winsock_ver, &wsadata)) {
760 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
761 MB_OK | MB_ICONEXCLAMATION);
762 return 1;
763 }
764 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
765 MessageBox(NULL, "WinSock version is incompatible with 2.0",
766 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
767 WSACleanup();
768 return 1;
769 }
770 sk_init();
771
772 /*
773 * Start up the connection.
774 */
775 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
776 {
777 char *error;
778 char *realhost;
779 /* nodelay is only useful if stdin is a character device (console) */
780 int nodelay = cfg.tcp_nodelay &&
781 (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
782
783 error = back->init(cfg.host, cfg.port, &realhost, nodelay);
784 if (error) {
785 fprintf(stderr, "Unable to open connection:\n%s", error);
786 return 1;
787 }
788 sfree(realhost);
789 }
790 connopen = 1;
791
792 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
793 stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
794 stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
795
796 inhandle = GetStdHandle(STD_INPUT_HANDLE);
797 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
798 errhandle = GetStdHandle(STD_ERROR_HANDLE);
799 GetConsoleMode(inhandle, &orig_console_mode);
800 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
801
802 /*
803 * Turn off ECHO and LINE input modes. We don't care if this
804 * call fails, because we know we aren't necessarily running in
805 * a console.
806 */
807 handles[0] = netevent;
808 handles[1] = stdinevent;
809 handles[2] = stdoutevent;
810 handles[3] = stderrevent;
811 sending = FALSE;
812
813 /*
814 * Create spare threads to write to stdout and stderr, so we
815 * can arrange asynchronous writes.
816 */
817 odata.event = stdoutevent;
818 odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
819 odata.is_stderr = 0;
820 odata.busy = odata.done = 0;
821 if (!CreateThread(NULL, 0, stdout_write_thread,
822 &odata, 0, &out_threadid)) {
823 fprintf(stderr, "Unable to create output thread\n");
824 exit(1);
825 }
826 edata.event = stderrevent;
827 edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
828 edata.is_stderr = 1;
829 edata.busy = edata.done = 0;
830 if (!CreateThread(NULL, 0, stdout_write_thread,
831 &edata, 0, &err_threadid)) {
832 fprintf(stderr, "Unable to create error output thread\n");
833 exit(1);
834 }
835
836 while (1) {
837 int n;
838
839 if (!sending && back->sendok()) {
840 /*
841 * Create a separate thread to read from stdin. This is
842 * a total pain, but I can't find another way to do it:
843 *
844 * - an overlapped ReadFile or ReadFileEx just doesn't
845 * happen; we get failure from ReadFileEx, and
846 * ReadFile blocks despite being given an OVERLAPPED
847 * structure. Perhaps we can't do overlapped reads
848 * on consoles. WHY THE HELL NOT?
849 *
850 * - WaitForMultipleObjects(netevent, console) doesn't
851 * work, because it signals the console when
852 * _anything_ happens, including mouse motions and
853 * other things that don't cause data to be readable
854 * - so we're back to ReadFile blocking.
855 */
856 idata.event = stdinevent;
857 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
858 if (!CreateThread(NULL, 0, stdin_read_thread,
859 &idata, 0, &in_threadid)) {
860 fprintf(stderr, "Unable to create input thread\n");
861 exit(1);
862 }
863 sending = TRUE;
864 }
865
866 n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
867 if (n == 0) {
868 WSANETWORKEVENTS things;
869 SOCKET socket;
870 extern SOCKET first_socket(int *), next_socket(int *);
871 extern int select_result(WPARAM, LPARAM);
872 int i, socketstate;
873
874 /*
875 * We must not call select_result() for any socket
876 * until we have finished enumerating within the tree.
877 * This is because select_result() may close the socket
878 * and modify the tree.
879 */
880 /* Count the active sockets. */
881 i = 0;
882 for (socket = first_socket(&socketstate);
883 socket != INVALID_SOCKET;
884 socket = next_socket(&socketstate)) i++;
885
886 /* Expand the buffer if necessary. */
887 if (i > sksize) {
888 sksize = i + 16;
889 sklist = srealloc(sklist, sksize * sizeof(*sklist));
890 }
891
892 /* Retrieve the sockets into sklist. */
893 skcount = 0;
894 for (socket = first_socket(&socketstate);
895 socket != INVALID_SOCKET;
896 socket = next_socket(&socketstate)) {
897 sklist[skcount++] = socket;
898 }
899
900 /* Now we're done enumerating; go through the list. */
901 for (i = 0; i < skcount; i++) {
902 WPARAM wp;
903 socket = sklist[i];
904 wp = (WPARAM) socket;
905 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
906 noise_ultralight(socket);
907 noise_ultralight(things.lNetworkEvents);
908 if (things.lNetworkEvents & FD_CONNECT)
909 connopen &= select_result(wp, (LPARAM) FD_CONNECT);
910 if (things.lNetworkEvents & FD_READ)
911 connopen &= select_result(wp, (LPARAM) FD_READ);
912 if (things.lNetworkEvents & FD_CLOSE)
913 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
914 if (things.lNetworkEvents & FD_OOB)
915 connopen &= select_result(wp, (LPARAM) FD_OOB);
916 if (things.lNetworkEvents & FD_WRITE)
917 connopen &= select_result(wp, (LPARAM) FD_WRITE);
918 if (things.lNetworkEvents & FD_ACCEPT)
919 connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
920
921 }
922 }
923 } else if (n == 1) {
924 reading = 0;
925 noise_ultralight(idata.len);
926 if (connopen && back->socket() != NULL) {
927 if (idata.len > 0) {
928 back->send(idata.buffer, idata.len);
929 } else {
930 back->special(TS_EOF);
931 }
932 }
933 } else if (n == 2) {
934 odata.busy = 0;
935 if (!odata.writeret) {
936 fprintf(stderr, "Unable to write to standard output\n");
937 exit(0);
938 }
939 bufchain_consume(&stdout_data, odata.lenwritten);
940 if (bufchain_size(&stdout_data) > 0)
941 try_output(0);
942 if (connopen && back->socket() != NULL) {
943 back->unthrottle(bufchain_size(&stdout_data) +
944 bufchain_size(&stderr_data));
945 }
946 } else if (n == 3) {
947 edata.busy = 0;
948 if (!edata.writeret) {
949 fprintf(stderr, "Unable to write to standard output\n");
950 exit(0);
951 }
952 bufchain_consume(&stderr_data, edata.lenwritten);
953 if (bufchain_size(&stderr_data) > 0)
954 try_output(1);
955 if (connopen && back->socket() != NULL) {
956 back->unthrottle(bufchain_size(&stdout_data) +
957 bufchain_size(&stderr_data));
958 }
959 }
960 if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
961 SetEvent(idata.eventback);
962 reading = 1;
963 }
964 if ((!connopen || back->socket() == NULL) &&
965 bufchain_size(&stdout_data) == 0 &&
966 bufchain_size(&stderr_data) == 0)
967 break; /* we closed the connection */
968 }
969 WSACleanup();
970 return 0;
971 }