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