]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Add -rlogin to Plink, for orthogonality (and because I documented it
[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, "-rlogin")) {
456                 default_protocol = cfg.protocol = PROT_RLOGIN;
457                 default_port = cfg.port = 513;
458             } else if (!strcmp(p, "-raw")) {
459                 default_protocol = cfg.protocol = PROT_RAW;
460             } else if (!strcmp(p, "-v")) {
461                 flags |= FLAG_VERBOSE;
462             } else if (!strcmp(p, "-log")) {
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';
471             } else if ((!strcmp(p, "-L") || !strcmp(p, "-R")) && argc > 1) {
472                 char *fwd, *ptr, *q;
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);
485                 q = strchr(ptr, ':');
486                 if (q) *q = '\t';      /* replace first : with \t */
487                 ptr[strlen(ptr)+1] = '\000';    /* append two '\000' */
488                 extra_portfwd[sizeof(extra_portfwd) - 1] = '\0';
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;
517                 cfg.remote_cmd_ptr2 = NULL;
518                 cfg.nopty = TRUE;      /* command => no terminal */
519             } else if (!strcmp(p, "-P") && argc > 1) {
520                 --argc, portnumber = atoi(*++argv);
521             }
522         } else if (*p) {
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             }
632         }
633     }
634
635     if (!*cfg.host) {
636         usage();
637     }
638
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
665     if (!*cfg.remote_cmd_ptr)
666         flags |= FLAG_INTERACTIVE;
667
668     /*
669      * Select protocol. This is farmed out into a table in a
670      * separate file to enable an ssh-free variant.
671      */
672     {
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         }
685     }
686
687     /*
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     /*
712      * Select port.
713      */
714     if (portnumber != -1)
715         cfg.port = portnumber;
716
717     /*
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     }
732     sk_init();
733
734     /*
735      * Start up the connection.
736      */
737     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
738     {
739         char *error;
740         char *realhost;
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);
744
745         error = back->init(cfg.host, cfg.port, &realhost, nodelay);
746         if (error) {
747             fprintf(stderr, "Unable to open connection:\n%s", error);
748             return 1;
749         }
750         sfree(realhost);
751     }
752     connopen = 1;
753
754     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
755     stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
756     stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
757
758     inhandle = GetStdHandle(STD_INPUT_HANDLE);
759     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
760     errhandle = GetStdHandle(STD_ERROR_HANDLE);
761     GetConsoleMode(inhandle, &orig_console_mode);
762     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
763
764     /*
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      */
769     handles[0] = netevent;
770     handles[1] = stdinevent;
771     handles[2] = stdoutevent;
772     handles[3] = stderrevent;
773     sending = FALSE;
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
798     while (1) {
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,
821                               &idata, 0, &in_threadid)) {
822                 fprintf(stderr, "Unable to create input thread\n");
823                 exit(1);
824             }
825             sending = TRUE;
826         }
827
828         n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
829         if (n == 0) {
830             WSANETWORKEVENTS things;
831             SOCKET socket;
832             extern SOCKET first_socket(int *), next_socket(int *);
833             extern int select_result(WPARAM, LPARAM);
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;
858                  socket = next_socket(&socketstate)) {
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;
867                 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
868                     noise_ultralight(socket);
869                     noise_ultralight(things.lNetworkEvents);
870                     if (things.lNetworkEvents & FD_CONNECT)
871                         connopen &= select_result(wp, (LPARAM) FD_CONNECT);
872                     if (things.lNetworkEvents & FD_READ)
873                         connopen &= select_result(wp, (LPARAM) FD_READ);
874                     if (things.lNetworkEvents & FD_CLOSE)
875                         connopen &= select_result(wp, (LPARAM) FD_CLOSE);
876                     if (things.lNetworkEvents & FD_OOB)
877                         connopen &= select_result(wp, (LPARAM) FD_OOB);
878                     if (things.lNetworkEvents & FD_WRITE)
879                         connopen &= select_result(wp, (LPARAM) FD_WRITE);
880                     if (things.lNetworkEvents & FD_ACCEPT)
881                         connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
882
883                 }
884             }
885         } else if (n == 1) {
886             reading = 0;
887             noise_ultralight(idata.len);
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                 }
894             }
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);
904             if (connopen && back->socket() != NULL) {
905                 back->unthrottle(bufchain_size(&stdout_data) +
906                                  bufchain_size(&stderr_data));
907             }
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);
917             if (connopen && back->socket() != NULL) {
918                 back->unthrottle(bufchain_size(&stdout_data) +
919                                  bufchain_size(&stderr_data));
920             }
921         }
922         if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
923             SetEvent(idata.eventback);
924             reading = 1;
925         }
926         if ((!connopen || back->socket() == NULL) &&
927             bufchain_size(&stdout_data) == 0 &&
928             bufchain_size(&stderr_data) == 0)
929             break;                     /* we closed the connection */
930     }
931     WSACleanup();
932     return 0;
933 }