This patch solves two issue with the NetScreen wiretap routine:
[metze/wireshark/wip.git] / wiretap / netscreen.c
1 /* netscreen.c
2  *
3  * Juniper NetScreen snoop output parser
4  * Created by re-using a lot of code from cosine.c
5  * Copyright (c) 2007 by Sake Blok <sake@euronet.nl>
6  *
7  * Wiretap Library
8  * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 #include "wtap-int.h"
29 #include "buffer.h"
30 #include "netscreen.h"
31 #include "file_wrappers.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <ctype.h>
37
38 /* XXX TODO:
39  *
40  * o  Create a wiki-page with instruction on how to make tracefiles
41  *    on Juniper NetScreen devices. Also put a few examples up
42  *    on the wiki (Done: wiki-page added 2007-08-03)
43  *
44  * o  Use the interface names to properly detect the encapsulation
45  *    type (ie adsl packets are now not properly dissected)
46  *    (Done: adsl packets are now correctly seen as PPP, 2007-08-03)
47  *
48  * o  Pass the interface names and the traffic direction to either
49  *    the frame-structure, a pseudo-header or use PPI. This needs
50  *    to be discussed on the dev-list first
51  *    (Posted a message to wireshark-dev abou this 2007-08-03)
52  *
53  */
54
55
56
57 static gboolean empty_line(const gchar *line);
58 static gboolean info_line(const gchar *line);
59 static gint64 netscreen_seek_next_packet(wtap *wth, int *err, char *hdr);
60 static gboolean netscreen_check_file_type(wtap *wth, int *err);
61 static gboolean netscreen_read(wtap *wth, int *err, gchar **err_info,
62         gint64 *data_offset);
63 static gboolean netscreen_seek_read(wtap *wth, gint64 seek_off,
64         union wtap_pseudo_header *pseudo_header, guint8 *pd,
65         int len, int *err, gchar **err_info);
66 static int parse_netscreen_rec_hdr(wtap *wth, const char *line,
67         char *cap_int, gboolean *cap_dir,
68         union wtap_pseudo_header *pseudo_header, int *err, gchar **err_info);
69 static int parse_netscreen_hex_dump(FILE_T fh, int pkt_len, guint8* buf,
70         int *err, gchar **err_info);
71 static int parse_single_hex_dump_line(char* rec, guint8 *buf,
72         guint byte_offset);
73
74 /* Returns TRUE if the line appears to be an empty line. Otherwise it
75    returns FALSE. */
76 static gboolean empty_line(const gchar *line)
77 {
78         while (*line) {
79                 if (isspace((guchar)*line)) {
80                         line++;
81                         continue;
82                 } else {
83                         break;
84                 }
85         }
86         if (*line == '\0')
87                 return TRUE;
88         else
89                 return FALSE;
90 }
91
92 /* Returns TRUE if the line appears to be a line with protocol info.
93    Otherwise it returns FALSE. */
94 static gboolean info_line(const gchar *line)
95 {
96         int i=NETSCREEN_SPACES_ON_INFO_LINE;
97         
98         while (i-- > 0) {
99                 if (isspace((guchar)*line)) {
100                         line++;
101                         continue;
102                 } else {
103                         return FALSE;
104                 }
105         }
106         return TRUE;
107 }
108
109 /* Seeks to the beginning of the next packet, and returns the
110    byte offset. Copy the header line to hdr. Returns -1 on failure,
111    and sets "*err" to the error and set hdr as NULL. */
112 static gint64 netscreen_seek_next_packet(wtap *wth, int *err, char *hdr)
113 {
114         gint64 cur_off;
115         char buf[NETSCREEN_LINE_LENGTH];
116
117         while (1) {
118                 cur_off = file_tell(wth->fh);
119                 if (cur_off == -1) {
120                         /* Error */
121                         *err = file_error(wth->fh);
122                         hdr = NULL;
123                         return -1;
124                 }
125                 if (file_gets(buf, sizeof(buf), wth->fh) != NULL) {
126                         if (strstr(buf, NETSCREEN_REC_MAGIC_STR1) ||
127                             strstr(buf, NETSCREEN_REC_MAGIC_STR2)) {
128                                 strncpy(hdr, buf, NETSCREEN_LINE_LENGTH-1);
129                                 hdr[NETSCREEN_LINE_LENGTH-1] = '\0';
130                                 return cur_off;
131                         }
132                 } else {
133                         if (file_eof(wth->fh)) {
134                                 /* We got an EOF. */
135                                 *err = 0;
136                         } else {
137                                 /* We (presumably) got an error (there's no
138                                    equivalent to "ferror()" in zlib, alas,
139                                    so we don't have a wrapper to check for
140                                    an error). */
141                                 *err = file_error(wth->fh);
142                         }
143                         break;
144                 }
145         }
146         hdr = NULL;
147         return -1;
148 }
149
150 /* Look through the first part of a file to see if this is
151  * NetScreen snoop output.
152  *
153  * Returns TRUE if it is, FALSE if it isn't or if we get an I/O error;
154  * if we get an I/O error, "*err" will be set to a non-zero value.
155  */
156 static gboolean netscreen_check_file_type(wtap *wth, int *err)
157 {
158         char    buf[NETSCREEN_LINE_LENGTH];
159         guint   reclen, line;
160
161         buf[NETSCREEN_LINE_LENGTH-1] = '\0';
162
163         for (line = 0; line < NETSCREEN_HEADER_LINES_TO_CHECK; line++) {
164                 if (file_gets(buf, NETSCREEN_LINE_LENGTH, wth->fh) != NULL) {
165
166                         reclen = strlen(buf);
167                         if (reclen < strlen(NETSCREEN_HDR_MAGIC_STR1) ||
168                                 reclen < strlen(NETSCREEN_HDR_MAGIC_STR2)) {
169                                 continue;
170                         }
171
172                         if (strstr(buf, NETSCREEN_HDR_MAGIC_STR1) ||
173                             strstr(buf, NETSCREEN_HDR_MAGIC_STR2)) {
174                                 return TRUE;
175                         }
176                 } else {
177                         /* EOF or error. */
178                         if (file_eof(wth->fh))
179                                 *err = 0;
180                         else
181                                 *err = file_error(wth->fh);
182                         return FALSE;
183                 }
184         }
185         *err = 0;
186         return FALSE;
187 }
188
189
190 int netscreen_open(wtap *wth, int *err, gchar **err_info _U_)
191 {
192
193         /* Look for a NetScreen snoop header line */
194         if (!netscreen_check_file_type(wth, err)) {
195                 if (*err == 0)
196                         return 0;
197                 else
198                         return -1;
199         }
200
201         if (file_seek(wth->fh, 0L, SEEK_SET, err) == -1)        /* rewind */
202                 return -1;
203
204         wth->data_offset = 0;
205         wth->file_encap = WTAP_ENCAP_PER_PACKET;
206         wth->file_type = WTAP_FILE_NETSCREEN;
207         wth->snapshot_length = 0; /* not known */
208         wth->subtype_read = netscreen_read;
209         wth->subtype_seek_read = netscreen_seek_read;
210         wth->tsprecision = WTAP_FILE_TSPREC_DSEC;
211         
212         return 1;
213 }
214
215 /* Find the next packet and parse it; called from wtap_read(). */
216 static gboolean netscreen_read(wtap *wth, int *err, gchar **err_info,
217     gint64 *data_offset)
218 {
219         gint64          offset;
220         guint8          *buf;
221         int             pkt_len, caplen;
222         char            line[NETSCREEN_LINE_LENGTH];
223         char            cap_int[NETSCREEN_MAX_INT_NAME_LENGTH];
224         gboolean        cap_dir;
225
226         /* Find the next packet */
227         offset = netscreen_seek_next_packet(wth, err, line);
228         if (offset < 0)
229                 return FALSE;
230
231         /* Parse the header */
232         pkt_len = parse_netscreen_rec_hdr(wth, line, cap_int, &cap_dir, 
233                 &wth->pseudo_header, err, err_info);
234         if (pkt_len == -1)
235                 return FALSE;
236
237         /* Make sure we have enough room for the packet */
238         buffer_assure_space(wth->frame_buffer, NETSCREEN_MAX_PACKET_LEN);
239         buf = buffer_start_ptr(wth->frame_buffer);
240
241         /* Convert the ASCII hex dump to binary data */
242         if ((caplen = parse_netscreen_hex_dump(wth->fh, pkt_len, buf, err,
243             err_info)) == -1) {
244                 return FALSE;
245         }
246
247         if (strncmp(cap_int, "adsl", 4) == 0) 
248                 wth->phdr.pkt_encap = WTAP_ENCAP_PPP;
249         else if (strncmp(cap_int, "seri", 4) == 0)
250                 wth->phdr.pkt_encap = WTAP_ENCAP_PPP;
251         else
252                 wth->phdr.pkt_encap = WTAP_ENCAP_ETHERNET;
253
254         wth->data_offset = offset;
255         wth->phdr.caplen = caplen;
256         *data_offset = offset;
257         return TRUE;
258 }
259
260 /* Used to read packets in random-access fashion */
261 static gboolean
262 netscreen_seek_read (wtap *wth, gint64 seek_off,
263         union wtap_pseudo_header *pseudo_header, guint8 *pd, int len,
264         int *err, gchar **err_info)
265 {
266         char            line[NETSCREEN_LINE_LENGTH];
267         char            cap_int[NETSCREEN_MAX_INT_NAME_LENGTH];
268         gboolean        cap_dir;
269
270         if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) {
271                 return FALSE;
272         }
273
274         if (file_gets(line, NETSCREEN_LINE_LENGTH, wth->random_fh) == NULL) {
275                 *err = file_error(wth->random_fh);
276                 if (*err == 0) {
277                         *err = WTAP_ERR_SHORT_READ;
278                 }
279                 return FALSE;
280         }
281
282         if (parse_netscreen_rec_hdr(NULL, line, cap_int, &cap_dir, pseudo_header, 
283            err, err_info) == -1) {
284                 return FALSE;
285         }
286
287         return parse_netscreen_hex_dump(wth->random_fh, len, pd, err, err_info);
288 }
289
290 /* Parses a packet record header. There are a few possible formats:
291  * 
292  * XXX list extra formats here!
293 6843828.0: trust(o) len=98:00121ebbd132->00600868d659/0800
294               192.168.1.1 -> 192.168.1.10/6
295               vhl=45, tos=00, id=37739, frag=0000, ttl=64 tlen=84
296               tcp:ports 2222->2333, seq=3452113890, ack=1540618280, flag=5018/ACK
297               00 60 08 68 d6 59 00 12 1e bb d1 32 08 00 45 00     .`.h.Y.....2..E.
298               00 54 93 6b 00 00 40 06 63 dd c0 a8 01 01 c0 a8     .T.k..@.c.......
299               01 0a 08 ae 09 1d cd c3 13 e2 5b d3 f8 28 50 18     ..........[..(P.
300               1f d4 79 21 00 00 e7 76 89 64 16 e2 19 0a 80 09     ..y!...v.d......
301               31 e7 04 28 04 58 f3 d9 b1 9f 3d 65 1a db d8 61     1..(.X....=e...a
302               2c 21 b6 d3 20 60 0c 8c 35 98 88 cf 20 91 0e a9     ,!...`..5.......
303               1d 0b                                               ..
304
305
306  */
307 static int
308 parse_netscreen_rec_hdr(wtap *wth, const char *line, char *cap_int, gboolean *cap_dir,
309     union wtap_pseudo_header *pseudo_header _U_, int *err, gchar **err_info)
310 {
311         int     sec;
312         int     dsec, pkt_len;
313         char    direction[2];
314
315         if (sscanf(line, "%d.%d: %[a-z0-9/](%[io]) len=%d:",
316                    &sec, &dsec, cap_int, direction, &pkt_len) != 5) {
317                 *err = WTAP_ERR_BAD_RECORD;
318                 *err_info = g_strdup("netscreen: Can't parse packet-header");
319                 return -1;
320         }
321
322         *cap_dir = (direction[0] == 'o' ? NETSCREEN_EGRESS : NETSCREEN_INGRESS);
323
324         if (wth) {
325                 wth->phdr.ts.secs  = sec;
326                 wth->phdr.ts.nsecs = dsec * 100000000;
327                 wth->phdr.len = pkt_len;
328         }
329
330         return pkt_len;
331 }
332
333 /* Converts ASCII hex dump to binary data. Returns the capture length.
334    If any error is encountered, -1 is returned. */
335 static int
336 parse_netscreen_hex_dump(FILE_T fh, int pkt_len, guint8* buf, int *err, gchar **err_info)
337 {
338         gchar   line[NETSCREEN_LINE_LENGTH];
339         int     n, i = 0, offset = 0;
340
341         while(1) {
342
343                 /* The last packet is not delimited by an empty line, but by EOF
344                  * So accept EOF as a valid delimiter too
345                  */
346                 if (file_gets(line, NETSCREEN_LINE_LENGTH, fh) == NULL) {
347                         break;
348                 }
349
350                 /* packets are delimited with empty lines */
351                 if (empty_line(line)) {
352                         break;
353                 }
354                 
355                 /* terminate the line before the ascii-data to prevent the 
356                  * parser from parsing one or more extra bytes from the 
357                  * ascii-data.
358                  * Check for longer lines to prevent wireless hexdumps to
359                  * be cut in the middle (they can have 14 extra spaces
360                  * before the hex-data)
361                  */
362                 if(strlen(line) != 98) 
363                         line[62] = '\0';
364                 else
365                         line[76] = '\0';
366
367                 n = parse_single_hex_dump_line(line, buf, offset);
368
369                 /* the smallest packet has a length of 6 bytes, if
370                  * the first hex-data is less then check whether 
371                  * it is a info-line and act accordingly
372                  */
373                 if (offset == 0 && n < 6) {
374                         if (info_line(line)) {
375                                 if (++i <= NETSCREEN_MAX_INFOLINES) {
376                                         continue;
377                                 }
378                         } else {
379                                 *err = WTAP_ERR_BAD_RECORD;
380                                 *err_info = g_strdup("netscreen: cannot parse hex-data");
381                                 return -1;
382                         }
383                 }
384
385                 /* If there is no more data and the line was not empty,
386                  * then there must be an error in the file
387                  */
388                 if(n == -1) {
389                         *err = WTAP_ERR_BAD_RECORD;
390                         *err_info = g_strdup("netscreen: cannot parse hex-data");
391                         return -1;
392                 }
393
394                 /* Adjust the offset to the data that was just added to the buffer */
395                 offset += n;
396
397                 /* If there was more hex-data than was announced in the len=x 
398                  * header, then then there must be an error in the file
399                  */
400                 if(offset > pkt_len) {
401                         *err = WTAP_ERR_BAD_RECORD;
402                         *err_info = g_strdup("netscreen: to much hex-data");
403                         return -1;
404                 }
405         }
406         return offset;
407 }
408
409
410 /* Take a string representing one line from a hex dump and converts
411  * the text to binary data. We place the bytes in the buffer at the
412  * specified offset.
413  *
414  * Returns number of bytes successfully read, -1 if bad.  */
415 static int
416 parse_single_hex_dump_line(char* rec, guint8 *buf, guint byte_offset)
417 {
418         int num_items_scanned, i;
419         unsigned int bytes[16];
420
421         num_items_scanned = sscanf(rec, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
422                                &bytes[0], &bytes[1], &bytes[2], &bytes[3],
423                                &bytes[4], &bytes[5], &bytes[6], &bytes[7],
424                                &bytes[8], &bytes[9], &bytes[10], &bytes[11],
425                                &bytes[12], &bytes[13], &bytes[14], &bytes[15]);
426         if (num_items_scanned == 0)
427                 return -1;
428
429         if (num_items_scanned > 16)
430                 num_items_scanned = 16;
431
432         for (i=0; i<num_items_scanned; i++) {
433                 buf[byte_offset + i] = (guint8)bytes[i];
434         }
435
436         return num_items_scanned;
437 }