/src/wireshark/wiretap/ascendtext.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* ascendtext.c |
2 | | * |
3 | | * Wiretap Library |
4 | | * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> |
5 | | * |
6 | | * SPDX-License-Identifier: GPL-2.0-or-later |
7 | | */ |
8 | | |
9 | | #include "config.h" |
10 | | #include "ascendtext.h" |
11 | | |
12 | | #include "wtap-int.h" |
13 | | #include "ascend-int.h" |
14 | | #include "file_wrappers.h" |
15 | | |
16 | | #ifdef HAVE_UNISTD_H |
17 | | #include <unistd.h> |
18 | | #endif |
19 | | |
20 | | #include <string.h> |
21 | | |
22 | | /* Last updated: Feb 03 2005: Josh Bailey (joshbailey@lucent.com). |
23 | | |
24 | | This module reads the text hex dump output of various TAOS |
25 | | (Avaya/Alcatel/Lucent/Ascend Max, Max TNT, APX, etc) debug commands, including: |
26 | | |
27 | | * pridisplay traces primary rate ISDN |
28 | | * ether-display traces Ethernet packets (dangerous! CPU intensive) |
29 | | * wanopening, wandisplay, wannext, wandsess |
30 | | traces PPP or other WAN connections |
31 | | |
32 | | Please see ascend_parser.lemon for examples. |
33 | | |
34 | | Detailed documentation on TAOS products was at http://support.lucent.com; |
35 | | that no longer works, and appears not to be available on the Wayback |
36 | | Machine. |
37 | | |
38 | | Some online manuals and other information include: |
39 | | |
40 | | MAX Administration Guide: |
41 | | https://downloads.avaya.com/elmodocs2/definity/def_r10_new/max/0678_002.pdf |
42 | | |
43 | | Other MAX documentation: |
44 | | https://support.avaya.com/products/P1192/max |
45 | | https://web.archive.org/web/20201127014004/https://support.avaya.com/products/P1192/max#Tab4 |
46 | | |
47 | | Ascend Router Information: |
48 | | http://maxrouter.rde.net/ |
49 | | https://web.archive.org/web/20200807215418/http://maxrouter.rde.net/ |
50 | | |
51 | | */ |
52 | | |
53 | | typedef struct _ascend_magic_string { |
54 | | unsigned type; |
55 | | const char *strptr; |
56 | | size_t strlength; |
57 | | } ascend_magic_string; |
58 | | |
59 | | /* these magic strings signify the headers of a supported debug commands */ |
60 | | #define ASCEND_MAGIC_ENTRY(type, string) \ |
61 | | { type, string, sizeof string - 1 } /* strlen of a constant string */ |
62 | | static const ascend_magic_string ascend_magic[] = { |
63 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_ISDN_X, "PRI-XMIT-"), |
64 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_ISDN_R, "PRI-RCV-"), |
65 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_WDS_X, "XMIT-"), |
66 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_WDS_R, "RECV-"), |
67 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_WDS_X, "XMIT:"), |
68 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_WDS_R, "RECV:"), |
69 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_WDS_X, "PPP-OUT"), |
70 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_WDS_R, "PPP-IN"), |
71 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_WDD, "WD_DIALOUT_DISP:"), |
72 | | ASCEND_MAGIC_ENTRY(ASCEND_PFX_ETHER, "ETHER"), |
73 | | }; |
74 | | |
75 | 0 | #define ASCEND_MAGIC_STRINGS G_N_ELEMENTS(ascend_magic) |
76 | | |
77 | 0 | #define ASCEND_DATE "Date:" |
78 | | |
79 | | static bool ascend_read(wtap *wth, wtap_rec *rec, |
80 | | int *err, char **err_info, int64_t *data_offset); |
81 | | static bool ascend_seek_read(wtap *wth, int64_t seek_off, |
82 | | wtap_rec *rec, int *err, char **err_info); |
83 | | |
84 | | static int ascend_file_type_subtype = -1; |
85 | | |
86 | | void register_ascend(void); |
87 | | |
88 | | /* Seeks to the beginning of the next packet, and returns the |
89 | | byte offset at which the header for that packet begins. |
90 | | Returns -1 on failure. */ |
91 | | static int64_t ascend_find_next_packet(wtap *wth, int *err, char **err_info) |
92 | 0 | { |
93 | 0 | int byte; |
94 | 0 | int64_t date_off = -1, cur_off, packet_off; |
95 | 0 | size_t string_level[ASCEND_MAGIC_STRINGS]; |
96 | 0 | unsigned string_i = 0; |
97 | 0 | static const char ascend_date[] = ASCEND_DATE; |
98 | 0 | size_t ascend_date_len = sizeof ascend_date - 1; /* strlen of a constant string */ |
99 | 0 | size_t ascend_date_string_level; |
100 | 0 | unsigned excessive_read_count = 262144; |
101 | |
|
102 | 0 | memset(&string_level, 0, sizeof(string_level)); |
103 | 0 | ascend_date_string_level = 0; |
104 | |
|
105 | 0 | while (((byte = file_getc(wth->fh)) != EOF)) { |
106 | 0 | excessive_read_count--; |
107 | |
|
108 | 0 | if (!excessive_read_count) { |
109 | 0 | *err = 0; |
110 | 0 | return -1; |
111 | 0 | } |
112 | | |
113 | | /* |
114 | | * See whether this is the string_level[string_i]th character of |
115 | | * Ascend magic string string_i. |
116 | | */ |
117 | 0 | for (string_i = 0; string_i < ASCEND_MAGIC_STRINGS; string_i++) { |
118 | 0 | const char *strptr = ascend_magic[string_i].strptr; |
119 | 0 | size_t len = ascend_magic[string_i].strlength; |
120 | |
|
121 | 0 | if (byte == *(strptr + string_level[string_i])) { |
122 | | /* |
123 | | * Yes, it is, so we need to check for the next character of |
124 | | * that string. |
125 | | */ |
126 | 0 | string_level[string_i]++; |
127 | | |
128 | | /* |
129 | | * Have we matched the entire string? |
130 | | */ |
131 | 0 | if (string_level[string_i] >= len) { |
132 | | /* |
133 | | * Yes. |
134 | | */ |
135 | 0 | cur_off = file_tell(wth->fh); |
136 | 0 | if (cur_off == -1) { |
137 | | /* Error. */ |
138 | 0 | *err = file_error(wth->fh, err_info); |
139 | 0 | return -1; |
140 | 0 | } |
141 | | |
142 | | /* We matched some other type of header. */ |
143 | 0 | if (date_off == -1) { |
144 | | /* We haven't yet seen a date header, so this packet |
145 | | doesn't have one. |
146 | | Back up over the header we just read; that's where a read |
147 | | of this packet should start. */ |
148 | 0 | packet_off = cur_off - len; |
149 | 0 | } else { |
150 | | /* This packet has a date/time header; a read of it should |
151 | | start at the beginning of *that* header. */ |
152 | 0 | packet_off = date_off; |
153 | 0 | } |
154 | |
|
155 | 0 | goto found; |
156 | 0 | } |
157 | 0 | } else { |
158 | | /* |
159 | | * Not a match for this string, so reset the match process. |
160 | | */ |
161 | 0 | string_level[string_i] = 0; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | /* |
166 | | * See whether this is the date_string_level'th character of |
167 | | * ASCEND_DATE. |
168 | | */ |
169 | 0 | if (byte == *(ascend_date + ascend_date_string_level)) { |
170 | | /* |
171 | | * Yes, it is, so we need to check for the next character of |
172 | | * that string. |
173 | | */ |
174 | 0 | ascend_date_string_level++; |
175 | | |
176 | | /* |
177 | | * Have we matched the entire string? |
178 | | */ |
179 | 0 | if (ascend_date_string_level >= ascend_date_len) { |
180 | | /* We matched a Date: header. It's a special case; |
181 | | remember the offset, but keep looking for other |
182 | | headers. |
183 | | |
184 | | Reset the amount of Date: header that we've matched, |
185 | | so that we start the process of matching a Date: |
186 | | header all over again. |
187 | | |
188 | | XXX - what if we match multiple Date: headers before |
189 | | matching some other header? */ |
190 | 0 | cur_off = file_tell(wth->fh); |
191 | 0 | if (cur_off == -1) { |
192 | | /* Error. */ |
193 | 0 | *err = file_error(wth->fh, err_info); |
194 | 0 | return -1; |
195 | 0 | } |
196 | | |
197 | 0 | date_off = cur_off - ascend_date_len; |
198 | 0 | ascend_date_string_level = 0; |
199 | 0 | } |
200 | 0 | } else { |
201 | | /* |
202 | | * Not a match for the Date: string, so reset the match process. |
203 | | */ |
204 | 0 | ascend_date_string_level = 0; |
205 | 0 | } |
206 | 0 | } |
207 | | |
208 | 0 | *err = file_error(wth->fh, err_info); |
209 | 0 | return -1; |
210 | | |
211 | 0 | found: |
212 | | /* |
213 | | * Move to where the read for this packet should start, and return |
214 | | * that seek offset. |
215 | | */ |
216 | 0 | if (file_seek(wth->fh, packet_off, SEEK_SET, err) == -1) |
217 | 0 | return -1; |
218 | | |
219 | 0 | return packet_off; |
220 | 0 | } |
221 | | |
222 | | wtap_open_return_val ascend_open(wtap *wth, int *err, char **err_info) |
223 | 0 | { |
224 | 0 | int64_t offset; |
225 | 0 | uint8_t buf[ASCEND_MAX_PKT_LEN]; |
226 | 0 | ascend_state_t parser_state = {0}; |
227 | 0 | ws_statb64 statbuf; |
228 | 0 | ascend_t *ascend; |
229 | 0 | wtap_rec rec; |
230 | | |
231 | | /* We haven't yet allocated a data structure for our private stuff; |
232 | | set the pointer to null, so that "ascend_find_next_packet()" knows |
233 | | not to fill it in. */ |
234 | 0 | wth->priv = NULL; |
235 | |
|
236 | 0 | offset = ascend_find_next_packet(wth, err, err_info); |
237 | 0 | if (offset == -1) { |
238 | 0 | if (*err != 0 && *err != WTAP_ERR_SHORT_READ) |
239 | 0 | return WTAP_OPEN_ERROR; /* read error */ |
240 | 0 | return WTAP_OPEN_NOT_MINE; /* EOF */ |
241 | 0 | } |
242 | | |
243 | | /* Do a trial parse of the first packet just found to see if we might |
244 | | really have an Ascend file. If it fails with an actual error, |
245 | | fail; those will be I/O errors. */ |
246 | 0 | parser_state.fh = wth->fh; |
247 | 0 | parser_state.pseudo_header = &rec.rec_header.packet_header.pseudo_header.ascend; |
248 | 0 | if (run_ascend_parser(buf, &parser_state, err, err_info) != 0 && *err != 0) { |
249 | | /* An I/O error. */ |
250 | 0 | return WTAP_OPEN_ERROR; |
251 | 0 | } |
252 | | |
253 | | /* Either the parse succeeded, or it failed but didn't get an I/O |
254 | | error. |
255 | | |
256 | | If we got at least some data, return success even if the parser |
257 | | reported an error. This is because the debug header gives the |
258 | | number of bytes on the wire, not actually how many bytes are in |
259 | | the trace. We won't know where the data ends until we run into |
260 | | the next packet. */ |
261 | 0 | if (parser_state.caplen == 0) { |
262 | | /* We read no data, so this presumably isn't an Ascend file. */ |
263 | 0 | return WTAP_OPEN_NOT_MINE; |
264 | 0 | } |
265 | | |
266 | 0 | wth->file_type_subtype = ascend_file_type_subtype; |
267 | 0 | wth->file_encap = WTAP_ENCAP_ASCEND; |
268 | |
|
269 | 0 | wth->snapshot_length = ASCEND_MAX_PKT_LEN; |
270 | 0 | wth->subtype_read = ascend_read; |
271 | 0 | wth->subtype_seek_read = ascend_seek_read; |
272 | 0 | ascend = g_new(ascend_t, 1); |
273 | 0 | wth->priv = (void *)ascend; |
274 | | |
275 | | /* The first packet we want to read is the one that |
276 | | "ascend_find_next_packet()" just found; start searching |
277 | | for it at the offset at which it found it. */ |
278 | 0 | ascend->next_packet_seek_start = offset; |
279 | | |
280 | | /* MAXen and Pipelines report the time since reboot. In order to keep |
281 | | from reporting packet times near the epoch, we subtract the first |
282 | | packet's timestamp from the capture file's ctime, which gives us an |
283 | | offset that we can apply to each packet. |
284 | | */ |
285 | 0 | if (wtap_fstat(wth, &statbuf, err) == -1) { |
286 | 0 | return WTAP_OPEN_ERROR; |
287 | 0 | } |
288 | 0 | ascend->inittime = statbuf.st_ctime; |
289 | 0 | ascend->adjusted = false; |
290 | 0 | wth->file_tsprec = WTAP_TSPREC_USEC; |
291 | | |
292 | | /* |
293 | | * Add an IDB; we don't know how many interfaces were |
294 | | * involved, so we just say one interface, about which |
295 | | * we only know the link-layer type, snapshot length, |
296 | | * and time stamp resolution. |
297 | | */ |
298 | 0 | wtap_add_generated_idb(wth); |
299 | |
|
300 | 0 | return WTAP_OPEN_MINE; |
301 | 0 | } |
302 | | |
303 | | /* Parse the capture file. |
304 | | Returns true if we got a packet, false otherwise. */ |
305 | | static bool |
306 | | parse_ascend(ascend_t *ascend, FILE_T fh, wtap_rec *rec, |
307 | | unsigned length, int64_t *next_packet_seek_start_ret, |
308 | | int *err, char **err_info) |
309 | 0 | { |
310 | 0 | ascend_state_t parser_state = {0}; |
311 | 0 | int retval; |
312 | |
|
313 | 0 | ws_buffer_assure_space(&rec->data, length); |
314 | 0 | parser_state.fh = fh; |
315 | 0 | parser_state.pseudo_header = &rec->rec_header.packet_header.pseudo_header.ascend; |
316 | |
|
317 | 0 | retval = run_ascend_parser(ws_buffer_start_ptr(&rec->data), &parser_state, err, err_info); |
318 | | |
319 | | /* Did we see any data (hex bytes)? */ |
320 | 0 | if (parser_state.first_hexbyte) { |
321 | | /* Yes. Provide the offset of the first byte so that our caller can |
322 | | tip off ascend_find_next_packet() as to where to look for the next |
323 | | packet, if any. */ |
324 | 0 | if (next_packet_seek_start_ret != NULL) |
325 | 0 | *next_packet_seek_start_ret = parser_state.first_hexbyte; |
326 | 0 | } else { |
327 | | /* No. Maybe this record was broken; sometimes, a header will be |
328 | | printed but the data will be omitted, or worse -- two headers will |
329 | | be printed, followed by the data for each. |
330 | | |
331 | | Because of this, we need to be fairly tolerant of what we accept |
332 | | here. Provide our current offset so that our caller can tell |
333 | | ascend_find_next_packet() to skip over what we've read so far so |
334 | | we can try reading a new packet. |
335 | | |
336 | | . That keeps us from getting into an infinite loop reading a broken |
337 | | trace. */ |
338 | 0 | if (next_packet_seek_start_ret != NULL) |
339 | 0 | *next_packet_seek_start_ret = file_tell(fh); |
340 | | |
341 | | /* Don't treat that as a fatal error; pretend the parse succeeded. */ |
342 | 0 | retval = 0; |
343 | 0 | } |
344 | | |
345 | | /* if we got at least some data, return success even if the parser |
346 | | reported an error. This is because the debug header gives the number |
347 | | of bytes on the wire, not actually how many bytes are in the trace. |
348 | | We won't know where the data ends until we run into the next packet. */ |
349 | 0 | if (parser_state.caplen) { |
350 | 0 | if (! ascend->adjusted) { |
351 | 0 | ascend->adjusted = true; |
352 | 0 | if (parser_state.saw_timestamp) { |
353 | | /* |
354 | | * Capture file contained a date and time. |
355 | | * We do this only if this is the very first packet we've seen - |
356 | | * i.e., if "ascend->adjusted" is false - because |
357 | | * if we get a date and time after the first packet, we can't |
358 | | * go back and adjust the time stamps of the packets we've already |
359 | | * processed, and basing the time stamps of this and following |
360 | | * packets on the time stamp from the file text rather than the |
361 | | * ctime of the capture file means times before this and after |
362 | | * this can't be compared. |
363 | | */ |
364 | 0 | ascend->inittime = parser_state.timestamp; |
365 | 0 | } |
366 | 0 | if (ascend->inittime > parser_state.secs) |
367 | 0 | ascend->inittime -= parser_state.secs; |
368 | 0 | } |
369 | 0 | rec->rec_type = REC_TYPE_PACKET; |
370 | 0 | rec->block = wtap_block_create(WTAP_BLOCK_PACKET); |
371 | 0 | rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN; |
372 | 0 | rec->ts.secs = parser_state.secs + ascend->inittime; |
373 | 0 | rec->ts.nsecs = parser_state.usecs * 1000; |
374 | 0 | rec->rec_header.packet_header.caplen = parser_state.caplen; |
375 | 0 | rec->rec_header.packet_header.len = parser_state.wirelen; |
376 | |
|
377 | 0 | return true; |
378 | 0 | } |
379 | | |
380 | | /* Didn't see any data. Still, perhaps the parser was happy. */ |
381 | 0 | if (retval) { |
382 | 0 | if (*err == 0) { |
383 | | /* Parser failed, but didn't report an I/O error, so a parse error. |
384 | | Return WTAP_ERR_BAD_FILE, with the parse error as the error string. */ |
385 | 0 | *err = WTAP_ERR_BAD_FILE; |
386 | 0 | *err_info = g_strdup((parser_state.ascend_parse_error != NULL) ? parser_state.ascend_parse_error : "parse error"); |
387 | 0 | } |
388 | 0 | } else { |
389 | 0 | if (*err == 0) { |
390 | | /* Parser succeeded, but got no data, and didn't report an I/O error. |
391 | | Return WTAP_ERR_BAD_FILE, with a "got no data" error string. */ |
392 | 0 | *err = WTAP_ERR_BAD_FILE; |
393 | 0 | *err_info = g_strdup("no data returned by parse"); |
394 | 0 | } |
395 | 0 | } |
396 | 0 | return false; |
397 | 0 | } |
398 | | |
399 | | /* Read the next packet; called from wtap_read(). */ |
400 | | static bool ascend_read(wtap *wth, wtap_rec *rec, int *err, |
401 | | char **err_info, int64_t *data_offset) |
402 | 0 | { |
403 | 0 | ascend_t *ascend = (ascend_t *)wth->priv; |
404 | 0 | int64_t offset; |
405 | | |
406 | | /* parse_ascend() will advance the point at which to look for the next |
407 | | packet's header, to just after the last packet's header (ie. at the |
408 | | start of the last packet's data). We have to get past the last |
409 | | packet's header because we might mistake part of it for a new header. */ |
410 | 0 | if (file_seek(wth->fh, ascend->next_packet_seek_start, |
411 | 0 | SEEK_SET, err) == -1) |
412 | 0 | return false; |
413 | | |
414 | 0 | offset = ascend_find_next_packet(wth, err, err_info); |
415 | 0 | if (offset == -1) { |
416 | | /* EOF or read error */ |
417 | 0 | return false; |
418 | 0 | } |
419 | 0 | if (!parse_ascend(ascend, wth->fh, rec, wth->snapshot_length, |
420 | 0 | &ascend->next_packet_seek_start, err, err_info)) |
421 | 0 | return false; |
422 | | |
423 | | /* Flex might have gotten an EOF and caused *err to be set to |
424 | | WTAP_ERR_SHORT_READ. If so, that's not an error, as the parser |
425 | | didn't return an error; set *err to 0, and get rid of any error |
426 | | string. */ |
427 | 0 | *err = 0; |
428 | 0 | if (*err_info != NULL) { |
429 | 0 | g_free(*err_info); |
430 | 0 | *err_info = NULL; |
431 | 0 | } |
432 | 0 | *data_offset = offset; |
433 | 0 | return true; |
434 | 0 | } |
435 | | |
436 | | static bool ascend_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, |
437 | | int *err, char **err_info) |
438 | 0 | { |
439 | 0 | ascend_t *ascend = (ascend_t *)wth->priv; |
440 | |
|
441 | 0 | if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) |
442 | 0 | return false; |
443 | 0 | if (!parse_ascend(ascend, wth->random_fh, rec, wth->snapshot_length, |
444 | 0 | NULL, err, err_info)) |
445 | 0 | return false; |
446 | | |
447 | | /* Flex might have gotten an EOF and caused *err to be set to |
448 | | WTAP_ERR_SHORT_READ. If so, that's not an error, as the parser |
449 | | didn't return an error; set *err to 0, and get rid of any error |
450 | | string. */ |
451 | 0 | *err = 0; |
452 | 0 | if (*err_info != NULL) { |
453 | 0 | g_free(*err_info); |
454 | 0 | *err_info = NULL; |
455 | 0 | } |
456 | 0 | return true; |
457 | 0 | } |
458 | | |
459 | | static const struct supported_block_type ascend_blocks_supported[] = { |
460 | | /* |
461 | | * We support packet blocks, with no comments or other options. |
462 | | */ |
463 | | { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED } |
464 | | }; |
465 | | |
466 | | static const struct file_type_subtype_info ascend_info = { |
467 | | "Lucent/Ascend access server trace", "ascend", "txt", NULL, |
468 | | false, BLOCKS_SUPPORTED(ascend_blocks_supported), |
469 | | NULL, NULL, NULL |
470 | | }; |
471 | | |
472 | | void register_ascend(void) |
473 | 2 | { |
474 | 2 | ascend_file_type_subtype = wtap_register_file_type_subtype(&ascend_info); |
475 | | |
476 | | /* |
477 | | * Register name for backwards compatibility with the |
478 | | * wtap_filetypes table in Lua. |
479 | | */ |
480 | 2 | wtap_register_backwards_compatibility_lua_name("ASCEND", |
481 | 2 | ascend_file_type_subtype); |
482 | 2 | } |