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