]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Add a new back-end function to return the exit code of the remote
[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     int exitcode;
448     char extra_portfwd[sizeof(cfg.portfwd)];
449
450     ssh_get_line = get_line;
451
452     sklist = NULL;
453     skcount = sksize = 0;
454     /*
455      * Initialise port and protocol to sensible defaults. (These
456      * will be overridden by more or less anything.)
457      */
458     default_protocol = PROT_SSH;
459     default_port = 22;
460
461     flags = FLAG_STDERR;
462     /*
463      * Process the command line.
464      */
465     do_defaults(NULL, &cfg);
466     default_protocol = cfg.protocol;
467     default_port = cfg.port;
468     {
469         /*
470          * Override the default protocol if PLINK_PROTOCOL is set.
471          */
472         char *p = getenv("PLINK_PROTOCOL");
473         int i;
474         if (p) {
475             for (i = 0; backends[i].backend != NULL; i++) {
476                 if (!strcmp(backends[i].name, p)) {
477                     default_protocol = cfg.protocol = backends[i].protocol;
478                     default_port = cfg.port =
479                         backends[i].backend->default_port;
480                     break;
481                 }
482             }
483         }
484     }
485     while (--argc) {
486         char *p = *++argv;
487         if (*p == '-') {
488             if (!strcmp(p, "-ssh")) {
489                 default_protocol = cfg.protocol = PROT_SSH;
490                 default_port = cfg.port = 22;
491             } else if (!strcmp(p, "-telnet")) {
492                 default_protocol = cfg.protocol = PROT_TELNET;
493                 default_port = cfg.port = 23;
494             } else if (!strcmp(p, "-rlogin")) {
495                 default_protocol = cfg.protocol = PROT_RLOGIN;
496                 default_port = cfg.port = 513;
497             } else if (!strcmp(p, "-raw")) {
498                 default_protocol = cfg.protocol = PROT_RAW;
499             } else if (!strcmp(p, "-v")) {
500                 flags |= FLAG_VERBOSE;
501             } else if (!strcmp(p, "-log")) {
502                 logfile = "putty.log";
503             } else if (!strcmp(p, "-pw") && argc > 1) {
504                 --argc, password = *++argv;
505             } else if (!strcmp(p, "-l") && argc > 1) {
506                 char *username;
507                 --argc, username = *++argv;
508                 strncpy(cfg.username, username, sizeof(cfg.username));
509                 cfg.username[sizeof(cfg.username) - 1] = '\0';
510             } else if ((!strcmp(p, "-L") || !strcmp(p, "-R")) && argc > 1) {
511                 char *fwd, *ptr, *q;
512                 int i=0;
513                 --argc, fwd = *++argv;
514                 ptr = extra_portfwd;
515                 /* if multiple forwards, find end of list */
516                 if (ptr[0]=='R' || ptr[0]=='L') {
517                     for (i = 0; i < sizeof(extra_portfwd) - 2; i++)
518                         if (ptr[i]=='\000' && ptr[i+1]=='\000')
519                             break;
520                     ptr = ptr + i + 1;  /* point to next forward slot */
521                 }
522                 ptr[0] = p[1];  /* insert a 'L' or 'R' at the start */
523                 strncpy(ptr+1, fwd, sizeof(extra_portfwd) - i);
524                 q = strchr(ptr, ':');
525                 if (q) *q = '\t';      /* replace first : with \t */
526                 ptr[strlen(ptr)+1] = '\000';    /* append two '\000' */
527                 extra_portfwd[sizeof(extra_portfwd) - 1] = '\0';
528             } else if (!strcmp(p, "-m") && argc > 1) {
529                 char *filename, *command;
530                 int cmdlen, cmdsize;
531                 FILE *fp;
532                 int c, d;
533
534                 --argc, filename = *++argv;
535
536                 cmdlen = cmdsize = 0;
537                 command = NULL;
538                 fp = fopen(filename, "r");
539                 if (!fp) {
540                     fprintf(stderr, "plink: unable to open command "
541                             "file \"%s\"\n", filename);
542                     return 1;
543                 }
544                 do {
545                     c = fgetc(fp);
546                     d = c;
547                     if (c == EOF)
548                         d = 0;
549                     if (cmdlen >= cmdsize) {
550                         cmdsize = cmdlen + 512;
551                         command = srealloc(command, cmdsize);
552                     }
553                     command[cmdlen++] = d;
554                 } while (c != EOF);
555                 cfg.remote_cmd_ptr = command;
556                 cfg.remote_cmd_ptr2 = NULL;
557                 cfg.nopty = TRUE;      /* command => no terminal */
558             } else if (!strcmp(p, "-P") && argc > 1) {
559                 --argc, portnumber = atoi(*++argv);
560             }
561         } else if (*p) {
562             if (!*cfg.host) {
563                 char *q = p;
564                 /*
565                  * If the hostname starts with "telnet:", set the
566                  * protocol to Telnet and process the string as a
567                  * Telnet URL.
568                  */
569                 if (!strncmp(q, "telnet:", 7)) {
570                     char c;
571
572                     q += 7;
573                     if (q[0] == '/' && q[1] == '/')
574                         q += 2;
575                     cfg.protocol = PROT_TELNET;
576                     p = q;
577                     while (*p && *p != ':' && *p != '/')
578                         p++;
579                     c = *p;
580                     if (*p)
581                         *p++ = '\0';
582                     if (c == ':')
583                         cfg.port = atoi(p);
584                     else
585                         cfg.port = -1;
586                     strncpy(cfg.host, q, sizeof(cfg.host) - 1);
587                     cfg.host[sizeof(cfg.host) - 1] = '\0';
588                 } else {
589                     char *r;
590                     /*
591                      * Before we process the [user@]host string, we
592                      * first check for the presence of a protocol
593                      * prefix (a protocol name followed by ",").
594                      */
595                     r = strchr(p, ',');
596                     if (r) {
597                         int i, j;
598                         for (i = 0; backends[i].backend != NULL; i++) {
599                             j = strlen(backends[i].name);
600                             if (j == r - p &&
601                                 !memcmp(backends[i].name, p, j)) {
602                                 default_protocol = cfg.protocol =
603                                     backends[i].protocol;
604                                 portnumber =
605                                     backends[i].backend->default_port;
606                                 p = r + 1;
607                                 break;
608                             }
609                         }
610                     }
611
612                     /*
613                      * Three cases. Either (a) there's a nonzero
614                      * length string followed by an @, in which
615                      * case that's user and the remainder is host.
616                      * Or (b) there's only one string, not counting
617                      * a potential initial @, and it exists in the
618                      * saved-sessions database. Or (c) only one
619                      * string and it _doesn't_ exist in the
620                      * database.
621                      */
622                     r = strrchr(p, '@');
623                     if (r == p)
624                         p++, r = NULL; /* discount initial @ */
625                     if (r == NULL) {
626                         /*
627                          * One string.
628                          */
629                         Config cfg2;
630                         do_defaults(p, &cfg2);
631                         if (cfg2.host[0] == '\0') {
632                             /* No settings for this host; use defaults */
633                             strncpy(cfg.host, p, sizeof(cfg.host) - 1);
634                             cfg.host[sizeof(cfg.host) - 1] = '\0';
635                             cfg.port = default_port;
636                         } else {
637                             cfg = cfg2;
638                             cfg.remote_cmd_ptr = cfg.remote_cmd;
639                         }
640                     } else {
641                         *r++ = '\0';
642                         strncpy(cfg.username, p, sizeof(cfg.username) - 1);
643                         cfg.username[sizeof(cfg.username) - 1] = '\0';
644                         strncpy(cfg.host, r, sizeof(cfg.host) - 1);
645                         cfg.host[sizeof(cfg.host) - 1] = '\0';
646                         cfg.port = default_port;
647                     }
648                 }
649             } else {
650                 int len = sizeof(cfg.remote_cmd) - 1;
651                 char *cp = cfg.remote_cmd;
652                 int len2;
653
654                 strncpy(cp, p, len);
655                 cp[len] = '\0';
656                 len2 = strlen(cp);
657                 len -= len2;
658                 cp += len2;
659                 while (--argc) {
660                     if (len > 0)
661                         len--, *cp++ = ' ';
662                     strncpy(cp, *++argv, len);
663                     cp[len] = '\0';
664                     len2 = strlen(cp);
665                     len -= len2;
666                     cp += len2;
667                 }
668                 cfg.nopty = TRUE;      /* command => no terminal */
669                 break;                 /* done with cmdline */
670             }
671         }
672     }
673
674     if (!*cfg.host) {
675         usage();
676     }
677
678     /*
679      * Trim leading whitespace off the hostname if it's there.
680      */
681     {
682         int space = strspn(cfg.host, " \t");
683         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
684     }
685
686     /* See if host is of the form user@host */
687     if (cfg.host[0] != '\0') {
688         char *atsign = strchr(cfg.host, '@');
689         /* Make sure we're not overflowing the user field */
690         if (atsign) {
691             if (atsign - cfg.host < sizeof cfg.username) {
692                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
693                 cfg.username[atsign - cfg.host] = '\0';
694             }
695             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
696         }
697     }
698
699     /*
700      * Trim a colon suffix off the hostname if it's there.
701      */
702     cfg.host[strcspn(cfg.host, ":")] = '\0';
703
704     if (!*cfg.remote_cmd_ptr)
705         flags |= FLAG_INTERACTIVE;
706
707     /*
708      * Select protocol. This is farmed out into a table in a
709      * separate file to enable an ssh-free variant.
710      */
711     {
712         int i;
713         back = NULL;
714         for (i = 0; backends[i].backend != NULL; i++)
715             if (backends[i].protocol == cfg.protocol) {
716                 back = backends[i].backend;
717                 break;
718             }
719         if (back == NULL) {
720             fprintf(stderr,
721                     "Internal fault: Unsupported protocol found\n");
722             return 1;
723         }
724     }
725
726     /*
727      * Add extra port forwardings (accumulated on command line) to
728      * cfg.
729      */
730     {
731         int i;
732         char *p;
733         p = extra_portfwd;
734         i = 0;
735         while (cfg.portfwd[i])
736             i += strlen(cfg.portfwd+i) + 1;
737         while (*p) {
738             if (strlen(p)+2 > sizeof(cfg.portfwd)-i) {
739                 fprintf(stderr, "Internal fault: not enough space for all"
740                         " port forwardings\n");
741                 break;
742             }
743             strncpy(cfg.portfwd+i, p, sizeof(cfg.portfwd)-i-1);
744             i += strlen(cfg.portfwd+i) + 1;
745             cfg.portfwd[i] = '\0';
746             p += strlen(p)+1;
747         }
748     }
749
750     /*
751      * Select port.
752      */
753     if (portnumber != -1)
754         cfg.port = portnumber;
755
756     /*
757      * Initialise WinSock.
758      */
759     winsock_ver = MAKEWORD(2, 0);
760     if (WSAStartup(winsock_ver, &wsadata)) {
761         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
762                    MB_OK | MB_ICONEXCLAMATION);
763         return 1;
764     }
765     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
766         MessageBox(NULL, "WinSock version is incompatible with 2.0",
767                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
768         WSACleanup();
769         return 1;
770     }
771     sk_init();
772
773     /*
774      * Start up the connection.
775      */
776     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
777     {
778         char *error;
779         char *realhost;
780         /* nodelay is only useful if stdin is a character device (console) */
781         int nodelay = cfg.tcp_nodelay &&
782             (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
783
784         error = back->init(cfg.host, cfg.port, &realhost, nodelay);
785         if (error) {
786             fprintf(stderr, "Unable to open connection:\n%s", error);
787             return 1;
788         }
789         sfree(realhost);
790     }
791     connopen = 1;
792
793     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
794     stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
795     stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
796
797     inhandle = GetStdHandle(STD_INPUT_HANDLE);
798     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
799     errhandle = GetStdHandle(STD_ERROR_HANDLE);
800     GetConsoleMode(inhandle, &orig_console_mode);
801     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
802
803     /*
804      * Turn off ECHO and LINE input modes. We don't care if this
805      * call fails, because we know we aren't necessarily running in
806      * a console.
807      */
808     handles[0] = netevent;
809     handles[1] = stdinevent;
810     handles[2] = stdoutevent;
811     handles[3] = stderrevent;
812     sending = FALSE;
813
814     /*
815      * Create spare threads to write to stdout and stderr, so we
816      * can arrange asynchronous writes.
817      */
818     odata.event = stdoutevent;
819     odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
820     odata.is_stderr = 0;
821     odata.busy = odata.done = 0;
822     if (!CreateThread(NULL, 0, stdout_write_thread,
823                       &odata, 0, &out_threadid)) {
824         fprintf(stderr, "Unable to create output thread\n");
825         exit(1);
826     }
827     edata.event = stderrevent;
828     edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
829     edata.is_stderr = 1;
830     edata.busy = edata.done = 0;
831     if (!CreateThread(NULL, 0, stdout_write_thread,
832                       &edata, 0, &err_threadid)) {
833         fprintf(stderr, "Unable to create error output thread\n");
834         exit(1);
835     }
836
837     while (1) {
838         int n;
839
840         if (!sending && back->sendok()) {
841             /*
842              * Create a separate thread to read from stdin. This is
843              * a total pain, but I can't find another way to do it:
844              *
845              *  - an overlapped ReadFile or ReadFileEx just doesn't
846              *    happen; we get failure from ReadFileEx, and
847              *    ReadFile blocks despite being given an OVERLAPPED
848              *    structure. Perhaps we can't do overlapped reads
849              *    on consoles. WHY THE HELL NOT?
850              * 
851              *  - WaitForMultipleObjects(netevent, console) doesn't
852              *    work, because it signals the console when
853              *    _anything_ happens, including mouse motions and
854              *    other things that don't cause data to be readable
855              *    - so we're back to ReadFile blocking.
856              */
857             idata.event = stdinevent;
858             idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
859             if (!CreateThread(NULL, 0, stdin_read_thread,
860                               &idata, 0, &in_threadid)) {
861                 fprintf(stderr, "Unable to create input thread\n");
862                 exit(1);
863             }
864             sending = TRUE;
865         }
866
867         n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
868         if (n == 0) {
869             WSANETWORKEVENTS things;
870             SOCKET socket;
871             extern SOCKET first_socket(int *), next_socket(int *);
872             extern int select_result(WPARAM, LPARAM);
873             int i, socketstate;
874
875             /*
876              * We must not call select_result() for any socket
877              * until we have finished enumerating within the tree.
878              * This is because select_result() may close the socket
879              * and modify the tree.
880              */
881             /* Count the active sockets. */
882             i = 0;
883             for (socket = first_socket(&socketstate);
884                  socket != INVALID_SOCKET;
885                  socket = next_socket(&socketstate)) i++;
886
887             /* Expand the buffer if necessary. */
888             if (i > sksize) {
889                 sksize = i + 16;
890                 sklist = srealloc(sklist, sksize * sizeof(*sklist));
891             }
892
893             /* Retrieve the sockets into sklist. */
894             skcount = 0;
895             for (socket = first_socket(&socketstate);
896                  socket != INVALID_SOCKET;
897                  socket = next_socket(&socketstate)) {
898                 sklist[skcount++] = socket;
899             }
900
901             /* Now we're done enumerating; go through the list. */
902             for (i = 0; i < skcount; i++) {
903                 WPARAM wp;
904                 socket = sklist[i];
905                 wp = (WPARAM) socket;
906                 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
907                     noise_ultralight(socket);
908                     noise_ultralight(things.lNetworkEvents);
909                     if (things.lNetworkEvents & FD_CONNECT)
910                         connopen &= select_result(wp, (LPARAM) FD_CONNECT);
911                     if (things.lNetworkEvents & FD_READ)
912                         connopen &= select_result(wp, (LPARAM) FD_READ);
913                     if (things.lNetworkEvents & FD_CLOSE)
914                         connopen &= select_result(wp, (LPARAM) FD_CLOSE);
915                     if (things.lNetworkEvents & FD_OOB)
916                         connopen &= select_result(wp, (LPARAM) FD_OOB);
917                     if (things.lNetworkEvents & FD_WRITE)
918                         connopen &= select_result(wp, (LPARAM) FD_WRITE);
919                     if (things.lNetworkEvents & FD_ACCEPT)
920                         connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
921
922                 }
923             }
924         } else if (n == 1) {
925             reading = 0;
926             noise_ultralight(idata.len);
927             if (connopen && back->socket() != NULL) {
928                 if (idata.len > 0) {
929                     back->send(idata.buffer, idata.len);
930                 } else {
931                     back->special(TS_EOF);
932                 }
933             }
934         } else if (n == 2) {
935             odata.busy = 0;
936             if (!odata.writeret) {
937                 fprintf(stderr, "Unable to write to standard output\n");
938                 exit(0);
939             }
940             bufchain_consume(&stdout_data, odata.lenwritten);
941             if (bufchain_size(&stdout_data) > 0)
942                 try_output(0);
943             if (connopen && back->socket() != NULL) {
944                 back->unthrottle(bufchain_size(&stdout_data) +
945                                  bufchain_size(&stderr_data));
946             }
947         } else if (n == 3) {
948             edata.busy = 0;
949             if (!edata.writeret) {
950                 fprintf(stderr, "Unable to write to standard output\n");
951                 exit(0);
952             }
953             bufchain_consume(&stderr_data, edata.lenwritten);
954             if (bufchain_size(&stderr_data) > 0)
955                 try_output(1);
956             if (connopen && back->socket() != NULL) {
957                 back->unthrottle(bufchain_size(&stdout_data) +
958                                  bufchain_size(&stderr_data));
959             }
960         }
961         if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
962             SetEvent(idata.eventback);
963             reading = 1;
964         }
965         if ((!connopen || back->socket() == NULL) &&
966             bufchain_size(&stdout_data) == 0 &&
967             bufchain_size(&stderr_data) == 0)
968             break;                     /* we closed the connection */
969     }
970     WSACleanup();
971     exitcode = back->exitcode();
972     if (exitcode < 0) {
973         fprintf(stderr, "Remote process exit code unavailable\n");
974         exitcode = 1;                  /* this is an error condition */
975     }
976     return exitcode;
977 }