3 * $Id: follow_dlg.c,v 1.13 2001/04/10 12:07:39 gram Exp $
5 * Ethereal - Network traffic analyzer
6 * By Gerald Combs <gerald@zing.org>
7 * Copyright 2000 Gerald Combs
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
37 #ifdef HAVE_SYS_TYPES_H
38 #include <sys/types.h>
41 #ifdef HAVE_SYS_STAT_H
46 #include <io.h> /* open/close on win32 */
53 #ifdef NEED_SNPRINTF_H
54 # include "snprintf.h"
58 #include "color_utils.h"
60 #include "follow_dlg.h"
62 #include "dlg_utils.h"
65 #include "gtkglobals.h"
67 #include "simple_dialog.h"
68 #include "packet-ipv6.h"
89 show_stream_t show_stream;
90 show_type_t show_type;
91 char data_out_filename[128 + 1];
95 GtkWidget *hexdump_bt;
96 GtkWidget *follow_save_as_w;
100 static void follow_destroy_cb(GtkWidget * win, gpointer data);
101 static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w);
102 static void follow_load_text(follow_info_t *follow_info);
103 static void follow_print_stream(GtkWidget * w, gpointer parent_w);
104 static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data);
105 static void follow_save_as_ok_cb(GtkWidget * w, GtkFileSelection * fs);
106 static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data);
107 static void follow_stream_om_both(GtkWidget * w, gpointer data);
108 static void follow_stream_om_client(GtkWidget * w, gpointer data);
109 static void follow_stream_om_server(GtkWidget * w, gpointer data);
112 FILE *data_out_file = NULL;
115 #define E_FOLLOW_INFO_KEY "follow_info_key"
117 /* List of "follow_info_t" structures for all "Follow TCP Stream" windows,
118 so we can redraw them all if the colors or font changes. */
119 static GList *follow_infos;
121 /* Add a "follow_info_t" structure to the list. */
123 remember_follow_info(follow_info_t *follow_info)
125 follow_infos = g_list_append(follow_infos, follow_info);
128 /* Remove a "follow_info_t" structure from the list. */
130 forget_follow_info(follow_info_t *follow_info)
132 follow_infos = g_list_remove(follow_infos, follow_info);
136 follow_redraw(gpointer data, gpointer user_data)
138 follow_load_text((follow_info_t *)data);
141 /* Redraw the text in all "Follow TCP Stream" windows. */
143 follow_redraw_all(void)
145 g_list_foreach(follow_infos, follow_redraw, NULL);
148 /* Follow the TCP stream, if any, to which the last packet that we called
149 a dissection routine on belongs (this might be the most recently
150 selected packet, or it might be the last packet in the file). */
152 follow_stream_cb(GtkWidget * w, gpointer data)
154 GtkWidget *streamwindow, *vbox, *txt_scrollw, *text, *filter_te;
155 GtkWidget *hbox, *button, *radio_bt;
156 GtkWidget *stream_om, *stream_menu, *stream_mi;
158 gchar *follow_filter;
159 const char *hostname0, *hostname1;
162 follow_tcp_stats_t stats;
163 follow_info_t *follow_info;
165 /* we got tcp so we can follow */
166 if (pi.ipproto != 6) {
167 simple_dialog(ESD_TYPE_CRIT, NULL,
168 "Error following stream. Please make\n"
169 "sure you have a TCP packet selected.");
173 follow_info = g_new0(follow_info_t, 1);
175 /* Create a temporary file into which to dump the reassembled data
176 from the TCP stream, and set "data_out_file" to refer to it, so
177 that the TCP code will write to it.
179 XXX - it might be nicer to just have the TCP code directly
180 append stuff to the text widget for the TCP stream window,
181 if we can arrange that said window not pop up until we're
183 tmp_fd = create_tempfile(follow_info->data_out_filename,
184 sizeof follow_info->data_out_filename, "follow");
187 simple_dialog(ESD_TYPE_WARN, NULL,
188 "Could not create temporary file %s: %s",
189 follow_info->data_out_filename, strerror(errno));
194 data_out_file = fdopen(tmp_fd, "wb");
195 if (data_out_file == NULL) {
196 simple_dialog(ESD_TYPE_WARN, NULL,
197 "Could not create temporary file %s: %s",
198 follow_info->data_out_filename, strerror(errno));
200 unlink(follow_info->data_out_filename);
205 /* Create a new filter that matches all packets in the TCP stream,
206 and set the display filter entry accordingly */
207 reset_tcp_reassembly();
208 follow_filter = build_follow_filter(&pi);
210 /* Set the display filter entry accordingly */
211 filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY);
212 gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
214 /* Run the display filter so it goes in effect. */
215 filter_packets(&cfile, follow_filter);
217 /* The data_out_file should now be full of the streams information */
218 fclose(data_out_file);
220 /* The data_out_filename file now has all the text that was in the session */
221 streamwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
222 gtk_widget_set_name(streamwindow, "TCP stream window");
224 gtk_signal_connect(GTK_OBJECT(streamwindow), "destroy",
225 GTK_SIGNAL_FUNC(follow_destroy_cb), NULL);
227 if (incomplete_tcp_stream) {
228 gtk_window_set_title(GTK_WINDOW(streamwindow),
229 "Contents of TCP stream (incomplete)");
231 gtk_window_set_title(GTK_WINDOW(streamwindow),
232 "Contents of TCP stream");
234 gtk_widget_set_usize(GTK_WIDGET(streamwindow), DEF_WIDTH,
236 gtk_container_border_width(GTK_CONTAINER(streamwindow), 2);
238 /* setup the container */
239 vbox = gtk_vbox_new(FALSE, 0);
240 gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
242 /* create a scrolled window for the text */
243 txt_scrollw = gtk_scrolled_window_new(NULL, NULL);
244 gtk_box_pack_start(GTK_BOX(vbox), txt_scrollw, TRUE, TRUE, 0);
245 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(txt_scrollw),
248 set_scrollbar_placement_scrollw(txt_scrollw,
249 prefs.gui_scrollbar_on_right);
250 remember_scrolled_window(txt_scrollw);
252 /* create a text box */
253 text = gtk_text_new(NULL, NULL);
254 gtk_text_set_editable(GTK_TEXT(text), FALSE);
255 gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
256 follow_info->text = text;
259 hbox = gtk_hbox_new(FALSE, 1);
260 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
264 follow_tcp_stats(&stats);
267 struct e_in6_addr ipaddr;
268 memcpy(&ipaddr, stats.ip_address[0], 16);
269 hostname0 = get_hostname6(&ipaddr);
270 memcpy(&ipaddr, stats.ip_address[0], 16);
271 hostname1 = get_hostname6(&ipaddr);
274 memcpy(&ipaddr, stats.ip_address[0], 4);
275 hostname0 = get_hostname(ipaddr);
276 memcpy(&ipaddr, stats.ip_address[1], 4);
277 hostname1 = get_hostname(ipaddr);
280 port0 = get_tcp_port(stats.tcp_port[0]);
281 port1 = get_tcp_port(stats.tcp_port[1]);
283 follow_info->is_ipv6 = stats.is_ipv6;
285 stream_om = gtk_option_menu_new();
286 stream_menu = gtk_menu_new();
289 snprintf(string, sizeof(string),
290 "Entire conversation (%u bytes)",
291 stats.bytes_written[0] + stats.bytes_written[1]);
292 stream_mi = gtk_menu_item_new_with_label(string);
293 gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
294 GTK_SIGNAL_FUNC(follow_stream_om_both), follow_info);
295 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
296 gtk_widget_show(stream_mi);
297 follow_info->show_stream = BOTH_HOSTS;
299 /* Host 0 --> Host 1 */
300 snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
301 hostname0, port0, hostname1, port1,
302 stats.bytes_written[0]);
303 stream_mi = gtk_menu_item_new_with_label(string);
304 gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
305 GTK_SIGNAL_FUNC(follow_stream_om_client), follow_info);
306 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
307 gtk_widget_show(stream_mi);
309 /* Host 1 --> Host 0 */
310 snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
311 hostname1, port1, hostname0, port0,
312 stats.bytes_written[1]);
313 stream_mi = gtk_menu_item_new_with_label(string);
314 gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
315 GTK_SIGNAL_FUNC(follow_stream_om_server), follow_info);
316 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
317 gtk_widget_show(stream_mi);
319 gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu);
320 /* Set history to 0th item, i.e., the first item. */
321 gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0);
322 gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0);
324 /* ASCII radio button */
325 radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
326 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
327 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
328 gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
329 GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
331 follow_info->ascii_bt = radio_bt;
332 follow_info->show_type = SHOW_ASCII;
334 /* EBCDIC radio button */
335 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
336 (GTK_RADIO_BUTTON(radio_bt)),
338 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
339 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
340 gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
341 GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
343 follow_info->ebcdic_bt = radio_bt;
345 /* HEX DUMP radio button */
346 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
347 (GTK_RADIO_BUTTON(radio_bt)),
349 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
350 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
351 gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
352 GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
354 follow_info->hexdump_bt = radio_bt;
356 /* Create Close Button */
357 button = gtk_button_new_with_label("Close");
358 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
359 GTK_SIGNAL_FUNC(gtk_widget_destroy),
360 GTK_OBJECT(streamwindow));
361 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
363 /* Catch the "key_press_event" signal in the window, so that we can catch
364 the ESC key being pressed and act as if the "Cancel" button had
366 dlg_set_cancel(streamwindow, button);
368 /* Create Save As Button */
369 button = gtk_button_new_with_label("Save As");
370 gtk_signal_connect(GTK_OBJECT(button), "clicked",
371 GTK_SIGNAL_FUNC(follow_save_as_cmd_cb),
373 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
375 /* Create Print Button */
376 button = gtk_button_new_with_label("Print");
377 gtk_signal_connect(GTK_OBJECT(button), "clicked",
378 GTK_SIGNAL_FUNC(follow_print_stream), follow_info);
379 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
382 /* Tuck away the follow_info object into the window */
383 gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_INFO_KEY,
386 follow_load_text(follow_info);
387 remember_follow_info(follow_info);
389 data_out_file = NULL;
391 /* Make sure this widget gets destroyed if we quit the main loop,
392 so that if we exit, we clean up any temporary files we have
393 for "Follow TCP Stream" windows. */
394 gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
395 gtk_widget_show_all(streamwindow);
398 /* The destroy call back has the responsibility of
399 * unlinking the temporary file */
401 follow_destroy_cb(GtkWidget *w, gpointer data)
403 follow_info_t *follow_info;
405 follow_info = gtk_object_get_data(GTK_OBJECT(w), E_FOLLOW_INFO_KEY);
406 unlink(follow_info->data_out_filename);
407 gtk_widget_destroy(w);
408 forget_follow_info(follow_info);
412 /* XXX - can I emulate follow_charset_toggle_cb() instead of having
413 * 3 different functions here? */
415 follow_stream_om_both(GtkWidget *w, gpointer data)
417 follow_info_t *follow_info = data;
418 follow_info->show_stream = BOTH_HOSTS;
419 follow_load_text(follow_info);
423 follow_stream_om_client(GtkWidget *w, gpointer data)
425 follow_info_t *follow_info = data;
426 follow_info->show_stream = FROM_CLIENT;
427 follow_load_text(follow_info);
431 follow_stream_om_server(GtkWidget *w, gpointer data)
433 follow_info_t *follow_info = data;
434 follow_info->show_stream = FROM_SERVER;
435 follow_load_text(follow_info);
439 /* Handles the ASCII/EBCDIC toggling */
441 follow_charset_toggle_cb(GtkWidget * w, gpointer data)
443 follow_info_t *follow_info = data;
445 if (GTK_TOGGLE_BUTTON(follow_info->ebcdic_bt)->active)
446 follow_info->show_type = SHOW_EBCDIC;
447 else if (GTK_TOGGLE_BUTTON(follow_info->hexdump_bt)->active)
448 follow_info->show_type = SHOW_HEXDUMP;
449 else if (GTK_TOGGLE_BUTTON(follow_info->ascii_bt)->active)
450 follow_info->show_type = SHOW_ASCII;
452 g_assert_not_reached();
454 follow_load_text(follow_info);
457 #define FLT_BUF_SIZE 1024
459 follow_read_stream(follow_info_t *follow_info,
460 void (*print_line) (char *, int, gboolean, void *),
465 guint8 client_addr[MAX_IPADDR_LEN];
466 guint16 client_port = 0;
468 guint16 current_pos, global_client_pos = 0, global_server_pos = 0;
472 iplen = (follow_info->is_ipv6) ? 16 : 4;
474 data_out_file = fopen(follow_info->data_out_filename, "rb");
476 char buffer[FLT_BUF_SIZE];
478 while (fread(&sc, 1, sizeof(sc), data_out_file)) {
479 if (client_port == 0) {
480 memcpy(client_addr, sc.src_addr, iplen);
481 client_port = sc.src_port;
484 if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
485 client_port == sc.src_port) {
487 global_pos = &global_client_pos;
488 if (follow_info->show_stream == FROM_SERVER) {
494 global_pos = &global_server_pos;
495 if (follow_info->show_stream == FROM_CLIENT) {
500 while (sc.dlen > 0) {
501 bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
502 nchars = fread(buffer, 1, bcount, data_out_file);
507 switch (follow_info->show_type) {
509 /* If our native arch is ASCII, call: */
510 EBCDIC_to_ASCII(buffer, nchars);
511 (*print_line) (buffer, nchars, is_server, arg);
514 /* If our native arch is EBCDIC, call:
515 * ASCII_TO_EBCDIC(buffer, nchars);
517 (*print_line) (buffer, nchars, is_server, arg);
521 while (current_pos < nchars) {
523 gchar hexchars[] = "0123456789abcdef";
525 /* is_server indentation : put 63 spaces at the begenning
527 sprintf(hexbuf, (is_server &&
528 follow_info->show_stream == BOTH_HOSTS) ?
531 "%08X ", *global_pos);
532 cur = strlen(hexbuf);
533 for (i = 0; i < 16 && current_pos + i < nchars;
536 hexchars[(buffer[current_pos + i] & 0xf0)
539 hexchars[buffer[current_pos + i] & 0x0f];
548 hexbuf[cur++] = '\n';
550 (*print_line) (hexbuf, strlen(hexbuf), is_server, arg);
557 if (ferror(data_out_file)) {
558 simple_dialog(ESD_TYPE_WARN, NULL,
559 "Error reading temporary file %s: %s", follow_info->data_out_filename,
562 fclose(data_out_file);
563 data_out_file = NULL;
565 simple_dialog(ESD_TYPE_WARN, NULL,
566 "Could not open temporary file %s: %s", follow_info->data_out_filename,
572 * XXX - for text printing, we probably want to wrap lines at 80 characters;
573 * for PostScript printing, we probably want to wrap them at the appropriate
574 * width, and perhaps put some kind of dingbat (to use the technical term)
575 * to indicate a wrapped line, along the lines of what's done when displaying
576 * this in a window, as per Warren Young's suggestion.
578 * For now, we support only text printing.
581 follow_print_text(char *buffer, int nchars, gboolean is_server, void *arg)
585 fwrite(buffer, nchars, 1, fh);
589 follow_print_stream(GtkWidget * w, gpointer data)
594 follow_info_t *follow_info = data;
596 switch (prefs.pr_dest) {
598 print_dest = prefs.pr_cmd;
603 print_dest = prefs.pr_file;
606 default: /* "Can't happen" */
607 simple_dialog(ESD_TYPE_CRIT, NULL,
608 "Couldn't figure out where to send the print "
609 "job. Check your preferences.");
613 fh = open_print_dest(to_file, print_dest);
617 simple_dialog(ESD_TYPE_WARN, NULL,
618 "Couldn't run print command %s.", prefs.pr_cmd);
622 simple_dialog(ESD_TYPE_WARN, NULL,
623 file_write_error_message(errno), prefs.pr_file);
629 print_preamble(fh, PR_FMT_TEXT);
630 follow_read_stream(follow_info, follow_print_text, fh);
631 print_finale(fh, PR_FMT_TEXT);
632 close_print_dest(to_file, fh);
636 follow_add_to_gtk_text(char *buffer, int nchars, gboolean is_server,
639 GtkWidget *text = arg;
643 /* While our isprint() hack is in place, we
644 * have to use convert some chars to '.' in order
645 * to be able to see the data we *should* see
646 * in the GtkText widget.
650 for (i = 0; i < nchars; i++) {
651 if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
654 else if (! isprint(buffer[i])) {
661 color_t_to_gdkcolor(&fg, &prefs.st_server_fg);
662 color_t_to_gdkcolor(&bg, &prefs.st_server_bg);
664 color_t_to_gdkcolor(&fg, &prefs.st_client_fg);
665 color_t_to_gdkcolor(&bg, &prefs.st_client_bg);
667 gtk_text_insert(GTK_TEXT(text), m_r_font, &fg, &bg, buffer, nchars);
671 follow_load_text(follow_info_t *follow_info)
675 /* Delete any info already in text box */
676 bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
677 if (bytes_already > 0) {
678 gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
679 gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
682 /* stop the updates while we fill the text box */
683 gtk_text_freeze(GTK_TEXT(follow_info->text));
684 follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
685 gtk_text_thaw(GTK_TEXT(follow_info->text));
690 * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
691 * any, so that if somebody tries to do "Save"
692 * while there's already a "Save TCP Follow Stream" window up, we just pop
693 * up the existing one, rather than creating a new one.
696 follow_save_as_cmd_cb(GtkWidget *w, gpointer data)
698 GtkWidget *ok_bt, *new_win;
699 follow_info_t *follow_info = data;
701 if (follow_info->follow_save_as_w != NULL) {
702 /* There's already a dialog box; reactivate it. */
703 reactivate_window(follow_info->follow_save_as_w);
707 new_win = gtk_file_selection_new("Ethereal: Save TCP Follow Stream As");
708 follow_info->follow_save_as_w = new_win;
709 gtk_signal_connect(GTK_OBJECT(new_win), "destroy",
710 GTK_SIGNAL_FUNC(follow_save_as_destroy_cb), follow_info);
712 /* Tuck away the follow_info object into the window */
713 gtk_object_set_data(GTK_OBJECT(new_win), E_FOLLOW_INFO_KEY,
716 /* If we've opened a file, start out by showing the files in the directory
717 in which that file resided. */
719 gtk_file_selection_complete(GTK_FILE_SELECTION(new_win),
722 /* Connect the ok_button to file_save_as_ok_cb function and pass along a
723 pointer to the file selection box widget */
724 ok_bt = GTK_FILE_SELECTION(new_win)->ok_button;
725 gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
726 (GtkSignalFunc) follow_save_as_ok_cb,
729 /* Connect the cancel_button to destroy the widget */
730 gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION
731 (new_win)->cancel_button),
733 (GtkSignalFunc) gtk_widget_destroy,
734 GTK_OBJECT(new_win));
736 /* Catch the "key_press_event" signal in the window, so that we can catch
737 the ESC key being pressed and act as if the "Cancel" button had
739 dlg_set_cancel(new_win,
740 GTK_FILE_SELECTION(new_win)->cancel_button);
742 gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
743 gtk_widget_show_all(new_win);
748 follow_save_as_ok_cb(GtkWidget * w, GtkFileSelection * fs)
751 follow_info_t *follow_info;
754 to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
756 gtk_widget_hide(GTK_WIDGET(fs));
757 follow_info = gtk_object_get_data(GTK_OBJECT(fs), E_FOLLOW_INFO_KEY);
758 gtk_widget_destroy(GTK_WIDGET(fs));
760 fh = fopen(to_name, "wb");
762 simple_dialog(ESD_TYPE_WARN, NULL,
763 file_write_error_message(errno), to_name);
767 follow_read_stream(follow_info, follow_print_text, fh);
773 follow_save_as_destroy_cb(GtkWidget * win, gpointer data)
775 follow_info_t *follow_info = data;
777 /* Note that we no longer have a dialog box. */
778 follow_info->follow_save_as_w = NULL;