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