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