/* * Dumpxje * * This program takes the output of a tcpdump of an RJE or NJE (either TCPNJE * format or raw format such as produced by a 2703 emulated under hercules) * data stream and formats it to be reasonably human readable. TCPNJE format * means the NJE over TCP/IP format used for BITNET II also known as VMNET. * * (C) Copyright Peter Coghlan 2014 * */ #include #include #include #include #include #include #define MIN(a, b) ((a < b) ? a : b) #define SOH 0x01 #define STX 0x02 #define DLE 0x10 #define ETB 0x26 #define ENQ 0x2D #define SYN 0x32 #define NAK 0x3D #define ACK0 0x70 static char buffer[256]; /* Accomodate max length lines in tcpdump output */ static int bytes = 0; /* Number of bytes read from current tcpdump line */ static int ipheaders = 0; /* Flag specify IP header information be printed */ int scanlineofinput(unsigned int *data) { /* Cut off any ASCII (or EBCDIC) dump after the hex dump as this may get interpreted as extra hex digits that aren't really there in cases where there are less than 16 bytes on the line. Unfortunately, I can't think of a good way of doing this without making even more assumptions about the format of the tcpdump output. */ if (strlen(buffer) > 43) buffer[43] = '\0'; /* Return the number of two byte hex numbers read from the input buffer */ return sscanf(buffer, "\t\t %2x%2x %2x%2x %2x%2x %2x%2x %2x%2x %2x%2x %2x%2x %2x%2x", &data[0], &data[1], &data[2], &data[3], &data[4], &data[5], &data[6], &data[7], &data[8], &data[9], &data[10], &data[11], &data[12], &data[13], &data[14], &data[15]); } int discardheaders(unsigned int *data, int *offset, FILE *input) /* Eat lines from the dump until we reach the line containing the byte specified by *offset, updating *offset to leave it pointing at the required byte when we return. */ { static int count; /* Remember last value if we loop 0 times */ char *ptr; while ((*offset) > 15) { /* Discard lines until we get to the requested offset */ ptr = fgets(buffer, sizeof(buffer), input); if (ptr == NULL) return 0; count = scanlineofinput(data); (*offset) -= 16; /* Last line we dumped contained 16 hex bytes */ } /* Return the number of hex numbers read from the current line processed */ return count; } int getbytes(unsigned char *record, int wanted, unsigned int *srcip, unsigned int *srcport, unsigned int *changed, int *offset, FILE *output, FILE *input) { /* Retrieve up to 16 data bytes from the input stream, starting on the current line, reading in another line if necessary, formatting and displaying IP and TCP headers encountered along the way */ unsigned int tmpsrcip, tmpsrcport; int startofpacket = 0; int i, part, next, assumed, iplength, tcplength, total, tcpflags, protocol; int zeros[9] = { 0, 0, 0 ,0, 0, 0, 0, 0, 0}; static unsigned int data[16]; /* Would like to use char but sscanf objects */ char *ptr; /* We can't cope with (much) more than 16 bytes at a time */ assert(wanted <= 16); *changed = 0; /* Flag ip address and port not changed since last call */ /* Copy up to the end of the current line or as far as wanted if possible */ part = MIN(wanted, (bytes - (*offset))); if (part) for (i = 0; i < part; i++, (*offset)++) *(record++) = data[*offset]; if (part < wanted) { while (!feof(input)) { /* Not that this feof() ever triggers... */ ptr = fgets(buffer, sizeof(buffer), input); if (ptr == NULL) break; /* NULL may mean EOF or other error */ if ((*offset) >= 16) (*offset) -= 16; if (buffer[0] != '\t') { /* Start of packet banner - just print it as is */ if (ipheaders) fprintf(output, "%s", buffer); startofpacket = 1; continue; } /* Process a line of the hex dump and decide what it is */ bytes = scanlineofinput(data); if (startofpacket) { /* Sometimes tcpdump zeros the start of the IP header on the loopback interface */ if (!memcmp(data, zeros, sizeof(data[0]) * 9)) { data[0] = 0x45; /* Make like an IPv4 packet */ data[3] = 0x34; /* Assume headers length 52 */ assumed = 1; } else assumed = 0; if ((data[0] >> 4) == 4) { /* IPv4 packet */ iplength = (data[0] & 15) * 4; /* b0-b3 = no of lwords */ total = (data[2] << 8) + data[3]; /* Total packet size */ protocol = data[9]; /* Usually TCP or UDP */ if (assumed) { if (ipheaders) fprintf(output, " Assumed IPv4 packet header size %d", iplength); } else { if (ipheaders) fprintf(output, " IPv4 packet size %d, header size %d", total, iplength); } /* Grab a copy of the source IP address (in network byte order) before we go past it */ tmpsrcip = (data[15] << 24) | (data[14] << 16) | (data[13] << 8) | (data[12] << 0); *offset = iplength; /* Begin again after the IP header */ /* Skip past the IP header without displaying it */ bytes = discardheaders(data, offset, input); if (bytes == 0) break; if (protocol == 6) { /* TCP */ /* Grab a copy of the source port before we go past it */ /* Cheating a bit here. *offset could be more than 14 in theory. */ tmpsrcport = ntohs((data[*offset+1] << 8) | (data[*offset+0] << 0)); /* Get the line with the TCP hdr length @ offset 12 */ (*offset) += 12; bytes = discardheaders(data, offset, input); if (bytes == 0) break; tcplength = (data[*offset] >> 4) * 4; /* Assume the TCP flags are on the same line */ tcpflags = data[*offset+1] & 63; if (ipheaders) fprintf(output, ", TCP header size %d", tcplength); if (ipheaders && !assumed) fprintf(output, ", data size %d", total - iplength - tcplength); /* Skip past the rest of the TCP header */ *offset = (*offset) - 12 + tcplength; /* RST packets sometimes have an extra trailing six bytes not accounted for in the headers */ if (tcpflags & 4) { if (bytes > (*offset) % 16) { (*offset) += bytes - ((*offset) % 16); } } bytes = discardheaders(data, offset, input); if (bytes == 0) break; /* Dont be upset by an ack packet containing no data coming from the wrong side of the connection. */ if (((total - iplength - tcplength) != 0) || assumed) { if (((tmpsrcip != *srcip) && (*srcip !=0)) || ((tmpsrcport != *srcport) && (*srcport != 0))) *changed = 1; *srcip = tmpsrcip; *srcport = tmpsrcport; } } if (ipheaders) fprintf(output, "\n"); } startofpacket = 0; } next = MIN(wanted - part, (bytes - (*offset))); if (next) for (i = 0; i < next; i++, (*offset)++) *(record++) = data[*offset]; part += next; if (part == wanted) break; } } return part; } void main(int argc, char **argv) { int args, argptr, i, j, k, count, number, offset, eor, part; int decompress = 0, compressed; int vmnet = 0, vmnetcount, vmnetlength; unsigned int srcip = 0, srcport = 0, changed = 0; unsigned char ebcdic_to_ascii[] = { "................................................................" " ...........<(+.&.........!$*);^-/........|,%_>?.........`:#@'=\"" ".abcdefghi.......jklmnopqr.......~stuvwxyz...[...............].." "{ABCDEFGHI......}JKLMNOPQR......\\.STUVWXYZ......0123456789......" }; unsigned char block[3], record[80 + 64], character; char *ptr; FILE *input, *output; struct { unsigned char flags; unsigned char unused1; unsigned short length; unsigned long int unused2; } TTB; struct { unsigned char flags; unsigned char unused; unsigned short length; } TTR; struct { unsigned char type[8]; unsigned char rhost[8]; unsigned long int rip; unsigned char ohost[8]; unsigned long int oip; unsigned char r; } TTC; struct in_addr intmp; unsigned int ebcdicspaces = 0x40404040; /* Process options first. Somebody could probably do better with getopt. */ args = argc; argptr = 1; while ((argptr < argc) && (argv[argptr][0] == '-')) { if (argv[argptr][1] == 'd') decompress = 1; else if (argv[argptr][1] == 'h') { printf("Usage: dumpxje \n\n"); printf(" Options: -d Decompress compressed strings\n"); printf(" -h Display usage information\n"); printf(" -i Include IP & TCP header information\n"); printf(" -t Assume input is TCPNJE/VMNET NJE over TCP format\n\n"); printf(" -v Assume input is TCPNJE/VMNET NJE over TCP format\n\n"); printf(" If an output file is not specified, output is written to\n"); printf(" standard output. If an input file is not specified\n"); printf(" either, input is taken from standard input.\n\n"); exit(EXIT_SUCCESS); } else if (argv[argptr][1] == 'i') ipheaders = 1; else if (argv[argptr][1] == 't') vmnet = 1; else if (argv[argptr][1] == 'v') vmnet = 1; else { fprintf(stderr, "Unrecognised option: -%s\n", argv[argptr]); exit(EXIT_FAILURE); } argptr++; args--; } /* Three arguments is too many */ if (args > 3) { fprintf(stderr, "Usage: Dumpxje \n"); exit(EXIT_FAILURE); } /* If there are one or more arguments, the first one is the input file */ if (args > 1) { input = fopen(argv[argptr], "r"); if (input == NULL) { fprintf(stderr, "Error opening input file %s\n", argv[argptr]); perror("open"); exit(EXIT_FAILURE); } } else input = stdin; /* If zero arguments, read from stdin. */ /* If there are two arguments, the second one is the output file */ if (args > 2) { output = fopen(argv[argptr + 1], "w"); if (output == NULL) { fprintf(stderr, "Error opening output file %s\n", argv[argptr + 1]); perror("open"); exit(EXIT_FAILURE); } } else output = stdout; /* If zero or one arguments, write to stdout. */ offset = 0; while (!feof(input)) { if (vmnet) { number = getbytes((unsigned char *)&TTB, sizeof(TTB), &srcip, &srcport, &changed, &offset, output, input); if (number < sizeof(TTB)) break; /* End of file */ intmp.s_addr = srcip; if (TTB.unused2 == ebcdicspaces) { memcpy(&TTC, &TTB, sizeof(TTB)); number = getbytes((unsigned char *)&TTC.rhost, sizeof(TTC) - sizeof(TTB) - 16, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number < (sizeof(TTC) - sizeof(TTB) - 16)) break; /* End of file */ number = getbytes((unsigned char *)&TTC.ohost, sizeof(TTC) - sizeof(TTB) - 15, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number < (sizeof(TTC) - sizeof(TTB) - 15)) break; /* End of file */ fprintf(output, "%s:%d - TCPNJE TTC: Type: ", inet_ntoa(intmp), srcport); for (i = 0; i < 8; i++) fprintf(output, "%c", ebcdic_to_ascii[TTC.type[i]]); fprintf(output, " Rhost: "); for (i = 0; i < 8; i++) fprintf(output, "%c", ebcdic_to_ascii[TTC.rhost[i]]); intmp.s_addr = TTC.rip; fprintf(output, " Rip: %s Ohost: ", inet_ntoa(intmp)); for (i = 0; i < 8; i++) fprintf(output, "%c", ebcdic_to_ascii[TTC.ohost[i]]); intmp.s_addr = TTC.oip; fprintf(output, " Oip: %s R: %2.2X\n", inet_ntoa(intmp), TTC.r); continue; } else { vmnetcount = ntohs(TTB.length); if ((TTB.unused1 != 0) || (TTB.unused2 != 0)) fprintf(output, "Warning: unused fields in TCPNJE TTB contain %2.2X and %8.8X\n", TTB.unused1, TTB.unused2); fprintf(output, "%s:%d - TCPNJE TTB: length %d flags %2.2X\n", inet_ntoa(intmp), srcport, vmnetcount, TTB.flags); vmnetcount -= sizeof(TTB); } } else vmnetcount = 1; while (vmnetcount > 0) { if (vmnet) { number = getbytes((unsigned char *)&TTR, sizeof(TTR), &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number < sizeof(TTR)) break; /* End of file */ if (TTR.unused != 0) fprintf(output, "Warning: unused field in TCPNJE TTR contains %2.2X\n", TTR.unused); vmnetlength = ntohs(TTR.length); vmnetcount -= (vmnetlength + sizeof(TTR)); intmp.s_addr = srcip; if (vmnetlength != 0) fprintf(output, "%s:%d - TCPNJE TTR: length %d flags: %2.2X\n", inet_ntoa(intmp), srcport, ntohs(TTR.length), TTR.flags); else { fprintf(output, "%s:%d - TCPNJE TTR: length 0 (End of block)\n", inet_ntoa(intmp), srcport); if (vmnetcount != 0) fprintf(output, "%s:%d - TCPNJE: %d bytes remaining after end of block TTR\n", inet_ntoa(intmp), srcport, vmnetcount); continue; } } /* Get two character from the stream */ number = getbytes(block, 2, &srcip, &srcport, &changed, &offset, output, input); if (vmnet && changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number < 2) break; /* End of file */ intmp.s_addr = srcip; /* Is this the start of a SYN SYN SYN SYN sequence? */ if ((block[0] == SYN) && (block[1] == SYN)) { /* Can we complete the sequence? */ number = getbytes(block, 2, &srcip, &srcport, &changed, &offset, output, input); if (vmnet && changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number < 2) break; /* End of file */ intmp.s_addr = srcip; fprintf(output, "%s:%d - SYN SYN ", inet_ntoa(intmp), srcport); if ((block[0] == SYN) && (block[1] == SYN)) fprintf(output, "SYN SYN"); fprintf(output, "\n"); if ((block[0] == SYN) && (block[1] == SYN)) continue; } if ((block[0] == SYN) && (block[1] == NAK)) { fprintf(output, "%s:%d - SYN NAK\n", inet_ntoa(intmp), srcport); continue; } /* SYN shouldn't be followed by anything else if we are on track... */ if ((block[0] == SYN) && (block[1] != SYN)) { fprintf(output, "%s:%d - SYN\n", inet_ntoa(intmp), srcport); /* Looks like we started on an odd byte boundary. Put one back. */ block[0] = block[1]; /* Fetch a replacement */ number = getbytes(&block[1], 1, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number < 1) break; /* End of file */ intmp.s_addr = srcip; } if ((block[0] == SOH) && (block[1] == ENQ)) { fprintf(output, "%s:%d - SOH ENQ\n", inet_ntoa(intmp), srcport); continue; } /* Is this the start of a DLE / LDR / TRL sequence? */ if ((block[0] == DLE) && (block[1] == ACK0)) { fprintf(output, "%s:%d - DLE ACK0\n", inet_ntoa(intmp), srcport); continue; } if ((block[0] == DLE) && (block[1] == ETB)) { fprintf(output, "%s:%d - DLE ETB\n", inet_ntoa(intmp), srcport); continue; } if ((block[0] == DLE) && (block[1] == STX)) { /* Get transmission buffer */ number = getbytes(block, 3, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number < 3) break; /* End of file */ intmp.s_addr = srcip; fprintf(output, "%s:%d - DLE STX\n%s:%d - BCB: %2.2X FCS: %2.2X%2.2X\n", inet_ntoa(intmp), srcport, inet_ntoa(intmp), srcport, block[0], block[1], block[2]); /* Get records. Assume records will be compressed. */ compressed = 1; /* Exit loop if record not compressed as only one per buffer */ while (compressed) { /* Get RCB */ number = getbytes(&block[0], 1, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number == 0) break; /* End of file */ intmp.s_addr = srcip; if (block[0] == 0) { /* End of transmission buffer */ fprintf(output, "%s:%d - RCB: 00 (EOB)\n", inet_ntoa(intmp), srcport); break; } /* Get SRCB */ number = getbytes(&block[1], 1, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number == 0) break; /* End of file */ intmp.s_addr = srcip; fprintf(output, "%s:%d - RCB: %2.2X SRCB %2.2X\n", inet_ntoa(intmp), srcport, block[0], block[1]); /* Connection control records are not compressed */ if (block[0] == 0xF0) compressed = 0; else compressed = 1; /* Get record */ eor = 0; i = 0; while (!eor) { /* Handle uncompressed control records specially. The length of I / J / K / L / M /N signon type records is given in the first byte after the SRCB. The length of A type signon records appears to be 80. The length of B type signoff records appears to be zero. */ if (!compressed) { if ((block[1] == 0xC9) || ((block[1] >= 0xD1) && (block[1] <= 0xD5))) { /* I/J/K/L/M/N - length of record in first byte */ number = getbytes(&record[i], 1, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number == 0) break; /* End of file */ i++; count = record[i-1] - 3; /* Length of remainder */ } else if (block[1] == 0xC1) count = 80; /* A */ else if (block[1] == 0xC2) count = 0; /* B */ /* Get rest of record now that we know length */ while (count > 0) { part = MIN(count, 16); number = getbytes(&record[i], part, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number == 0) break; /* End of file */ i += part; count -= part; } eor = 1; } /* Get a block of up to (a fairly arbitrary) 80 compressed characters at a time, dump them out and go back and get more if we have not yet reached end of record. */ if (compressed) while (i < 80) { /* Get next SCB */ number = getbytes(&record[i], 1, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number == 0) break; /* End of file */ i++; /* SCB of 00 indicates end of record */ if (record[i-1] == 0) { eor = 1; break; } else if ((record[i-1] & 0xE0) == 0x80) { /* Insert j (EBCDIC) spaces */ if (decompress) { j = record[i-1] & 0x1F; i--; /* Overwrite SCB */ for (; j > 0; j--, i++) record[i] = 0x40; } } else if ((record[i-1] & 0xE0) == 0xA0) { /* Insert next character j times */ number = getbytes(&record[i], 1, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number == 0) break; /* End of file */ i++; if (decompress) { j = record[i-2] & 0x1F; character = record[i-1]; i -= 2; /* Overwrite SCB */ for (; j > 0; j--, i++) record[i] = character; } } else if ((record[i-1] & 0xC0) == 0xC0) { /* Next j characters are as-is uncompressed */ j = record[i-1] & 0x3F; if (decompress) i--; /* Overwrite SCB */ while (j > 0) { part = MIN(j, 16); number = getbytes(&record[i], part, &srcip, &srcport, &changed, &offset, output, input); if (changed) fprintf(output, "Warning: source ip address or port changed unexpectedly\n"); if (number == 0) break; /* End of file */ i += part; j -= part; } } } /* Round down number of characters to nearest 16 */ count = i & 0xFFFFFFF0; k = 0; while (k < count) { fprintf(output, " "); for (j = k; j < (k + 16); j++) fprintf(output, "%2.2x ",record[j]); fprintf(output, " "); for (j = k; j < (k + 16); j++) fprintf(output, "%c", ebcdic_to_ascii[record[j]]); fprintf(output, "\n"); k += 16; } /* Move any remainder to the beginning of the record */ i &= 0x0000000F; if (i != 0) for (j = 0; j < i; j++) record[j] = record[count + j]; } /* Display any remaining part of record */ if (i != 0) { fprintf(output, " "); for (j = 0; j < i; j++) fprintf(output, "%2.2x ",record[j]); /* Finish out short lines to same length as full lines */ while (j++ < 16) fprintf(output, " "); fprintf(output, " "); for (j = 0; j < i; j++) fprintf(output, "%c", ebcdic_to_ascii[record[j]]); fprintf(output, "\n"); } } continue; } fprintf(output, "%s:%d - Unexpected characters: %2.2x %2.2x\n", inet_ntoa(intmp), srcport, block[0], block[1]); } } fclose(input); fclose(output); }