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