Move the new files to the same places as in automake.
[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         wth->phdr.presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
237
238         /* Parse the header */
239         pkt_len = parse_netscreen_rec_hdr(wth, line, cap_int, &cap_dir, cap_dst,
240                 &wth->pseudo_header, err, err_info);
241         if (pkt_len == -1)
242                 return FALSE;
243
244         /* Make sure we have enough room for the packet */
245         buffer_assure_space(wth->frame_buffer, NETSCREEN_MAX_PACKET_LEN);
246         buf = buffer_start_ptr(wth->frame_buffer);
247
248         /* Convert the ASCII hex dump to binary data */
249         if ((caplen = parse_netscreen_hex_dump(wth->fh, pkt_len, buf, err,
250             err_info)) == -1) {
251                 return FALSE;
252         }
253
254         /*
255          * Determine the encapsulation type, based on the
256          * first 4 characters of the interface name
257          *
258          * XXX  convert this to a 'case' structure when adding more
259          *      (non-ethernet) interfacetypes
260          */
261         if (strncmp(cap_int, "adsl", 4) == 0) {
262                 /* The ADSL interface can be bridged with or without
263                  * PPP encapsulation. Check whether the first six bytes
264                  * of the hex data are the same as the destination mac
265                  * address in the header. If they are, assume ethernet
266                  * LinkLayer or else PPP
267                  */
268                 g_snprintf(dststr, 13, "%02x%02x%02x%02x%02x%02x",
269                    buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
270                 if (strncmp(dststr, cap_dst, 12) == 0) 
271                         wth->phdr.pkt_encap = WTAP_ENCAP_ETHERNET;
272                 else
273                         wth->phdr.pkt_encap = WTAP_ENCAP_PPP;
274                 }
275         else if (strncmp(cap_int, "seri", 4) == 0)
276                 wth->phdr.pkt_encap = WTAP_ENCAP_PPP;
277         else
278                 wth->phdr.pkt_encap = WTAP_ENCAP_ETHERNET;
279
280         /*
281          * If the per-file encapsulation isn't known, set it to this
282          * packet's encapsulation.
283          *
284          * If it *is* known, and it isn't this packet's encapsulation,
285          * set it to WTAP_ENCAP_PER_PACKET, as this file doesn't
286          * have a single encapsulation for all packets in the file.
287          */
288         if (wth->file_encap == WTAP_ENCAP_UNKNOWN)
289                 wth->file_encap = wth->phdr.pkt_encap;
290         else {
291                 if (wth->file_encap != wth->phdr.pkt_encap)
292                         wth->file_encap = WTAP_ENCAP_PER_PACKET;
293         }
294
295         wth->data_offset = offset;
296         wth->phdr.caplen = caplen;
297         *data_offset = offset;
298         return TRUE;
299 }
300
301 /* Used to read packets in random-access fashion */
302 static gboolean
303 netscreen_seek_read (wtap *wth, gint64 seek_off,
304         union wtap_pseudo_header *pseudo_header, guint8 *pd, int len,
305         int *err, gchar **err_info)
306 {
307         char            line[NETSCREEN_LINE_LENGTH];
308         char            cap_int[NETSCREEN_MAX_INT_NAME_LENGTH];
309         gboolean        cap_dir;
310         char            cap_dst[13];
311
312         if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) {
313                 return FALSE;
314         }
315
316         if (file_gets(line, NETSCREEN_LINE_LENGTH, wth->random_fh) == NULL) {
317                 *err = file_error(wth->random_fh, err_info);
318                 if (*err == 0) {
319                         *err = WTAP_ERR_SHORT_READ;
320                 }
321                 return FALSE;
322         }
323
324         if (parse_netscreen_rec_hdr(NULL, line, cap_int, &cap_dir, cap_dst,
325            pseudo_header, err, err_info) == -1) {
326                 return FALSE;
327         }
328
329         if (parse_netscreen_hex_dump(wth->random_fh, len, pd, err, err_info)
330             == -1) {
331                 return FALSE;
332         }
333         return TRUE;
334 }
335
336 /* Parses a packet record header. There are a few possible formats:
337  * 
338  * XXX list extra formats here!
339 6843828.0: trust(o) len=98:00121ebbd132->00600868d659/0800
340               192.168.1.1 -> 192.168.1.10/6
341               vhl=45, tos=00, id=37739, frag=0000, ttl=64 tlen=84
342               tcp:ports 2222->2333, seq=3452113890, ack=1540618280, flag=5018/ACK
343               00 60 08 68 d6 59 00 12 1e bb d1 32 08 00 45 00     .`.h.Y.....2..E.
344               00 54 93 6b 00 00 40 06 63 dd c0 a8 01 01 c0 a8     .T.k..@.c.......
345               01 0a 08 ae 09 1d cd c3 13 e2 5b d3 f8 28 50 18     ..........[..(P.
346               1f d4 79 21 00 00 e7 76 89 64 16 e2 19 0a 80 09     ..y!...v.d......
347               31 e7 04 28 04 58 f3 d9 b1 9f 3d 65 1a db d8 61     1..(.X....=e...a
348               2c 21 b6 d3 20 60 0c 8c 35 98 88 cf 20 91 0e a9     ,!...`..5.......
349               1d 0b                                               ..
350
351
352  */
353 static int
354 parse_netscreen_rec_hdr(wtap *wth, const char *line, char *cap_int,
355     gboolean *cap_dir, char *cap_dst, union wtap_pseudo_header *pseudo_header _U_,
356     int *err, gchar **err_info)
357 {
358         int     sec;
359         int     dsec, pkt_len;
360         char    direction[2];
361         char    cap_src[13];
362
363         if (sscanf(line, "%9d.%9d: %15[a-z0-9/:.](%1[io]) len=%9d:%12s->%12s/",
364                    &sec, &dsec, cap_int, direction, &pkt_len, cap_src, cap_dst) < 5) {
365                 *err = WTAP_ERR_BAD_FILE;
366                 *err_info = g_strdup("netscreen: Can't parse packet-header");
367                 return -1;
368         }
369
370         *cap_dir = (direction[0] == 'o' ? NETSCREEN_EGRESS : NETSCREEN_INGRESS);
371
372         if (wth) {
373                 wth->phdr.ts.secs  = sec;
374                 wth->phdr.ts.nsecs = dsec * 100000000;
375                 wth->phdr.len = pkt_len;
376         }
377
378         return pkt_len;
379 }
380
381 /* Converts ASCII hex dump to binary data. Returns the capture length.
382    If any error is encountered, -1 is returned. */
383 static int
384 parse_netscreen_hex_dump(FILE_T fh, int pkt_len, guint8* buf, int *err, gchar **err_info)
385 {
386         gchar   line[NETSCREEN_LINE_LENGTH];
387         int     n, i = 0, offset = 0;
388
389         while(1) {
390
391                 /* The last packet is not delimited by an empty line, but by EOF
392                  * So accept EOF as a valid delimiter too
393                  */
394                 if (file_gets(line, NETSCREEN_LINE_LENGTH, fh) == NULL) {
395                         break;
396                 }
397
398                 /* packets are delimited with empty lines */
399                 if (empty_line(line)) {
400                         break;
401                 }
402                 
403                 /* terminate the line before the ascii-data to prevent the 
404                  * parser from parsing one or more extra bytes from the 
405                  * ascii-data.
406                  * Check for longer lines to prevent wireless hexdumps to
407                  * be cut in the middle (they can have 14 extra spaces
408                  * before the hex-data)
409                  */
410                 if(strlen(line) != 98) 
411                         line[62] = '\0';
412                 else
413                         line[76] = '\0';
414
415                 n = parse_single_hex_dump_line(line, buf, offset);
416
417                 /* the smallest packet has a length of 6 bytes, if
418                  * the first hex-data is less then check whether 
419                  * it is a info-line and act accordingly
420                  */
421                 if (offset == 0 && n < 6) {
422                         if (info_line(line)) {
423                                 if (++i <= NETSCREEN_MAX_INFOLINES) {
424                                         continue;
425                                 }
426                         } else {
427                                 *err = WTAP_ERR_BAD_FILE;
428                                 *err_info = g_strdup("netscreen: cannot parse hex-data");
429                                 return -1;
430                         }
431                 }
432
433                 /* If there is no more data and the line was not empty,
434                  * then there must be an error in the file
435                  */
436                 if(n == -1) {
437                         *err = WTAP_ERR_BAD_FILE;
438                         *err_info = g_strdup("netscreen: cannot parse hex-data");
439                         return -1;
440                 }
441
442                 /* Adjust the offset to the data that was just added to the buffer */
443                 offset += n;
444
445                 /* If there was more hex-data than was announced in the len=x 
446                  * header, then then there must be an error in the file
447                  */
448                 if(offset > pkt_len) {
449                         *err = WTAP_ERR_BAD_FILE;
450                         *err_info = g_strdup("netscreen: to much hex-data");
451                         return -1;
452                 }
453         }
454         return offset;
455 }
456
457
458 /* Take a string representing one line from a hex dump and converts
459  * the text to binary data. We place the bytes in the buffer at the
460  * specified offset.
461  *
462  * Returns number of bytes successfully read, -1 if bad.  */
463 static int
464 parse_single_hex_dump_line(char* rec, guint8 *buf, guint byte_offset)
465 {
466         int num_items_scanned, i;
467         unsigned int bytes[16];
468
469         num_items_scanned = sscanf(rec, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
470                                &bytes[0], &bytes[1], &bytes[2], &bytes[3],
471                                &bytes[4], &bytes[5], &bytes[6], &bytes[7],
472                                &bytes[8], &bytes[9], &bytes[10], &bytes[11],
473                                &bytes[12], &bytes[13], &bytes[14], &bytes[15]);
474         if (num_items_scanned == 0)
475                 return -1;
476
477         if (num_items_scanned > 16)
478                 num_items_scanned = 16;
479
480         for (i=0; i<num_items_scanned; i++) {
481                 buf[byte_offset + i] = (guint8)bytes[i];
482         }
483
484         return num_items_scanned;
485 }