Allow filter names and expressions of arbitrary length, and, in the
[metze/wireshark/wip.git] / packet-telnet.c
index 947f46d8f17002bb835c452c0be8e9269d4d2641..93a8809a61cee4a698a307b52ea37590b0c208ab 100644 (file)
@@ -1,11 +1,11 @@
-/* packet-pop.c
+/* packet-telnet.c
  * Routines for telnet packet dissection
  * Copyright 1999, Richard Sharpe <rsharpe@ns.aus.com>
  *
- * 
+ * $Id: packet-telnet.c,v 1.24 2001/01/25 06:14:14 guy Exp $
  *
  * Ethereal - Network traffic analyzer
- * By Gerald Combs <gerald@unicom.net>
+ * By Gerald Combs <gerald@zing.org>
  * Copyright 1998 Gerald Combs
  *
  * Copied from packet-pop.c
 #include <string.h>
 #include <glib.h>
 #include "packet.h"
-#include "etypes.h"
+#include "strutil.h"
+
+static int proto_telnet = -1;
+
+static gint ett_telnet = -1;
+static gint ett_telnet_subopt = -1;
 
 /* Some defines for Telnet */
 
+#define TCP_PORT_TELNET                        23
+
 #define TN_IAC   255
 #define TN_DONT  254
 #define TN_DO    253
@@ -67,7 +74,7 @@
 #define TN_SUSP  237
 #define TN_EOF   236
 
-char *options[] = {
+static const char *options[] = {
   "Binary Transmission",
   "Echo",
   "Reconnection",
@@ -111,274 +118,320 @@ char *options[] = {
   "TN3270E"
 };
 
-extern packet_info pi;
+#define        NOPTIONS        (sizeof options / sizeof options[0])
 
-void telnet_sub_option(proto_tree *telnet_tree, char *rr, int *i, int offset, int max_data)
+static int
+telnet_sub_option(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
 {
   proto_tree *ti, *option_tree;
-  int subneg_len, req, si1, not_found = 1;
-  volatile int i1;
-  char *opt, sub_opt_data[1500];
+  int offset = start_offset;
+  guint8 opt_byte;
+  int subneg_len, req;
+  const u_char *opt;
+  guint len;
 
-  memset(sub_opt_data, '\0', sizeof(sub_opt_data));
+  offset += 2; /* skip IAC and SB */
 
   /* Figure out the option and type */
-
-  opt = options[(unsigned int)rr[*i]];
-  req = (unsigned int)rr[*i + 1];
-
-  i1 = *i + 2; si1 = i1;
-  while ((i1 < max_data) && (not_found)) {  
-
-    if ((unsigned char)rr[i1] == (unsigned char)TN_IAC)
-      not_found = 0;
-    else
-      i1++;
-
+  opt_byte = tvb_get_guint8(tvb, offset);
+  if (opt_byte > NOPTIONS)
+    opt = "<unknown option>";
+  else
+    opt = options[opt_byte];
+  offset++;
+  req = tvb_get_guint8(tvb, offset);
+  offset++;
+
+  /* Search for an IAC. */
+  len = tvb_length_remaining(tvb, offset);
+  offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
+  if (offset == -1) {
+    /* None found - run to the end of the packet. */
+    offset += len;
   }
 
-  subneg_len = i1 - *i + 2;
+  subneg_len = offset - start_offset;
 
-  ti = proto_tree_add_item(telnet_tree, offset, subneg_len, "Suboption Begin: %s", opt);
+  ti = proto_tree_add_text(telnet_tree, tvb, start_offset, subneg_len,
+                       "Suboption Begin: %s", opt);
 
-  option_tree = proto_tree_new();
+  option_tree = proto_item_add_subtree(ti, ett_telnet_subopt);
 
-  proto_item_add_subtree(ti, option_tree, ETT_TELNET_SUBOPT);
-
-  proto_tree_add_item(option_tree, offset + 2, subneg_len - 2, "%s %s", (req ? "Send your" : "Here's my"), opt);
+  proto_tree_add_text(option_tree, tvb, start_offset + 2, 2,
+                       "%s %s", (req ? "Send your" : "Here's my"), opt);
 
   if (req == 0) {  /* Add the value */
-
-    memcpy(sub_opt_data, rr + *i + 2, subneg_len - 2);
-    proto_tree_add_item(option_tree, offset + 4, subneg_len - 4, "Value: %s", format_text(sub_opt_data, subneg_len - 4));
-    *i += subneg_len - 2;
-
+    proto_tree_add_text(option_tree, tvb, start_offset + 4, subneg_len - 4,
+       "Value: %s", tvb_format_text(tvb, start_offset + 4, subneg_len - 4));
   }
-  else {
-
-    *i += subneg_len - 2;
+  return offset;
+}
 
-  }
+static int
+telnet_will_wont_do_dont(proto_tree *telnet_tree, tvbuff_t *tvb,
+                       int start_offset, char *type)
+{
+  int offset = start_offset;
+  guint8 opt_byte;
+  const char *opt;
+
+  offset += 2; /* skip IAC and WILL,WONT,DO,DONT} */
+  opt_byte = tvb_get_guint8(tvb, offset);
+  if (opt_byte > NOPTIONS)
+    opt = "<unknown option>";
+  else
+    opt = options[opt_byte];
+  offset++;
+                     
+  proto_tree_add_text(telnet_tree, tvb, start_offset, 3,
+                       "Command: %s %s", type, opt);
+  return offset;
 }
 
-void telnet_command(proto_tree *telnet_tree, char *rr, int *i, int offset, int max_data) 
+static int
+telnet_command(proto_tree *telnet_tree, tvbuff_t *tvb, int start_offset)
 {
-  char *opt;
+  int offset = start_offset;
+  u_char optcode;
   
-  switch((unsigned char)rr[*i]) {
+  offset += 1; /* skip IAC */
+  optcode = tvb_get_guint8(tvb, offset);
+  offset++;
+  switch(optcode) {
 
   case TN_EOF:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: End of File");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: End of File");
     break;
 
   case TN_SUSP:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Suspend Current Process");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Suspend Current Process");
     break;
 
   case TN_ABORT:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Abort Process");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Abort Process");
     break;
 
   case TN_EOR:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: End of Record");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: End of Record");
     break;
 
   case TN_SE:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Suboption End");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Suboption End");
     break;
 
   case TN_NOP:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: No Operation");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: No Operation");
     break;
 
   case TN_DM:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Data Mark");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Data Mark");
     break;
 
   case TN_BRK:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Break");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Break");
     break;
 
   case TN_IP:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Interrupt Process");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Interrupt Process");
     break;
 
   case TN_AO:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Abort Output");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Abort Output");
     break;
 
   case TN_AYT:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Are You There?");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Are You There?");
     break;
 
   case TN_EC:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Escape Character");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Escape Character");
     break;
 
   case TN_EL:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Erase Line");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Erase Line");
     break;
 
   case TN_GA:
-
-    proto_tree_add_item(telnet_tree, offset, 2, "Command: Go Ahead");
-    (*i)++;
+    proto_tree_add_text(telnet_tree, tvb, start_offset, 2,
+                       "Command: Go Ahead");
     break;
 
   case TN_SB:
-
-    (*i)++;
-    telnet_sub_option(telnet_tree, rr, i, offset, max_data);
+    offset = telnet_sub_option(telnet_tree, tvb, start_offset);
     break;
 
   case TN_WILL:
-
-    if (rr[*i + 1] > (sizeof(options)/sizeof(char *)))
-      opt = "<unknown option>";
-    else
-      opt = options[(unsigned int)rr[*i + 1]];
-                     
-    proto_tree_add_item(telnet_tree, offset, 3, "Command: Will %s", opt);
-    *i += 2; /* skip two chars */
+    offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
+                                       "Will");
     break;
 
   case TN_WONT:
-
-    if (rr[*i + 1] > (sizeof(options)/sizeof(char *)))
-      opt = "<unknown option>";
-    else
-      opt = options[(unsigned int)rr[*i + 1]];
-                     
-    proto_tree_add_item(telnet_tree, offset, 3, "Command: Won't %s", opt);
-    *i += 2; /* skip two chars */
+    offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
+                                       "Won't");
     break;
 
   case TN_DO:
-
-    if (rr[*i + 1] > (sizeof(options)/sizeof(char *)))
-      opt = "<unknown option>";
-    else
-      opt = options[(unsigned int)rr[*i + 1]];
-                     
-    proto_tree_add_item(telnet_tree, offset, 3, "Command: Do %s", opt);
-    *i += 2; /* skip two chars */
+    offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
+                                       "Do");
     break;
 
   case TN_DONT:
-
-    if (rr[*i + 1] > (sizeof(options)/sizeof(char *)))
-      opt = "<unknown option>";
-    else
-      opt = options[(unsigned int)rr[*i + 1]];
-                     
-    proto_tree_add_item(telnet_tree, offset, 3, "Command: Don't %s", opt);
-    *i += 2; /* skip two chars */
+    offset = telnet_will_wont_do_dont(telnet_tree, tvb, start_offset,
+                                       "Don't");
     break;
-
   }
 
+  return offset;
 }
 
-void
-dissect_telnet(const u_char *pd, int offset, frame_data *fd, proto_tree *tree, int max_data)
+static void
+telnet_add_text(proto_tree *tree, tvbuff_t *tvb, int offset, int len)
 {
-        proto_tree      *telnet_tree, *ti;
-       gchar           rr[1500];
-       int i1;
-       int i2;
-
-       memset(rr, '\0', sizeof(rr));
-
-       if (check_col(fd, COL_PROTOCOL))
-               col_add_str(fd, COL_PROTOCOL, "TELNET");
+  gint next_offset;
+  int linelen;
+  guint8 c;
+  gboolean last_char_was_cr;
+
+  while (len != 0 && tvb_offset_exists(tvb, offset)) {
+    /*
+     * Find the end of the line.
+     */
+    linelen = tvb_find_line_end(tvb, offset, len, &next_offset);
+    len -= next_offset - offset;       /* subtract out the line's characters */
+
+    /*
+     * In Telnet, CR NUL is the way you send a CR by itself in the
+     * default ASCII mode; don't treat CR by itself as a line ending,
+     * treat only CR NUL, CR LF, or LF by itself as a line ending.
+     */
+    if (next_offset == offset + linelen + 1 && len >= 1) {
+      /*
+       * Well, we saw a one-character line ending, so either it's a CR
+       * or an LF; we have at least two characters left, including the
+       * CR.
+       *
+       * If the line ending is a CR, skip all subsequent CRs; at
+       * least one capture appeared to have multiple CRs at the end of
+       * a line.
+       */
+      if (tvb_get_guint8(tvb, offset + linelen) == '\r') {
+       last_char_was_cr = TRUE;
+       while (len != 0 && tvb_offset_exists(tvb, next_offset)) {
+          c = tvb_get_guint8(tvb, next_offset);
+         next_offset++;        /* skip over that character */
+         len--;
+          if (c == '\n' || (c == '\0' && last_char_was_cr)) {
+            /*
+            * LF is a line ending, whether preceded by CR or not.
+            * NUL is a line ending if preceded by CR.
+            */
+            break;
+          }
+         last_char_was_cr = (c == '\r');
+       }
+      }
+    }
+
+    /*
+     * Now compute the length of the line *including* the end-of-line
+     * indication, if any; we display it all.
+     */
+    linelen = next_offset - offset;
+
+    proto_tree_add_text(tree, tvb, offset, linelen,
+                       "Data: %s",
+                       tvb_format_text(tvb, offset, linelen));
+    offset = next_offset;
+  }
+}
 
-       if (check_col(fd, COL_INFO)) {
+static void
+dissect_telnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+        proto_tree      *telnet_tree, *ti;
 
-         col_add_fstr(fd, COL_INFO, "Telnet Data ...");
+       if (check_col(pinfo->fd, COL_PROTOCOL))
+               col_set_str(pinfo->fd, COL_PROTOCOL, "TELNET");
 
-       }
+       if (check_col(pinfo->fd, COL_INFO))
+               col_add_fstr(pinfo->fd, COL_INFO, "Telnet Data ...");
 
        if (tree) {
-
-         char data[1500];
-         int i3;
-
-         memset(data, '\0', sizeof(data));
-
-         memcpy(rr, pd + offset, max_data);
-
-         ti = proto_tree_add_item(tree, offset, END_OF_FRAME,
-                               "Telnet Protocol");
-         telnet_tree = proto_tree_new();
-         proto_item_add_subtree(ti, telnet_tree, ETT_TELNET);
-
-         i1 = i2 = i3 = 0;
-
-         while (i1 < max_data) {
-
-           if ((unsigned char)rr[i1] == (unsigned char)TN_IAC) {
-
-             if (strlen(data) > 0) {
-
-               proto_tree_add_item(telnet_tree, offset + i2, strlen(data), "Data: %s", format_text(data, strlen(data)));
-               memset(data, '\0', sizeof(data));
-               i3 = 0;
-
-             }
+         gint offset = 0;
+         guint len;
+         int data_len;
+         gint iac_offset;
+
+         ti = proto_tree_add_item(tree, proto_telnet, tvb, offset,
+           tvb_length_remaining(tvb, offset), FALSE);
+         telnet_tree = proto_item_add_subtree(ti, ett_telnet);
+
+         /*
+          * Scan through the buffer looking for an IAC byte.
+          */
+         while ((len = tvb_length_remaining(tvb, offset)) > 0) {
+           iac_offset = tvb_find_guint8(tvb, offset, len, TN_IAC);
+           if (iac_offset != -1) {
+             /*
+              * We found an IAC byte.
+              * If there's any data before it, add that data to the
+              * tree, a line at a time.
+              */
+             data_len = iac_offset - offset;
+             if (data_len > 0)
+               telnet_add_text(telnet_tree, tvb, offset, data_len);
              
-             i1++;
-             telnet_command(telnet_tree, rr, &i1, offset + i1 - 1, max_data);
-             i2 = i1;
-
+             /*
+              * Now interpret the command.
+              */
+             offset = telnet_command(telnet_tree, tvb, iac_offset);
            }
            else {
-
-             data[i3] = rr[i1];
-             i3++;
-             i1++;
-
-
+             /*
+              * We found no IAC byte, so what remains in the buffer
+              * is the last of the data in the packet.
+              * Add it to the tree, a line at a time, and then quit.
+              */
+             telnet_add_text(telnet_tree, tvb, offset, len);
+             break;
            }
          }
-
-         if (strlen(data) > 0) { /* Still some data to add */
-
-           proto_tree_add_item(telnet_tree, offset + i2, strlen(data), "Data: %s", format_text(data, strlen(data)));
-
-         }
-
        }
-
 }
 
+void
+proto_register_telnet(void)
+{
+/*        static hf_register_info hf[] = {
+                { &variable,
+                { "Name",           "telnet.abbreviation", TYPE, VALS_POINTER }},
+        };*/
+       static gint *ett[] = {
+               &ett_telnet,
+               &ett_telnet_subopt,
+       };
+
+        proto_telnet = proto_register_protocol("Telnet", "TELNET", "telnet");
+ /*       proto_register_field_array(proto_telnet, hf, array_length(hf));*/
+       proto_register_subtree_array(ett, array_length(ett));
+}
 
-
-
-
-
+void
+proto_reg_handoff_telnet(void)
+{
+       dissector_add("tcp.port", TCP_PORT_TELNET, dissect_telnet,
+           proto_telnet);
+}