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