2 * Routines for SMTP packet disassembly
4 * $Id: packet-smtp.c,v 1.19 2001/07/03 04:56:46 guy Exp $
6 * Copyright (c) 2000 by Richard Sharpe <rsharpe@ns.aus.com>
8 * Ethereal - Network traffic analyzer
10 * Copyright 1999 Gerald Combs
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30 #ifdef HAVE_SYS_TYPES_H
31 # include <sys/types.h>
34 #ifdef HAVE_NETINET_IN_H
35 #include <netinet/in.h>
45 #include "conversation.h"
50 #define TCP_PORT_SMTP 25
52 void proto_reg_handoff_smtp(void);
54 static int proto_smtp = -1;
56 static int hf_smtp_req = -1;
57 static int hf_smtp_rsp = -1;
59 static int ett_smtp = -1;
61 static int global_smtp_tcp_port = TCP_PORT_SMTP;
64 * A CMD is an SMTP command, MESSAGE is the message portion, and EOM is the
65 * last part of a message
68 #define SMTP_PDU_CMD 0
69 #define SMTP_PDU_MESSAGE 1
70 #define SMTP_PDU_EOM 2
72 struct smtp_proto_data {
76 static int smtp_packet_init_count = 100;
78 struct smtp_request_key {
83 * State information stored with a conversation.
85 struct smtp_request_val {
86 gboolean reading_data; /* Reading message data, not commands */
87 guint16 crlf_seen; /* Have we seen a CRLF on the end of a packet */
90 GHashTable *smtp_request_hash = NULL;
91 GMemChunk *smtp_request_keys = NULL;
92 GMemChunk *smtp_request_vals = NULL;
93 GMemChunk *smtp_packet_infos = NULL;
97 smtp_equal(gconstpointer v, gconstpointer w)
99 struct smtp_request_key *v1 = (struct smtp_request_key *)v;
100 struct smtp_request_key *v2 = (struct smtp_request_key *)w;
102 #if defined(DEBUG_SMTP_HASH)
103 printf("Comparing %08X\n and %08X\n",
104 v1->conversation, v2->conversation);
107 if (v1->conversation == v2->conversation)
115 smtp_hash(gconstpointer v)
117 struct smtp_request_key *key = (struct smtp_request_key *)v;
120 val = key->conversation;
122 #if defined(DEBUG_SMTP_HASH)
123 printf("SMTP Hash calculated as %u\n", val);
131 smtp_init_protocol(void)
133 #if defined(DEBUG_SMTP_HASH)
134 printf("Initializing SMTP hashtable area\n");
137 if (smtp_request_hash)
138 g_hash_table_destroy(smtp_request_hash);
139 if (smtp_request_keys)
140 g_mem_chunk_destroy(smtp_request_keys);
141 if (smtp_request_vals)
142 g_mem_chunk_destroy(smtp_request_vals);
143 if (smtp_packet_infos)
144 g_mem_chunk_destroy(smtp_packet_infos);
146 smtp_request_hash = g_hash_table_new(smtp_hash, smtp_equal);
147 smtp_request_keys = g_mem_chunk_new("smtp_request_keys",
148 sizeof(struct smtp_request_key),
149 smtp_packet_init_count * sizeof(struct smtp_request_key), G_ALLOC_AND_FREE);
150 smtp_request_vals = g_mem_chunk_new("smtp_request_vals",
151 sizeof(struct smtp_request_val),
152 smtp_packet_init_count * sizeof(struct smtp_request_val), G_ALLOC_AND_FREE);
153 smtp_packet_infos = g_mem_chunk_new("smtp_packet_infos",
154 sizeof(struct smtp_proto_data),
155 smtp_packet_init_count * sizeof(struct smtp_proto_data), G_ALLOC_AND_FREE);
160 dissect_smtp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
162 struct smtp_proto_data *frame_data;
163 proto_tree *smtp_tree;
167 conversation_t *conversation;
168 struct smtp_request_key request_key, *new_request_key;
169 struct smtp_request_val *request_val;
172 gboolean eom_seen = FALSE;
174 gboolean is_continuation_line;
177 /* As there is no guarantee that we will only see frames in the
178 * the SMTP conversation once, and that we will see them in
179 * order - in Ethereal, the user could randomly click on frames
180 * in the conversation in any order in which they choose - we
181 * have to store information with each frame indicating whether
182 * it contains commands or data or an EOM indication.
184 * XXX - what about frames that contain *both*? TCP is a
185 * byte-stream protocol, and there are no guarantees that
186 * TCP segment boundaries will correspond to SMTP commands
187 * or EOM indications.
189 * We only need that for the client->server stream; responses
190 * are easy to manage.
192 * If we have per frame data, use that, else, we must be on the first
193 * pass, so we figure it out on the first pass.
196 /* Find out what conversation this packet is part of ... but only
197 * if we have no information on this packet, so find the per-frame
201 /* SMTP messages have a simple format ... */
203 request = pinfo -> destport == pinfo -> match_port;
206 * Get the first line from the buffer.
208 * Note that "tvb_find_line_end()" will return a value that
209 * is not longer than what's in the buffer, so the
210 * "tvb_get_ptr()" call won't throw an exception.
212 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset);
213 line = tvb_get_ptr(tvb, offset, linelen);
215 frame_data = p_get_proto_data(pinfo->fd, proto_smtp);
219 conversation = find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype,
220 pinfo->srcport, pinfo->destport, 0);
221 if (conversation == NULL) { /* No conversation, create one */
222 conversation = conversation_new(&pinfo->src, &pinfo->dst, pinfo->ptype,
223 pinfo->srcport, pinfo->destport, NULL,
229 * Check for and insert an entry in the request table if does not exist
231 request_key.conversation = conversation->index;
233 request_val = (struct smtp_request_val *)g_hash_table_lookup(smtp_request_hash, &request_key);
235 if (!request_val) { /* Create one */
237 new_request_key = g_mem_chunk_alloc(smtp_request_keys);
238 new_request_key->conversation = conversation->index;
240 request_val = g_mem_chunk_alloc(smtp_request_vals);
241 request_val->reading_data = FALSE;
242 request_val->crlf_seen = 0;
244 g_hash_table_insert(smtp_request_hash, new_request_key, request_val);
249 * Check whether or not this packet is an end of message packet
250 * We should look for CRLF.CRLF and they may be split.
251 * We have to keep in mind that we may see what we want on
252 * two passes through here ...
255 if (request_val->reading_data) {
258 * The order of these is important ... We want to avoid
259 * cases where there is a CRLF at the end of a packet and a
260 * .CRLF at the begining of the same packet.
263 if ((request_val->crlf_seen && tvb_strneql(tvb, offset, ".\r\n", 3) == 0) ||
264 tvb_strneql(tvb, offset, "\r\n.\r\n", 5) == 0) {
270 if (tvb_strneql(tvb, offset + tvb_length_remaining(tvb, offset) - 2, "\r\n", 2) == 0) {
272 request_val->crlf_seen = 1;
277 request_val->crlf_seen = 0;
283 * OK, Check if we have seen a DATA request. We do it here for
284 * simplicity, but we have to be careful below.
289 frame_data = g_mem_chunk_alloc(smtp_packet_infos);
291 if (request_val->reading_data) {
293 * This is message data.
295 if (eom_seen) { /* Seen the EOM */
298 * Everything that comes after it is commands.
300 * XXX - what if the EOM isn't at the beginning of
301 * the TCP segment? It can occur anywhere....
303 frame_data->pdu_type = SMTP_PDU_EOM;
304 request_val->reading_data = FALSE;
307 * Message data with no EOM.
309 frame_data->pdu_type = SMTP_PDU_MESSAGE;
313 * This is commands - unless the capture started in the
314 * middle of a session, and we're in the middle of data.
315 * To quote RFC 821, "Command codes are four alphabetic
316 * characters"; if we don't see four alphabetic characters
317 * and, if there's anything else in the line, a space, we
318 * assume it's not a command.
319 * (We treat only A-Z and a-z as alphabetic.)
321 #define ISALPHA(c) (((c) >= 'A' && (c) <= 'Z') || \
322 ((c) >= 'a' && (c) <= 'z'))
323 if (linelen >= 4 && ISALPHA(line[0]) && ISALPHA(line[1]) &&
324 ISALPHA(line[2]) && ISALPHA(line[3]) &&
325 (linelen == 4 || line[4] == ' ')) {
326 if (strncasecmp(line, "DATA", 4) == 0) {
330 * This is a command, but everything that comes after it,
331 * until an EOM, is data.
333 frame_data->pdu_type = SMTP_PDU_CMD;
334 request_val->reading_data = TRUE;
341 frame_data->pdu_type = SMTP_PDU_CMD;
347 * Assume it's message data.
350 frame_data->pdu_type = SMTP_PDU_MESSAGE;
356 p_add_proto_data(pinfo->fd, proto_smtp, frame_data);
362 * From here, we simply add items to the tree and info to the info
366 if (check_col(pinfo->fd, COL_PROTOCOL))
367 col_set_str(pinfo->fd, COL_PROTOCOL, "SMTP");
369 if (check_col(pinfo->fd, COL_INFO)) { /* Add the appropriate type here */
372 * If it is a request, we have to look things up, otherwise, just
373 * display the right things
378 /* We must have frame_data here ... */
380 switch (frame_data->pdu_type) {
381 case SMTP_PDU_MESSAGE:
383 col_set_str(pinfo->fd, COL_INFO, "Message Body");
388 col_add_fstr(pinfo->fd, COL_INFO, "EOM: %s",
389 format_text(line, linelen));
394 col_add_fstr(pinfo->fd, COL_INFO, "Command: %s",
395 format_text(line, linelen));
403 col_add_fstr(pinfo->fd, COL_INFO, "Response: %s",
404 format_text(line, linelen));
409 if (tree) { /* Build the tree info ... */
411 ti = proto_tree_add_item(tree, proto_smtp, tvb, offset,
412 tvb_length_remaining(tvb, offset), FALSE);
413 smtp_tree = proto_item_add_subtree(ti, ett_smtp);
414 proto_tree_add_boolean_hidden(smtp_tree, (request ? hf_smtp_req : hf_smtp_rsp),
415 tvb, offset, 4, TRUE);
419 * Check out whether or not we can see a command in there ...
420 * What we are looking for is not data_seen and the word DATA
423 * We will see DATA and request_val->data_seen when we process the
424 * tree view after we have seen a DATA packet when processing
425 * the packet list pane.
427 * On the first pass, we will not have any info on the packets
428 * On second and subsequent passes, we will.
431 switch (frame_data->pdu_type) {
433 case SMTP_PDU_MESSAGE:
437 * Put its lines into the protocol tree, a line at a time.
439 while (tvb_offset_exists(tvb, offset)) {
442 * Find the end of the line.
444 tvb_find_line_end(tvb, offset, -1, &next_offset);
449 proto_tree_add_text(smtp_tree, tvb, offset, next_offset - offset,
451 tvb_format_text(tvb, offset, next_offset - offset));
454 * Step to the next line.
456 offset = next_offset;
465 * End-of-message-body indicator.
467 * XXX - what about stuff after the first line?
468 * Unlikely, as the client should wait for a response to the
469 * DATA command this terminates before sending another
470 * request, but we should probably handle it.
472 proto_tree_add_text(smtp_tree, tvb, offset, linelen,
473 "EOM: %s", format_text(line, linelen));
482 * XXX - what about stuff after the first line?
483 * Unlikely, as the client should wait for a response to the
484 * previous command before sending another request, but we
485 * should probably handle it.
491 proto_tree_add_text(smtp_tree, tvb, offset, cmdlen,
492 "Command: %s", format_text(line, cmdlen));
494 proto_tree_add_text(smtp_tree, tvb, offset + 5, linelen - 5,
495 "Parameter: %s", format_text(line + 5, linelen - 5));
504 * Process the response, a line at a time, until we hit a line
505 * that doesn't have a continuation indication on it.
508 while (tvb_offset_exists(tvb, offset)) {
511 * Find the end of the line.
513 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset);
516 * Is it a continuation line?
518 is_continuation_line =
519 (linelen >= 4 && tvb_get_guint8(tvb, offset + 3) == '-');
522 * Put it into the protocol tree.
524 proto_tree_add_text(smtp_tree, tvb, offset, 3,
525 "Response: %s", tvb_format_text(tvb, offset, 3));
527 proto_tree_add_text(smtp_tree, tvb, offset + 4, linelen - 4,
528 "Parameter: %s", tvb_format_text(tvb, offset + 4, linelen - 4));
532 * Step past this line.
534 offset = next_offset;
537 * If it's not a continuation line, quit.
539 if (!is_continuation_line)
548 /* Register all the bits needed by the filtering engine */
551 proto_register_smtp(void)
553 static hf_register_info hf[] = {
555 { "Request", "smtp.req", FT_BOOLEAN, BASE_NONE, NULL, 0x0, "", HFILL }},
558 { "Response", "smtp.rsp", FT_BOOLEAN, BASE_NONE, NULL, 0x0, "", HFILL }},
560 static gint *ett[] = {
563 /*module_t *smtp_module = NULL; */ /* Not yet used */
565 /* No Configuration options to register? */
567 proto_smtp = proto_register_protocol("Simple Mail Transfer Protocol",
570 proto_register_field_array(proto_smtp, hf, array_length(hf));
571 proto_register_subtree_array(ett, array_length(ett));
572 register_init_routine(&smtp_init_protocol);
576 /* The registration hand-off routine */
578 proto_reg_handoff_smtp(void)
580 static int smtp_prefs_initialized = FALSE;
581 static int tcp_port = 0;
583 if (smtp_prefs_initialized) {
585 dissector_delete("tcp.port", tcp_port, dissect_smtp);
590 smtp_prefs_initialized = TRUE;
594 tcp_port = global_smtp_tcp_port;
596 dissector_add("tcp.port", global_smtp_tcp_port, dissect_smtp, proto_smtp);