Post-release destabilisation! Completely remove the struct type
[u/mdw/putty] / unix / uxplink.c
index cbfdef7..4d36e50 100644 (file)
 
 #define MAX_STDIN_BACKLOG 4096
 
+void *logctx;
+
+static struct termios orig_termios;
+
 void fatalbox(char *p, ...)
 {
+    struct termios cf;
     va_list ap;
+    premsg(&cf);
     fprintf(stderr, "FATAL ERROR: ");
     va_start(ap, p);
     vfprintf(stderr, p, ap);
     va_end(ap);
     fputc('\n', stderr);
+    postmsg(&cf);
+    if (logctx) {
+        log_free(logctx);
+        logctx = NULL;
+    }
     cleanup_exit(1);
 }
 void modalfatalbox(char *p, ...)
 {
+    struct termios cf;
     va_list ap;
+    premsg(&cf);
     fprintf(stderr, "FATAL ERROR: ");
     va_start(ap, p);
     vfprintf(stderr, p, ap);
     va_end(ap);
     fputc('\n', stderr);
+    postmsg(&cf);
+    if (logctx) {
+        log_free(logctx);
+        logctx = NULL;
+    }
     cleanup_exit(1);
 }
 void connection_fatal(void *frontend, char *p, ...)
 {
+    struct termios cf;
     va_list ap;
+    premsg(&cf);
     fprintf(stderr, "FATAL ERROR: ");
     va_start(ap, p);
     vfprintf(stderr, p, ap);
     va_end(ap);
     fputc('\n', stderr);
+    postmsg(&cf);
+    if (logctx) {
+        log_free(logctx);
+        logctx = NULL;
+    }
     cleanup_exit(1);
 }
 void cmdline_error(char *p, ...)
 {
+    struct termios cf;
     va_list ap;
+    premsg(&cf);
     fprintf(stderr, "plink: ");
     va_start(ap, p);
     vfprintf(stderr, p, ap);
     va_end(ap);
     fputc('\n', stderr);
+    postmsg(&cf);
     exit(1);
 }
 
-static int local_tty = 0; /* do we have a local tty? */
-static struct termios orig_termios;
+static int local_tty = FALSE; /* do we have a local tty? */
 
 static Backend *back;
 static void *backhandle;
-static Config cfg;
+static Conf *conf;
 
 /*
  * Default settings that are specific to pterm.
@@ -80,8 +107,8 @@ char *platform_default_s(const char *name)
 {
     if (!strcmp(name, "TermType"))
        return dupstr(getenv("TERM"));
-    if (!strcmp(name, "UserName"))
-       return get_username();
+     if (!strcmp(name, "UserName"))
+       return get_username();
     if (!strcmp(name, "SerialLine"))
        return dupstr("/dev/ttyS0");
     return NULL;
@@ -92,7 +119,7 @@ int platform_default_i(const char *name, int def)
     if (!strcmp(name, "TermWidth") ||
        !strcmp(name, "TermHeight")) {
        struct winsize size;
-       if (ioctl(0, TIOCGWINSZ, (void *)&size) >= 0)
+       if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0)
            return (!strcmp(name, "TermWidth") ? size.ws_col : size.ws_row);
     }
     return def;
@@ -166,7 +193,7 @@ void ldisc_update(void *frontend, int echo, int edit)
      */
     mode.c_iflag = (mode.c_iflag | INPCK | PARMRK) & ~IGNPAR;
 
-    tcsetattr(0, TCSANOW, &mode);
+    tcsetattr(STDIN_FILENO, TCSANOW, &mode);
 }
 
 /* Helper function to extract a special character from a termios. */
@@ -352,48 +379,49 @@ char *get_ttymode(void *frontend, const char *mode)
 void cleanup_termios(void)
 {
     if (local_tty)
-       tcsetattr(0, TCSANOW, &orig_termios);
+       tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
 }
 
 bufchain stdout_data, stderr_data;
 
-void try_output(int is_stderr)
+int try_output(int is_stderr)
 {
     bufchain *chain = (is_stderr ? &stderr_data : &stdout_data);
-    int fd = (is_stderr ? 2 : 1);
+    int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO);
     void *senddata;
-    int sendlen, ret;
+    int sendlen, ret, fl;
 
     if (bufchain_size(chain) == 0)
-        return;
-
-    bufchain_prefix(chain, &senddata, &sendlen);
-    ret = write(fd, senddata, sendlen);
-    if (ret > 0)
-       bufchain_consume(chain, ret);
-    else if (ret < 0) {
+        return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
+
+    fl = fcntl(fd, F_GETFL);
+    if (fl != -1 && !(fl & O_NONBLOCK))
+       fcntl(fd, F_SETFL, fl | O_NONBLOCK);
+    do {
+       bufchain_prefix(chain, &senddata, &sendlen);
+       ret = write(fd, senddata, sendlen);
+       if (ret > 0)
+           bufchain_consume(chain, ret);
+    } while (ret == sendlen && bufchain_size(chain) != 0);
+    if (fl != -1 && !(fl & O_NONBLOCK))
+       fcntl(fd, F_SETFL, fl);
+    if (ret < 0 && errno != EAGAIN) {
        perror(is_stderr ? "stderr: write" : "stdout: write");
        exit(1);
     }
+    return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
 }
 
 int from_backend(void *frontend_handle, int is_stderr,
                 const char *data, int len)
 {
-    int osize, esize;
-
     if (is_stderr) {
        bufchain_add(&stderr_data, data, len);
-       try_output(1);
+       return try_output(TRUE);
     } else {
        bufchain_add(&stdout_data, data, len);
-       try_output(0);
+       return try_output(FALSE);
     }
-
-    osize = bufchain_size(&stdout_data);
-    esize = bufchain_size(&stderr_data);
-
-    return osize + esize;
 }
 
 int from_backend_untrusted(void *frontend_handle, const char *data, int len)
@@ -418,9 +446,9 @@ int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 /*
  * Handle data from a local tty in PARMRK format.
  */
-static void from_tty(void *buf, unsigned len)
+static void from_tty(void *vbuf, unsigned len)
 {
-    char *p, *q, *end;
+    char *p, *q, *end, *buf = vbuf;
     static enum {NORMAL, FF, FF00} state = NORMAL;
 
     p = buf; end = buf + len;
@@ -485,7 +513,8 @@ int signalpipe[2];
 
 void sigwinch(int signum)
 {
-    write(signalpipe[1], "x", 1);
+    if (write(signalpipe[1], "x", 1) <= 0)
+       /* not much we can do about it */;
 }
 
 /*
@@ -509,7 +538,7 @@ static void usage(void)
     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");
@@ -536,6 +565,8 @@ static void usage(void)
     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);
 }
 
@@ -556,7 +587,7 @@ int main(int argc, char **argv)
     int exitcode;
     int errors;
     int use_subsystem = 0;
-    void *ldisc, *logctx;
+    int got_host = FALSE;
     long now;
 
     fdlist = NULL;
@@ -568,29 +599,30 @@ int main(int argc, char **argv)
     default_protocol = PROT_SSH;
     default_port = 22;
 
-    flags = FLAG_STDERR;
+    flags = FLAG_STDERR | FLAG_STDERR_TTY;
+
+    stderr_tty_init();
     /*
      * 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);
            }
        }
     }
@@ -598,7 +630,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);
@@ -610,7 +642,7 @@ 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")) {
                 version();
@@ -631,7 +663,7 @@ int main(int argc, char **argv)
                errors = 1;
            }
        } else if (*p) {
-           if (!cfg_launchable(&cfg)) {
+           if (!conf_launchable(conf) || !(got_host || loaded_session)) {
                char *q = p;
 
                /*
@@ -645,7 +677,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++;
@@ -653,11 +685,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;
                    /*
@@ -667,19 +699,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;
                    }
 
                    /*
@@ -703,24 +732,24 @@ int main(int argc, char **argv)
                     * same name as the hostname.
                     */
                    {
-                       Config cfg2;
-                       do_defaults(host, &cfg2);
-                       if (loaded_session || !cfg_launchable(&cfg2)) {
+                       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;
+                           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);
                    }
 
                }
@@ -747,9 +776,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 */
            }
@@ -759,89 +788,89 @@ int main(int argc, char **argv)
     if (errors)
        return 1;
 
-    if (!cfg_launchable(&cfg)) {
+    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;
-
-    /*
-     * Trim a colon suffix off the hostname if it's there.
-     */
-    cfg.host[strcspn(cfg.host, ":")] = '\0';
+        conf_set_int(conf, CONF_ssh_subsys, TRUE);
 
-    /*
-     * 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 && !*cfg.remote_cmd && !*cfg.ssh_nc_host)
+    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);
 
     /*
      * Set up the pipe we'll use to tell us about SIGWINCH.
@@ -856,24 +885,38 @@ int main(int argc, char **argv)
     uxsel_init();
 
     /*
+     * Unix Plink doesn't provide any way to add forwardings after the
+     * connection is set up, so if there are none now, we can safely set
+     * the "simple" flag.
+     */
+    if (conf_get_int(conf, CONF_protocol) == PROT_SSH &&
+       !conf_get_int(conf, CONF_x11_forward) &&
+       !conf_get_int(conf, CONF_agentfwd) &&
+       !conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
+       conf_set_int(conf, CONF_ssh_simple, TRUE);
+
+    /*
      * Start up the connection.
      */
-    logctx = log_init(NULL, &cfg);
+    logctx = log_init(NULL, conf);
     console_provide_logctx(logctx);
     {
        const char *error;
        char *realhost;
        /* nodelay is only useful if stdin is a terminal device */
-       int nodelay = cfg.tcp_nodelay && isatty(0);
+       int nodelay = conf_get_int(conf, CONF_tcp_nodelay) && isatty(0);
 
-       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\n", error);
            return 1;
        }
        back->provide_logctx(backhandle, logctx);
-       ldisc = ldisc_create(&cfg, NULL, back, backhandle, NULL);
+       ldisc_create(conf, NULL, back, backhandle, NULL);
        sfree(realhost);
     }
     connopen = 1;
@@ -883,7 +926,7 @@ int main(int argc, char **argv)
      * fails, because we know we aren't necessarily running in a
      * console.
      */
-    local_tty = (tcgetattr(0, &orig_termios) == 0);
+    local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0);
     atexit(cleanup_termios);
     ldisc_update(NULL, 1, 1);
     sending = FALSE;
@@ -907,17 +950,17 @@ int main(int argc, char **argv)
            back->sendok(backhandle) &&
            back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
            /* If we're OK to send, then try to read from stdin. */
-           FD_SET_MAX(0, maxfd, rset);
+           FD_SET_MAX(STDIN_FILENO, maxfd, rset);
        }
 
        if (bufchain_size(&stdout_data) > 0) {
            /* If we have data for stdout, try to write to stdout. */
-           FD_SET_MAX(1, maxfd, wset);
+           FD_SET_MAX(STDOUT_FILENO, maxfd, wset);
        }
 
        if (bufchain_size(&stderr_data) > 0) {
            /* If we have data for stderr, try to write to stderr. */
-           FD_SET_MAX(2, maxfd, wset);
+           FD_SET_MAX(STDERR_FILENO, maxfd, wset);
        }
 
        /* Count the currently active fds. */
@@ -1009,17 +1052,19 @@ int main(int argc, char **argv)
        if (FD_ISSET(signalpipe[0], &rset)) {
            char c[1];
            struct winsize size;
-           read(signalpipe[0], c, 1); /* ignore its value; it'll be `x' */
+           if (read(signalpipe[0], c, 1) <= 0)
+               /* ignore error */;
+           /* ignore its value; it'll be `x' */
            if (ioctl(0, TIOCGWINSZ, (void *)&size) >= 0)
                back->size(backhandle, size.ws_col, size.ws_row);
        }
 
-       if (FD_ISSET(0, &rset)) {
+       if (FD_ISSET(STDIN_FILENO, &rset)) {
            char buf[4096];
            int ret;
 
            if (connopen && back->connected(backhandle)) {
-               ret = read(0, buf, sizeof(buf));
+               ret = read(STDIN_FILENO, buf, sizeof(buf));
                if (ret < 0) {
                    perror("stdin: read");
                    exit(1);
@@ -1035,12 +1080,12 @@ int main(int argc, char **argv)
            }
        }
 
-       if (FD_ISSET(1, &wset)) {
-           try_output(0);
+       if (FD_ISSET(STDOUT_FILENO, &wset)) {
+           back->unthrottle(backhandle, try_output(FALSE));
        }
 
-       if (FD_ISSET(2, &wset)) {
-           try_output(1);
+       if (FD_ISSET(STDERR_FILENO, &wset)) {
+           back->unthrottle(backhandle, try_output(TRUE));
        }
 
        if ((!connopen || !back->connected(backhandle)) &&