Bug fix from Marc Milgram: occasionally the VMS parser would read off
[metze/wireshark/wip.git] / wiretap / vms.c
1 /* vms.c
2  *
3  * $Id: vms.c,v 1.11 2002/03/07 21:08:33 guy Exp $
4  *
5  * Wiretap Library
6  * Copyright (c) 2001 by Marc Milgram <mmilgram@arrayinc.com>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include "wtap-int.h"
27 #include "buffer.h"
28 #include "vms.h"
29 #include "file_wrappers.h"
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35
36 /* This module reads the output of the 'TCPIPTRACE' command in VMS
37  * It was initially based on toshiba.c.
38  */
39
40 /*
41    Example 'TCPIPTRACE' output data:
42    TCPIPtrace full display RCV packet 8 at 10-JUL-2001 14:54:19.56
43
44    IP Version = 4,  IHL = 5,  TOS = 00,   Total Length = 84 = ^x0054
45    IP Identifier  = ^x178F,  Flags (0=0,DF=0,MF=0),
46          Fragment Offset = 0 = ^x0000,   Calculated Offset = 0 = ^x0000
47    IP TTL = 64 = ^x40,  Protocol = 17 = ^x11,  Header Checksum = ^x4C71
48    IP Source Address      = 10.12.1.80
49    IP Destination Address = 10.12.1.50
50
51    UDP Source Port = 731,   UDP Destination Port = 111
52    UDP Header and Datagram Length = 64 = ^x0040,   Checksum = ^xB6C0
53
54    50010C0A   714C1140   00008F17   54000045    0000    E..T....@.Lq...P
55    27E54C3C | C0B64000   6F00DB02 | 32010C0A    0010    ...2...o.@..<L.'
56    02000000   A0860100   02000000   00000000    0020    ................
57    00000000   00000000   00000000   03000000    0030    ................
58    06000000   01000000   A5860100   00000000    0040    ................
59                                     00000000    0050    ....
60
61 --------------------------------------------------------------------------------
62
63  */
64
65 /* Magic text to check for VMS-ness of file */
66 static const char vms_hdr_magic[]  =
67 { 'T', 'C', 'P', 'I', 'P', 't', 'r', 'a', 'c', 'e', ' '};
68 #define VMS_HDR_MAGIC_SIZE  (sizeof vms_hdr_magic  / sizeof vms_hdr_magic[0])
69
70 /* Magic text for start of packet */
71 #define vms_rec_magic vms_hdr_magic
72 #define VMS_REC_MAGIC_SIZE  (sizeof vms_rec_magic  / sizeof vms_rec_magic[0])
73
74 static gboolean vms_read(wtap *wth, int *err, long *data_offset);
75 static gboolean vms_seek_read(wtap *wth, long seek_off,
76     union wtap_pseudo_header *pseudo_header, guint8 *pd, int len, int *err);
77 static gboolean parse_single_hex_dump_line(char* rec, guint8 *buf,
78     long byte_offset, int in_off, int remaining_bytes);
79 static gboolean parse_vms_hex_dump(FILE_T fh, int pkt_len, guint8* buf,
80     int *err);
81 static int parse_vms_rec_hdr(wtap *wth, FILE_T fh, int *err);
82
83
84 /* Seeks to the beginning of the next packet, and returns the
85    byte offset.  Returns -1 on failure, and sets "*err" to the error. */
86 static long vms_seek_next_packet(wtap *wth, int *err)
87 {
88   int byte;
89   unsigned int level = 0;
90   long cur_off;
91
92   while ((byte = file_getc(wth->fh)) != EOF) {
93     if ((level == 3) && (byte != vms_rec_magic[level]))
94       level += 2;  /* Accept TCPtrace as well as TCPIPtrace */
95     if (byte == vms_rec_magic[level]) {
96       level++;
97       if (level >= VMS_REC_MAGIC_SIZE) {
98         /* note: we're leaving file pointer right after the magic characters */
99         cur_off = file_tell(wth->fh);
100         if (cur_off == -1) {
101           /* Error. */
102           *err = file_error(wth->fh);
103           return -1;
104         }
105         return cur_off + 1;
106       }
107     } else {
108       level = 0;
109     }
110   }
111   if (file_eof(wth->fh)) {
112     /* We got an EOF. */
113     *err = 0;
114   } else {
115     /* We (presumably) got an error (there's no equivalent to "ferror()"
116        in zlib, alas, so we don't have a wrapper to check for an error). */
117     *err = file_error(wth->fh);
118   }
119   return -1;
120 }
121
122 #define VMS_HEADER_LINES_TO_CHECK    200
123 #define VMS_LINE_LENGTH        240
124
125 /* Look through the first part of a file to see if this is
126  * a VMS trace file.
127  *
128  * Returns TRUE if it is, FALSE if it isn't or if we get an I/O error;
129  * if we get an I/O error, "*err" will be set to a non-zero value.
130  *
131  * Leaves file handle at begining of line that contains the VMS Magic
132  * identifier.
133  */
134 static gboolean vms_check_file_type(wtap *wth, int *err)
135 {
136     char    buf[VMS_LINE_LENGTH];
137     int    line, byte;
138     unsigned int reclen, i, level;
139     long mpos;
140    
141     buf[VMS_LINE_LENGTH-1] = 0;
142
143     for (line = 0; line < VMS_HEADER_LINES_TO_CHECK; line++) {
144         mpos = file_tell(wth->fh);
145         if (mpos == -1) {
146             /* Error. */
147             *err = file_error(wth->fh);
148             return FALSE;
149         }
150         if (file_gets(buf, VMS_LINE_LENGTH, wth->fh) != NULL) {
151
152             reclen = strlen(buf);
153             if (reclen < VMS_HDR_MAGIC_SIZE)
154                 continue;
155
156             level = 0;
157             for (i = 0; i < reclen; i++) {
158                 byte = buf[i];
159                 if ((level == 3) && (byte != vms_hdr_magic[level]))
160                     level += 2; /* Accept TCPIPtrace as well as TCPtrace */
161                 if (byte == vms_hdr_magic[level]) {
162                     level++;
163                     if (level >= VMS_HDR_MAGIC_SIZE) {
164                         if (file_seek(wth->fh, mpos, SEEK_SET) == -1) {
165                             /* Error. */
166                             *err = file_error(wth->fh);
167                             return FALSE;
168                         }
169                         return TRUE;
170                     }
171                 }
172                 else
173                     level = 0;
174             }
175         }
176         else {
177             /* EOF or error. */
178             if (file_eof(wth->fh))
179                 *err = 0;
180             else
181                 *err = file_error(wth->fh);
182             return FALSE;
183         }
184     }
185     *err = 0;
186     return FALSE;
187 }
188
189
190 int vms_open(wtap *wth, int *err)
191 {
192     /* Look for VMS header */
193     if (!vms_check_file_type(wth, err)) {
194         if (*err == 0)
195             return 0;
196         else
197             return -1;
198     }
199
200     wth->data_offset = 0;
201     wth->file_encap = WTAP_ENCAP_RAW_IP;
202     wth->file_type = WTAP_FILE_VMS;
203     wth->snapshot_length = 0; /* not known */
204     wth->subtype_read = vms_read;
205     wth->subtype_seek_read = vms_seek_read;
206
207     return 1;
208 }
209
210 /* Find the next packet and parse it; called from wtap_loop(). */
211 static gboolean vms_read(wtap *wth, int *err, long *data_offset)
212 {
213     long   offset = 0;
214     guint8    *buf;
215     int    pkt_len;
216
217     /* Find the next packet */
218     offset = vms_seek_next_packet(wth, err);
219     if (offset < 1)
220         return FALSE;
221
222     /* Parse the header */
223     pkt_len = parse_vms_rec_hdr(wth, wth->fh, err);
224     if (pkt_len == -1)
225         return FALSE;
226
227     /* Make sure we have enough room for the packet */
228     buffer_assure_space(wth->frame_buffer, pkt_len);
229     buf = buffer_start_ptr(wth->frame_buffer);
230
231     /* Convert the ASCII hex dump to binary data */
232     if (!parse_vms_hex_dump(wth->fh, pkt_len, buf, err))
233         return FALSE;
234
235     wth->data_offset = offset;
236     *data_offset = offset;
237     return TRUE;
238 }
239
240 /* Used to read packets in random-access fashion */
241 static gboolean
242 vms_seek_read (wtap *wth, long seek_off,
243     union wtap_pseudo_header *pseudo_header _U_,
244     guint8 *pd, int len, int *err)
245 {
246     int    pkt_len;
247
248     if (file_seek(wth->random_fh, seek_off - 1, SEEK_SET) == -1) {
249         *err = file_error(wth->random_fh);
250         return FALSE;
251     }
252
253     pkt_len = parse_vms_rec_hdr(NULL, wth->random_fh, err);
254
255     if (pkt_len != len) {
256         if (pkt_len != -1)
257             *err = WTAP_ERR_BAD_RECORD;
258         return FALSE;
259     }
260
261     return parse_vms_hex_dump(wth->random_fh, pkt_len, pd, err);
262 }
263
264 /* isdumpline assumes that dump lines start with some spaces followed by a
265  * hex number.
266  */
267 static int
268 isdumpline( guchar *line )
269 {
270     int i = 0;
271
272     while (i<VMS_LINE_LENGTH && line[i] && !isalnum(line[i]))
273         i++;
274
275     if (! isxdigit(line[i]))
276         return 0;
277
278     while (i<VMS_LINE_LENGTH && isxdigit(line[i]))
279         i++;
280
281     return isspace(line[i]);
282 }
283
284 /* Parses a packet record header. */
285 static int
286 parse_vms_rec_hdr(wtap *wth, FILE_T fh, int *err)
287 {
288     char    line[VMS_LINE_LENGTH];
289     int    num_items_scanned;
290     int    pkt_len = 0;
291     int    pktnum;
292     int    csec = 101;
293     struct tm time;
294     char mon[4] = {'J', 'A', 'N', 0};
295     guchar *p;
296     static guchar months[] = "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC";
297
298     time.tm_year = 1970;
299     time.tm_hour = 1;
300     time.tm_min = 1;
301     time.tm_sec = 1;
302
303
304     /* Skip lines until one starts with a hex number */
305     do {
306         if (file_gets(line, VMS_LINE_LENGTH, fh) == NULL) {
307             *err = file_error(fh);
308             if ((*err == 0) && (csec != 101)) {
309                 *err = WTAP_ERR_SHORT_READ;
310             }
311             return -1;
312         }
313         if ((csec == 101) && (p = strstr(line, "packet "))
314             && (! strstr(line, "could not save "))) {
315             /* Find text in line starting with "packet ". */
316             num_items_scanned = sscanf(p,
317                                        "packet %d at %d-%3s-%d %d:%d:%d.%d",
318                                        &pktnum, &time.tm_mday, mon,
319                                        &time.tm_year, &time.tm_hour,
320                                        &time.tm_min, &time.tm_sec, &csec);
321
322             if (num_items_scanned != 8) {
323                 *err = WTAP_ERR_BAD_RECORD;
324                 return -1;
325             }
326         }
327         if ( (! pkt_len) && (p = strstr(line, "Length"))) {
328             p += sizeof("Length ");
329             while (*p && ! isdigit(*p))
330                 p++;
331
332             if ( !*p ) {
333                 *err = WTAP_ERR_BAD_RECORD;
334                 return -1;
335             }
336
337             pkt_len = atoi(p);
338             break;
339         }
340     } while (! isdumpline(line));
341
342     if (wth) {
343         p = strstr(months, mon);
344         if (p)
345             time.tm_mon = (p - months) / 3;
346         time.tm_year -= 1900;
347
348         wth->phdr.ts.tv_sec = mktime(&time);
349
350         wth->phdr.ts.tv_usec = csec * 10000;
351         wth->phdr.caplen = pkt_len;
352         wth->phdr.len = pkt_len;
353         wth->phdr.pkt_encap = WTAP_ENCAP_RAW_IP;
354     }
355
356     return pkt_len;
357 }
358
359 /* Converts ASCII hex dump to binary data */
360 static gboolean
361 parse_vms_hex_dump(FILE_T fh, int pkt_len, guint8* buf, int *err)
362 {
363     guchar line[VMS_LINE_LENGTH];
364     int    i;
365     int    offset = 0;
366
367     for (i = 0; i < pkt_len; i += 16) {
368         if (file_gets(line, VMS_LINE_LENGTH, fh) == NULL) {
369             *err = file_error(fh);
370             if (*err == 0) {
371                 *err = WTAP_ERR_SHORT_READ;
372             }
373             return FALSE;
374         }
375         if (i == 0) {
376             while (! isdumpline(line)) /* advance to start of hex data */
377                 if (file_gets(line, VMS_LINE_LENGTH, fh) == NULL) {
378                     *err = file_error(fh);
379                     if (*err == 0) {
380                         *err = WTAP_ERR_SHORT_READ;
381                     }
382                     return FALSE;
383                 }
384             while (line[offset] && !isxdigit(line[offset]))
385                 offset++;
386         }
387         if (!parse_single_hex_dump_line(line, buf, i,
388                                         offset, pkt_len - i)) {
389             *err = WTAP_ERR_BAD_RECORD;
390             return FALSE;
391         }
392     }
393     /* Avoid TCPIPTRACE-W-BUFFERSFUL, TCPIPtrace could not save n packets.
394      * errors. */
395     file_gets(line, VMS_LINE_LENGTH, fh);
396     return TRUE;
397 }
398
399 /*
400           1         2         3         4
401 0123456789012345678901234567890123456789012345
402    50010C0A   A34C0640   00009017   2C000045    0000    E..,....@.L....P
403    00000000   14945E52   0A00DC02 | 32010C0A    0010    ...2....R^......
404        0000 | B4050402   00003496   00020260    0020    `....4........
405 */
406
407 #define START_POS    7
408 #define HEX_LENGTH    ((8 * 4) + 7) /* eight clumps of 4 bytes with 7 inner spaces */
409 /* Take a string representing one line from a hex dump and converts the
410  * text to binary data. We check the printed offset with the offset
411  * we are passed to validate the record. We place the bytes in the buffer
412  * at the specified offset.
413  *
414  * Returns TRUE if good hex dump, FALSE if bad.
415  */
416 static gboolean
417 parse_single_hex_dump_line(char* rec, guint8 *buf, long byte_offset,
418                int in_off, int remaining) {
419
420     int        i;
421     char        *s;
422     int        value;
423     static int offsets[16] = {39,37,35,33,28,26,24,22,17,15,13,11,6,4,2,0};
424     char lbuf[3] = {0,0,0};
425    
426
427     /* Get the byte_offset directly from the record */
428     s = rec;
429     value = strtoul(s + 45 + in_off, NULL, 16);
430    
431     if (value != byte_offset) {
432         return FALSE;
433     }
434
435     if (remaining > 16)
436         remaining = 16;
437
438     /* Read the octets right to left, as that is how they are displayed
439      * in VMS.
440      */
441
442     for (i = 0; i < remaining; i++) {
443         lbuf[0] = rec[offsets[i] + in_off];
444         lbuf[1] = rec[offsets[i] + 1 + in_off];
445
446         buf[byte_offset + i] = (guint8) strtoul(lbuf, NULL, 16);
447     }
448
449     return TRUE;
450 }