Add svn:eol-style.
[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  Construct a list of interfaces, with interface names, give
41  *    them link-layer types based on the interface name and packet
42  *    data, and supply interface IDs with each packet (i.e., make
43  *    this supply a pcap-ng-style set of interfaces and associate
44  *    packets with interfaces).  This is probably the right way
45  *    to "Pass the interface names and the traffic direction to either
46  *    the frame-structure, a pseudo-header or use PPI."  See the
47  *    message at
48  *
49  *        http://www.wireshark.org/lists/wireshark-dev/200708/msg00029.html
50  *
51  *    to see whether any further discussion is still needed. I suspect
52  *    it doesn't; pcap-NG existed at the time, as per the final
53  *    message in that thread:
54  *
55  *        http://www.wireshark.org/lists/wireshark-dev/200708/msg00039.html
56  *
57  *    but I don't think we fully *supported* it at that point.  Now
58  *    that we do, we have the infrastructure to support this, except
59  *    that we currently have no way to translate interface IDs to
60  *    interface names in the "frame" dissector or to supply interface
61  *    information as part of the packet metadata from Wiretap modules.
62  *    That should be fixed so that we can show interface information,
63  *    such as the interface name, in packet dissections from, for example,
64  *    pcap-NG captures.
65  */
66
67 static gboolean info_line(const gchar *line);
68 static gint64 netscreen_seek_next_packet(wtap *wth, int *err, gchar **err_info,
69         char *hdr);
70 static gboolean netscreen_check_file_type(wtap *wth, int *err,
71         gchar **err_info);
72 static gboolean netscreen_read(wtap *wth, int *err, gchar **err_info,
73         gint64 *data_offset);
74 static gboolean netscreen_seek_read(wtap *wth, gint64 seek_off,
75         struct wtap_pkthdr *phdr, Buffer *buf,
76         int len, int *err, gchar **err_info);
77 static int parse_netscreen_rec_hdr(struct wtap_pkthdr *phdr, const char *line,
78         char *cap_int, gboolean *cap_dir, char *cap_dst,
79         int *err, gchar **err_info);
80 static gboolean parse_netscreen_hex_dump(FILE_T fh, int pkt_len,
81         const char *cap_int, const char *cap_dst, struct wtap_pkthdr *phdr,
82         Buffer* buf, int *err, gchar **err_info);
83 static int parse_single_hex_dump_line(char* rec, guint8 *buf,
84         guint byte_offset);
85
86 /* Returns TRUE if the line appears to be a line with protocol info.
87    Otherwise it returns FALSE. */
88 static gboolean info_line(const gchar *line)
89 {
90         int i=NETSCREEN_SPACES_ON_INFO_LINE;
91
92         while (i-- > 0) {
93                 if (isspace((guchar)*line)) {
94                         line++;
95                         continue;
96                 } else {
97                         return FALSE;
98                 }
99         }
100         return TRUE;
101 }
102
103 /* Seeks to the beginning of the next packet, and returns the
104    byte offset. Copy the header line to hdr. Returns -1 on failure,
105    and sets "*err" to the error and sets "*err_info" to null or an
106    additional error string. */
107 static gint64 netscreen_seek_next_packet(wtap *wth, int *err, gchar **err_info,
108     char *hdr)
109 {
110         gint64 cur_off;
111         char buf[NETSCREEN_LINE_LENGTH];
112
113         while (1) {
114                 cur_off = file_tell(wth->fh);
115                 if (cur_off == -1) {
116                         /* Error */
117                         *err = file_error(wth->fh, err_info);
118                         return -1;
119                 }
120                 if (file_gets(buf, sizeof(buf), wth->fh) == NULL) {
121                         /* EOF or error. */
122                         *err = file_error(wth->fh, err_info);
123                         break;
124                 }
125                 if (strstr(buf, NETSCREEN_REC_MAGIC_STR1) ||
126                     strstr(buf, NETSCREEN_REC_MAGIC_STR2)) {
127                         g_strlcpy(hdr, buf, NETSCREEN_LINE_LENGTH);
128                         return cur_off;
129                 }
130         }
131         return -1;
132 }
133
134 /* Look through the first part of a file to see if this is
135  * NetScreen snoop output.
136  *
137  * Returns TRUE if it is, FALSE if it isn't or if we get an I/O error;
138  * if we get an I/O error, "*err" will be set to a non-zero value and
139  * "*err_info" is set to null or an additional error string.
140  */
141 static gboolean netscreen_check_file_type(wtap *wth, int *err, gchar **err_info)
142 {
143         char    buf[NETSCREEN_LINE_LENGTH];
144         guint   reclen, line;
145
146         buf[NETSCREEN_LINE_LENGTH-1] = '\0';
147
148         for (line = 0; line < NETSCREEN_HEADER_LINES_TO_CHECK; line++) {
149                 if (file_gets(buf, NETSCREEN_LINE_LENGTH, wth->fh) == NULL) {
150                         /* EOF or error. */
151                         *err = file_error(wth->fh, err_info);
152                         return FALSE;
153                 }
154
155                 reclen = (guint) strlen(buf);
156                 if (reclen < strlen(NETSCREEN_HDR_MAGIC_STR1) ||
157                         reclen < strlen(NETSCREEN_HDR_MAGIC_STR2)) {
158                         continue;
159                 }
160
161                 if (strstr(buf, NETSCREEN_HDR_MAGIC_STR1) ||
162                     strstr(buf, NETSCREEN_HDR_MAGIC_STR2)) {
163                         return TRUE;
164                 }
165         }
166         *err = 0;
167         return FALSE;
168 }
169
170
171 int netscreen_open(wtap *wth, int *err, gchar **err_info)
172 {
173
174         /* Look for a NetScreen snoop header line */
175         if (!netscreen_check_file_type(wth, err, err_info)) {
176                 if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
177                         return -1;
178                 return 0;
179         }
180
181         if (file_seek(wth->fh, 0L, SEEK_SET, err) == -1)        /* rewind */
182                 return -1;
183
184         wth->file_encap = WTAP_ENCAP_UNKNOWN;
185         wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_NETSCREEN;
186         wth->snapshot_length = 0; /* not known */
187         wth->subtype_read = netscreen_read;
188         wth->subtype_seek_read = netscreen_seek_read;
189         wth->tsprecision = WTAP_FILE_TSPREC_DSEC;
190
191         return 1;
192 }
193
194 /* Find the next packet and parse it; called from wtap_read(). */
195 static gboolean netscreen_read(wtap *wth, int *err, gchar **err_info,
196     gint64 *data_offset)
197 {
198         gint64          offset;
199         int             pkt_len;
200         char            line[NETSCREEN_LINE_LENGTH];
201         char            cap_int[NETSCREEN_MAX_INT_NAME_LENGTH];
202         gboolean        cap_dir;
203         char            cap_dst[13];
204
205         /* Find the next packet */
206         offset = netscreen_seek_next_packet(wth, err, err_info, line);
207         if (offset < 0)
208                 return FALSE;
209
210         /* Parse the header */
211         pkt_len = parse_netscreen_rec_hdr(&wth->phdr, line, cap_int, &cap_dir, cap_dst,
212                 err, err_info);
213         if (pkt_len == -1)
214                 return FALSE;
215
216         /* Convert the ASCII hex dump to binary data, and fill in some
217            struct wtap_pkthdr fields */
218         if (!parse_netscreen_hex_dump(wth->fh, pkt_len, cap_int,
219             cap_dst, &wth->phdr, wth->frame_buffer, err, err_info))
220                 return FALSE;
221
222         /*
223          * If the per-file encapsulation isn't known, set it to this
224          * packet's encapsulation.
225          *
226          * If it *is* known, and it isn't this packet's encapsulation,
227          * set it to WTAP_ENCAP_PER_PACKET, as this file doesn't
228          * have a single encapsulation for all packets in the file.
229          */
230         if (wth->file_encap == WTAP_ENCAP_UNKNOWN)
231                 wth->file_encap = wth->phdr.pkt_encap;
232         else {
233                 if (wth->file_encap != wth->phdr.pkt_encap)
234                         wth->file_encap = WTAP_ENCAP_PER_PACKET;
235         }
236
237         *data_offset = offset;
238         return TRUE;
239 }
240
241 /* Used to read packets in random-access fashion */
242 static gboolean
243 netscreen_seek_read(wtap *wth, gint64 seek_off,
244         struct wtap_pkthdr *phdr, Buffer *buf, int len,
245         int *err, gchar **err_info)
246 {
247         char            line[NETSCREEN_LINE_LENGTH];
248         char            cap_int[NETSCREEN_MAX_INT_NAME_LENGTH];
249         gboolean        cap_dir;
250         char            cap_dst[13];
251
252         if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) {
253                 return FALSE;
254         }
255
256         if (file_gets(line, NETSCREEN_LINE_LENGTH, wth->random_fh) == NULL) {
257                 *err = file_error(wth->random_fh, err_info);
258                 if (*err == 0) {
259                         *err = WTAP_ERR_SHORT_READ;
260                 }
261                 return FALSE;
262         }
263
264         if (parse_netscreen_rec_hdr(phdr, line, cap_int, &cap_dir, cap_dst,
265            err, err_info) == -1) {
266                 return FALSE;
267         }
268
269         if (!parse_netscreen_hex_dump(wth->random_fh, len, cap_int, cap_dst,
270             phdr, buf, err, err_info))
271                 return FALSE;
272         return TRUE;
273 }
274
275 /* Parses a packet record header. There are a few possible formats:
276  *
277  * XXX list extra formats here!
278 6843828.0: trust(o) len=98:00121ebbd132->00600868d659/0800
279               192.168.1.1 -> 192.168.1.10/6
280               vhl=45, tos=00, id=37739, frag=0000, ttl=64 tlen=84
281               tcp:ports 2222->2333, seq=3452113890, ack=1540618280, flag=5018/ACK
282               00 60 08 68 d6 59 00 12 1e bb d1 32 08 00 45 00     .`.h.Y.....2..E.
283               00 54 93 6b 00 00 40 06 63 dd c0 a8 01 01 c0 a8     .T.k..@.c.......
284               01 0a 08 ae 09 1d cd c3 13 e2 5b d3 f8 28 50 18     ..........[..(P.
285               1f d4 79 21 00 00 e7 76 89 64 16 e2 19 0a 80 09     ..y!...v.d......
286               31 e7 04 28 04 58 f3 d9 b1 9f 3d 65 1a db d8 61     1..(.X....=e...a
287               2c 21 b6 d3 20 60 0c 8c 35 98 88 cf 20 91 0e a9     ,!...`..5.......
288               1d 0b                                               ..
289
290
291  */
292 static int
293 parse_netscreen_rec_hdr(struct wtap_pkthdr *phdr, const char *line, char *cap_int,
294     gboolean *cap_dir, char *cap_dst, int *err, gchar **err_info)
295 {
296         int     sec;
297         int     dsec, pkt_len;
298         char    direction[2];
299         char    cap_src[13];
300
301         phdr->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
302
303         if (sscanf(line, "%9d.%9d: %15[a-z0-9/:.-](%1[io]) len=%9d:%12s->%12s/",
304                    &sec, &dsec, cap_int, direction, &pkt_len, cap_src, cap_dst) < 5) {
305                 *err = WTAP_ERR_BAD_FILE;
306                 *err_info = g_strdup("netscreen: Can't parse packet-header");
307                 return -1;
308         }
309
310         *cap_dir = (direction[0] == 'o' ? NETSCREEN_EGRESS : NETSCREEN_INGRESS);
311
312         phdr->ts.secs  = sec;
313         phdr->ts.nsecs = dsec * 100000000;
314         phdr->len = pkt_len;
315
316         return pkt_len;
317 }
318
319 /* Converts ASCII hex dump to binary data, and fills in some struct
320    wtap_pkthdr fields.  Returns TRUE on success and FALSE on any error. */
321 static gboolean
322 parse_netscreen_hex_dump(FILE_T fh, int pkt_len, const char *cap_int,
323     const char *cap_dst, struct wtap_pkthdr *phdr, Buffer* buf,
324     int *err, gchar **err_info)
325 {
326         guint8  *pd;
327         gchar   line[NETSCREEN_LINE_LENGTH];
328         gchar   *p;
329         int     n, i = 0, offset = 0;
330         gchar   dststr[13];
331
332         /* Make sure we have enough room for the packet */
333         buffer_assure_space(buf, NETSCREEN_MAX_PACKET_LEN);
334         pd = buffer_start_ptr(buf);
335
336         while(1) {
337
338                 /* The last packet is not delimited by an empty line, but by EOF
339                  * So accept EOF as a valid delimiter too
340                  */
341                 if (file_gets(line, NETSCREEN_LINE_LENGTH, fh) == NULL) {
342                         break;
343                 }
344
345                 /*
346                  * Skip blanks.
347                  * The number of blanks is not fixed - for wireless
348                  * interfaces, there may be 14 extra spaces before
349                  * the hex data.
350                  */
351                 for (p = &line[0]; isspace((guchar)*p); p++)
352                         ;
353                 /* packets are delimited with empty lines */
354                 if (*p == '\0') {
355                         break;
356                 }
357
358                 n = parse_single_hex_dump_line(p, pd, offset);
359
360                 /* the smallest packet has a length of 6 bytes, if
361                  * the first hex-data is less then check whether
362                  * it is a info-line and act accordingly
363                  */
364                 if (offset == 0 && n < 6) {
365                         if (info_line(line)) {
366                                 if (++i <= NETSCREEN_MAX_INFOLINES) {
367                                         continue;
368                                 }
369                         } else {
370                                 *err = WTAP_ERR_BAD_FILE;
371                                 *err_info = g_strdup("netscreen: cannot parse hex-data");
372                                 return FALSE;
373                         }
374                 }
375
376                 /* If there is no more data and the line was not empty,
377                  * then there must be an error in the file
378                  */
379                 if(n == -1) {
380                         *err = WTAP_ERR_BAD_FILE;
381                         *err_info = g_strdup("netscreen: cannot parse hex-data");
382                         return FALSE;
383                 }
384
385                 /* Adjust the offset to the data that was just added to the buffer */
386                 offset += n;
387
388                 /* If there was more hex-data than was announced in the len=x
389                  * header, then then there must be an error in the file
390                  */
391                 if(offset > pkt_len) {
392                         *err = WTAP_ERR_BAD_FILE;
393                         *err_info = g_strdup("netscreen: too much hex-data");
394                         return FALSE;
395                 }
396         }
397
398         /*
399          * Determine the encapsulation type, based on the
400          * first 4 characters of the interface name
401          *
402          * XXX  convert this to a 'case' structure when adding more
403          *      (non-ethernet) interfacetypes
404          */
405         if (strncmp(cap_int, "adsl", 4) == 0) {
406                 /* The ADSL interface can be bridged with or without
407                  * PPP encapsulation. Check whether the first six bytes
408                  * of the hex data are the same as the destination mac
409                  * address in the header. If they are, assume ethernet
410                  * LinkLayer or else PPP
411                  */
412                 g_snprintf(dststr, 13, "%02x%02x%02x%02x%02x%02x",
413                    pd[0], pd[1], pd[2], pd[3], pd[4], pd[5]);
414                 if (strncmp(dststr, cap_dst, 12) == 0)
415                         phdr->pkt_encap = WTAP_ENCAP_ETHERNET;
416                 else
417                         phdr->pkt_encap = WTAP_ENCAP_PPP;
418                 }
419         else if (strncmp(cap_int, "seri", 4) == 0)
420                 phdr->pkt_encap = WTAP_ENCAP_PPP;
421         else
422                 phdr->pkt_encap = WTAP_ENCAP_ETHERNET;
423
424         phdr->caplen = offset;
425
426         return TRUE;
427 }
428
429 /* Take a string representing one line from a hex dump, with leading white
430  * space removed, and converts the text to binary data. We place the bytes
431  * in the buffer at the specified offset.
432  *
433  * Returns number of bytes successfully read, -1 if bad.  */
434 static int
435 parse_single_hex_dump_line(char* rec, guint8 *buf, guint byte_offset)
436 {
437         int num_items_scanned;
438         guint8 character;
439         guint8 byte;
440
441
442         for (num_items_scanned = 0; num_items_scanned < 16; num_items_scanned++) {
443                 character = *rec++;
444                 if (character >= '0' && character <= '9')
445                         byte = character - '0' + 0;
446                 else if (character >= 'A' && character <= 'F')
447                         byte = character - 'A' + 0xA;
448                 else if (character >= 'a' && character <= 'f')
449                         byte = character - 'a' + 0xa;
450                 else if (character == ' ' || character == '\r' || character == '\n' || character == '\0') {
451                         /* Nothing more to parse */
452                         break;
453                 } else
454                         return -1; /* not a hex digit, space before ASCII dump, or EOL */
455                 byte <<= 4;
456                 character = *rec++ & 0xFF;
457                 if (character >= '0' && character <= '9')
458                         byte += character - '0' + 0;
459                 else if (character >= 'A' && character <= 'F')
460                         byte += character - 'A' + 0xA;
461                 else if (character >= 'a' && character <= 'f')
462                         byte += character - 'a' + 0xa;
463                 else
464                         return -1; /* not a hex digit */
465                 buf[byte_offset + num_items_scanned] = byte;
466                 character = *rec++ & 0xFF;
467                 if (character == '\0' || character == '\r' || character == '\n') {
468                         /* Nothing more to parse */
469                         break;
470                 } else if (character != ' ') {
471                         /* not space before ASCII dump */
472                         return -1;
473                 }
474         }
475         if (num_items_scanned == 0)
476                 return -1;
477
478         return num_items_scanned;
479 }