We don't use anything from <arpa/inet.h>, so we don't need to include
[obnox/wireshark/wip.git] / packet-smtp.c
index 99baa7469bb26c16cfb24bd340333ca5b73a81aa..5a1da4476ae015f712d85b8fd748f456e670e274 100644 (file)
@@ -1,13 +1,14 @@
 /* packet-smtp.c
  * Routines for SMTP packet disassembly
  *
- * $Id: packet-smtp.c,v 1.8 2000/11/11 07:48:30 guy Exp $
+ * $Id: packet-smtp.c,v 1.31 2002/08/02 23:36:02 jmayer Exp $
  *
  * Copyright (c) 2000 by Richard Sharpe <rsharpe@ns.aus.com>
  *
  * Ethereal - Network traffic analyzer
- * By Gerald Combs
+ * By Gerald Combs <gerald@ethereal.com>
  * Copyright 1999 Gerald Combs
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
 #include "config.h"
 #endif
 
-#ifdef HAVE_SYS_TYPES_H
-# include <sys/types.h>
-#endif
-
-#ifdef HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#endif
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
 #include <time.h>
 #include <glib.h>
 #include <string.h>
-#include "packet.h"
-#include "conversation.h"
-#include "resolv.h"
+#include <epan/packet.h>
+#include <epan/conversation.h>
+#include <epan/resolv.h>
 #include "prefs.h"
-#include "strutil.h"
+#include <epan/strutil.h>
 
 #define TCP_PORT_SMTP 25
 
@@ -55,11 +48,18 @@ static int proto_smtp = -1;
 
 static int hf_smtp_req = -1;
 static int hf_smtp_rsp = -1;
+static int hf_smtp_req_command = -1;
+static int hf_smtp_req_parameter = -1;
+static int hf_smtp_rsp_code = -1;
+static int hf_smtp_rsp_parameter = -1;
 
 static int ett_smtp = -1;
 
 static int global_smtp_tcp_port = TCP_PORT_SMTP;
 
+/* desegmentation of SMTP command and response lines */
+static gboolean smtp_desegment = TRUE;
+
 /*
  * A CMD is an SMTP command, MESSAGE is the message portion, and EOM is the
  * last part of a message
@@ -75,10 +75,6 @@ struct smtp_proto_data {
 
 static int smtp_packet_init_count = 100;
 
-struct smtp_request_key {
-  guint32 conversation;
-};
-
 /*
  * State information stored with a conversation.
  */
@@ -87,66 +83,17 @@ struct smtp_request_val {
   guint16 crlf_seen;     /* Have we seen a CRLF on the end of a packet */
 };
 
-GHashTable *smtp_request_hash = NULL;
-GMemChunk  *smtp_request_keys = NULL;
-GMemChunk  *smtp_request_vals = NULL;
-GMemChunk  *smtp_packet_infos = NULL;
-
-/* Hash Functions */
-gint
-smtp_equal(gconstpointer v, gconstpointer w)
-{
-  struct smtp_request_key *v1 = (struct smtp_request_key *)v;
-  struct smtp_request_key *v2 = (struct smtp_request_key *)w;
-
-#if defined(DEBUG_SMTP_HASH)
-  printf("Comparing %08X\n      and %08X\n",
-        v1->conversation, v2->conversation);
-#endif
-
-  if (v1->conversation == v2->conversation)
-    return 1;
-
-  return 0;
-
-}
-
-static guint
-smtp_hash(gconstpointer v)
-{
-  struct smtp_request_key *key = (struct smtp_request_key *)v;
-  guint val;
-
-  val = key->conversation;
-
-#if defined(DEBUG_SMTP_HASH)
-  printf("SMTP Hash calculated as %u\n", val);
-#endif
-
-  return val;
-
-}
+static GMemChunk  *smtp_request_vals = NULL;
+static GMemChunk  *smtp_packet_infos = NULL;
 
 static void
 smtp_init_protocol(void)
 {
-#if defined(DEBUG_SMTP_HASH)
-  printf("Initializing SMTP hashtable area\n");
-#endif
-
-  if (smtp_request_hash)
-    g_hash_table_destroy(smtp_request_hash);
-  if (smtp_request_keys)
-    g_mem_chunk_destroy(smtp_request_keys);
   if (smtp_request_vals)
     g_mem_chunk_destroy(smtp_request_vals);
   if (smtp_packet_infos)
     g_mem_chunk_destroy(smtp_packet_infos);
 
-  smtp_request_hash = g_hash_table_new(smtp_hash, smtp_equal);
-  smtp_request_keys = g_mem_chunk_new("smtp_request_keys",
-                                      sizeof(struct smtp_request_key),
-                                      smtp_packet_init_count * sizeof(struct smtp_request_key), G_ALLOC_AND_FREE);
   smtp_request_vals = g_mem_chunk_new("smtp_request_vals", 
                                      sizeof(struct smtp_request_val),
                                      smtp_packet_init_count * sizeof(struct smtp_request_val), G_ALLOC_AND_FREE);
@@ -156,57 +103,23 @@ smtp_init_protocol(void)
 
 }
 
-static
-int find_smtp_resp_end(const u_char *pd, int offset)
-{
-  int cntr = 0;
-
-  /* Look for the CRLF ... but keep in mind the END_OF_FRAME */
-
-  while (END_OF_FRAME >= cntr) {
-
-    if (pd[offset + cntr] == 0x0A) { /* Found it */
-
-      if (END_OF_FRAME >= cntr + 1) cntr++;
-
-      return cntr;
-
-    }
-
-    cntr++;
-
-  }
-
-  return cntr;
-
-}
-
-#if 0
 static void
 dissect_smtp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
 {
-#else
-static void
-dissect_smtp(const u_char *pd, int offset, frame_data *fd,
-            proto_tree *tree)
-{
-  /*    tvbuff_t *tvb = tvb_create_from_top(offset);*/
-    packet_info *pinfo = &pi;
-#endif
     struct smtp_proto_data  *frame_data;
-    proto_tree              *smtp_tree, *ti;
+    proto_tree              *smtp_tree;
+    proto_item              *ti;
+    int                     offset = 0;
     int                     request = 0;
-    const u_char            *cmd = NULL;
     conversation_t          *conversation;
-    struct smtp_request_key request_key, *new_request_key;
     struct smtp_request_val *request_val;
+    const guchar            *line;
+    guint32                 code;
+    int                     linelen;
     gboolean                eom_seen = FALSE;
-
-#if 0
-    CHECK_DISPLAY_AS_DATA(proto_smtp, tvb, pinfo, tree);
-#else
-    OLD_CHECK_DISPLAY_AS_DATA(proto_smtp, pd, offset, fd, tree);
-#endif
+    gint                    next_offset;
+    gboolean                is_continuation_line;
+    int                     cmdlen;
 
     /* As there is no guarantee that we will only see frames in the
      * the SMTP conversation once, and that we will see them in
@@ -234,39 +147,60 @@ dissect_smtp(const u_char *pd, int offset, frame_data *fd,
 
     /* SMTP messages have a simple format ... */
 
-    request = pinfo -> destport == TCP_PORT_SMTP;
-    cmd = pd + offset;   /* FIXME: What about tvb */
+    request = pinfo -> destport == pinfo -> match_port;
+
+    /*
+     * Get the first line from the buffer.
+     *
+     * Note that "tvb_find_line_end()" will, if it doesn't return
+     * -1, return a value that is not longer than what's in the buffer,
+     * and "tvb_find_line_end()" will always return a value that is not
+     * longer than what's in the buffer, so the "tvb_get_ptr()" call
+     * won't throw an exception.
+     */
+    linelen = tvb_find_line_end(tvb, offset, -1, &next_offset,
+      smtp_desegment && pinfo->can_desegment);
+    if (linelen == -1) {
+      /*
+       * We didn't find a line ending, and we're doing desegmentation;
+       * tell the TCP dissector where the data for this message starts
+       * in the data it handed us, and tell it we need one more byte
+       * (we may need more, but we'll try again if what we get next
+       * isn't enough), and return.
+       */
+      pinfo->desegment_offset = offset;
+      pinfo->desegment_len = 1;
+      return;
+    }
+    line = tvb_get_ptr(tvb, offset, linelen);
 
     frame_data = p_get_proto_data(pinfo->fd, proto_smtp);
 
     if (!frame_data) {
 
-      conversation = find_conversation(&pinfo->src, &pinfo->dst, pi.ptype,
+      conversation = find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype,
                                       pinfo->srcport, pinfo->destport, 0);
       if (conversation == NULL) { /* No conversation, create one */
        conversation = conversation_new(&pinfo->src, &pinfo->dst, pinfo->ptype,
-                                       pinfo->srcport, pinfo->destport, NULL,
-                                       0);
+                                       pinfo->srcport, pinfo->destport, 0);
 
       }
 
-      /* 
-       * Check for and insert an entry in the request table if does not exist
+      /*
+       * Is there a request structure attached to this conversation?
        */
-      request_key.conversation = conversation->index;
+      request_val = conversation_get_proto_data(conversation, proto_smtp);
 
-      request_val = (struct smtp_request_val *)g_hash_table_lookup(smtp_request_hash, &request_key);
-      
-      if (!request_val) { /* Create one */
-
-       new_request_key = g_mem_chunk_alloc(smtp_request_keys);
-       new_request_key->conversation = conversation->index;
+      if (!request_val) {
 
+        /*
+         * No - create one and attach it.
+         */
        request_val = g_mem_chunk_alloc(smtp_request_vals);
        request_val->reading_data = FALSE;
        request_val->crlf_seen = 0;
 
-       g_hash_table_insert(smtp_request_hash, new_request_key, request_val);
+       conversation_add_proto_data(conversation, proto_smtp, request_val);
 
       }
 
@@ -285,14 +219,14 @@ dissect_smtp(const u_char *pd, int offset, frame_data *fd,
         * .CRLF at the begining of the same packet.
         */
 
-       if ((request_val->crlf_seen && strncmp(pd + offset, ".\r\n", 3) == 0) ||
-           (strncmp(pd + offset, "\r\n.\r\n", 5) == 0)) {
+       if ((request_val->crlf_seen && tvb_strneql(tvb, offset, ".\r\n", 3) == 0) ||
+           tvb_strneql(tvb, offset, "\r\n.\r\n", 5) == 0) {
 
          eom_seen = TRUE;
 
        }
 
-       if (strncmp(pd + offset + END_OF_FRAME - 2, "\r\n", 2) == 0) {
+       if (tvb_strneql(tvb, offset + tvb_length_remaining(tvb, offset) - 2, "\r\n", 2) == 0) {
 
          request_val->crlf_seen = 1;
 
@@ -335,24 +269,45 @@ dissect_smtp(const u_char *pd, int offset, frame_data *fd,
          }
        } else {
          /*
-          * This is commands.
+          * This is commands - unless the capture started in the
+          * middle of a session, and we're in the middle of data.
+          * To quote RFC 821, "Command codes are four alphabetic
+          * characters"; if we don't see four alphabetic characters
+          * and, if there's anything else in the line, a space, we
+          * assume it's not a command.
+          * (We treat only A-Z and a-z as alphabetic.)
           */
-         if (strncmp(pd + offset, "DATA", 4)==0) {
+#define        ISALPHA(c)      (((c) >= 'A' && (c) <= 'Z') || \
+                        ((c) >= 'a' && (c) <= 'z'))
+         if (linelen >= 4 && ISALPHA(line[0]) && ISALPHA(line[1]) &&
+             ISALPHA(line[2]) && ISALPHA(line[3]) &&
+             (linelen == 4 || line[4] == ' ')) {
+           if (strncasecmp(line, "DATA", 4) == 0) {
+
+             /*
+              * DATA command.
+              * This is a command, but everything that comes after it,
+              * until an EOM, is data.
+              */
+             frame_data->pdu_type = SMTP_PDU_CMD;
+             request_val->reading_data = TRUE;
+
+           } else {
+
+             /*
+              * Regular command.
+              */
+             frame_data->pdu_type = SMTP_PDU_CMD;
+
+           }
+         } else {
 
            /*
-            * DATA command.
-            * This is a command, but everything that comes after it,
-            * until an EOM, is data.
+            * Assume it's message data.
             */
-           frame_data->pdu_type = SMTP_PDU_CMD;
-           request_val->reading_data = TRUE;
 
-         } else {
+           frame_data->pdu_type = SMTP_PDU_MESSAGE;
 
-           /*
-            * Regular command.
-            */
-           frame_data->pdu_type = SMTP_PDU_CMD;
          }
 
        }
@@ -367,10 +322,10 @@ dissect_smtp(const u_char *pd, int offset, frame_data *fd,
      * fields ...
      */
 
-    if (check_col(fd, COL_PROTOCOL))
-      col_add_str(fd, COL_PROTOCOL, "SMTP");
+    if (check_col(pinfo->cinfo, COL_PROTOCOL))
+      col_set_str(pinfo->cinfo, COL_PROTOCOL, "SMTP");
 
-    if (check_col(fd, COL_INFO)) {  /* Add the appropriate type here */
+    if (check_col(pinfo->cinfo, COL_INFO)) {  /* Add the appropriate type here */
 
       /*
        * If it is a request, we have to look things up, otherwise, just
@@ -384,34 +339,36 @@ dissect_smtp(const u_char *pd, int offset, frame_data *fd,
        switch (frame_data->pdu_type) {
        case SMTP_PDU_MESSAGE:
 
-         col_add_fstr(pinfo->fd, COL_INFO, "Message: %s", format_text(cmd, END_OF_FRAME));
+         col_set_str(pinfo->cinfo, COL_INFO, "Message Body");
          break;
 
        case SMTP_PDU_EOM:
 
-         col_add_fstr(pinfo->fd, COL_INFO, "EOM: %s", format_text(cmd, END_OF_FRAME));
+         col_add_fstr(pinfo->cinfo, COL_INFO, "EOM: %s",
+             format_text(line, linelen));
          break;
 
        case SMTP_PDU_CMD:
 
-         col_add_fstr(pinfo->fd, COL_INFO, "%s", format_text(cmd, END_OF_FRAME));
+         col_add_fstr(pinfo->cinfo, COL_INFO, "Command: %s",
+             format_text(line, linelen));
+         break;
 
        }
 
       }
       else {
 
-       col_add_fstr(pinfo->fd, COL_INFO, "%s", format_text(cmd, END_OF_FRAME));
+       col_add_fstr(pinfo->cinfo, COL_INFO, "Response: %s",
+           format_text(line, linelen));
 
       }
     }
 
     if (tree) { /* Build the tree info ... */
 
-      ti = proto_tree_add_item(tree, proto_smtp, NullTVB, offset, END_OF_FRAME, FALSE);
+      ti = proto_tree_add_item(tree, proto_smtp, tvb, offset, -1, FALSE);
       smtp_tree = proto_item_add_subtree(ti, ett_smtp);
-      proto_tree_add_boolean_hidden(smtp_tree, (request ? hf_smtp_req : hf_smtp_rsp),
-                                   NullTVB, offset, 4, TRUE);
       if (request) {
 
        /* 
@@ -431,38 +388,134 @@ dissect_smtp(const u_char *pd, int offset, frame_data *fd,
 
        case SMTP_PDU_MESSAGE:
 
-         proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset, END_OF_FRAME, "Message: %s", format_text(cmd, END_OF_FRAME));
+         /*
+          * Message body.
+          * Put its lines into the protocol tree, a line at a time.
+          */
+         while (tvb_offset_exists(tvb, offset)) {
+
+           /*
+            * Find the end of the line.
+            */
+           tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
+
+           /*
+            * Put this line.
+            */
+           proto_tree_add_text(smtp_tree, tvb, offset, next_offset - offset,
+               "Message: %s",
+               tvb_format_text(tvb, offset, next_offset - offset));
+
+           /*
+            * Step to the next line.
+            */
+           offset = next_offset;
+
+         }
 
          break;
 
        case SMTP_PDU_EOM:
 
-         proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset, END_OF_FRAME, "EOM: %s", format_text(cmd, END_OF_FRAME));
+         /*
+          * End-of-message-body indicator.
+          *
+          * XXX - what about stuff after the first line?
+          * Unlikely, as the client should wait for a response to the
+          * DATA command this terminates before sending another
+          * request, but we should probably handle it.
+          */
+         proto_tree_add_text(smtp_tree, tvb, offset, linelen,
+             "EOM: %s", format_text(line, linelen));
 
          break;
 
        case SMTP_PDU_CMD:
-         proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset, 4, "Command: %s", format_text(cmd, 4));
-         proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset + 5, END_OF_FRAME, "Parameter: %s", format_text(cmd + 5, END_OF_FRAME - 5));
+
+         /*
+          * Command.
+          *
+          * XXX - what about stuff after the first line?
+          * Unlikely, as the client should wait for a response to the
+          * previous command before sending another request, but we
+          * should probably handle it.
+          */
+         if (linelen >= 4)
+           cmdlen = 4;
+         else
+           cmdlen = linelen;
+         proto_tree_add_boolean_hidden(smtp_tree, hf_smtp_req, tvb,
+                                       0, 0, TRUE);
+         proto_tree_add_item(smtp_tree, hf_smtp_req_command, tvb,
+                             offset, cmdlen, FALSE);
+         if (linelen > 5) {
+           proto_tree_add_item(smtp_tree, hf_smtp_req_parameter, tvb,
+                               offset + 5, linelen - 5, FALSE);
+         }
 
        }
 
       }
       else {
 
-       /* Must consider a multi-line response here ... */
+        /*
+        * Process the response, a line at a time, until we hit a line
+        * that doesn't have a continuation indication on it.
+        */
+       proto_tree_add_boolean_hidden(smtp_tree, hf_smtp_rsp, tvb,
+                                       0, 0, TRUE);
 
-       while (END_OF_FRAME >= 4 && pd[offset + 3] == '-') {
-         int resp_len = find_smtp_resp_end(pd, offset);
+       while (tvb_offset_exists(tvb, offset)) {
 
-         proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset, 3, "Response: %s", format_text(pd + offset, 3));
-         proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset + 4, resp_len, "Parameter: %s", format_text(pd + offset + 4, resp_len - 4));
+         /*
+          * Find the end of the line.
+          */
+         linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
 
-         offset += resp_len;
-       }
+         /*
+          * Is it a continuation line?
+          */
+         is_continuation_line =
+             (linelen >= 4 && tvb_get_guint8(tvb, offset + 3) == '-');
+
+         /*
+          * Put it into the protocol tree.
+          */
+         line = tvb_get_ptr(tvb, offset, linelen);
+         if (linelen >= 3 && isdigit(line[0]) && isdigit(line[1])
+                          && isdigit(line[2])) {
+           /*
+            * We have a 3-digit response code.
+            */
+           code = (line[0] - '0')*100 + (line[1] - '0')*10 + (line[2] - '0');
+           proto_tree_add_uint(smtp_tree, hf_smtp_rsp_code, tvb, offset, 3,
+                               code);
+
+           if (linelen >= 4) {
+             proto_tree_add_item(smtp_tree, hf_smtp_rsp_parameter, tvb,
+                                 offset + 4, linelen - 4, FALSE);
+           }
+         } else {
+           /*
+            * No 3-digit response code.
+            */
+           proto_tree_add_text(smtp_tree, tvb, offset, linelen,
+                               "Bogus reply line (no response code): %s",
+                               tvb_format_text(tvb, offset, linelen));
+         }
 
-       proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset, 3, "Response: %s", format_text(pd + offset, 3));
-       proto_tree_add_protocol_format(smtp_tree, proto_smtp, NullTVB, offset + 4, END_OF_FRAME, "Parameter: %s", format_text(pd + offset + 4, END_OF_FRAME - 4));
+         /*
+          * Step past this line.
+          */
+         offset = next_offset;
+
+         /*
+          * If it's not a continuation line, quit.
+          */
+         if (!is_continuation_line)
+           break;
+
+       }
        
       }
     }
@@ -475,24 +528,46 @@ proto_register_smtp(void)
 {
   static hf_register_info hf[] = {
     { &hf_smtp_req,
-      { "Request", "smtp.req", FT_BOOLEAN, BASE_NONE, NULL, 0x0, ""}},
+      { "Request", "smtp.req", FT_BOOLEAN, BASE_NONE, NULL, 0x0, "", HFILL }},
 
     { &hf_smtp_rsp,
-      { "Response", "smtp.rsp", FT_BOOLEAN, BASE_NONE, NULL, 0x0, ""}},
+      { "Response", "smtp.rsp", FT_BOOLEAN, BASE_NONE, NULL, 0x0, "", HFILL }},
+
+    { &hf_smtp_req_command,
+      { "Command", "smtp.req.command", FT_STRING,  BASE_NONE, NULL, 0x0,
+       "", HFILL }},
+
+    { &hf_smtp_req_parameter,
+      { "Request parameter", "smtp.req.parameter", FT_STRING, BASE_NONE, NULL, 0x0,
+       "", HFILL }},
+
+    { &hf_smtp_rsp_code,
+      { "Response code", "smtp.response.code", FT_UINT32, BASE_DEC, NULL, 0x0,
+       "", HFILL }},
+
+    { &hf_smtp_rsp_parameter,
+      { "Response parameter", "smtp.rsp.parameter", FT_STRING, BASE_NONE, NULL, 0x0,
+       "", HFILL }}
   };
   static gint *ett[] = {
     &ett_smtp
   };
-  /*module_t *smtp_module = NULL; */  /* Not yet used */
+  module_t *smtp_module;
 
   /* No Configuration options to register? */
 
-  proto_smtp = proto_register_protocol("Simple Mail Transfer Protocol", "smtp");
+  proto_smtp = proto_register_protocol("Simple Mail Transfer Protocol",
+                                      "SMTP", "smtp");
 
   proto_register_field_array(proto_smtp, hf, array_length(hf));
   proto_register_subtree_array(ett, array_length(ett));
   register_init_routine(&smtp_init_protocol);
 
+  smtp_module = prefs_register_protocol(proto_smtp, NULL);
+  prefs_register_bool_preference(smtp_module, "desegment_lines",
+    "Desegment all SMTP command and response lines spanning multiple TCP segments",
+    "Whether the SMTP dissector should desegment all command and response lines spanning multiple TCP segments",
+    &smtp_desegment);
 }
 
 /* The registration hand-off routine */
@@ -500,21 +575,24 @@ void
 proto_reg_handoff_smtp(void)
 {
   static int smtp_prefs_initialized = FALSE;
+  static dissector_handle_t smtp_handle;
   static int tcp_port = 0;
 
-  if (smtp_prefs_initialized) {
+  if (!smtp_prefs_initialized) {
 
-    old_dissector_delete("tcp.port", tcp_port, dissect_smtp);
+    smtp_handle = create_dissector_handle(dissect_smtp, proto_smtp);
+
+    smtp_prefs_initialized = TRUE;
 
   }
   else {
 
-    smtp_prefs_initialized = TRUE;
+    dissector_delete("tcp.port", tcp_port, smtp_handle);
 
   }
 
   tcp_port = global_smtp_tcp_port;
 
-  old_dissector_add("tcp.port", global_smtp_tcp_port, dissect_smtp);
+  dissector_add("tcp.port", global_smtp_tcp_port, smtp_handle);
 
 }