/* main.c * * $Id: main.c,v 1.169 2000/12/22 12:05:38 gram Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * Richard Sharpe, 13-Feb-1999, added support for initializing structures * needed by dissect routines * * 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 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * To do: * - Graphs * - Check for end of packet in dissect_* routines. * - Playback window * - Multiple window support * - Add cut/copy/paste * - Create header parsing routines * - Make byte view selections more fancy? * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_IO_H #include /* open/close on win32 */ #endif #ifdef HAVE_DIRECT_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #include #ifdef NEED_SNPRINTF_H # include "snprintf.h" #endif #if defined(HAVE_UCD_SNMP_SNMP_H) #ifdef HAVE_UCD_SNMP_VERSION_H #include #endif /* HAVE_UCD_SNMP_VERSION_H */ #elif defined(HAVE_SNMP_SNMP_H) #ifdef HAVE_SNMP_VERSION_H #include #endif /* HAVE_SNMP_VERSION_H */ #endif /* SNMP */ #ifdef NEED_STRERROR_H #include "strerror.h" #endif #ifdef NEED_GETOPT_H #include "getopt.h" #endif #include #include "main.h" #include "timestamp.h" #include "packet.h" #include "capture.h" #include "summary.h" #include "file.h" #include "menu.h" #include "../menu.h" #include "color.h" #include "color_utils.h" #include "filter_prefs.h" #include "prefs_dlg.h" #include "column.h" #include "print.h" #include "resolv.h" #include "util.h" #include "simple_dialog.h" #include "proto_draw.h" #include "dfilter.h" #include "keys.h" #include "packet_win.h" #include "gtkglobals.h" #include "plugins.h" #include "colors.h" #include "strutil.h" packet_info pi; capture_file cfile; GtkWidget *top_level, *packet_list, *tree_view, *byte_view, *info_bar, *tv_scrollw, *pkt_scrollw; static GtkWidget *bv_scrollw; GdkFont *m_r_font, *m_b_font; guint main_ctx, file_ctx, help_ctx; gchar comp_info_str[256]; gchar *ethereal_path = NULL; gchar *last_open_dir = NULL; ts_type timestamp_type = RELATIVE; GtkStyle *item_style; /* Specifies the field currently selected in the GUI protocol tree */ field_info *finfo_selected = NULL; static char* hfinfo_numeric_format(header_field_info *hfinfo); static void create_main_window(gint, gint, gint, e_prefs*); /* About Ethereal window */ void about_ethereal( GtkWidget *w, gpointer data ) { simple_dialog(ESD_TYPE_INFO, NULL, "Ethereal - Network Protocol Analyzer\n" "Version " VERSION " (C) 1998-2000 Gerald Combs \n" "Compiled with %s\n\n" "Check the man page for complete documentation and\n" "for the list of contributors.\n" "\nSee http://www.ethereal.com/ for more information.", comp_info_str); } /* Match selected byte pattern */ void match_selected_cb(GtkWidget *w, gpointer data) { char *buf; GtkWidget *filter_te; char *ptr, *format, *stringified; int i, dfilter_len, abbrev_len; guint8 *c; header_field_info *hfinfo; filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY); if (!finfo_selected) { simple_dialog(ESD_TYPE_CRIT, NULL, "Error determining selected bytes. Please make\n" "sure you have selected a field within the tree\n" "view to be matched."); return; } hfinfo = finfo_selected->hfinfo; g_assert(hfinfo); abbrev_len = strlen(hfinfo->abbrev); switch(hfinfo->type) { case FT_BOOLEAN: dfilter_len = abbrev_len + 2; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s%s", finfo_selected->value.numeric ? "" : "!", hfinfo->abbrev); break; case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: case FT_INT8: case FT_INT16: case FT_INT24: case FT_INT32: dfilter_len = abbrev_len + 20; buf = g_malloc0(dfilter_len); format = hfinfo_numeric_format(hfinfo); snprintf(buf, dfilter_len, format, hfinfo->abbrev, finfo_selected->value.numeric); break; case FT_IPv4: dfilter_len = abbrev_len + 4 + 15 + 1; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s == %s", hfinfo->abbrev, ipv4_addr_str(&(finfo_selected->value.ipv4))); break; case FT_IPXNET: dfilter_len = abbrev_len + 15; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s == 0x%08x", hfinfo->abbrev, finfo_selected->value.numeric); break; case FT_IPv6: stringified = ip6_to_str((struct e_in6_addr*) &(finfo_selected->value.ipv6)); dfilter_len = abbrev_len + 4 + strlen(stringified) + 1; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s == %s", hfinfo->abbrev, stringified); break; case FT_DOUBLE: dfilter_len = abbrev_len + 30; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s == %f", hfinfo->abbrev, finfo_selected->value.floating); break; case FT_ETHER: dfilter_len = abbrev_len + 22; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s == %s", hfinfo->abbrev, ether_to_str(finfo_selected->value.ether)); break; #if 0 case FT_ABSOLUTE_TIME: case FT_RELATIVE_TIME: memcpy(&fi->value.time, va_arg(ap, struct timeval*), sizeof(struct timeval)); break; case FT_TEXT_ONLY: ; /* nothing */ break; #endif case FT_STRING: dfilter_len = abbrev_len + strlen(finfo_selected->value.string) + 7; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s == \"%s\"", hfinfo->abbrev, finfo_selected->value.string); break; case FT_BYTES: dfilter_len = finfo_selected->length*3 - 1; dfilter_len += abbrev_len + 7; buf = g_malloc0(dfilter_len); snprintf(buf, dfilter_len, "%s == %s", hfinfo->abbrev, bytes_to_str_punct(finfo_selected->value.bytes, finfo_selected->length,':')); break; default: c = cfile.pd + finfo_selected->start; buf = g_malloc0(32 + finfo_selected->length * 3); ptr = buf; sprintf(ptr, "frame[%d] == ", finfo_selected->start); ptr = buf+strlen(buf); if (finfo_selected->length == 1) { sprintf(ptr, "0x%02x", *c++); } else { for (i=0;ilength; i++) { if (i == 0 ) { sprintf(ptr, "%02x", *c++); } else { sprintf(ptr, ":%02x", *c++); } ptr = buf+strlen(buf); } } break; } /* create a new one and set the display filter entry accordingly */ gtk_entry_set_text(GTK_ENTRY(filter_te), buf); /* Run the display filter so it goes in effect. */ filter_packets(&cfile, buf); /* Don't g_free(buf) here. filter_packets() will do it the next time it's called */ } static char* hfinfo_numeric_format(header_field_info *hfinfo) { char *format = NULL; /* Pick the proper format string */ switch(hfinfo->display) { case BASE_DEC: case BASE_NONE: case BASE_OCT: /* I'm lazy */ case BASE_BIN: /* I'm lazy */ switch(hfinfo->type) { case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: format = "%s == %u"; break; case FT_INT8: case FT_INT16: case FT_INT24: case FT_INT32: format = "%s == %d"; break; default: g_assert_not_reached(); ; } break; case BASE_HEX: switch(hfinfo->type) { case FT_UINT8: format = "%s == 0x%02x"; break; case FT_UINT16: format = "%s == 0x%04x"; break; case FT_UINT24: format = "%s == 0x%06x"; break; case FT_UINT32: format = "%s == 0x%08x"; break; default: g_assert_not_reached(); ; } break; default: g_assert_not_reached(); ; } return format; } /* Run the current display filter on the current packet set, and redisplay. */ static void filter_activate_cb(GtkWidget *w, gpointer data) { GtkCombo *filter_cm = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_CM_KEY); GList *filter_list = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_FL_KEY); GList *li, *nl = NULL; gboolean add_filter = TRUE; char *s = gtk_entry_get_text(GTK_ENTRY(w)); /* GtkCombos don't let us get at their list contents easily, so we maintain our own filter list, and feed it to gtk_combo_set_popdown_strings when a new filter is added. */ if (filter_packets(&cfile, g_strdup(s))) { li = g_list_first(filter_list); while (li) { if (li->data && strcmp(s, li->data) == 0) add_filter = FALSE; li = li->next; } if (add_filter) { filter_list = g_list_append(filter_list, g_strdup(s)); li = g_list_first(filter_list); while (li) { nl = g_list_append(nl, strdup(li->data)); li = li->next; } gtk_combo_set_popdown_strings(filter_cm, nl); gtk_entry_set_text(GTK_ENTRY(filter_cm->entry), g_list_last(filter_list)->data); } } } /* redisplay with no display filter */ static void filter_reset_cb(GtkWidget *w, gpointer data) { GtkWidget *filter_te = NULL; if ((filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY))) { gtk_entry_set_text(GTK_ENTRY(filter_te), ""); } filter_packets(&cfile, NULL); } /* GTKClist compare routine, overrides default to allow numeric comparison */ static gint packet_list_compare(GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2) { /* Get row text strings */ char *text1 = GTK_CELL_TEXT (((GtkCListRow *)ptr1)->cell[clist->sort_column])->text; char *text2 = GTK_CELL_TEXT (((GtkCListRow *)ptr2)->cell[clist->sort_column])->text; /* Attempt to convert to numbers */ double num1 = atof(text1); double num2 = atof(text2); gint col_fmt = cfile.cinfo.col_fmt[clist->sort_column]; if ((col_fmt == COL_NUMBER) || (col_fmt == COL_REL_TIME) || (col_fmt == COL_DELTA_TIME) || ((col_fmt == COL_CLS_TIME) && (timestamp_type == RELATIVE)) || ((col_fmt == COL_CLS_TIME) && (timestamp_type == DELTA)) || (col_fmt == COL_UNRES_SRC_PORT) || (col_fmt == COL_UNRES_DST_PORT) || ((num1 != 0) && (num2 != 0) && ((col_fmt == COL_DEF_SRC_PORT) || (col_fmt == COL_RES_SRC_PORT) || (col_fmt == COL_DEF_DST_PORT) || (col_fmt == COL_RES_DST_PORT))) || (col_fmt == COL_PACKET_LENGTH)) { /* Compare numeric column */ if (num1 < num2) return -1; else if (num1 > num2) return 1; else return 0; } else { /* Compare text column */ if (!text2) return (text1 != NULL); if (!text1) return -1; return strcmp(text1, text2); } } /* What to do when a column is clicked */ static void packet_list_click_column_cb(GtkCList *clist, gint column, gpointer data) { if (column == clist->sort_column) { if (clist->sort_type == GTK_SORT_ASCENDING) clist->sort_type = GTK_SORT_DESCENDING; else clist->sort_type = GTK_SORT_ASCENDING; } else { clist->sort_type = GTK_SORT_ASCENDING; gtk_clist_set_sort_column(clist, column); } gtk_clist_sort(clist); } /* mark packets */ static void set_frame_mark(gboolean set, frame_data *frame, gint row) { GdkColor fg, bg; if (frame == NULL || row == -1) return; frame->flags.marked = set; if (set) { color_t_to_gdkcolor(&fg, &prefs.gui_marked_fg); color_t_to_gdkcolor(&bg, &prefs.gui_marked_bg); } else { fg = BLACK; bg = WHITE; } gtk_clist_set_background(GTK_CLIST(packet_list), row, &bg); gtk_clist_set_foreground(GTK_CLIST(packet_list), row, &fg); } static void packet_list_button_pressed_cb(GtkWidget *w, GdkEvent *event, gpointer data) { GdkEventButton *event_button = (GdkEventButton *)event; gint row, column; if (w == NULL || event == NULL) return; if (event->type == GDK_BUTTON_PRESS && event_button->button == 2 && gtk_clist_get_selection_info(GTK_CLIST(w), event_button->x, event_button->y, &row, &column)) { frame_data *fdata = (frame_data *) gtk_clist_get_row_data(GTK_CLIST(w), row); set_frame_mark(!fdata->flags.marked, fdata, row); } } void mark_frame_cb(GtkWidget *w, gpointer data) { if (cfile.current_frame) { /* XXX hum, should better have a "cfile->current_row" here ... */ set_frame_mark(!cfile.current_frame->flags.marked, cfile.current_frame, gtk_clist_find_row_from_data(GTK_CLIST(packet_list), cfile.current_frame)); } } static void mark_all_frames(gboolean set) { frame_data *fdata; if (cfile.plist == NULL) return; for (fdata = cfile.plist; fdata != NULL; fdata = fdata->next) { set_frame_mark(set, fdata, gtk_clist_find_row_from_data(GTK_CLIST(packet_list), fdata)); } } void update_marked_frames(void) { frame_data *fdata; if (cfile.plist == NULL) return; for (fdata = cfile.plist; fdata != NULL; fdata = fdata->next) { if (fdata->flags.marked) set_frame_mark(TRUE, fdata, gtk_clist_find_row_from_data(GTK_CLIST(packet_list), fdata)); } } void mark_all_frames_cb(GtkWidget *w, gpointer data) { mark_all_frames(TRUE); } void unmark_all_frames_cb(GtkWidget *w, gpointer data) { mark_all_frames(FALSE); } /* What to do when a list item is selected/unselected */ static void packet_list_select_cb(GtkWidget *w, gint row, gint col, gpointer evt) { blank_packetinfo(); select_packet(&cfile, row); } static void packet_list_unselect_cb(GtkWidget *w, gint row, gint col, gpointer evt) { unselect_packet(&cfile); } static void tree_view_select_row_cb(GtkCTree *ctree, GList *node, gint column, gpointer user_data) { field_info *finfo; gchar *help_str = NULL; gboolean has_blurb = FALSE; guint length = 0; g_assert(node); finfo = gtk_ctree_node_get_row_data( ctree, GTK_CTREE_NODE(node) ); if (!finfo) return; finfo_selected = finfo; set_menus_for_selected_tree_row(TRUE); if (finfo->hfinfo && finfo->hfinfo->type != FT_TEXT_ONLY) { if (finfo->hfinfo->blurb != NULL && finfo->hfinfo->blurb[0] != '\0') { has_blurb = TRUE; length = strlen(finfo->hfinfo->blurb); } else { length = strlen(finfo->hfinfo->name); } length += strlen(finfo->hfinfo->abbrev) + 10; help_str = g_malloc(sizeof(gchar) * length); sprintf(help_str, "%s (%s)", (has_blurb) ? finfo->hfinfo->blurb : finfo->hfinfo->name, finfo->hfinfo->abbrev); gtk_statusbar_push(GTK_STATUSBAR(info_bar), help_ctx, help_str); g_free(help_str); } packet_hex_print(GTK_TEXT(byte_view), cfile.pd, cfile.current_frame, finfo); } static void tree_view_unselect_row_cb(GtkCTree *ctree, GList *node, gint column, gpointer user_data) { gtk_statusbar_pop(GTK_STATUSBAR(info_bar), help_ctx); finfo_selected = NULL; set_menus_for_selected_tree_row(FALSE); packet_hex_print(GTK_TEXT(byte_view), cfile.pd, cfile.current_frame, NULL); } void collapse_all_cb(GtkWidget *widget, gpointer data) { if (cfile.protocol_tree) collapse_all_tree(cfile.protocol_tree, tree_view); } void expand_all_cb(GtkWidget *widget, gpointer data) { if (cfile.protocol_tree) expand_all_tree(cfile.protocol_tree, tree_view); } void resolve_name_cb(GtkWidget *widget, gpointer data) { if (cfile.protocol_tree) { int tmp = g_resolving_actif; g_resolving_actif = 1; gtk_clist_clear ( GTK_CLIST(tree_view) ); proto_tree_draw(cfile.protocol_tree, tree_view); g_resolving_actif = tmp; } } /* Set the scrollbar placement of a scrolled window based upon pos value: 0 = left, 1 = right */ void set_scrollbar_placement_scrollw(GtkWidget *scrollw, int pos) /* 0=left, 1=right */ { if (pos) { gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(scrollw), GTK_CORNER_TOP_LEFT); } else { gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(scrollw), GTK_CORNER_TOP_RIGHT); } } /* List of all scrolled windows, so we can globally set the scrollbar placement of them. */ static GList *scrolled_windows; /* Add a scrolled window to the list of scrolled windows. */ static void forget_scrolled_window(GtkWidget *scrollw, gpointer data); void remember_scrolled_window(GtkWidget *scrollw) { scrolled_windows = g_list_append(scrolled_windows, scrollw); /* Catch the "destroy" event on the widget, so that we remove it from the list when it's destroyed. */ gtk_signal_connect(GTK_OBJECT(scrollw), "destroy", GTK_SIGNAL_FUNC(forget_scrolled_window), NULL); } /* Remove a scrolled window from the list of scrolled windows. */ static void forget_scrolled_window(GtkWidget *scrollw, gpointer data) { scrolled_windows = g_list_remove(scrolled_windows, scrollw); } static void set_scrollbar_placement_cb(gpointer data, gpointer user_data) { set_scrollbar_placement_scrollw((GtkWidget *)data, *(int *)user_data); } /* Set the scrollbar placement of all scrolled windows based on pos value: 0 = left, 1 = right */ void set_scrollbar_placement_all(int pos) { g_list_foreach(scrolled_windows, set_scrollbar_placement_cb, &pos); } /* Set the selection mode of the packet list window. */ void set_plist_sel_browse(gboolean val) { gboolean old_val; old_val = (GTK_CLIST(packet_list)->selection_mode == GTK_SELECTION_SINGLE); if (val == old_val) { /* * The mode isn't changing, so don't do anything. * In particular, don't gratuitiously unselect the * current packet. * * XXX - why do we have to unselect the current packet * ourselves? The documentation for the GtkCList at * * http://developer.gnome.org/doc/API/gtk/gtkclist.html * * says "Note that setting the widget's selection mode to * one of GTK_SELECTION_BROWSE or GTK_SELECTION_SINGLE will * cause all the items in the GtkCList to become deselected." */ return; } if (finfo_selected) unselect_packet(&cfile); /* Yeah, GTK uses "browse" in the case where we do not, but oh well. I think * "browse" in Ethereal makes more sense than "SINGLE" in GTK+ */ if (val) { gtk_clist_set_selection_mode(GTK_CLIST(packet_list), GTK_SELECTION_SINGLE); } else { gtk_clist_set_selection_mode(GTK_CLIST(packet_list), GTK_SELECTION_BROWSE); } } /* Set the font of the packet list window. */ void set_plist_font(GdkFont *font) { GtkStyle *style; int i; style = gtk_style_new(); gdk_font_unref(style->font); style->font = font; gdk_font_ref(font); gtk_widget_set_style(packet_list, style); /* Compute static column sizes to use during a "-S" capture, so that the columns don't resize during a live capture. */ for (i = 0; i < cfile.cinfo.num_cols; i++) { cfile.cinfo.col_width[i] = gdk_string_width(font, get_column_longest_string(get_column_format(i))); } } static gboolean main_window_delete_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data) { file_quit_cmd_cb(widget, data); /* Say that the window should be deleted. */ return FALSE; } void file_quit_cmd_cb (GtkWidget *widget, gpointer data) { /* XXX - should we check whether the capture file is an unsaved temporary file for a live capture and, if so, pop up a "do you want to exit without saving the capture file?" dialog, and then just return, leaving said dialog box to forcibly quit if the user clicks "OK"? If so, note that this should be done in a subroutine that returns TRUE if we do so, and FALSE otherwise, and that "main_window_delete_event_cb()" should return its return value. */ /* Are we in the middle of reading a capture? */ if (cfile.state == FILE_READ_IN_PROGRESS) { /* Yes, so we can't just close the file and quit, as that may yank the rug out from under the read in progress; instead, just set the state to "FILE_READ_ABORTED" and return - the code doing the read will check for that and, if it sees that, will clean up and quit. */ cfile.state = FILE_READ_ABORTED; } else { /* Close any capture file we have open; on some OSes, you can't unlink a temporary capture file if you have it open. "close_cap_file()" will unlink it after closing it if it's a temporary file. We do this here, rather than after the main loop returns, as, after the main loop returns, the main window may have been destroyed (if this is called due to a "destroy" even on the main window rather than due to the user selecting a menu item), and there may be a crash or other problem when "close_cap_file()" tries to clean up stuff in the main window. XXX - is there a better place to put this? Or should we have a routine that *just* closes the capture file, and doesn't do anything with the UI, which we'd call here, and another routine that calls that routine and also cleans up the UI, which we'd call elsewhere? */ close_cap_file(&cfile, info_bar); /* Exit by leaving the main loop, so that any quit functions we registered get called. */ gtk_main_quit(); } } static void print_usage(void) { fprintf(stderr, "This is GNU " PACKAGE " " VERSION ", compiled with %s\n", comp_info_str); #ifdef HAVE_LIBPCAP fprintf(stderr, "%s [ -vh ] [ -kpQS ] [ -B ] [ -c count ]\n", PACKAGE); fprintf(stderr, "\t[ -f ] [ -i interface ] [ -m ] \n"); fprintf(stderr, "\t[ -n ] [ -o ] ... [ -P ]\n"); fprintf(stderr, "\t[ -r infile ] [ -R ] [ -s snaplen ] \n"); fprintf(stderr, "\t[ -t