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