Add a nonfatal() function everywhere, to be used for reporting things
[sgt/putty] / windows / winplink.c
index 602ec7b..37453bb 100644 (file)
@@ -12,9 +12,7 @@
 #include "storage.h"
 #include "tree234.h"
 
-#define WM_AGENT_CALLBACK (WM_XUSER + 4)
-
-#define MAX_STDIN_BACKLOG 4096
+#define WM_AGENT_CALLBACK (WM_APP + 4)
 
 struct agent_callback {
     void (*callback)(void *, void *, int);
@@ -31,6 +29,10 @@ void fatalbox(char *p, ...)
     vfprintf(stderr, p, ap);
     va_end(ap);
     fputc('\n', stderr);
+    if (logctx) {
+        log_free(logctx);
+        logctx = NULL;
+    }
     cleanup_exit(1);
 }
 void modalfatalbox(char *p, ...)
@@ -41,8 +43,25 @@ void modalfatalbox(char *p, ...)
     vfprintf(stderr, p, ap);
     va_end(ap);
     fputc('\n', stderr);
+    if (logctx) {
+        log_free(logctx);
+        logctx = NULL;
+    }
     cleanup_exit(1);
 }
+void nonfatal(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    if (logctx) {
+        log_free(logctx);
+        logctx = NULL;
+    }
+}
 void connection_fatal(void *frontend, char *p, ...)
 {
     va_list ap;
@@ -51,6 +70,10 @@ void connection_fatal(void *frontend, char *p, ...)
     vfprintf(stderr, p, ap);
     va_end(ap);
     fputc('\n', stderr);
+    if (logctx) {
+        log_free(logctx);
+        logctx = NULL;
+    }
     cleanup_exit(1);
 }
 void cmdline_error(char *p, ...)
@@ -65,13 +88,15 @@ void cmdline_error(char *p, ...)
 }
 
 HANDLE inhandle, outhandle, errhandle;
+struct handle *stdin_handle, *stdout_handle, *stderr_handle;
 DWORD orig_console_mode;
+int connopen;
 
 WSAEVENT netevent;
 
 static Backend *back;
 static void *backhandle;
-static Config cfg;
+static Conf *conf;
 
 int term_ldisc(Terminal *term, int mode)
 {
@@ -94,97 +119,43 @@ void ldisc_update(void *frontend, int echo, int edit)
     SetConsoleMode(inhandle, mode);
 }
 
-struct input_data {
-    DWORD len;
-    char buffer[4096];
-    HANDLE event, eventback;
-};
+char *get_ttymode(void *frontend, const char *mode) { return NULL; }
 
-static DWORD WINAPI stdin_read_thread(void *param)
+int from_backend(void *frontend_handle, int is_stderr,
+                const char *data, int len)
 {
-    struct input_data *idata = (struct input_data *) param;
-    HANDLE inhandle;
-
-    inhandle = GetStdHandle(STD_INPUT_HANDLE);
-
-    while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
-                   &idata->len, NULL) && idata->len > 0) {
-       SetEvent(idata->event);
-       WaitForSingleObject(idata->eventback, INFINITE);
+    if (is_stderr) {
+       handle_write(stderr_handle, data, len);
+    } else {
+       handle_write(stdout_handle, data, len);
     }
 
-    idata->len = 0;
-    SetEvent(idata->event);
-
-    return 0;
+    return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
 }
 
-struct output_data {
-    DWORD len, lenwritten;
-    int writeret;
-    char *buffer;
-    int is_stderr, done;
-    HANDLE event, eventback;
-    int busy;
-};
-
-static DWORD WINAPI stdout_write_thread(void *param)
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
 {
-    struct output_data *odata = (struct output_data *) param;
-    HANDLE outhandle, errhandle;
-
-    outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
-    errhandle = GetStdHandle(STD_ERROR_HANDLE);
-
-    while (1) {
-       WaitForSingleObject(odata->eventback, INFINITE);
-       if (odata->done)
-           break;
-       odata->writeret =
-           WriteFile(odata->is_stderr ? errhandle : outhandle,
-                     odata->buffer, odata->len, &odata->lenwritten, NULL);
-       SetEvent(odata->event);
-    }
-
-    return 0;
+    /*
+     * No "untrusted" output should get here (the way the code is
+     * currently, it's all diverted by FLAG_STDERR).
+     */
+    assert(!"Unexpected call to from_backend_untrusted()");
+    return 0; /* not reached */
 }
 
-bufchain stdout_data, stderr_data;
-struct output_data odata, edata;
-
-void try_output(int is_stderr)
+int from_backend_eof(void *frontend_handle)
 {
-    struct output_data *data = (is_stderr ? &edata : &odata);
-    void *senddata;
-    int sendlen;
-
-    if (!data->busy) {
-       bufchain_prefix(is_stderr ? &stderr_data : &stdout_data,
-                       &senddata, &sendlen);
-       data->buffer = senddata;
-       data->len = sendlen;
-       SetEvent(data->eventback);
-       data->busy = 1;
-    }
+    handle_write_eof(stdout_handle);
+    return FALSE;   /* do not respond to incoming EOF with outgoing */
 }
 
-int from_backend(void *frontend_handle, int is_stderr,
-                const char *data, int len)
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 {
-    int osize, esize;
-
-    if (is_stderr) {
-       bufchain_add(&stderr_data, data, len);
-       try_output(1);
-    } else {
-       bufchain_add(&stdout_data, data, len);
-       try_output(0);
-    }
-
-    osize = bufchain_size(&stdout_data);
-    esize = bufchain_size(&stderr_data);
-
-    return osize + esize;
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+       ret = console_get_userpass_input(p, in, inlen);
+    return ret;
 }
 
 static DWORD main_thread_id;
@@ -210,14 +181,14 @@ static void usage(void)
     printf("Usage: plink [options] [user@]host [command]\n");
     printf("       (\"host\" can also be a PuTTY saved session name)\n");
     printf("Options:\n");
-    printf("  -V        print version information\n");
+    printf("  -V        print version information and exit\n");
+    printf("  -pgpfp    print PGP key fingerprints and exit\n");
     printf("  -v        show verbose messages\n");
     printf("  -load sessname  Load settings from saved session\n");
-    printf("  -ssh -telnet -rlogin -raw\n");
+    printf("  -ssh -telnet -rlogin -raw -serial\n");
     printf("            force use of a particular protocol\n");
     printf("  -P port   connect to specified port\n");
     printf("  -l user   connect with specified username\n");
-    printf("  -m file   read remote command(s) from file\n");
     printf("  -batch    disable all interactive prompts\n");
     printf("The following options only apply to SSH connections:\n");
     printf("  -pw passw login with specified password\n");
@@ -234,8 +205,15 @@ static void usage(void)
     printf("  -4 -6     force use of IPv4 or IPv6\n");
     printf("  -C        enable compression\n");
     printf("  -i key    private key file for authentication\n");
+    printf("  -noagent  disable use of Pageant\n");
+    printf("  -agent    enable use of Pageant\n");
+    printf("  -m file   read remote command(s) from file\n");
     printf("  -s        remote command is an SSH subsystem (SSH-2 only)\n");
     printf("  -N        don't start a shell/command (SSH-2 only)\n");
+    printf("  -nc host:port\n");
+    printf("            open tunnel in place of session (SSH-2 only)\n");
+    printf("  -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
+    printf("            Specify the serial configuration (serial only)\n");
     exit(1);
 }
 
@@ -265,24 +243,66 @@ char *do_select(SOCKET skt, int startup)
     return NULL;
 }
 
+int stdin_gotdata(struct handle *h, void *data, int len)
+{
+    if (len < 0) {
+       /*
+        * Special case: report read error.
+        */
+       char buf[4096];
+       FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 0,
+                     buf, lenof(buf), NULL);
+       buf[lenof(buf)-1] = '\0';
+       if (buf[strlen(buf)-1] == '\n')
+           buf[strlen(buf)-1] = '\0';
+       fprintf(stderr, "Unable to read from standard input: %s\n", buf);
+       cleanup_exit(0);
+    }
+    noise_ultralight(len);
+    if (connopen && back->connected(backhandle)) {
+       if (len > 0) {
+           return back->send(backhandle, data, len);
+       } else {
+           back->special(backhandle, TS_EOF);
+           return 0;
+       }
+    } else
+       return 0;
+}
+
+void stdouterr_sent(struct handle *h, int new_backlog)
+{
+    if (new_backlog < 0) {
+       /*
+        * Special case: report write error.
+        */
+       char buf[4096];
+       FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 0,
+                     buf, lenof(buf), NULL);
+       buf[lenof(buf)-1] = '\0';
+       if (buf[strlen(buf)-1] == '\n')
+           buf[strlen(buf)-1] = '\0';
+       fprintf(stderr, "Unable to write to standard %s: %s\n",
+               (h == stdout_handle ? "output" : "error"), buf);
+       cleanup_exit(0);
+    }
+    if (connopen && back->connected(backhandle)) {
+       back->unthrottle(backhandle, (handle_backlog(stdout_handle) +
+                                     handle_backlog(stderr_handle)));
+    }
+}
+
 int main(int argc, char **argv)
 {
-    WSAEVENT stdinevent, stdoutevent, stderrevent;
-    HANDLE handles[4];
-    DWORD in_threadid, out_threadid, err_threadid;
-    struct input_data idata;
-    int reading = FALSE;
     int sending;
     int portnumber = -1;
     SOCKET *sklist;
     int skcount, sksize;
-    int connopen;
     int exitcode;
     int errors;
+    int got_host = FALSE;
     int use_subsystem = 0;
-    long now, next;
-
-    ssh_get_line = console_get_line;
+    unsigned long now, next, then;
 
     sklist = NULL;
     skcount = sksize = 0;
@@ -297,25 +317,24 @@ int main(int argc, char **argv)
     /*
      * Process the command line.
      */
-    do_defaults(NULL, &cfg);
+    conf = conf_new();
+    do_defaults(NULL, conf);
     loaded_session = FALSE;
-    default_protocol = cfg.protocol;
-    default_port = cfg.port;
+    default_protocol = conf_get_int(conf, CONF_protocol);
+    default_port = conf_get_int(conf, CONF_port);
     errors = 0;
     {
        /*
         * Override the default protocol if PLINK_PROTOCOL is set.
         */
        char *p = getenv("PLINK_PROTOCOL");
-       int i;
        if (p) {
-           for (i = 0; backends[i].backend != NULL; i++) {
-               if (!strcmp(backends[i].name, p)) {
-                   default_protocol = cfg.protocol = backends[i].protocol;
-                   default_port = cfg.port =
-                       backends[i].backend->default_port;
-                   break;
-               }
+           const Backend *b = backend_from_name(p);
+           if (b) {
+               default_protocol = b->protocol;
+               default_port = b->default_port;
+               conf_set_int(conf, CONF_protocol, default_protocol);
+               conf_set_int(conf, CONF_port, default_port);
            }
        }
     }
@@ -323,7 +342,7 @@ int main(int argc, char **argv)
        char *p = *++argv;
        if (*p == '-') {
            int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
-                                           1, &cfg);
+                                           1, conf);
            if (ret == -2) {
                fprintf(stderr,
                        "plink: option \"%s\" requires an argument\n", p);
@@ -335,16 +354,21 @@ int main(int argc, char **argv)
            } else if (!strcmp(p, "-batch")) {
                console_batch_mode = 1;
            } else if (!strcmp(p, "-s")) {
-               /* Save status to write to cfg later. */
+               /* Save status to write to conf later. */
                use_subsystem = 1;
-           } else if (!strcmp(p, "-V")) {
+           } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
                 version();
+           } else if (!strcmp(p, "--help")) {
+                usage();
+            } else if (!strcmp(p, "-pgpfp")) {
+                pgp_fingerprints();
+                exit(1);
            } else {
                fprintf(stderr, "plink: unknown option \"%s\"\n", p);
                errors = 1;
            }
        } else if (*p) {
-           if (!*cfg.host) {
+           if (!conf_launchable(conf) || !(got_host || loaded_session)) {
                char *q = p;
                /*
                 * If the hostname starts with "telnet:", set the
@@ -357,7 +381,7 @@ int main(int argc, char **argv)
                    q += 7;
                    if (q[0] == '/' && q[1] == '/')
                        q += 2;
-                   cfg.protocol = PROT_TELNET;
+                   conf_set_int(conf, CONF_protocol, PROT_TELNET);
                    p = q;
                    while (*p && *p != ':' && *p != '/')
                        p++;
@@ -365,11 +389,11 @@ int main(int argc, char **argv)
                    if (*p)
                        *p++ = '\0';
                    if (c == ':')
-                       cfg.port = atoi(p);
+                       conf_set_int(conf, CONF_port, atoi(p));
                    else
-                       cfg.port = -1;
-                   strncpy(cfg.host, q, sizeof(cfg.host) - 1);
-                   cfg.host[sizeof(cfg.host) - 1] = '\0';
+                       conf_set_int(conf, CONF_port, -1);
+                   conf_set_str(conf, CONF_host, q);
+                   got_host = TRUE;
                } else {
                    char *r, *user, *host;
                    /*
@@ -379,19 +403,16 @@ int main(int argc, char **argv)
                     */
                    r = strchr(p, ',');
                    if (r) {
-                       int i, j;
-                       for (i = 0; backends[i].backend != NULL; i++) {
-                           j = strlen(backends[i].name);
-                           if (j == r - p &&
-                               !memcmp(backends[i].name, p, j)) {
-                               default_protocol = cfg.protocol =
-                                   backends[i].protocol;
-                               portnumber =
-                                   backends[i].backend->default_port;
-                               p = r + 1;
-                               break;
-                           }
+                       const Backend *b;
+                       *r = '\0';
+                       b = backend_from_name(p);
+                       if (b) {
+                           default_protocol = b->protocol;
+                           conf_set_int(conf, CONF_protocol,
+                                        default_protocol);
+                           portnumber = b->default_port;
                        }
+                       p = r + 1;
                    }
 
                    /*
@@ -415,26 +436,24 @@ int main(int argc, char **argv)
                     * same name as the hostname.
                     */
                    {
-                       Config cfg2;
-                       do_defaults(host, &cfg2);
-                       if (loaded_session || cfg2.host[0] == '\0') {
+                       Conf *conf2 = conf_new();
+                       do_defaults(host, conf2);
+                       if (loaded_session || !conf_launchable(conf2)) {
                            /* No settings for this host; use defaults */
                            /* (or session was already loaded with -load) */
-                           strncpy(cfg.host, host, sizeof(cfg.host) - 1);
-                           cfg.host[sizeof(cfg.host) - 1] = '\0';
-                           cfg.port = default_port;
+                           conf_set_str(conf, CONF_host, host);
+                           conf_set_int(conf, CONF_port, default_port);
+                           got_host = TRUE;
                        } else {
-                           cfg = cfg2;
-                           /* Ick: patch up internal pointer after copy */
-                           cfg.remote_cmd_ptr = cfg.remote_cmd;
+                           conf_copy_into(conf, conf2);
+                           loaded_session = TRUE;
                        }
+                       conf_free(conf2);
                    }
 
                    if (user) {
                        /* Patch in specified username. */
-                       strncpy(cfg.username, user,
-                               sizeof(cfg.username) - 1);
-                       cfg.username[sizeof(cfg.username) - 1] = '\0';
+                       conf_set_str(conf, CONF_username, user);
                    }
 
                }
@@ -461,9 +480,9 @@ int main(int argc, char **argv)
                }
                if (cmdlen) command[--cmdlen]='\0';
                                       /* change trailing blank to NUL */
-               cfg.remote_cmd_ptr = command;
-               cfg.remote_cmd_ptr2 = NULL;
-               cfg.nopty = TRUE;      /* command => no terminal */
+               conf_set_str(conf, CONF_remote_cmd, command);
+               conf_set_str(conf, CONF_remote_cmd2, "");
+               conf_set_int(conf, CONF_nopty, TRUE);  /* command => no tty */
 
                break;                 /* done with cmdline */
            }
@@ -473,89 +492,89 @@ int main(int argc, char **argv)
     if (errors)
        return 1;
 
-    if (!*cfg.host) {
+    if (!conf_launchable(conf) || !(got_host || loaded_session)) {
        usage();
     }
 
     /*
-     * Trim leading whitespace off the hostname if it's there.
+     * Muck about with the hostname in various ways.
      */
     {
-       int space = strspn(cfg.host, " \t");
-       memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
-    }
+       char *hostbuf = dupstr(conf_get_str(conf, CONF_host));
+       char *host = hostbuf;
+       char *p, *q;
+
+       /*
+        * Trim leading whitespace.
+        */
+       host += strspn(host, " \t");
 
-    /* See if host is of the form user@host */
-    if (cfg.host[0] != '\0') {
-       char *atsign = strrchr(cfg.host, '@');
-       /* Make sure we're not overflowing the user field */
-       if (atsign) {
-           if (atsign - cfg.host < sizeof cfg.username) {
-               strncpy(cfg.username, cfg.host, atsign - cfg.host);
-               cfg.username[atsign - cfg.host] = '\0';
+       /*
+        * See if host is of the form user@host, and separate out
+        * the username if so.
+        */
+       if (host[0] != '\0') {
+           char *atsign = strrchr(host, '@');
+           if (atsign) {
+               *atsign = '\0';
+               conf_set_str(conf, CONF_username, host);
+               host = atsign + 1;
            }
-           memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
        }
+
+       /*
+        * Trim off a colon suffix if it's there.
+        */
+       host[strcspn(host, ":")] = '\0';
+
+       /*
+        * Remove any remaining whitespace.
+        */
+       p = hostbuf;
+       q = host;
+       while (*q) {
+           if (*q != ' ' && *q != '\t')
+               *p++ = *q;
+           q++;
+       }
+       *p = '\0';
+
+       conf_set_str(conf, CONF_host, hostbuf);
+       sfree(hostbuf);
     }
 
     /*
      * Perform command-line overrides on session configuration.
      */
-    cmdline_run_saved(&cfg);
+    cmdline_run_saved(conf);
 
     /*
      * Apply subsystem status.
      */
     if (use_subsystem)
-       cfg.ssh_subsys = TRUE;
+        conf_set_int(conf, CONF_ssh_subsys, TRUE);
 
-    /*
-     * Trim a colon suffix off the hostname if it's there.
-     */
-    cfg.host[strcspn(cfg.host, ":")] = '\0';
-
-    /*
-     * Remove any remaining whitespace from the hostname.
-     */
-    {
-       int p1 = 0, p2 = 0;
-       while (cfg.host[p2] != '\0') {
-           if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
-               cfg.host[p1] = cfg.host[p2];
-               p1++;
-           }
-           p2++;
-       }
-       cfg.host[p1] = '\0';
-    }
-
-    if (!*cfg.remote_cmd_ptr)
+    if (!*conf_get_str(conf, CONF_remote_cmd) &&
+       !*conf_get_str(conf, CONF_remote_cmd2) &&
+       !*conf_get_str(conf, CONF_ssh_nc_host))
        flags |= FLAG_INTERACTIVE;
 
     /*
      * Select protocol. This is farmed out into a table in a
      * separate file to enable an ssh-free variant.
      */
-    {
-       int i;
-       back = NULL;
-       for (i = 0; backends[i].backend != NULL; i++)
-           if (backends[i].protocol == cfg.protocol) {
-               back = backends[i].backend;
-               break;
-           }
-       if (back == NULL) {
-           fprintf(stderr,
-                   "Internal fault: Unsupported protocol found\n");
-           return 1;
-       }
+    back = backend_from_proto(conf_get_int(conf, CONF_protocol));
+    if (back == NULL) {
+       fprintf(stderr,
+               "Internal fault: Unsupported protocol found\n");
+       return 1;
     }
 
     /*
      * Select port.
      */
     if (portnumber != -1)
-       cfg.port = portnumber;
+       conf_set_int(conf, CONF_port, portnumber);
 
     sk_init();
     if (p_WSAEventSelect == NULL) {
@@ -563,6 +582,9 @@ int main(int argc, char **argv)
        return 1;
     }
 
+    logctx = log_init(NULL, conf);
+    console_provide_logctx(logctx);
+
     /*
      * Start up the connection.
      */
@@ -571,112 +593,80 @@ int main(int argc, char **argv)
        const char *error;
        char *realhost;
        /* nodelay is only useful if stdin is a character device (console) */
-       int nodelay = cfg.tcp_nodelay &&
+       int nodelay = conf_get_int(conf, CONF_tcp_nodelay) &&
            (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
 
-       error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port,
-                          &realhost, nodelay, cfg.tcp_keepalives);
+       error = back->init(NULL, &backhandle, conf,
+                          conf_get_str(conf, CONF_host),
+                          conf_get_int(conf, CONF_port),
+                          &realhost, nodelay,
+                          conf_get_int(conf, CONF_tcp_keepalives));
        if (error) {
            fprintf(stderr, "Unable to open connection:\n%s", error);
            return 1;
        }
-       logctx = log_init(NULL, &cfg);
        back->provide_logctx(backhandle, logctx);
-       console_provide_logctx(logctx);
        sfree(realhost);
     }
     connopen = 1;
 
-    stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
-    stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
-    stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
-
     inhandle = GetStdHandle(STD_INPUT_HANDLE);
     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
     errhandle = GetStdHandle(STD_ERROR_HANDLE);
-    GetConsoleMode(inhandle, &orig_console_mode);
-    SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
-
-    main_thread_id = GetCurrentThreadId();
 
     /*
      * Turn off ECHO and LINE input modes. We don't care if this
      * call fails, because we know we aren't necessarily running in
      * a console.
      */
-    handles[0] = netevent;
-    handles[1] = stdinevent;
-    handles[2] = stdoutevent;
-    handles[3] = stderrevent;
-    sending = FALSE;
+    GetConsoleMode(inhandle, &orig_console_mode);
+    SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
 
     /*
-     * Create spare threads to write to stdout and stderr, so we
-     * can arrange asynchronous writes.
+     * Pass the output handles to the handle-handling subsystem.
+     * (The input one we leave until we're through the
+     * authentication process.)
      */
-    odata.event = stdoutevent;
-    odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
-    odata.is_stderr = 0;
-    odata.busy = odata.done = 0;
-    if (!CreateThread(NULL, 0, stdout_write_thread,
-                     &odata, 0, &out_threadid)) {
-       fprintf(stderr, "Unable to create output thread\n");
-       cleanup_exit(1);
-    }
-    edata.event = stderrevent;
-    edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
-    edata.is_stderr = 1;
-    edata.busy = edata.done = 0;
-    if (!CreateThread(NULL, 0, stdout_write_thread,
-                     &edata, 0, &err_threadid)) {
-       fprintf(stderr, "Unable to create error output thread\n");
-       cleanup_exit(1);
-    }
+    stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
+    stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
+
+    main_thread_id = GetCurrentThreadId();
+
+    sending = FALSE;
 
     now = GETTICKCOUNT();
 
     while (1) {
+       int nhandles;
+       HANDLE *handles;        
        int n;
        DWORD ticks;
 
        if (!sending && back->sendok(backhandle)) {
-           /*
-            * Create a separate thread to read from stdin. This is
-            * a total pain, but I can't find another way to do it:
-            *
-            *  - an overlapped ReadFile or ReadFileEx just doesn't
-            *    happen; we get failure from ReadFileEx, and
-            *    ReadFile blocks despite being given an OVERLAPPED
-            *    structure. Perhaps we can't do overlapped reads
-            *    on consoles. WHY THE HELL NOT?
-            * 
-            *  - WaitForMultipleObjects(netevent, console) doesn't
-            *    work, because it signals the console when
-            *    _anything_ happens, including mouse motions and
-            *    other things that don't cause data to be readable
-            *    - so we're back to ReadFile blocking.
-            */
-           idata.event = stdinevent;
-           idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
-           if (!CreateThread(NULL, 0, stdin_read_thread,
-                             &idata, 0, &in_threadid)) {
-               fprintf(stderr, "Unable to create input thread\n");
-               cleanup_exit(1);
-           }
+           stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,
+                                           0);
            sending = TRUE;
-           reading = TRUE;
        }
 
        if (run_timers(now, &next)) {
-           ticks = next - GETTICKCOUNT();
-           if (ticks < 0) ticks = 0;  /* just in case */
+           then = now;
+           now = GETTICKCOUNT();
+           if (now - then > next - then)
+               ticks = 0;
+           else
+               ticks = next - now;
        } else {
            ticks = INFINITE;
        }
 
-       n = MsgWaitForMultipleObjects(4, handles, FALSE, ticks,
+       handles = handle_get_events(&nhandles);
+       handles = sresize(handles, nhandles+1, HANDLE);
+       handles[nhandles] = netevent;
+       n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,
                                      QS_POSTMESSAGE);
-       if (n == WAIT_OBJECT_0 + 0) {
+       if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
+           handle_got_event(handles[n - WAIT_OBJECT_0]);
+       } else if (n == WAIT_OBJECT_0 + nhandles) {
            WSANETWORKEVENTS things;
            SOCKET socket;
            extern SOCKET first_socket(int *), next_socket(int *);
@@ -737,43 +727,7 @@ int main(int argc, char **argv)
                         }
                }
            }
-       } else if (n == WAIT_OBJECT_0 + 1) {
-           reading = 0;
-           noise_ultralight(idata.len);
-           if (connopen && back->socket(backhandle) != NULL) {
-               if (idata.len > 0) {
-                   back->send(backhandle, idata.buffer, idata.len);
-               } else {
-                   back->special(backhandle, TS_EOF);
-               }
-           }
-       } else if (n == WAIT_OBJECT_0 + 2) {
-           odata.busy = 0;
-           if (!odata.writeret) {
-               fprintf(stderr, "Unable to write to standard output\n");
-               cleanup_exit(0);
-           }
-           bufchain_consume(&stdout_data, odata.lenwritten);
-           if (bufchain_size(&stdout_data) > 0)
-               try_output(0);
-           if (connopen && back->socket(backhandle) != NULL) {
-               back->unthrottle(backhandle, bufchain_size(&stdout_data) +
-                                bufchain_size(&stderr_data));
-           }
-       } else if (n == WAIT_OBJECT_0 + 3) {
-           edata.busy = 0;
-           if (!edata.writeret) {
-               fprintf(stderr, "Unable to write to standard output\n");
-               cleanup_exit(0);
-           }
-           bufchain_consume(&stderr_data, edata.lenwritten);
-           if (bufchain_size(&stderr_data) > 0)
-               try_output(1);
-           if (connopen && back->socket(backhandle) != NULL) {
-               back->unthrottle(backhandle, bufchain_size(&stdout_data) +
-                                bufchain_size(&stderr_data));
-           }
-       } else if (n == WAIT_OBJECT_0 + 4) {
+       } else if (n == WAIT_OBJECT_0 + nhandles + 1) {
            MSG msg;
            while (PeekMessage(&msg, INVALID_HANDLE_VALUE,
                               WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,
@@ -790,13 +744,13 @@ int main(int argc, char **argv)
            now = GETTICKCOUNT();
        }
 
-       if (!reading && back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
-           SetEvent(idata.eventback);
-           reading = 1;
-       }
-       if ((!connopen || back->socket(backhandle) == NULL) &&
-           bufchain_size(&stdout_data) == 0 &&
-           bufchain_size(&stderr_data) == 0)
+       sfree(handles);
+
+       if (sending)
+           handle_unthrottle(stdin_handle, back->sendbuffer(backhandle));
+
+       if ((!connopen || !back->connected(backhandle)) &&
+           handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
            break;                     /* we closed the connection */
     }
     exitcode = back->exitcode(backhandle);