Cleanups from yesterday's destabilisation: lots of stuff in
[u/mdw/putty] / telnet.c
index b2e4999..b8ce417 100644 (file)
--- a/telnet.c
+++ b/telnet.c
@@ -13,6 +13,9 @@
 
 static Socket s = NULL;
 
+static void *frontend;
+static int telnet_term_width, telnet_term_height;
+
 #define        IAC     255                    /* interpret as command: */
 #define        DONT    254                    /* you are not to use option */
 #define        DO      253                    /* please, you use option */
@@ -174,8 +177,11 @@ static struct Opt *opts[] = {
     &o_we_sga, &o_they_sga, NULL
 };
 
-static int echoing = TRUE, editing = TRUE;
+#define TELNET_MAX_BACKLOG 4096
 
+static int echoing = TRUE, editing = TRUE;
+static int activated = FALSE;
+static int telnet_bufsize;
 static int in_synch;
 static int sb_opt, sb_len;
 static char *sb_buf = NULL;
@@ -184,8 +190,10 @@ static int sb_size = 0;
 
 static void c_write1(int c)
 {
+    int backlog;
     char cc = (char) c;
-    from_backend(0, &cc, 1);
+    backlog = from_backend(frontend, 0, &cc, 1);
+    sk_set_frozen(s, backlog > TELNET_MAX_BACKLOG);
 }
 
 static void log_option(char *sender, int cmd, int option)
@@ -205,7 +213,7 @@ static void send_opt(int cmd, int option)
     b[0] = IAC;
     b[1] = cmd;
     b[2] = option;
-    sk_write(s, b, 3);
+    telnet_bufsize = sk_write(s, b, 3);
     log_option("client", cmd, option);
 }
 
@@ -225,7 +233,24 @@ static void option_side_effects(struct Opt *o, int enabled)
        echoing = !enabled;
     else if (o->option == TELOPT_SGA && o->send == DO)
        editing = !enabled;
-    ldisc_send(NULL, 0);              /* cause ldisc to notice the change */
+    ldisc_send(NULL, 0, 0);           /* cause ldisc to notice the change */
+
+    /* Ensure we get the minimum options */
+    if (!activated) {
+       if (o_echo.state == INACTIVE) {
+           o_echo.state = REQUESTED;
+           send_opt(o_echo.send, o_echo.option);
+       }
+       if (o_we_sga.state == INACTIVE) {
+           o_we_sga.state = REQUESTED;
+           send_opt(o_we_sga.send, o_we_sga.option);
+       }
+       if (o_they_sga.state == INACTIVE) {
+           o_they_sga.state = REQUESTED;
+           send_opt(o_they_sga.send, o_they_sga.option);
+       }
+       activated = TRUE;
+    }
 }
 
 static void activate_option(struct Opt *o)
@@ -322,7 +347,7 @@ static void process_subneg(void)
            n = 4 + strlen(cfg.termspeed);
            b[n] = IAC;
            b[n + 1] = SE;
-           sk_write(s, b, n + 2);
+           telnet_bufsize = sk_write(s, b, n + 2);
            logevent("server:\tSB TSPEED SEND");
            sprintf(logbuf, "client:\tSB TSPEED IS %s", cfg.termspeed);
            logevent(logbuf);
@@ -343,7 +368,7 @@ static void process_subneg(void)
                            'a' : cfg.termtype[n]);
            b[n + 4] = IAC;
            b[n + 5] = SE;
-           sk_write(s, b, n + 6);
+           telnet_bufsize = sk_write(s, b, n + 6);
            b[n + 4] = 0;
            logevent("server:\tSB TTYPE SEND");
            sprintf(logbuf, "client:\tSB TTYPE IS %s", b + 4);
@@ -419,7 +444,7 @@ static void process_subneg(void)
            }
            b[n++] = IAC;
            b[n++] = SE;
-           sk_write(s, b, n);
+           telnet_bufsize = sk_write(s, b, n);
            sprintf(logbuf, "client:\tSB %s IS %s", telopt(sb_opt),
                    n == 6 ? "<nothing>" : "<stuff>");
            logevent(logbuf);
@@ -429,9 +454,9 @@ static void process_subneg(void)
 }
 
 static enum {
-    TOPLEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
+    TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
     SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
-} telnet_state = TOPLEVEL;
+} telnet_state = TOP_LEVEL;
 
 static void do_telnet_read(char *buf, int len)
 {
@@ -440,10 +465,10 @@ static void do_telnet_read(char *buf, int len)
        int c = (unsigned char) *buf++;
 
        switch (telnet_state) {
-         case TOPLEVEL:
+         case TOP_LEVEL:
          case SEENCR:
            if (c == NUL && telnet_state == SEENCR)
-               telnet_state = TOPLEVEL;
+               telnet_state = TOP_LEVEL;
            else if (c == IAC)
                telnet_state = SEENIAC;
            else {
@@ -465,7 +490,7 @@ static void do_telnet_read(char *buf, int len)
                if (c == CR)
                    telnet_state = SEENCR;
                else
-                   telnet_state = TOPLEVEL;
+                   telnet_state = TOP_LEVEL;
            }
            break;
          case SEENIAC:
@@ -481,30 +506,30 @@ static void do_telnet_read(char *buf, int len)
                telnet_state = SEENSB;
            else if (c == DM) {
                in_synch = 0;
-               telnet_state = TOPLEVEL;
+               telnet_state = TOP_LEVEL;
            } else {
                /* ignore everything else; print it if it's IAC */
                if (c == IAC) {
                    c_write1(c);
                }
-               telnet_state = TOPLEVEL;
+               telnet_state = TOP_LEVEL;
            }
            break;
          case SEENWILL:
            proc_rec_opt(WILL, c);
-           telnet_state = TOPLEVEL;
+           telnet_state = TOP_LEVEL;
            break;
          case SEENWONT:
            proc_rec_opt(WONT, c);
-           telnet_state = TOPLEVEL;
+           telnet_state = TOP_LEVEL;
            break;
          case SEENDO:
            proc_rec_opt(DO, c);
-           telnet_state = TOPLEVEL;
+           telnet_state = TOP_LEVEL;
            break;
          case SEENDONT:
            proc_rec_opt(DONT, c);
-           telnet_state = TOPLEVEL;
+           telnet_state = TOP_LEVEL;
            break;
          case SEENSB:
            sb_opt = c;
@@ -537,7 +562,7 @@ static void do_telnet_read(char *buf, int len)
                goto subneg_addchar;   /* yes, it's a hack, I know, but... */
            else {
                process_subneg();
-               telnet_state = TOPLEVEL;
+               telnet_state = TOP_LEVEL;
            }
            break;
        }
@@ -547,11 +572,14 @@ static void do_telnet_read(char *buf, int len)
 static int telnet_closing(Plug plug, char *error_msg, int error_code,
                          int calling_back)
 {
-    sk_close(s);
-    s = NULL;
+    if (s) {
+        sk_close(s);
+        s = NULL;
+    }
     if (error_msg) {
        /* A socket error has occurred. */
-       connection_fatal(error_msg);
+       logevent(error_msg);
+       connection_fatal("%s", error_msg);
     }                                 /* Otherwise, the remote side closed the connection normally. */
     return 0;
 }
@@ -564,26 +592,43 @@ static int telnet_receive(Plug plug, int urgent, char *data, int len)
     return 1;
 }
 
+static void telnet_sent(Plug plug, int bufsize)
+{
+    telnet_bufsize = bufsize;
+}
+
 /*
  * Called to set up the Telnet connection.
  *
  * Returns an error message, or NULL on success.
  *
- * Also places the canonical host name into `realhost'.
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
  */
-static char *telnet_init(char *host, int port, char **realhost)
+static char *telnet_init(void *frontend_handle,
+                        char *host, int port, char **realhost, int nodelay)
 {
     static struct plug_function_table fn_table = {
        telnet_closing,
-       telnet_receive
+       telnet_receive,
+       telnet_sent
     }, *fn_table_ptr = &fn_table;
 
     SockAddr addr;
     char *err;
 
+    frontend = frontend_handle;
+    telnet_term_width = cfg.width;
+    telnet_term_height = cfg.height;
+
     /*
      * Try to find host.
      */
+    {
+       char buf[200];
+       sprintf(buf, "Looking up host \"%.170s\"", host);
+       logevent(buf);
+    }
     addr = sk_namelookup(host, realhost);
     if ((err = sk_addr_error(addr)))
        return err;
@@ -594,7 +639,13 @@ static char *telnet_init(char *host, int port, char **realhost)
     /*
      * Open socket.
      */
-    s = sk_new(addr, port, 0, 1, &fn_table_ptr);
+    {
+       char buf[200], addrbuf[100];
+       sk_getaddr(addr, addrbuf, 100);
+       sprintf(buf, "Connecting to %.100s port %d", addrbuf, port);
+       logevent(buf);
+    }
+    s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr);
     if ((err = sk_socket_error(s)))
        return err;
 
@@ -603,12 +654,19 @@ static char *telnet_init(char *host, int port, char **realhost)
     /*
      * Initialise option states.
      */
-    {
+    if (cfg.passive_telnet) {
+       struct Opt **o;
+
+       for (o = opts; *o; o++)
+           if ((*o)->state == REQUESTED)
+               (*o)->state = INACTIVE;
+    } else {
        struct Opt **o;
 
        for (o = opts; *o; o++)
            if ((*o)->state == REQUESTED)
                send_opt((*o)->send, (*o)->option);
+       activated = TRUE;
     }
 
     /*
@@ -622,15 +680,17 @@ static char *telnet_init(char *host, int port, char **realhost)
 /*
  * Called to send data down the Telnet connection.
  */
-static void telnet_send(char *buf, int len)
+static int telnet_send(char *buf, int len)
 {
     char *p;
     static unsigned char iac[2] = { IAC, IAC };
     static unsigned char cr[2] = { CR, NUL };
+#if 0
     static unsigned char nl[2] = { CR, LF };
+#endif
 
     if (s == NULL)
-       return;
+       return 0;
 
     p = buf;
     while (p < buf + len) {
@@ -638,35 +698,49 @@ static void telnet_send(char *buf, int len)
 
        while (iswritable((unsigned char) *p) && p < buf + len)
            p++;
-       sk_write(s, q, p - q);
+       telnet_bufsize = sk_write(s, q, p - q);
 
        while (p < buf + len && !iswritable((unsigned char) *p)) {
-           sk_write(s, (unsigned char) *p == IAC ? iac : nl, 2);
+           telnet_bufsize = 
+               sk_write(s, (unsigned char) *p == IAC ? iac : cr, 2);
            p++;
        }
     }
+
+    return telnet_bufsize;
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int telnet_sendbuffer(void)
+{
+    return telnet_bufsize;
 }
 
 /*
  * Called to set the size of the window from Telnet's POV.
  */
-static void telnet_size(void)
+static void telnet_size(int width, int height)
 {
     unsigned char b[16];
     char logbuf[50];
 
+    telnet_term_width = width;
+    telnet_term_height = height;
+
     if (s == NULL || o_naws.state != ACTIVE)
        return;
     b[0] = IAC;
     b[1] = SB;
     b[2] = TELOPT_NAWS;
-    b[3] = cols >> 8;
-    b[4] = cols & 0xFF;
-    b[5] = rows >> 8;
-    b[6] = rows & 0xFF;
+    b[3] = telnet_term_width >> 8;
+    b[4] = telnet_term_width & 0xFF;
+    b[5] = telnet_term_height >> 8;
+    b[6] = telnet_term_height & 0xFF;
     b[7] = IAC;
     b[8] = SE;
-    sk_write(s, b, 9);
+    telnet_bufsize = sk_write(s, b, 9);
     sprintf(logbuf, "client:\tSB NAWS %d,%d",
            ((unsigned char) b[3] << 8) + (unsigned char) b[4],
            ((unsigned char) b[5] << 8) + (unsigned char) b[6]);
@@ -687,56 +761,59 @@ static void telnet_special(Telnet_Special code)
     switch (code) {
       case TS_AYT:
        b[1] = AYT;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_BRK:
        b[1] = BREAK;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_EC:
        b[1] = EC;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_EL:
        b[1] = EL;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_GA:
        b[1] = GA;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_NOP:
        b[1] = NOP;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_ABORT:
        b[1] = ABORT;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_AO:
        b[1] = AO;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_IP:
        b[1] = IP;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_SUSP:
        b[1] = SUSP;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_EOR:
        b[1] = EOR;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
        break;
       case TS_EOF:
        b[1] = xEOF;
-       sk_write(s, b, 2);
+       telnet_bufsize = sk_write(s, b, 2);
+       break;
+      case TS_EOL:
+       telnet_bufsize = sk_write(s, "\r\n", 2);
        break;
       case TS_SYNCH:
        b[1] = DM;
-       sk_write(s, b, 1);
-       sk_write_oob(s, b + 1, 1);
+       telnet_bufsize = sk_write(s, b, 1);
+       telnet_bufsize = sk_write_oob(s, b + 1, 1);
        break;
       case TS_RECHO:
        if (o_echo.state == INACTIVE || o_echo.state == REALLY_INACTIVE) {
@@ -753,7 +830,7 @@ static void telnet_special(Telnet_Special code)
       case TS_PING:
        if (o_they_sga.state == ACTIVE) {
            b[1] = NOP;
-           sk_write(s, b, 2);
+           telnet_bufsize = sk_write(s, b, 2);
        }
        break;
     }
@@ -769,6 +846,11 @@ static int telnet_sendok(void)
     return 1;
 }
 
+static void telnet_unthrottle(int backlog)
+{
+    sk_set_frozen(s, backlog > TELNET_MAX_BACKLOG);
+}
+
 static int telnet_ldisc(int option)
 {
     if (option == LD_ECHO)
@@ -778,13 +860,22 @@ static int telnet_ldisc(int option)
     return FALSE;
 }
 
+static int telnet_exitcode(void)
+{
+    /* Telnet doesn't transmit exit codes back to the client */
+    return 0;
+}
+
 Backend telnet_backend = {
     telnet_init,
     telnet_send,
+    telnet_sendbuffer,
     telnet_size,
     telnet_special,
     telnet_socket,
+    telnet_exitcode,
     telnet_sendok,
     telnet_ldisc,
+    telnet_unthrottle,
     23
 };