TELNET: improved treatment of options
authorLaurent Rabret <laurent.rabret@orange.com>
Fri, 25 Nov 2011 09:35:34 +0000 (10:35 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 25 Nov 2011 09:46:49 +0000 (10:46 +0100)
1) enables the Window Size option
2) allows the server to enable the echo mode
3) allows an app using libcurl to disable the default binary mode

Signed-off-by: Laurent Rabret
lib/arpa_telnet.h
lib/telnet.c

index 6626928..098d9a9 100644 (file)
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
  * Telnet option defines. Add more here if in need.
  */
 #define CURL_TELOPT_BINARY   0  /* binary 8bit data */
+#define CURL_TELOPT_ECHO     1  /* just echo! */
 #define CURL_TELOPT_SGA      3  /* Suppress Go Ahead */
 #define CURL_TELOPT_EXOPL  255  /* EXtended OPtions List */
 #define CURL_TELOPT_TTYPE   24  /* Terminal TYPE */
+#define CURL_TELOPT_NAWS    31  /* Negotiate About Window Size */
 #define CURL_TELOPT_XDISPLOC 35 /* X DISPlay LOCation */
 
 #define CURL_TELOPT_NEW_ENVIRON 39  /* NEW ENVIRONment variables */
index bbad08d..0b63b12 100644 (file)
@@ -116,10 +116,13 @@ static void printsub(struct SessionHandle *data,
                      int direction, unsigned char *pointer,
                      size_t length);
 static void suboption(struct connectdata *);
+static void sendsuboption(struct connectdata *conn, int option);
 
 static CURLcode telnet_do(struct connectdata *conn, bool *done);
 static CURLcode telnet_done(struct connectdata *conn,
                                  CURLcode, bool premature);
+static CURLcode send_telnet_data(struct connectdata *conn,
+                                 char *buffer, ssize_t nread);
 
 /* For negotiation compliant to RFC 1143 */
 #define CURL_NO          0
@@ -155,9 +158,12 @@ struct TELNET {
   int him[256];
   int himq[256];
   int him_preferred[256];
+  int subnegotiation[256];
   char subopt_ttype[32];             /* Set with suboption TTYPE */
-  char subopt_xdisploc[128];          /* Set with suboption XDISPLOC */
-  struct curl_slist *telnet_vars; /* Environment variables */
+  char subopt_xdisploc[128];         /* Set with suboption XDISPLOC */
+  unsigned short subopt_wsx;         /* Set with suboption NAWS */
+  unsigned short subopt_wsy;         /* Set with suboption NAWS */
+  struct curl_slist *telnet_vars;    /* Environment variables */
 
   /* suboptions */
   unsigned char subbuffer[SUBBUFSIZE];
@@ -249,11 +255,37 @@ CURLcode init_telnet(struct connectdata *conn)
   CURL_SB_CLEAR(tn);
 
   /* Set the options we want by default */
-  tn->us_preferred[CURL_TELOPT_BINARY] = CURL_YES;
   tn->us_preferred[CURL_TELOPT_SGA] = CURL_YES;
-  tn->him_preferred[CURL_TELOPT_BINARY] = CURL_YES;
   tn->him_preferred[CURL_TELOPT_SGA] = CURL_YES;
 
+  /* To be compliant with previous releases of libcurl
+     we enable this option by default. This behaviour
+         can be changed thanks to the "BINARY" option in
+         CURLOPT_TELNETOPTIONS
+  */
+  tn->us_preferred[CURL_TELOPT_BINARY] = CURL_YES;
+  tn->him_preferred[CURL_TELOPT_BINARY] = CURL_YES;
+
+  /* We must allow the server to echo what we sent
+         but it is not necessary to request the server
+         to do so (it might forces the server to close
+         the connection). Hence, we ignore ECHO in the
+         negotiate function
+  */
+  tn->him_preferred[CURL_TELOPT_ECHO] = CURL_YES;
+
+  /* Set the subnegotiation fields to send information
+    just after negotiation passed (do/will)
+
+     Default values are (0,0) initialized by calloc.
+     According to the RFC1013 it is valid:
+     A value equal to zero is acceptable for the width (or height),
+         and means that no character width (or height) is being sent.
+         In this case, the width (or height) that will be assumed by the
+         Telnet server is operating system specific (it will probably be
+         based upon the terminal type information that may have been sent
+         using the TERMINAL TYPE Telnet option). */
+  tn->subnegotiation[CURL_TELOPT_NAWS] = CURL_YES;
   return CURLE_OK;
 }
 
@@ -263,6 +295,9 @@ static void negotiate(struct connectdata *conn)
   struct TELNET *tn = (struct TELNET *) conn->data->state.proto.telnet;
 
   for(i = 0;i < CURL_NTELOPTS;i++) {
+    if(i==CURL_TELOPT_ECHO)
+      continue;
+
     if(tn->us_preferred[i] == CURL_YES)
       set_local_option(conn, i, CURL_YES);
 
@@ -575,6 +610,15 @@ void rec_do(struct connectdata *conn, int option)
     if(tn->us_preferred[option] == CURL_YES) {
       tn->us[option] = CURL_YES;
       send_negotiation(conn, CURL_WILL, option);
+      if(tn->subnegotiation[option] == CURL_YES)
+        /* transmission of data option */
+        sendsuboption(conn, option);
+    }
+    else if(tn->subnegotiation[option] == CURL_YES) {
+      /* send information to achieve this option*/
+      tn->us[option] = CURL_YES;
+      send_negotiation(conn, CURL_WILL, option);
+      sendsuboption(conn, option);
     }
     else
       send_negotiation(conn, CURL_WONT, option);
@@ -602,6 +646,10 @@ void rec_do(struct connectdata *conn, int option)
     switch(tn->usq[option]) {
     case CURL_EMPTY:
       tn->us[option] = CURL_YES;
+      if(tn->subnegotiation[option] == CURL_YES) {
+        /* transmission of data option */
+        sendsuboption(conn, option);
+      }
       break;
     case CURL_OPPOSITE:
       tn->us[option] = CURL_WANTNO;
@@ -662,6 +710,7 @@ static void printsub(struct SessionHandle *data,
                      size_t length)             /* length of suboption data */
 {
   unsigned int i = 0;
+  unsigned short *pval;
 
   if(data->set.verbose) {
     if(direction) {
@@ -698,20 +747,28 @@ static void printsub(struct SessionHandle *data,
 
     if(CURL_TELOPT_OK(pointer[0])) {
       switch(pointer[0]) {
-        case CURL_TELOPT_TTYPE:
-        case CURL_TELOPT_XDISPLOC:
-        case CURL_TELOPT_NEW_ENVIRON:
-          infof(data, "%s", CURL_TELOPT(pointer[0]));
-          break;
-        default:
-          infof(data, "%s (unsupported)", CURL_TELOPT(pointer[0]));
-          break;
+      case CURL_TELOPT_TTYPE:
+      case CURL_TELOPT_XDISPLOC:
+      case CURL_TELOPT_NEW_ENVIRON:
+      case CURL_TELOPT_NAWS:
+        infof(data, "%s", CURL_TELOPT(pointer[0]));
+        break;
+      default:
+        infof(data, "%s (unsupported)", CURL_TELOPT(pointer[0]));
+        break;
       }
     }
     else
       infof(data, "%d (unknown)", pointer[i]);
 
-    switch(pointer[1]) {
+    switch(pointer[0]) {
+    case CURL_TELOPT_NAWS:
+      pval = (unsigned short*)(pointer+1);
+      infof(data, "Width: %hu ; Height: %hu",
+            ntohs(pval[0]), ntohs(pval[1]));
+      break;
+    default:
+      switch(pointer[1]) {
       case CURL_TELQUAL_IS:
         infof(data, " IS");
         break;
@@ -724,9 +781,9 @@ static void printsub(struct SessionHandle *data,
       case CURL_TELQUAL_NAME:
         infof(data, " NAME");
         break;
-    }
+      }
 
-    switch(pointer[0]) {
+      switch(pointer[0]) {
       case CURL_TELOPT_TTYPE:
       case CURL_TELOPT_XDISPLOC:
         pointer[length] = 0;
@@ -737,15 +794,15 @@ static void printsub(struct SessionHandle *data,
           infof(data, " ");
           for(i = 3;i < length;i++) {
             switch(pointer[i]) {
-              case CURL_NEW_ENV_VAR:
-                infof(data, ", ");
-                break;
-              case CURL_NEW_ENV_VALUE:
-                infof(data, " = ");
-                break;
-              default:
-                infof(data, "%c", pointer[i]);
-                break;
+            case CURL_NEW_ENV_VAR:
+              infof(data, ", ");
+              break;
+            case CURL_NEW_ENV_VALUE:
+              infof(data, " = ");
+              break;
+            default:
+              infof(data, "%c", pointer[i]);
+              break;
             }
           }
         }
@@ -754,8 +811,8 @@ static void printsub(struct SessionHandle *data,
         for(i = 2; i < length; i++)
           infof(data, " %.2x", pointer[i]);
         break;
+      }
     }
-
     if(direction)
       infof(data, "\n");
   }
@@ -770,6 +827,7 @@ static CURLcode check_telnet_options(struct connectdata *conn)
   struct SessionHandle *data = conn->data;
   struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet;
   CURLcode result = CURLE_OK;
+  int binary_option;
 
   /* Add the user name as an environment variable if it
      was given on the command line */
@@ -817,6 +875,29 @@ static CURLcode check_telnet_options(struct connectdata *conn)
         continue;
       }
 
+          /* Window Size */
+      if(Curl_raw_equal(option_keyword, "WS")) {
+        if(sscanf(option_arg, "%hu%*[xX]%hu",
+                  &tn->subopt_wsx, &tn->subopt_wsy) == 2)
+          tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES;
+        else {
+          failf(data, "Syntax error in telnet option: %s", head->data);
+          result = CURLE_TELNET_OPTION_SYNTAX;
+          break;
+        }
+        continue;
+      }
+
+      /* To take care or not of the 8th bit in data exchange */
+      if(Curl_raw_equal(option_keyword, "BINARY")) {
+        binary_option=atoi(option_arg);
+        if(binary_option!=1) {
+          tn->us_preferred[CURL_TELOPT_BINARY] = CURL_NO;
+          tn->him_preferred[CURL_TELOPT_BINARY] = CURL_NO;
+        }
+        continue;
+      }
+
       failf(data, "Unknown telnet option %s", head->data);
       result = CURLE_UNKNOWN_TELNET_OPTION;
       break;
@@ -913,6 +994,69 @@ static void suboption(struct connectdata *conn)
   return;
 }
 
+
+/*
+ * sendsuboption()
+ *
+ * Send suboption information to the server side.
+ */
+
+static void sendsuboption(struct connectdata *conn, int option)
+{
+  ssize_t bytes_written;
+  int err;
+  unsigned short x, y;
+  unsigned char*uc1, *uc2;
+
+  struct SessionHandle *data = conn->data;
+  struct TELNET *tn = (struct TELNET *)data->state.proto.telnet;
+
+  switch (option) {
+  case CURL_TELOPT_NAWS:
+    /* We prepare data to be sent */
+    CURL_SB_CLEAR(tn)
+    CURL_SB_ACCUM(tn,CURL_IAC)
+    CURL_SB_ACCUM(tn,CURL_SB)
+    CURL_SB_ACCUM(tn,CURL_TELOPT_NAWS)
+    /* We must deal either with litte or big endien processors */
+    /* Window size must be sent according to the 'network order' */
+    x=htons(tn->subopt_wsx);
+    y=htons(tn->subopt_wsy);
+    uc1 = (unsigned char*)&x;
+    uc2 = (unsigned char*)&y;
+    CURL_SB_ACCUM(tn,uc1[0])
+      CURL_SB_ACCUM(tn,uc1[1])
+      CURL_SB_ACCUM(tn,uc2[0])
+      CURL_SB_ACCUM(tn,uc2[1])
+
+      CURL_SB_ACCUM(tn,CURL_IAC)
+      CURL_SB_ACCUM(tn,CURL_SE)
+      CURL_SB_TERM(tn)
+      /* data suboption is now ready */
+
+      printsub(data, '>', (unsigned char *)tn->subbuffer+2,
+               CURL_SB_LEN(tn)-2);
+
+    /* we send the header of the suboption... */
+    bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer, 3);
+    if(bytes_written < 0) {
+      err = SOCKERRNO;
+      failf(data, "Sending data failed (%d)", err);
+    }
+    /* ... then the window size with the send_telnet_data() function
+       to deal with 0xFF cases ... */
+    send_telnet_data(conn, (char *)tn->subbuffer+3, 4);
+    /* ... and the footer */
+    bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer+7, 2);
+    if(bytes_written < 0) {
+      err = SOCKERRNO;
+      failf(data, "Sending data failed (%d)", err);
+    }
+    break;
+  }
+}
+
+
 static
 CURLcode telrcv(struct connectdata *conn,
                 const unsigned char *inbuf, /* Data received from socket */