2b02dbf0d0f44a598cf46c5b6d636fe006d882d2
[metze/wireshark/wip.git] / wiretap / vms.c
1 /* vms.c
2  *
3  * $Id$
4  *
5  * Wiretap Library
6  * Copyright (c) 2001 by Marc Milgram <ethereal@mmilgram.NOSPAMmail.net>
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 various VMS TCPIP trace utilities
44  * such as TCPIPTRACE, TCPTRACE and UCX$TRACE
45  *
46  * It was initially based on toshiba.c and refined with code from cosine.c
47
48 --------------------------------------------------------------------------------
49    Example TCPIPTRACE TCPTRACE output data:
50
51    TCPIPtrace full display RCV packet 8 at 10-JUL-2001 14:54:19.56
52
53    IP Version = 4,  IHL = 5,  TOS = 00,   Total Length = 84 = ^x0054
54    IP Identifier  = ^x178F,  Flags (0=0,DF=0,MF=0),
55          Fragment Offset = 0 = ^x0000,   Calculated Offset = 0 = ^x0000
56    IP TTL = 64 = ^x40,  Protocol = 17 = ^x11,  Header Checksum = ^x4C71
57    IP Source Address      = 10.12.1.80
58    IP Destination Address = 10.12.1.50
59
60    UDP Source Port = 731,   UDP Destination Port = 111
61    UDP Header and Datagram Length = 64 = ^x0040,   Checksum = ^xB6C0
62
63    50010C0A   714C1140   00008F17   54000045    0000    E..T....@.Lq...P
64    27E54C3C | C0B64000   6F00DB02 | 32010C0A    0010    ...2...o.@..<L.'
65    02000000   A0860100   02000000   00000000    0020    ................
66    00000000   00000000   00000000   03000000    0030    ................
67    06000000   01000000   A5860100   00000000    0040    ................
68                                     00000000    0050    ....
69 --------------------------------------------------------------------------------
70
71    Example UCX$TRACE output data:
72
73     UCX INTERnet trace RCV packet seq # = 1 at 14-MAY-2003 11:32:10.93 
74
75    IP Version = 4,  IHL = 5,  TOS = 00,   Total Length = 583 = ^x0247 
76    IP Identifier  = ^x702E,  Flags (0=0,DF=0,MF=0),  
77          Fragment Offset = 0 = ^x0000,   Calculated Offset = 0 = ^x0000 
78    IP TTL = 128 = ^x80,  Protocol = 17 = ^x11,  Header Checksum = ^x70EC 
79    IP Source Address      = 10.20.4.159 
80    IP Destination Address = 10.20.4.255 
81
82    UDP Source Port = 138,   UDP Destination Port = 138 
83    UDP Header and Datagram Length = 563 = ^x0233,   Checksum = ^xB913 
84
85    9F04140A   70EC1180   0000702E   47020045    0000    E..G.p.....p....
86    B1B80E11 | B9133302   8A008A00 | FF04140A    0010    .........3......
87    46484648   45200000   1D028A00   9F04140A    0020    ...........EHFHF
88    43414341   4341434D   454D4546   45454550    0030    PEEEFEMEMCACACAC
89
90 --------------------------------------------------------------------------------
91
92    Alternate UCX$TRACE type output data:
93
94    TCPIP INTERnet trace RCV packet seq # = 1 at 23-OCT-1998 15:19:33.29
95
96    IP Version = 4,  IHL = 5,  TOS = 00,   Total Length = 217 = ^x00D9
97    IP Identifier  = ^x0065,  Flags (0=0,DF=0,MF=0),
98          Fragment Offset = 0 = ^x0000,   Calculated Offset = 0 = ^x0000
99    IP TTL = 32 = ^x20,  Protocol = 17 = ^x11,  Header Checksum = ^x8F6C
100    IP Source Address      = 16.20.168.93
101    IP Destination Address = 16.20.255.255
102
103    UDP Source Port = 138,   UDP Destination Port = 138
104    UDP Header and Datagram Length = 197 = ^x00C5,   Checksum = ^x0E77
105
106    5DA81410   8F6C1120   00000065   D9000045    0000    E...awe.....l....]
107             | 0E77C500   8A008A00 | FFFF1410    0010    ..........w.
108
109 --------------------------------------------------------------------------------
110
111 The only difference between the utilities is the Packet header line, primarily
112 the utility identifier and the packet sequence formats.
113
114 There appear to be 2 formats for packet seqencing
115
116 Format 1:
117
118  ... packet nn at DD-MMM-YYYY hh:mm:ss.ss
119
120 Format 2:
121
122  ... packet seq # = nn at DD-MMM-YYYY hh:mm:ss.ss
123
124 If there are other formats then code will have to be written in parse_vms_rec_hdr()
125 to handle them.
126
127 --------------------------------------------------------------------------------
128
129  */
130
131 /* Magic text to check for VMS-ness of file using possible utility names
132  *
133  */
134 #define VMS_HDR_MAGIC_STR1      "TCPIPtrace"
135 #define VMS_HDR_MAGIC_STR2      "TCPtrace"
136 #define VMS_HDR_MAGIC_STR3      "INTERnet trace"
137
138 /* Magic text for start of packet */
139 #define VMS_REC_MAGIC_STR1      VMS_HDR_MAGIC_STR1
140 #define VMS_REC_MAGIC_STR2      VMS_HDR_MAGIC_STR2
141 #define VMS_REC_MAGIC_STR3      VMS_HDR_MAGIC_STR3
142
143 #define VMS_HEADER_LINES_TO_CHECK    200
144 #define VMS_LINE_LENGTH              240
145
146 static gboolean vms_read(wtap *wth, int *err, gchar **err_info,
147     gint64 *data_offset);
148 static gboolean vms_seek_read(wtap *wth, gint64 seek_off,
149     union wtap_pseudo_header *pseudo_header, guint8 *pd, int len,
150     int *err, gchar **err_info);
151 static gboolean parse_single_hex_dump_line(char* rec, guint8 *buf,
152     long byte_offset, int in_off, int remaining_bytes);
153 static gboolean parse_vms_hex_dump(FILE_T fh, int pkt_len, guint8* buf,
154     int *err, gchar **err_info);
155 static int parse_vms_rec_hdr(wtap *wth, FILE_T fh, int *err, gchar **err_info);
156
157 #ifdef TCPIPTRACE_FRAGMENTS_HAVE_HEADER_LINE
158 /* Seeks to the beginning of the next packet, and returns the
159    byte offset.  Returns -1 on failure, and sets "*err" to the error. */
160 static long vms_seek_next_packet(wtap *wth, int *err)
161 {
162   long cur_off;
163   char buf[VMS_LINE_LENGTH];
164   
165   while (1) {
166     cur_off = file_tell(wth->fh);
167     if (cur_off == -1) {
168       /* Error */
169       *err = file_error(wth->fh, err_info);
170       hdr = NULL;
171       return -1;
172     }
173     if (file_gets(buf, sizeof(buf), wth->fh) != NULL) {
174       if (strstr(buf, VMS_REC_MAGIC_STR1) ||
175           strstr(buf, VMS_REC_MAGIC_STR2) ||
176           strstr(buf, VMS_REC_MAGIC_STR2)) {
177                   g_strlcpy(hdr, buf,VMS_LINE_LENGTH);
178                   return cur_off;
179       }
180     } else {
181       if (file_eof(wth->fh)) {
182         /* We got an EOF. */
183         *err = 0;
184       } else {
185         /* We (presumably) got an error (there's no
186            equivalent to "ferror()" in zlib, alas,
187            so we don't have a wrapper to check for
188            an error). */
189         *err = file_error(wth->fh, err_info);
190       }
191       break;
192     }
193   }
194   hdr = NULL;
195   return -1;
196 }
197 #endif /* TCPIPTRACE_FRAGMENTS_HAVE_HEADER_LINE */
198
199 /* Look through the first part of a file to see if this is
200  * a VMS trace file.
201  *
202  * Returns TRUE if it is, FALSE if it isn't or if we get an I/O error;
203  * if we get an I/O error, "*err" will be set to a non-zero value and
204  * "*err_info will be set to null or an additional error string.
205  *
206  * Leaves file handle at begining of line that contains the VMS Magic
207  * identifier.
208  */
209 static gboolean vms_check_file_type(wtap *wth, int *err, gchar **err_info)
210 {
211   char  buf[VMS_LINE_LENGTH];
212   guint reclen, line;
213   gint64 mpos;
214   
215   buf[VMS_LINE_LENGTH-1] = '\0';
216   
217   for (line = 0; line < VMS_HEADER_LINES_TO_CHECK; line++) {
218     mpos = file_tell(wth->fh);
219     if (mpos == -1) {
220       /* Error. */
221       *err = file_error(wth->fh, err_info);
222       return FALSE;
223     }
224     if (file_gets(buf, VMS_LINE_LENGTH, wth->fh) != NULL) {
225       
226       reclen = (guint) strlen(buf);
227       if (reclen < strlen(VMS_HDR_MAGIC_STR1) ||
228           reclen < strlen(VMS_HDR_MAGIC_STR2) || 
229           reclen < strlen(VMS_HDR_MAGIC_STR3)) {
230         continue;
231       }
232       
233       if (strstr(buf, VMS_HDR_MAGIC_STR1) ||
234           strstr(buf, VMS_HDR_MAGIC_STR2) ||
235           strstr(buf, VMS_HDR_MAGIC_STR3)) {
236         /* Go back to the beginning of this line, so we will
237          * re-read it. */
238         if (file_seek(wth->fh, mpos, SEEK_SET, err) == -1) {
239           /* Error. */
240           return FALSE;
241         }
242         return TRUE;
243       }
244     } else {
245       /* EOF or error. */
246       if (file_eof(wth->fh))
247         *err = 0;
248       else
249         *err = file_error(wth->fh, err_info);
250       return FALSE;
251     }
252   }
253   *err = 0;
254   return FALSE;
255 }
256
257
258 int vms_open(wtap *wth, int *err, gchar **err_info)
259 {
260     /* Look for VMS header */
261     if (!vms_check_file_type(wth, err, err_info)) {
262         if (*err == 0)
263             return 0;
264         else
265             return -1;
266     }
267
268     wth->data_offset = 0;
269     wth->file_encap = WTAP_ENCAP_RAW_IP;
270     wth->file_type = WTAP_FILE_VMS;
271     wth->snapshot_length = 0; /* not known */
272     wth->subtype_read = vms_read;
273     wth->subtype_seek_read = vms_seek_read;
274         wth->tsprecision = WTAP_FILE_TSPREC_CSEC;
275
276     return 1;
277 }
278
279 /* Find the next packet and parse it; called from wtap_read(). */
280 static gboolean vms_read(wtap *wth, int *err, gchar **err_info,
281     gint64 *data_offset)
282 {
283     gint64   offset = 0;
284     guint8    *buf;
285     int    pkt_len;
286
287     /* Find the next packet */
288 #ifdef TCPIPTRACE_FRAGMENTS_HAVE_HEADER_LINE
289     offset = vms_seek_next_packet(wth, err);
290 #else
291     offset = file_tell(wth->fh);
292 #endif
293     if (offset < 1) {
294         *err = file_error(wth->fh, err_info);
295         return FALSE;
296     }
297
298     /* Parse the header */
299     pkt_len = parse_vms_rec_hdr(wth, wth->fh, err, err_info);
300     if (pkt_len == -1)
301         return FALSE;
302
303     /* Make sure we have enough room for the packet */
304     buffer_assure_space(wth->frame_buffer, pkt_len);
305     buf = buffer_start_ptr(wth->frame_buffer);
306
307     /* Convert the ASCII hex dump to binary data */
308     if (!parse_vms_hex_dump(wth->fh, pkt_len, buf, err, err_info))
309         return FALSE;
310
311     wth->data_offset = offset;
312     *data_offset = offset;
313     return TRUE;
314 }
315
316 /* Used to read packets in random-access fashion */
317 static gboolean
318 vms_seek_read (wtap *wth, gint64 seek_off,
319     union wtap_pseudo_header *pseudo_header _U_,
320     guint8 *pd, int len, int *err, gchar **err_info)
321 {
322     int    pkt_len;
323
324     if (file_seek(wth->random_fh, seek_off - 1, SEEK_SET, err) == -1)
325         return FALSE;
326
327     pkt_len = parse_vms_rec_hdr(NULL, wth->random_fh, err, err_info);
328
329     if (pkt_len != len) {
330         if (pkt_len != -1) {
331             *err = WTAP_ERR_BAD_FILE;
332             *err_info = g_strdup_printf("vms: requested length %d doesn't match length %d",
333                 len, pkt_len);
334         }
335         return FALSE;
336     }
337
338     return parse_vms_hex_dump(wth->random_fh, pkt_len, pd, err, err_info);
339 }
340
341 /* isdumpline assumes that dump lines start with some non-alphanumerics
342  * followed by 4 hex numbers - each 8 digits long, each hex number followed
343  * by 3 spaces.
344  */
345 static int
346 isdumpline( gchar *line )
347 {
348     int i, j;
349
350     while (*line && !isalnum((guchar)*line))
351         line++;
352
353     for (j=0; j<4; j++) {
354         for (i=0; i<8; i++, line++)
355             if (! isxdigit((guchar)*line))
356                 return FALSE;
357
358         for (i=0; i<3; i++, line++)
359             if (*line != ' ')
360                 return FALSE;
361     }
362
363     return isspace((guchar)*line);
364 }
365
366 /* Parses a packet record header. */
367 static int
368 parse_vms_rec_hdr(wtap *wth, FILE_T fh, int *err, gchar **err_info)
369 {
370     char   line[VMS_LINE_LENGTH + 1];
371     int    num_items_scanned;
372     int    pkt_len = 0;
373     int    pktnum;
374     int    csec = 101;
375     struct tm tm;
376     char mon[4] = {'J', 'A', 'N', 0};
377     gchar *p;
378     static const gchar months[] = "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC";
379
380     tm.tm_year = 1970;
381     tm.tm_mon = 0;
382     tm.tm_mday = 1;
383     tm.tm_hour = 1;
384     tm.tm_min = 1;
385     tm.tm_sec = 1;
386
387     /* Skip lines until one starts with a hex number */
388     do {
389         if (file_gets(line, VMS_LINE_LENGTH, fh) == NULL) {
390             *err = file_error(fh, err_info);
391             if ((*err == 0) && (csec != 101)) {
392                 *err = WTAP_ERR_SHORT_READ;
393             }
394             return -1;
395         }
396         line[VMS_LINE_LENGTH] = '\0';
397
398         if ((csec == 101) && (p = strstr(line, "packet ")) != NULL
399             && (! strstr(line, "could not save "))) {
400             /* Find text in line starting with "packet ". */
401
402             /* First look for the Format 1 type sequencing */
403             num_items_scanned = sscanf(p,  
404                                        "packet %9d at %2d-%3s-%4d %2d:%2d:%2d.%9d",
405                                        &pktnum, &tm.tm_mday, mon,
406                                        &tm.tm_year, &tm.tm_hour,
407                                        &tm.tm_min, &tm.tm_sec, &csec);
408             /* Next look for the Format 2 type sequencing */
409             if (num_items_scanned != 8) {
410               num_items_scanned = sscanf(p,
411                                          "packet seq # = %9d at %2d-%3s-%4d %2d:%2d:%2d.%9d",
412                                          &pktnum, &tm.tm_mday, mon,
413                                          &tm.tm_year, &tm.tm_hour,
414                                          &tm.tm_min, &tm.tm_sec, &csec);
415             }
416             /* if unknown format then exit with error        */
417             /* We will need to add code to handle new format */
418             if (num_items_scanned != 8) {
419                 *err = WTAP_ERR_BAD_FILE;
420                 *err_info = g_strdup_printf("vms: header line not valid");
421                 return -1;
422             }
423         }
424         if ( (! pkt_len) && (p = strstr(line, "Length"))) {
425             p += sizeof("Length ");
426             while (*p && ! isdigit((guchar)*p))
427                 p++;
428
429             if ( !*p ) {
430                 *err = WTAP_ERR_BAD_FILE;
431                 *err_info = g_strdup_printf("vms: Length field not valid");
432                 return -1;
433             }
434
435             pkt_len = atoi(p);
436             break;
437         }
438     } while (! isdumpline(line));
439
440     if (wth) {
441         p = strstr(months, mon);
442         if (p)
443             tm.tm_mon = (int) (p - months) / 3;
444         tm.tm_year -= 1900;
445
446         tm.tm_isdst = -1;
447         wth->phdr.presence_flags = WTAP_HAS_TS;
448         wth->phdr.ts.secs = mktime(&tm);
449         wth->phdr.ts.nsecs = csec * 10000000;
450         wth->phdr.caplen = pkt_len;
451         wth->phdr.len = pkt_len;
452     }
453
454     return pkt_len;
455 }
456
457 /* Converts ASCII hex dump to binary data */
458 static gboolean
459 parse_vms_hex_dump(FILE_T fh, int pkt_len, guint8* buf, int *err,
460     gchar **err_info)
461 {
462     gchar line[VMS_LINE_LENGTH + 1];
463     int    i;
464     int    offset = 0;
465
466     for (i = 0; i < pkt_len; i += 16) {
467         if (file_gets(line, VMS_LINE_LENGTH, fh) == NULL) {
468             *err = file_error(fh, err_info);
469             if (*err == 0) {
470                 *err = WTAP_ERR_SHORT_READ;
471             }
472             return FALSE;
473         }
474         line[VMS_LINE_LENGTH] = '\0';
475         if (i == 0) {
476             while (! isdumpline(line)) { /* advance to start of hex data */
477                 if (file_gets(line, VMS_LINE_LENGTH, fh) == NULL) {
478                     *err = file_error(fh, err_info);
479                     if (*err == 0) {
480                         *err = WTAP_ERR_SHORT_READ;
481                     }
482                     return FALSE;
483                 }
484                 line[VMS_LINE_LENGTH] = '\0';
485             }
486             while (line[offset] && !isxdigit((guchar)line[offset]))
487                 offset++;
488         }
489         if (!parse_single_hex_dump_line(line, buf, i,
490                                         offset, pkt_len - i)) {
491             *err = WTAP_ERR_BAD_FILE;
492             *err_info = g_strdup_printf("vms: hex dump not valid");
493             return FALSE;
494         }
495     }
496     /* Avoid TCPIPTRACE-W-BUFFERSFUL, TCPIPtrace could not save n packets.
497      * errors.
498      *
499      * XXX - when we support packet drop report information in the
500      * Wiretap API, we should parse those lines and return "n" as
501      * a packet drop count. */
502     if (!file_gets(line, VMS_LINE_LENGTH, fh)) {
503         *err = file_error(fh, err_info);
504         if (*err == 0) {
505             /* There is no next line, so there's no "TCPIPtrace could not
506              * save n packets" line; not an error. */
507             return TRUE;
508         }
509         return FALSE;
510     }
511     return TRUE;
512 }
513
514 /*
515           1         2         3         4
516 0123456789012345678901234567890123456789012345
517    50010C0A   A34C0640   00009017   2C000045    0000    E..,....@.L....P
518    00000000   14945E52   0A00DC02 | 32010C0A    0010    ...2....R^......
519        0000 | B4050402   00003496   00020260    0020    `....4........
520 */
521
522 #define START_POS    7
523 #define HEX_LENGTH    ((8 * 4) + 7) /* eight clumps of 4 bytes with 7 inner spaces */
524 /* Take a string representing one line from a hex dump and converts the
525  * text to binary data. We check the printed offset with the offset
526  * we are passed to validate the record. We place the bytes in the buffer
527  * at the specified offset.
528  *
529  * Returns TRUE if good hex dump, FALSE if bad.
530  */
531 static gboolean
532 parse_single_hex_dump_line(char* rec, guint8 *buf, long byte_offset,
533                int in_off, int remaining) {
534
535     int        i;
536     char        *s;
537     int        value;
538     static const int offsets[16] = {39,37,35,33,28,26,24,22,17,15,13,11,6,4,2,0};
539     char lbuf[3] = {0,0,0};
540
541
542     /* Get the byte_offset directly from the record */
543     s = rec;
544     value = (int)strtoul(s + 45 + in_off, NULL, 16);    /* XXX - error check? */
545
546     if (value != byte_offset) {
547         return FALSE;
548     }
549
550     if (remaining > 16)
551         remaining = 16;
552
553     /* Read the octets right to left, as that is how they are displayed
554      * in VMS.
555      */
556
557     for (i = 0; i < remaining; i++) {
558         lbuf[0] = rec[offsets[i] + in_off];
559         lbuf[1] = rec[offsets[i] + 1 + in_off];
560
561         buf[byte_offset + i] = (guint8) strtoul(lbuf, NULL, 16);
562     }
563
564     return TRUE;
565 }