s/stat/eth_stat/ and s/unlink/eth_unlink/ to avoid non-ASCII filename problems on...
[obnox/wireshark/wip.git] / gtk / follow_tcp.c
1 /* follow_tcp.c
2  * TCP specific routines for following traffic streams
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
23  * USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include <gtk/gtk.h>
31
32 #include <stdio.h>
33 #include <string.h>
34
35
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39
40 #include <ctype.h>
41
42 #include "isprint.h"
43
44 #include "file_util.h"
45 #include "color.h"
46 #include "colors.h"
47 #include "file.h"
48 #include "follow_tcp.h"
49 #include <epan/follow.h>
50 #include "dlg_utils.h"
51 #include "file_dlg.h"
52 #include "keys.h"
53 #include "globals.h"
54 #include "main.h"
55 #include "alert_box.h"
56 #include "simple_dialog.h"
57 #include <epan/dissectors/packet-ipv6.h>
58 #include <epan/prefs.h>
59 #include <epan/addr_resolv.h>
60 #include <epan/charsets.h>
61 #include "tempfile.h"
62 #include "gui_utils.h"
63 #include <epan/epan_dissect.h>
64 #include <epan/filesystem.h>
65 #include "compat_macros.h"
66 #include <epan/ipproto.h>
67 #include "print_mswin.h"
68 #include "font_utils.h"
69 #include "help_dlg.h"
70 #include <epan/charsets.h>
71
72 #include "follow_stream.h"
73
74 /* This is backwards-compatibility code for old versions of GTK+ (2.2.1 and
75  * earlier).  It defines the new wrap behavior (unknown in earlier versions)
76  * as the old (slightly buggy) wrap behavior.
77  */
78 #ifndef GTK_WRAP_WORD_CHAR
79 #define GTK_WRAP_WORD_CHAR GTK_WRAP_WORD
80 #endif
81
82 /* With MSVC and a libwireshark.dll, we need a special declaration. */
83 WS_VAR_IMPORT FILE *data_out_file;
84
85 static void
86 follow_redraw(gpointer data, gpointer user_data _U_)
87 {
88         follow_load_text((follow_info_t *)data);
89 }
90
91 /* Redraw the text in all "Follow TCP Stream" windows. */
92 void
93 follow_tcp_redraw_all(void)
94 {
95         g_list_foreach(follow_infos, follow_redraw, NULL);
96 }
97
98 /* Follow the TCP stream, if any, to which the last packet that we called
99    a dissection routine on belongs (this might be the most recently
100    selected packet, or it might be the last packet in the file). */
101 void
102 follow_tcp_stream_cb(GtkWidget * w, gpointer data _U_)
103 {
104         GtkWidget       *filter_te;
105         int             tmp_fd;
106         gchar           *follow_filter;
107         const gchar     *previous_filter;
108         int             filter_out_filter_len;
109         const char      *hostname0, *hostname1;
110         char            *port0, *port1;
111         gchar           *server_to_client_string = NULL;
112         gchar           *client_to_server_string = NULL;
113         gchar           *both_directions_string = NULL;
114         follow_stats_t stats;
115         follow_info_t   *follow_info;
116         tcp_stream_chunk sc;
117         size_t              nchars;
118
119         /* we got tcp so we can follow */
120         if (cfile.edt->pi.ipproto != IP_PROTO_TCP) {
121                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
122                               "Error following stream.  Please make\n"
123                               "sure you have a TCP packet selected.");
124                 return;
125         }
126
127         follow_info = g_new0(follow_info_t, 1);
128         follow_info->follow_type = FOLLOW_TCP;
129
130         /* Create a temporary file into which to dump the reassembled data
131            from the TCP stream, and set "data_out_file" to refer to it, so
132            that the TCP code will write to it.
133
134            XXX - it might be nicer to just have the TCP code directly
135            append stuff to the text widget for the TCP stream window,
136            if we can arrange that said window not pop up until we're
137            done. */
138         tmp_fd = create_tempfile(follow_info->data_out_filename,
139                         sizeof follow_info->data_out_filename, "follow");
140
141         if (tmp_fd == -1) {
142             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
143                           "Could not create temporary file %s: %s",
144                           follow_info->data_out_filename, strerror(errno));
145             g_free(follow_info);
146             return;
147         }
148
149         data_out_file = fdopen(tmp_fd, "w+b");
150         if (data_out_file == NULL) {
151             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
152                           "Could not create temporary file %s: %s",
153                           follow_info->data_out_filename, strerror(errno));
154             eth_close(tmp_fd);
155             eth_unlink(follow_info->data_out_filename);
156             g_free(follow_info);
157             return;
158         }
159
160         /* Create a new filter that matches all packets in the TCP stream,
161            and set the display filter entry accordingly */
162         reset_tcp_reassembly();
163         follow_filter = build_follow_filter(&cfile.edt->pi);
164
165         /* Set the display filter entry accordingly */
166         filter_te = OBJECT_GET_DATA(w, E_DFILTER_TE_KEY);
167
168         /* needed in follow_filter_out_stream(), is there a better way? */
169         follow_info->filter_te = filter_te;
170
171         /* save previous filter, const since we're not supposed to alter */
172         previous_filter =
173             (const gchar *)gtk_entry_get_text(GTK_ENTRY(filter_te));
174
175         /* allocate our new filter. API claims g_malloc terminates program on failure */
176         /* my calc for max alloc needed is really +10 but when did a few extra bytes hurt ? */
177         filter_out_filter_len = strlen(follow_filter) + strlen(previous_filter) + 16;
178         follow_info->filter_out_filter = (gchar *)g_malloc(filter_out_filter_len);
179
180         /* append the negation */
181         if(strlen(previous_filter)) {
182             g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
183             "%s and !(%s)", previous_filter, follow_filter);
184         } else {
185             g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
186             "!(%s)", follow_filter);
187         }
188
189         gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
190
191         /* Run the display filter so it goes in effect - even if it's the
192            same as the previous display filter. */
193         main_filter_packets(&cfile, follow_filter, TRUE);
194
195         /* Free the filter string, as we're done with it. */
196         g_free(follow_filter);
197
198         /* Check whether we got any data written to the file. */
199         if (empty_tcp_stream) {
200             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
201                           "The packets in the capture file for that stream have no data.");
202             eth_close(tmp_fd);
203             eth_unlink(follow_info->data_out_filename);
204             g_free(follow_info);
205             return;
206         }
207
208         /* Go back to the top of the file and read the first tcp_stream_chunk
209          * to ensure that the IP addresses and port numbers in the drop-down
210          * list are tied to the correct lines displayed by follow_read_stream()
211          * later on (which also reads from this file).  Close the file when
212          * we're done.
213          *
214          * We read the data now, before we pop up a window, in case the
215          * read fails.  We use the data later.
216          */
217
218         rewind(data_out_file);
219         nchars=fread(&sc, 1, sizeof(sc), data_out_file);
220         if (nchars != sizeof(sc)) {
221             if (ferror(data_out_file)) {
222                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
223                               "Could not read from temporary file %s: %s",
224                               follow_info->data_out_filename, strerror(errno));
225             } else {
226                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
227                               "Short read from temporary file %s: expected %lu, got %lu",
228                               follow_info->data_out_filename,
229                               (unsigned long)sizeof(sc),
230                               (unsigned long)nchars);
231             }
232             eth_close(tmp_fd);
233             eth_unlink(follow_info->data_out_filename);
234             g_free(follow_info);
235             return;
236         }
237         fclose(data_out_file);
238
239         /* The data_out_filename file now has all the text that was in the
240            session (this is dumped to file by the TCP dissector). */
241
242         /* Stream to show */
243         follow_stats(&stats);
244
245         if (stats.is_ipv6) {
246                 struct e_in6_addr ipaddr;
247                 memcpy(&ipaddr, stats.ip_address[0], 16);
248                 hostname0 = get_hostname6(&ipaddr);
249                 memcpy(&ipaddr, stats.ip_address[0], 16);
250                 hostname1 = get_hostname6(&ipaddr);
251         } else {
252                 guint32 ipaddr;
253                 memcpy(&ipaddr, stats.ip_address[0], 4);
254                 hostname0 = get_hostname(ipaddr);
255                 memcpy(&ipaddr, stats.ip_address[1], 4);
256                 hostname1 = get_hostname(ipaddr);
257         }
258
259         follow_info->is_ipv6 = stats.is_ipv6;
260
261         port0 = get_tcp_port(stats.port[0]);
262         port1 = get_tcp_port(stats.port[1]);
263
264         /* Host 0 --> Host 1 */
265         if(sc.src_port == strtol(port0, NULL, 10)) {
266                 server_to_client_string =
267                         g_strdup_printf("%s:%s --> %s:%s (%u bytes)",
268                                         hostname0, port0,
269                                         hostname1, port1,
270                                         stats.bytes_written[0]);
271         } else {
272                 server_to_client_string =
273                         g_strdup_printf("%s:%s --> %s:%s (%u bytes)",
274                                         hostname1, port1,
275                                         hostname0,port0,
276                                         stats.bytes_written[0]);
277         }
278
279         /* Host 1 --> Host 0 */
280         if(sc.src_port == strtol(port0, NULL, 10)) {
281                 client_to_server_string =
282                         g_strdup_printf("%s:%s --> %s:%s (%u bytes)",
283                                         hostname1, port1,
284                                         hostname0, port0,
285                                         stats.bytes_written[1]);
286
287         } else {
288                 client_to_server_string =
289                         g_strdup_printf("%s:%s --> %s:%s (%u bytes)",
290                                         hostname0, port0,
291                                         hostname1, port1,
292                                         stats.bytes_written[1]);
293         }
294
295         /* Both Stream Directions */
296         both_directions_string = g_strdup_printf("Entire conversation (%u bytes)", stats.bytes_written[0] + stats.bytes_written[1]);
297
298         follow_stream("Follow TCP Stream", follow_info, both_directions_string,
299                       server_to_client_string, client_to_server_string);
300
301         g_free(both_directions_string);
302         g_free(server_to_client_string);
303         g_free(client_to_server_string);
304
305         data_out_file = NULL;
306 }
307
308 #define FLT_BUF_SIZE 1024
309
310 /*
311  * XXX - the routine pointed to by "print_line" doesn't get handed lines,
312  * it gets handed bufferfuls.  That's fine for "follow_write_raw()"
313  * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
314  * the "print_line()" routine from "print.c", and as that routine might
315  * genuinely expect to be handed a line (if, for example, it's using
316  * some OS or desktop environment's printing API, and that API expects
317  * to be handed lines), "follow_print_text()" should probably accumulate
318  * lines in a buffer and hand them "print_line()".  (If there's a
319  * complete line in a buffer - i.e., there's nothing of the line in
320  * the previous buffer or the next buffer - it can just hand that to
321  * "print_line()" after filtering out non-printables, as an
322  * optimization.)
323  *
324  * This might or might not be the reason why C arrays display
325  * correctly but get extra blank lines very other line when printed.
326  */
327 frs_return_t
328 follow_read_tcp_stream(follow_info_t *follow_info,
329                        gboolean (*print_line)(char *, size_t, gboolean, void *),
330                        void *arg)
331 {
332     tcp_stream_chunk    sc;
333     int                 bcount, iplen;
334     guint8              client_addr[MAX_IPADDR_LEN];
335     guint16             client_port = 0;
336     gboolean            is_server;
337     guint32             global_client_pos = 0, global_server_pos = 0;
338     guint32             *global_pos;
339     gboolean            skip;
340     char                buffer[FLT_BUF_SIZE+1]; /* +1 to fix ws bug 1043 */
341     size_t              nchars;
342     frs_return_t        frs_return;
343
344     iplen = (follow_info->is_ipv6) ? 16 : 4;
345
346     data_out_file = eth_fopen(follow_info->data_out_filename, "rb");
347     if (data_out_file == NULL) {
348         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
349                       "Could not open temporary file %s: %s", follow_info->data_out_filename,
350                       strerror(errno));
351         return FRS_OPEN_ERROR;
352     }
353
354     while ((nchars=fread(&sc, 1, sizeof(sc), data_out_file))) {
355         if (nchars != sizeof(sc)) {
356             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
357                           "Short read from temporary file %s: expected %lu, got %lu",
358                           follow_info->data_out_filename,
359                           (unsigned long)sizeof(sc),
360                           (unsigned long)nchars);
361             fclose(data_out_file);
362             data_out_file = NULL;
363             return FRS_READ_ERROR;
364         }
365         if (client_port == 0) {
366             memcpy(client_addr, sc.src_addr, iplen);
367             client_port = sc.src_port;
368         }
369         skip = FALSE;
370         if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
371             client_port == sc.src_port) {
372             is_server = FALSE;
373             global_pos = &global_client_pos;
374             if (follow_info->show_stream == FROM_SERVER) {
375                 skip = TRUE;
376             }
377         }
378         else {
379             is_server = TRUE;
380             global_pos = &global_server_pos;
381             if (follow_info->show_stream == FROM_CLIENT) {
382                 skip = TRUE;
383             }
384         }
385
386         while (sc.dlen > 0) {
387             bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
388             nchars = fread(buffer, 1, bcount, data_out_file);
389             if (nchars == 0)
390                 break;
391             /* XXX - if we don't get "bcount" bytes, is that an error? */
392             sc.dlen -= nchars;
393
394             if (!skip) {
395                     frs_return = follow_show(follow_info, print_line, buffer,
396                                              nchars, is_server, arg,
397                                              global_pos);
398                     if(frs_return == FRS_PRINT_ERROR) {
399                             fclose(data_out_file);
400                             data_out_file = NULL;
401                             return frs_return;
402                 
403                     }
404             }
405         }
406     }
407
408     if (ferror(data_out_file)) {
409         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
410                       "Error reading temporary file %s: %s", follow_info->data_out_filename,
411                       strerror(errno));
412         fclose(data_out_file);
413         data_out_file = NULL;
414         return FRS_READ_ERROR;
415     }
416
417     fclose(data_out_file);
418     data_out_file = NULL;
419     return FRS_OK;
420 }