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