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