Fixed issue #1499: Allow to show log if current HEAD and selected ref is orphan branc...
[TortoiseGit.git] / src / TortoisePlink / Windows / WINPLINK.C
blob88c2219d594b34f61bad1edf530402216e66a040
1 /*\r
2  * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.\r
3  */\r
4 \r
5 #include <stdio.h>\r
6 #include <stdlib.h>\r
7 #include <assert.h>\r
8 #include <stdarg.h>\r
9 \r
10 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */\r
11 #include "putty.h"\r
12 #include "storage.h"\r
13 #include "tree234.h"\r
15 #define WM_AGENT_CALLBACK (WM_APP + 4)\r
17 struct agent_callback {\r
18     void (*callback)(void *, void *, int);\r
19     void *callback_ctx;\r
20     void *data;\r
21     int len;\r
22 };\r
24 void fatalbox(char *p, ...)\r
25 {\r
26         va_list ap;\r
27         char *stuff, morestuff[100];\r
29         va_start(ap, p);\r
30         stuff = dupvprintf(p, ap);\r
31         va_end(ap);\r
32         sprintf(morestuff, "%.70s Fatal Error", appname);\r
33         MessageBox(GetParentHwnd(), stuff, morestuff, MB_ICONERROR | MB_OK);\r
34         sfree(stuff);\r
35     if (logctx) {\r
36         log_free(logctx);\r
37         logctx = NULL;\r
38     }\r
39         cleanup_exit(1);\r
40 }\r
41 void modalfatalbox(char *p, ...)\r
42 {\r
43         va_list ap;\r
44         char *stuff, morestuff[100];\r
46         va_start(ap, p);\r
47         stuff = dupvprintf(p, ap);\r
48         va_end(ap);\r
49         sprintf(morestuff, "%.70s Fatal Error", appname);\r
50         MessageBox(GetParentHwnd(), stuff, morestuff,\r
51                 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);\r
52         sfree(stuff);\r
53     if (logctx) {\r
54         log_free(logctx);\r
55         logctx = NULL;\r
56     }\r
57         cleanup_exit(1);\r
58 }\r
59 void connection_fatal(void *frontend, char *p, ...)\r
60 {\r
61         va_list ap;\r
62         char *stuff, morestuff[100];\r
64         va_start(ap, p);\r
65         stuff = dupvprintf(p, ap);\r
66         va_end(ap);\r
67         sprintf(morestuff, "%.70s Fatal Error", appname);\r
68         MessageBox(GetParentHwnd(), stuff, morestuff,\r
69                 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);\r
70         sfree(stuff);\r
71     if (logctx) {\r
72         log_free(logctx);\r
73         logctx = NULL;\r
74     }\r
75         cleanup_exit(1);\r
76 }\r
77 void cmdline_error(char *p, ...)\r
78 {\r
79         va_list ap;\r
80         char *stuff, morestuff[100];\r
82         va_start(ap, p);\r
83         stuff = dupvprintf(p, ap);\r
84         va_end(ap);\r
85         sprintf(morestuff, "%.70s Command Line Error", appname);\r
86         MessageBox(GetParentHwnd(), stuff, morestuff, MB_ICONERROR | MB_OK);\r
87         sfree(stuff);\r
88     exit(1);\r
89 }\r
91 HANDLE inhandle, outhandle, errhandle;\r
92 struct handle *stdin_handle, *stdout_handle, *stderr_handle;\r
93 DWORD orig_console_mode;\r
94 int connopen;\r
96 WSAEVENT netevent;\r
98 static Backend *back;\r
99 static void *backhandle;\r
100 static Config cfg;\r
102 int term_ldisc(Terminal *term, int mode)\r
104     return FALSE;\r
106 void ldisc_update(void *frontend, int echo, int edit)\r
108     /* Update stdin read mode to reflect changes in line discipline. */\r
109     DWORD mode;\r
111     mode = ENABLE_PROCESSED_INPUT;\r
112     if (echo)\r
113         mode = mode | ENABLE_ECHO_INPUT;\r
114     else\r
115         mode = mode & ~ENABLE_ECHO_INPUT;\r
116     if (edit)\r
117         mode = mode | ENABLE_LINE_INPUT;\r
118     else\r
119         mode = mode & ~ENABLE_LINE_INPUT;\r
120     SetConsoleMode(inhandle, mode);\r
123 char *get_ttymode(void *frontend, const char *mode) { return NULL; }\r
125 int from_backend(void *frontend_handle, int is_stderr,\r
126                  const char *data, int len)\r
128     if (is_stderr) {\r
129         handle_write(stderr_handle, data, len);\r
130     } else {\r
131         handle_write(stdout_handle, data, len);\r
132     }\r
134     return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);\r
137 int from_backend_untrusted(void *frontend_handle, const char *data, int len)\r
139     /*\r
140      * No "untrusted" output should get here (the way the code is\r
141      * currently, it's all diverted by FLAG_STDERR).\r
142      */\r
143     assert(!"Unexpected call to from_backend_untrusted()");\r
144     return 0; /* not reached */\r
147 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)\r
149     int ret;\r
150     ret = cmdline_get_passwd_input(p, in, inlen);\r
151     if (ret == -1)\r
152         ret = console_get_userpass_input(p, in, inlen);\r
153     return ret;\r
156 static DWORD main_thread_id;\r
158 void agent_schedule_callback(void (*callback)(void *, void *, int),\r
159                              void *callback_ctx, void *data, int len)\r
161     struct agent_callback *c = snew(struct agent_callback);\r
162     c->callback = callback;\r
163     c->callback_ctx = callback_ctx;\r
164     c->data = data;\r
165     c->len = len;\r
166     PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c);\r
169 /*\r
170  *  Short description of parameters.\r
171  */\r
172 static void usage(void)\r
174         char buf[10000];\r
175         int j = 0;\r
177         j += sprintf(buf+j, "PuTTY Link: command-line connection utility\n");\r
178     j += sprintf(buf+j, "%s\n", ver);\r
179     j += sprintf(buf+j, "Usage: plink [options] [user@]host [command]\n");\r
180     j += sprintf(buf+j, "       (\"host\" can also be a PuTTY saved session name)\n");\r
181     j += sprintf(buf+j, "Options:\n");\r
182     j += sprintf(buf+j, "  -V        print version information and exit\n");\r
183     j += sprintf(buf+j, "  -pgpfp    print PGP key fingerprints and exit\n");\r
184     j += sprintf(buf+j, "  -v        show verbose messages\n");\r
185     j += sprintf(buf+j, "  -load sessname  Load settings from saved session\n");\r
186     j += sprintf(buf+j, "  -ssh -telnet -rlogin -raw\n");\r
187     j += sprintf(buf+j, "            force use of a particular protocol\n");\r
188     j += sprintf(buf+j, "  -P port   connect to specified port\n");\r
189     j += sprintf(buf+j, "  -l user   connect with specified username\n");\r
190     j += sprintf(buf+j, "  -batch    disable all interactive prompts\n");\r
191     j += sprintf(buf+j, "The following options only apply to SSH connections:\n");\r
192     j += sprintf(buf+j, "  -pw passw login with specified password\n");\r
193     j += sprintf(buf+j, "  -D [listen-IP:]listen-port\n");\r
194     j += sprintf(buf+j, "            Dynamic SOCKS-based port forwarding\n");\r
195     j += sprintf(buf+j, "  -L [listen-IP:]listen-port:host:port\n");\r
196     j += sprintf(buf+j, "            Forward local port to remote address\n");\r
197     j += sprintf(buf+j, "  -R [listen-IP:]listen-port:host:port\n");\r
198     j += sprintf(buf+j, "            Forward remote port to local address\n");\r
199     j += sprintf(buf+j, "  -X -x     enable / disable X11 forwarding\n");\r
200     j += sprintf(buf+j, "  -A -a     enable / disable agent forwarding\n");\r
201     j += sprintf(buf+j, "  -t -T     enable / disable pty allocation\n");\r
202     j += sprintf(buf+j, "  -1 -2     force use of particular protocol version\n");\r
203     j += sprintf(buf+j, "  -4 -6     force use of IPv4 or IPv6\n");\r
204     j += sprintf(buf+j, "  -C        enable compression\n");\r
205     j += sprintf(buf+j, "  -i key    private key file for authentication\n");\r
206     j += sprintf(buf+j, "  -noagent  disable use of Pageant\n");\r
207     j += sprintf(buf+j, "  -agent    enable use of Pageant\n");\r
208     j += sprintf(buf+j, "  -m file   read remote command(s) from file\n");\r
209     j += sprintf(buf+j, "  -s        remote command is an SSH subsystem (SSH-2 only)\n");\r
210     j += sprintf(buf+j, "  -N        don't start a shell/command (SSH-2 only)\n");\r
211     j += sprintf(buf+j, "  -nc host:port\n");\r
212     j += sprintf(buf+j, "            open tunnel in place of session (SSH-2 only)\n");\r
213         MessageBox(NULL, buf, "TortoisePlink", MB_ICONINFORMATION);\r
214         exit(1);\r
217 static void version(void)\r
219         printf("TortoisePlink: %s\n", ver);\r
220         exit(1);\r
223 char *do_select(SOCKET skt, int startup)\r
225     int events;\r
226     if (startup) {\r
227         events = (FD_CONNECT | FD_READ | FD_WRITE |\r
228                   FD_OOB | FD_CLOSE | FD_ACCEPT);\r
229     } else {\r
230         events = 0;\r
231     }\r
232     if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {\r
233         switch (p_WSAGetLastError()) {\r
234           case WSAENETDOWN:\r
235             return "Network is down";\r
236           default:\r
237             return "WSAEventSelect(): unknown error";\r
238         }\r
239     }\r
240     return NULL;\r
243 int stdin_gotdata(struct handle *h, void *data, int len)\r
245     if (len < 0) {\r
246         /*\r
247          * Special case: report read error.\r
248          */\r
249         char buf[4096];\r
250         FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 0,\r
251                       buf, lenof(buf), NULL);\r
252         buf[lenof(buf)-1] = '\0';\r
253         if (buf[strlen(buf)-1] == '\n')\r
254             buf[strlen(buf)-1] = '\0';\r
255         fprintf(stderr, "Unable to read from standard input: %s\n", buf);\r
256         cleanup_exit(0);\r
257     }\r
258     noise_ultralight(len);\r
259     if (connopen && back->connected(backhandle)) {\r
260         if (len > 0) {\r
261             return back->send(backhandle, data, len);\r
262         } else {\r
263             back->special(backhandle, TS_EOF);\r
264             return 0;\r
265         }\r
266     } else\r
267         return 0;\r
270 void stdouterr_sent(struct handle *h, int new_backlog)\r
272     if (new_backlog < 0) {\r
273         /*\r
274          * Special case: report write error.\r
275          */\r
276         char buf[4096];\r
277         FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 0,\r
278                       buf, lenof(buf), NULL);\r
279         buf[lenof(buf)-1] = '\0';\r
280         if (buf[strlen(buf)-1] == '\n')\r
281             buf[strlen(buf)-1] = '\0';\r
282         fprintf(stderr, "Unable to write to standard %s: %s\n",\r
283                 (h == stdout_handle ? "output" : "error"), buf);\r
284         cleanup_exit(0);\r
285     }\r
286     if (connopen && back->connected(backhandle)) {\r
287         back->unthrottle(backhandle, (handle_backlog(stdout_handle) +\r
288                                       handle_backlog(stderr_handle)));\r
289     }\r
292 int main(int argc, char **argv)\r
294     int sending;\r
295     int portnumber = -1;\r
296     SOCKET *sklist;\r
297     int skcount, sksize;\r
298     int exitcode;\r
299     int errors;\r
300     int got_host = FALSE;\r
301     int use_subsystem = 0;\r
302     long now, next;\r
304     sklist = NULL;\r
305     skcount = sksize = 0;\r
306     /*\r
307      * Initialise port and protocol to sensible defaults. (These\r
308      * will be overridden by more or less anything.)\r
309      */\r
310     default_protocol = PROT_SSH;\r
311     default_port = 22;\r
313     flags = FLAG_STDERR;\r
314     /*\r
315      * Process the command line.\r
316      */\r
317     do_defaults(NULL, &cfg);\r
318     loaded_session = FALSE;\r
319     default_protocol = cfg.protocol;\r
320     default_port = cfg.port;\r
321     errors = 0;\r
322     {\r
323         /*\r
324          * Override the default protocol if PLINK_PROTOCOL is set.\r
325          */\r
326         char *p = getenv("PLINK_PROTOCOL");\r
327         if (p) {\r
328             const Backend *b = backend_from_name(p);\r
329             if (b) {\r
330                 default_protocol = cfg.protocol = b->protocol;\r
331                 default_port = cfg.port = b->default_port;\r
332             }\r
333         }\r
334     }\r
335     while (--argc) {\r
336         char *p = *++argv;\r
337         if (*p == '-') {\r
338             int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),\r
339                                             1, &cfg);\r
340             if (ret == -2) {\r
341                 fprintf(stderr,\r
342                         "plink: option \"%s\" requires an argument\n", p);\r
343                 errors = 1;\r
344             } else if (ret == 2) {\r
345                 --argc, ++argv;\r
346             } else if (ret == 1) {\r
347                 continue;\r
348             } else if (!strcmp(p, "-batch")) {\r
349                 console_batch_mode = 1;\r
350             } else if (!strcmp(p, "-s")) {\r
351                 /* Save status to write to cfg later. */\r
352                 use_subsystem = 1;\r
353             } else if (!strcmp(p, "-V")) {\r
354                 version();\r
355             } else if (!strcmp(p, "-pgpfp")) {\r
356                 pgp_fingerprints();\r
357                 exit(1);\r
358             } else {\r
359                 fprintf(stderr, "plink: unknown option \"%s\"\n", p);\r
360                 errors = 1;\r
361             }\r
362         } else if (*p) {\r
363             if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) {\r
364                 char *q = p;\r
365                 /*\r
366                  * If the hostname starts with "telnet:", set the\r
367                  * protocol to Telnet and process the string as a\r
368                  * Telnet URL.\r
369                  */\r
370                 if (!strncmp(q, "telnet:", 7)) {\r
371                     char c;\r
373                     q += 7;\r
374                     if (q[0] == '/' && q[1] == '/')\r
375                         q += 2;\r
376                     cfg.protocol = PROT_TELNET;\r
377                     p = q;\r
378                     while (*p && *p != ':' && *p != '/')\r
379                         p++;\r
380                     c = *p;\r
381                     if (*p)\r
382                         *p++ = '\0';\r
383                     if (c == ':')\r
384                         cfg.port = atoi(p);\r
385                     else\r
386                         cfg.port = -1;\r
387                     strncpy(cfg.host, q, sizeof(cfg.host) - 1);\r
388                     cfg.host[sizeof(cfg.host) - 1] = '\0';\r
389                     got_host = TRUE;\r
390                 } else {\r
391                     char *r, *user, *host;\r
392                     /*\r
393                      * Before we process the [user@]host string, we\r
394                      * first check for the presence of a protocol\r
395                      * prefix (a protocol name followed by ",").\r
396                      */\r
397                     r = strchr(p, ',');\r
398                     if (r) {\r
399                         const Backend *b;\r
400                         *r = '\0';\r
401                         b = backend_from_name(p);\r
402                         if (b) {\r
403                             default_protocol = cfg.protocol = b->protocol;\r
404                             portnumber = b->default_port;\r
405                         }\r
406                         p = r + 1;\r
407                     }\r
409                     /*\r
410                      * A nonzero length string followed by an @ is treated\r
411                      * as a username. (We discount an _initial_ @.) The\r
412                      * rest of the string (or the whole string if no @)\r
413                      * is treated as a session name and/or hostname.\r
414                      */\r
415                     r = strrchr(p, '@');\r
416                     if (r == p)\r
417                         p++, r = NULL; /* discount initial @ */\r
418                     if (r) {\r
419                         *r++ = '\0';\r
420                         user = p, host = r;\r
421                     } else {\r
422                         user = NULL, host = p;\r
423                     }\r
425                     /*\r
426                      * Now attempt to load a saved session with the\r
427                      * same name as the hostname.\r
428                      */\r
429                     {\r
430                         Config cfg2;\r
431                         do_defaults(host, &cfg2);\r
432                         if (loaded_session || !cfg_launchable(&cfg2)) {\r
433                             /* No settings for this host; use defaults */\r
434                             /* (or session was already loaded with -load) */\r
435                             strncpy(cfg.host, host, sizeof(cfg.host) - 1);\r
436                             cfg.host[sizeof(cfg.host) - 1] = '\0';\r
437                             cfg.port = default_port;\r
438                             got_host = TRUE;\r
439                         } else {\r
440                             cfg = cfg2;\r
441                             loaded_session = TRUE;\r
442                         }\r
443                     }\r
445                     if (user) {\r
446                         /* Patch in specified username. */\r
447                         strncpy(cfg.username, user,\r
448                                 sizeof(cfg.username) - 1);\r
449                         cfg.username[sizeof(cfg.username) - 1] = '\0';\r
450                     }\r
452                 }\r
453             } else {\r
454                 char *command;\r
455                 int cmdlen, cmdsize;\r
456                 cmdlen = cmdsize = 0;\r
457                 command = NULL;\r
459                 while (argc) {\r
460                     while (*p) {\r
461                         if (cmdlen >= cmdsize) {\r
462                             cmdsize = cmdlen + 512;\r
463                             command = sresize(command, cmdsize, char);\r
464                         }\r
465                         command[cmdlen++]=*p++;\r
466                     }\r
467                     if (cmdlen >= cmdsize) {\r
468                         cmdsize = cmdlen + 512;\r
469                         command = sresize(command, cmdsize, char);\r
470                     }\r
471                     command[cmdlen++]=' '; /* always add trailing space */\r
472                     if (--argc) p = *++argv;\r
473                 }\r
474                 if (cmdlen) command[--cmdlen]='\0';\r
475                                        /* change trailing blank to NUL */\r
476                 cfg.remote_cmd_ptr = command;\r
477                 cfg.remote_cmd_ptr2 = NULL;\r
478                 cfg.nopty = TRUE;      /* command => no terminal */\r
480                 break;                 /* done with cmdline */\r
481             }\r
482         }\r
483     }\r
485     if (errors)\r
486         return 1;\r
488     if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) {\r
489         usage();\r
490     }\r
492     /*\r
493      * Trim leading whitespace off the hostname if it's there.\r
494      */\r
495     {\r
496         int space = strspn(cfg.host, " \t");\r
497         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);\r
498     }\r
500     /* See if host is of the form user@host */\r
501     if (cfg_launchable(&cfg)) {\r
502         char *atsign = strrchr(cfg.host, '@');\r
503         /* Make sure we're not overflowing the user field */\r
504         if (atsign) {\r
505             if (atsign - cfg.host < sizeof cfg.username) {\r
506                 strncpy(cfg.username, cfg.host, atsign - cfg.host);\r
507                 cfg.username[atsign - cfg.host] = '\0';\r
508             }\r
509             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));\r
510         }\r
511     }\r
513     /*\r
514      * Perform command-line overrides on session configuration.\r
515      */\r
516     cmdline_run_saved(&cfg);\r
518     /*\r
519      * Apply subsystem status.\r
520      */\r
521     if (use_subsystem)\r
522         cfg.ssh_subsys = TRUE;\r
524     /*\r
525      * Trim a colon suffix off the hostname if it's there.\r
526      */\r
527     cfg.host[strcspn(cfg.host, ":")] = '\0';\r
529     /*\r
530      * Remove any remaining whitespace from the hostname.\r
531      */\r
532     {\r
533         int p1 = 0, p2 = 0;\r
534         while (cfg.host[p2] != '\0') {\r
535             if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {\r
536                 cfg.host[p1] = cfg.host[p2];\r
537                 p1++;\r
538             }\r
539             p2++;\r
540         }\r
541         cfg.host[p1] = '\0';\r
542     }\r
544     if (!cfg.remote_cmd_ptr && !*cfg.remote_cmd && !*cfg.ssh_nc_host)\r
545         flags |= FLAG_INTERACTIVE;\r
547     /*\r
548      * Select protocol. This is farmed out into a table in a\r
549      * separate file to enable an ssh-free variant.\r
550      */\r
551     back = backend_from_proto(cfg.protocol);\r
552     if (back == NULL) {\r
553         fprintf(stderr,\r
554                 "Internal fault: Unsupported protocol found\n");\r
555         return 1;\r
556     }\r
558     /*\r
559      * Select port.\r
560      */\r
561     if (portnumber != -1)\r
562         cfg.port = portnumber;\r
564     sk_init();\r
565     if (p_WSAEventSelect == NULL) {\r
566         fprintf(stderr, "Plink requires WinSock 2\n");\r
567         return 1;\r
568     }\r
570     logctx = log_init(NULL, &cfg);\r
571     console_provide_logctx(logctx);\r
573     /*\r
574      * Start up the connection.\r
575      */\r
576     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);\r
577     {\r
578         const char *error;\r
579         char *realhost;\r
580         /* nodelay is only useful if stdin is a character device (console) */\r
581         int nodelay = cfg.tcp_nodelay &&\r
582             (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);\r
584         error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port,\r
585                            &realhost, nodelay, cfg.tcp_keepalives);\r
586         if (error) {\r
587             fprintf(stderr, "Unable to open connection:\n%s", error);\r
588             return 1;\r
589         }\r
590         back->provide_logctx(backhandle, logctx);\r
591         sfree(realhost);\r
592     }\r
593     connopen = 1;\r
595     inhandle = GetStdHandle(STD_INPUT_HANDLE);\r
596     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);\r
597     errhandle = GetStdHandle(STD_ERROR_HANDLE);\r
599     /*\r
600      * Turn off ECHO and LINE input modes. We don't care if this\r
601      * call fails, because we know we aren't necessarily running in\r
602      * a console.\r
603      */\r
604     GetConsoleMode(inhandle, &orig_console_mode);\r
605     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);\r
607     /*\r
608      * Pass the output handles to the handle-handling subsystem.\r
609      * (The input one we leave until we're through the\r
610      * authentication process.)\r
611      */\r
612     stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);\r
613     stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);\r
615     main_thread_id = GetCurrentThreadId();\r
617     sending = FALSE;\r
619     now = GETTICKCOUNT();\r
621     while (1) {\r
622         int nhandles;\r
623         HANDLE *handles;        \r
624         int n;\r
625         DWORD ticks;\r
627         if (!sending && back->sendok(backhandle)) {\r
628             stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,\r
629                                             0);\r
630             sending = TRUE;\r
631         }\r
633         if (run_timers(now, &next)) {\r
634             ticks = next - GETTICKCOUNT();\r
635             if (ticks < 0) ticks = 0;  /* just in case */\r
636         } else {\r
637             ticks = INFINITE;\r
638         }\r
640         handles = handle_get_events(&nhandles);\r
641         handles = sresize(handles, nhandles+1, HANDLE);\r
642         handles[nhandles] = netevent;\r
643         n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,\r
644                                       QS_POSTMESSAGE);\r
645         if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {\r
646             handle_got_event(handles[n - WAIT_OBJECT_0]);\r
647         } else if (n == WAIT_OBJECT_0 + nhandles) {\r
648             WSANETWORKEVENTS things;\r
649             SOCKET socket;\r
650             extern SOCKET first_socket(int *), next_socket(int *);\r
651             extern int select_result(WPARAM, LPARAM);\r
652             int i, socketstate;\r
654             /*\r
655              * We must not call select_result() for any socket\r
656              * until we have finished enumerating within the tree.\r
657              * This is because select_result() may close the socket\r
658              * and modify the tree.\r
659              */\r
660             /* Count the active sockets. */\r
661             i = 0;\r
662             for (socket = first_socket(&socketstate);\r
663                  socket != INVALID_SOCKET;\r
664                  socket = next_socket(&socketstate)) i++;\r
666             /* Expand the buffer if necessary. */\r
667             if (i > sksize) {\r
668                 sksize = i + 16;\r
669                 sklist = sresize(sklist, sksize, SOCKET);\r
670             }\r
672             /* Retrieve the sockets into sklist. */\r
673             skcount = 0;\r
674             for (socket = first_socket(&socketstate);\r
675                  socket != INVALID_SOCKET;\r
676                  socket = next_socket(&socketstate)) {\r
677                 sklist[skcount++] = socket;\r
678             }\r
680             /* Now we're done enumerating; go through the list. */\r
681             for (i = 0; i < skcount; i++) {\r
682                 WPARAM wp;\r
683                 socket = sklist[i];\r
684                 wp = (WPARAM) socket;\r
685                 if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {\r
686                     static const struct { int bit, mask; } eventtypes[] = {\r
687                         {FD_CONNECT_BIT, FD_CONNECT},\r
688                         {FD_READ_BIT, FD_READ},\r
689                         {FD_CLOSE_BIT, FD_CLOSE},\r
690                         {FD_OOB_BIT, FD_OOB},\r
691                         {FD_WRITE_BIT, FD_WRITE},\r
692                         {FD_ACCEPT_BIT, FD_ACCEPT},\r
693                     };\r
694                     int e;\r
696                     noise_ultralight(socket);\r
697                     noise_ultralight(things.lNetworkEvents);\r
699                     for (e = 0; e < lenof(eventtypes); e++)\r
700                         if (things.lNetworkEvents & eventtypes[e].mask) {\r
701                             LPARAM lp;\r
702                             int err = things.iErrorCode[eventtypes[e].bit];\r
703                             lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);\r
704                             connopen &= select_result(wp, lp);\r
705                         }\r
706                 }\r
707             }\r
708         } else if (n == WAIT_OBJECT_0 + nhandles + 1) {\r
709             MSG msg;\r
710             while (PeekMessage(&msg, INVALID_HANDLE_VALUE,\r
711                                WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,\r
712                                PM_REMOVE)) {\r
713                 struct agent_callback *c = (struct agent_callback *)msg.lParam;\r
714                 c->callback(c->callback_ctx, c->data, c->len);\r
715                 sfree(c);\r
716             }\r
717         }\r
719         if (n == WAIT_TIMEOUT) {\r
720             now = next;\r
721         } else {\r
722             now = GETTICKCOUNT();\r
723         }\r
725         sfree(handles);\r
727         if (sending)\r
728             handle_unthrottle(stdin_handle, back->sendbuffer(backhandle));\r
730         if ((!connopen || !back->connected(backhandle)) &&\r
731             handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)\r
732             break;                     /* we closed the connection */\r
733     }\r
734     exitcode = back->exitcode(backhandle);\r
735     if (exitcode < 0) {\r
736         fprintf(stderr, "Remote process exit code unavailable\n");\r
737         exitcode = 1;                  /* this is an error condition */\r
738     }\r
739     cleanup_exit(exitcode);\r
740     return 0;                          /* placate compiler warning */\r
743 int WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)\r
745         main(__argc,__argv);\r