Add wtap_pseudo_header union to wtap_pkthdr structure.
[metze/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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25  */
26
27 #include "config.h"
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, gchar **err_info,
60         char *hdr);
61 static gboolean netscreen_check_file_type(wtap *wth, int *err,
62         gchar **err_info);
63 static gboolean netscreen_read(wtap *wth, int *err, gchar **err_info,
64         gint64 *data_offset);
65 static gboolean netscreen_seek_read(wtap *wth, gint64 seek_off,
66         struct wtap_pkthdr *phdr, guint8 *pd,
67         int len, int *err, gchar **err_info);
68 static int parse_netscreen_rec_hdr(wtap *wth, const char *line,
69         char *cap_int, gboolean *cap_dir, char *cap_dst,
70         union wtap_pseudo_header *pseudo_header, int *err, gchar **err_info);
71 static int parse_netscreen_hex_dump(FILE_T fh, int pkt_len, guint8* buf,
72         int *err, gchar **err_info);
73 static int parse_single_hex_dump_line(char* rec, guint8 *buf,
74         guint byte_offset);
75
76 /* Returns TRUE if the line appears to be an empty line. Otherwise it
77    returns FALSE. */
78 static gboolean empty_line(const gchar *line)
79 {
80         while (*line) {
81                 if (isspace((guchar)*line)) {
82                         line++;
83                         continue;
84                 } else {
85                         break;
86                 }
87         }
88         if (*line == '\0')
89                 return TRUE;
90         else
91                 return FALSE;
92 }
93
94 /* Returns TRUE if the line appears to be a line with protocol info.
95    Otherwise it returns FALSE. */
96 static gboolean info_line(const gchar *line)
97 {
98         int i=NETSCREEN_SPACES_ON_INFO_LINE;
99         
100         while (i-- > 0) {
101                 if (isspace((guchar)*line)) {
102                         line++;
103                         continue;
104                 } else {
105                         return FALSE;
106                 }
107         }
108         return TRUE;
109 }
110
111 /* Seeks to the beginning of the next packet, and returns the
112    byte offset. Copy the header line to hdr. Returns -1 on failure,
113    and sets "*err" to the error, sets "*err_info" to null or an
114    additional error string, and sets hdr to NULL. */
115 static gint64 netscreen_seek_next_packet(wtap *wth, int *err, gchar **err_info,
116     char *hdr)
117 {
118         gint64 cur_off;
119         char buf[NETSCREEN_LINE_LENGTH];
120
121         while (1) {
122                 cur_off = file_tell(wth->fh);
123                 if (cur_off == -1) {
124                         /* Error */
125                         *err = file_error(wth->fh, err_info);
126                         hdr = NULL;
127                         return -1;
128                 }
129                 if (file_gets(buf, sizeof(buf), wth->fh) != NULL) {
130                         if (strstr(buf, NETSCREEN_REC_MAGIC_STR1) ||
131                             strstr(buf, NETSCREEN_REC_MAGIC_STR2)) {
132                                 g_strlcpy(hdr, buf, NETSCREEN_LINE_LENGTH);
133                                 return cur_off;
134                         }
135                 } else {
136                         if (file_eof(wth->fh)) {
137                                 /* We got an EOF. */
138                                 *err = 0;
139                         } else {
140                                 /* We got an error. */
141                                 *err = file_error(wth->fh, err_info);
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 and
155  * "*err_info" is set to null or an additional error string.
156  */
157 static gboolean netscreen_check_file_type(wtap *wth, int *err, gchar **err_info)
158 {
159         char    buf[NETSCREEN_LINE_LENGTH];
160         guint   reclen, line;
161
162         buf[NETSCREEN_LINE_LENGTH-1] = '\0';
163
164         for (line = 0; line < NETSCREEN_HEADER_LINES_TO_CHECK; line++) {
165                 if (file_gets(buf, NETSCREEN_LINE_LENGTH, wth->fh) != NULL) {
166
167                         reclen = (guint) strlen(buf);
168                         if (reclen < strlen(NETSCREEN_HDR_MAGIC_STR1) ||
169                                 reclen < strlen(NETSCREEN_HDR_MAGIC_STR2)) {
170                                 continue;
171                         }
172
173                         if (strstr(buf, NETSCREEN_HDR_MAGIC_STR1) ||
174                             strstr(buf, NETSCREEN_HDR_MAGIC_STR2)) {
175                                 return TRUE;
176                         }
177                 } else {
178                         /* EOF or error. */
179                         if (file_eof(wth->fh))
180                                 *err = 0;
181                         else
182                                 *err = file_error(wth->fh, err_info);
183                         return FALSE;
184                 }
185         }
186         *err = 0;
187         return FALSE;
188 }
189
190
191 int netscreen_open(wtap *wth, int *err, gchar **err_info)
192 {
193
194         /* Look for a NetScreen snoop header line */
195         if (!netscreen_check_file_type(wth, err, err_info)) {
196                 if (*err == 0)
197                         return 0;
198                 else
199                         return -1;
200         }
201
202         if (file_seek(wth->fh, 0L, SEEK_SET, err) == -1)        /* rewind */
203                 return -1;
204
205         wth->file_encap = WTAP_ENCAP_UNKNOWN;
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         char            cap_dst[13];
226         gchar           dststr[13];
227
228         /* Find the next packet */
229         offset = netscreen_seek_next_packet(wth, err, err_info, line);
230         if (offset < 0)
231                 return FALSE;
232
233         wth->phdr.presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
234
235         /* Parse the header */
236         pkt_len = parse_netscreen_rec_hdr(wth, line, cap_int, &cap_dir, cap_dst,
237                 &wth->phdr.pseudo_header, err, err_info);
238         if (pkt_len == -1)
239                 return FALSE;
240
241         /* Make sure we have enough room for the packet */
242         buffer_assure_space(wth->frame_buffer, NETSCREEN_MAX_PACKET_LEN);
243         buf = buffer_start_ptr(wth->frame_buffer);
244
245         /* Convert the ASCII hex dump to binary data */
246         if ((caplen = parse_netscreen_hex_dump(wth->fh, pkt_len, buf, err,
247             err_info)) == -1) {
248                 return FALSE;
249         }
250
251         /*
252          * Determine the encapsulation type, based on the
253          * first 4 characters of the interface name
254          *
255          * XXX  convert this to a 'case' structure when adding more
256          *      (non-ethernet) interfacetypes
257          */
258         if (strncmp(cap_int, "adsl", 4) == 0) {
259                 /* The ADSL interface can be bridged with or without
260                  * PPP encapsulation. Check whether the first six bytes
261                  * of the hex data are the same as the destination mac
262                  * address in the header. If they are, assume ethernet
263                  * LinkLayer or else PPP
264                  */
265                 g_snprintf(dststr, 13, "%02x%02x%02x%02x%02x%02x",
266                    buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
267                 if (strncmp(dststr, cap_dst, 12) == 0) 
268                         wth->phdr.pkt_encap = WTAP_ENCAP_ETHERNET;
269                 else
270                         wth->phdr.pkt_encap = WTAP_ENCAP_PPP;
271                 }
272         else if (strncmp(cap_int, "seri", 4) == 0)
273                 wth->phdr.pkt_encap = WTAP_ENCAP_PPP;
274         else
275                 wth->phdr.pkt_encap = WTAP_ENCAP_ETHERNET;
276
277         /*
278          * If the per-file encapsulation isn't known, set it to this
279          * packet's encapsulation.
280          *
281          * If it *is* known, and it isn't this packet's encapsulation,
282          * set it to WTAP_ENCAP_PER_PACKET, as this file doesn't
283          * have a single encapsulation for all packets in the file.
284          */
285         if (wth->file_encap == WTAP_ENCAP_UNKNOWN)
286                 wth->file_encap = wth->phdr.pkt_encap;
287         else {
288                 if (wth->file_encap != wth->phdr.pkt_encap)
289                         wth->file_encap = WTAP_ENCAP_PER_PACKET;
290         }
291
292         wth->phdr.caplen = caplen;
293         *data_offset = offset;
294         return TRUE;
295 }
296
297 /* Used to read packets in random-access fashion */
298 static gboolean
299 netscreen_seek_read (wtap *wth, gint64 seek_off,
300         struct wtap_pkthdr *phdr, guint8 *pd, int len,
301         int *err, gchar **err_info)
302 {
303         union wtap_pseudo_header *pseudo_header = &phdr->pseudo_header;
304         char            line[NETSCREEN_LINE_LENGTH];
305         char            cap_int[NETSCREEN_MAX_INT_NAME_LENGTH];
306         gboolean        cap_dir;
307         char            cap_dst[13];
308
309         if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) {
310                 return FALSE;
311         }
312
313         if (file_gets(line, NETSCREEN_LINE_LENGTH, wth->random_fh) == NULL) {
314                 *err = file_error(wth->random_fh, err_info);
315                 if (*err == 0) {
316                         *err = WTAP_ERR_SHORT_READ;
317                 }
318                 return FALSE;
319         }
320
321         if (parse_netscreen_rec_hdr(NULL, line, cap_int, &cap_dir, cap_dst,
322            pseudo_header, err, err_info) == -1) {
323                 return FALSE;
324         }
325
326         if (parse_netscreen_hex_dump(wth->random_fh, len, pd, err, err_info)
327             == -1) {
328                 return FALSE;
329         }
330         return TRUE;
331 }
332
333 /* Parses a packet record header. There are a few possible formats:
334  * 
335  * XXX list extra formats here!
336 6843828.0: trust(o) len=98:00121ebbd132->00600868d659/0800
337               192.168.1.1 -> 192.168.1.10/6
338               vhl=45, tos=00, id=37739, frag=0000, ttl=64 tlen=84
339               tcp:ports 2222->2333, seq=3452113890, ack=1540618280, flag=5018/ACK
340               00 60 08 68 d6 59 00 12 1e bb d1 32 08 00 45 00     .`.h.Y.....2..E.
341               00 54 93 6b 00 00 40 06 63 dd c0 a8 01 01 c0 a8     .T.k..@.c.......
342               01 0a 08 ae 09 1d cd c3 13 e2 5b d3 f8 28 50 18     ..........[..(P.
343               1f d4 79 21 00 00 e7 76 89 64 16 e2 19 0a 80 09     ..y!...v.d......
344               31 e7 04 28 04 58 f3 d9 b1 9f 3d 65 1a db d8 61     1..(.X....=e...a
345               2c 21 b6 d3 20 60 0c 8c 35 98 88 cf 20 91 0e a9     ,!...`..5.......
346               1d 0b                                               ..
347
348
349  */
350 static int
351 parse_netscreen_rec_hdr(wtap *wth, const char *line, char *cap_int,
352     gboolean *cap_dir, char *cap_dst, union wtap_pseudo_header *pseudo_header _U_,
353     int *err, gchar **err_info)
354 {
355         int     sec;
356         int     dsec, pkt_len;
357         char    direction[2];
358         char    cap_src[13];
359
360         if (sscanf(line, "%9d.%9d: %15[a-z0-9/:.-](%1[io]) len=%9d:%12s->%12s/",
361                    &sec, &dsec, cap_int, direction, &pkt_len, cap_src, cap_dst) < 5) {
362                 *err = WTAP_ERR_BAD_FILE;
363                 *err_info = g_strdup("netscreen: Can't parse packet-header");
364                 return -1;
365         }
366
367         *cap_dir = (direction[0] == 'o' ? NETSCREEN_EGRESS : NETSCREEN_INGRESS);
368
369         if (wth) {
370                 wth->phdr.ts.secs  = sec;
371                 wth->phdr.ts.nsecs = dsec * 100000000;
372                 wth->phdr.len = pkt_len;
373         }
374
375         return pkt_len;
376 }
377
378 /* Converts ASCII hex dump to binary data. Returns the capture length.
379    If any error is encountered, -1 is returned. */
380 static int
381 parse_netscreen_hex_dump(FILE_T fh, int pkt_len, guint8* buf, int *err, gchar **err_info)
382 {
383         gchar   line[NETSCREEN_LINE_LENGTH];
384         int     n, i = 0, offset = 0;
385
386         while(1) {
387
388                 /* The last packet is not delimited by an empty line, but by EOF
389                  * So accept EOF as a valid delimiter too
390                  */
391                 if (file_gets(line, NETSCREEN_LINE_LENGTH, fh) == NULL) {
392                         break;
393                 }
394
395                 /* packets are delimited with empty lines */
396                 if (empty_line(line)) {
397                         break;
398                 }
399                 
400                 /* terminate the line before the ascii-data to prevent the 
401                  * parser from parsing one or more extra bytes from the 
402                  * ascii-data.
403                  * Check for longer lines to prevent wireless hexdumps to
404                  * be cut in the middle (they can have 14 extra spaces
405                  * before the hex-data)
406                  */
407                 if(strlen(line) != 98) 
408                         line[62] = '\0';
409                 else
410                         line[76] = '\0';
411
412                 n = parse_single_hex_dump_line(line, buf, offset);
413
414                 /* the smallest packet has a length of 6 bytes, if
415                  * the first hex-data is less then check whether 
416                  * it is a info-line and act accordingly
417                  */
418                 if (offset == 0 && n < 6) {
419                         if (info_line(line)) {
420                                 if (++i <= NETSCREEN_MAX_INFOLINES) {
421                                         continue;
422                                 }
423                         } else {
424                                 *err = WTAP_ERR_BAD_FILE;
425                                 *err_info = g_strdup("netscreen: cannot parse hex-data");
426                                 return -1;
427                         }
428                 }
429
430                 /* If there is no more data and the line was not empty,
431                  * then there must be an error in the file
432                  */
433                 if(n == -1) {
434                         *err = WTAP_ERR_BAD_FILE;
435                         *err_info = g_strdup("netscreen: cannot parse hex-data");
436                         return -1;
437                 }
438
439                 /* Adjust the offset to the data that was just added to the buffer */
440                 offset += n;
441
442                 /* If there was more hex-data than was announced in the len=x 
443                  * header, then then there must be an error in the file
444                  */
445                 if(offset > pkt_len) {
446                         *err = WTAP_ERR_BAD_FILE;
447                         *err_info = g_strdup("netscreen: to much hex-data");
448                         return -1;
449                 }
450         }
451         return offset;
452 }
453
454
455 /* Take a string representing one line from a hex dump and converts
456  * the text to binary data. We place the bytes in the buffer at the
457  * specified offset.
458  *
459  * Returns number of bytes successfully read, -1 if bad.  */
460 static int
461 parse_single_hex_dump_line(char* rec, guint8 *buf, guint byte_offset)
462 {
463         int num_items_scanned, i;
464         unsigned int bytes[16];
465
466         num_items_scanned = sscanf(rec, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
467                                &bytes[0], &bytes[1], &bytes[2], &bytes[3],
468                                &bytes[4], &bytes[5], &bytes[6], &bytes[7],
469                                &bytes[8], &bytes[9], &bytes[10], &bytes[11],
470                                &bytes[12], &bytes[13], &bytes[14], &bytes[15]);
471         if (num_items_scanned == 0)
472                 return -1;
473
474         if (num_items_scanned > 16)
475                 num_items_scanned = 16;
476
477         for (i=0; i<num_items_scanned; i++) {
478                 buf[byte_offset + i] = (guint8)bytes[i];
479         }
480
481         return num_items_scanned;
482 }