1 /*
2 * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
3 * 2002, 2003, 2004
4 * Ohio University.
5 *
6 * ---
7 *
8 * Starting with the release of tcptrace version 6 in 2001, tcptrace
9 * is licensed under the GNU General Public License (GPL). We believe
10 * that, among the available licenses, the GPL will do the best job of
11 * allowing tcptrace to continue to be a valuable, freely-available
12 * and well-maintained tool for the networking community.
13 *
14 * Previous versions of tcptrace were released under a license that
15 * was much less restrictive with respect to how tcptrace could be
16 * used in commercial products. Because of this, I am willing to
17 * consider alternate license arrangements as allowed in Section 10 of
18 * the GNU GPL. Before I would consider licensing tcptrace under an
19 * alternate agreement with a particular individual or company,
20 * however, I would have to be convinced that such an alternative
21 * would be to the greater benefit of the networking community.
22 *
23 * ---
24 *
25 * This file is part of Tcptrace.
26 *
27 * Tcptrace was originally written and continues to be maintained by
28 * Shawn Ostermann with the help of a group of devoted students and
29 * users (see the file 'THANKS'). The work on tcptrace has been made
30 * possible over the years through the generous support of NASA GRC,
31 * the National Science Foundation, and Sun Microsystems.
32 *
33 * Tcptrace is free software; you can redistribute it and/or modify it
34 * under the terms of the GNU General Public License as published by
35 * the Free Software Foundation; either version 2 of the License, or
36 * (at your option) any later version.
37 *
38 * Tcptrace is distributed in the hope that it will be useful, but
39 * WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
41 * General Public License for more details.
42 *
43 * You should have received a copy of the GNU General Public License
44 * along with Tcptrace (in the file 'COPYING'); if not, write to the
45 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
46 * MA 02111-1307 USA
47 *
48 * Author: Shawn Ostermann
49 * School of Electrical Engineering and Computer Science
50 * Ohio University
51 * Athens, OH
52 * ostermann@cs.ohiou.edu
53 * http://www.tcptrace.org/
54 */
55 #include "tcptrace.h"
56 static char const GCC_UNUSED copyright[] =
57 "@(#)Copyright (c) 2004 -- Ohio University.\n";
58 static char const GCC_UNUSED rcsid[] =
59 "$Header$";
60
61
62 #include "compress.h"
63 #include <sys/wait.h>
64
65 /*
66 * OK, this stuff is a little complicated. Here's why:
67 * 1) the routines that examine the file to see if it's of
68 * a particular type want a real file that they can do
69 * a "seek" on. Seeking backwards won't work on a stream
70 * 2) What I do for compressed files is to decompress twice:
71 * - The first time I just save the first COMP_HDR_SIZE bytes
72 * into a temporary file and then stop the decompression.
73 * I then use that file to determine that file type
74 * - After I know the file type, I restart the decompression
75 * and reconnect the decompress pipe to stdin
76 * 3) If the "file" input _IS_ standard input, then it's harder,
77 * because I can't restart it. In that case, I use a helper process
78 * that reads the rest of the header file and then starts reading
79 * the rest of the data from standard input. It's slightly inefficient
80 * because of the extra process, but I don't know a way around...
81 */
82
83
84 /* local routines */
85 static char *FindBinary(char *binname);
86 static struct comp_formats *WhichFormat(char *filename);
87 static FILE *CompSaveHeader(char *filename, struct comp_formats *pf);
88 static int CompOpenPipe(char *filename, struct comp_formats *pf);
89 static FILE *PipeHelper(void);
90 static void PipeFitting(FILE *f_pipe, FILE *f_header, FILE *f_stdin);
91
92
93 /* local globals */
94 static int header_length = -1;
95 static Bool is_compressed = FALSE;
96 static FILE * f_orig_stdin = NULL;
97 static int child_pid = -1;
98 static char *tempfile;
99 int posn;
100
101
FindBinary(char * binname)102 static char *FindBinary(
103 char *binname)
104 {
105 char *path;
106 char *pch;
107 char *pch_colon;
108 static char abspath[256];
109
110 /* quick check for absolute path */
111 if (*binname == '/') {
112 if (access(binname,X_OK) == 0) {
113 if (debug>1)
114 fprintf(stderr,"FindBinary: abs path '%s' is OK\n", binname);
115 return(binname);
116 } else {
117 if (debug>1)
118 fprintf(stderr,"FindBinary: abs path '%s' not found\n", binname);
119 return(NULL);
120 }
121 }
122
123 path = getenv("PATH");
124 if (path == NULL) {
125 if (debug)
126 fprintf(stderr,"FindBinary: couldn't get PATH envariable\n");
127 return(NULL);
128 }
129
130 path = strdup(path);
131 pch = path;
132
133 while (pch && *pch) {
134 pch_colon = strchr(pch,':');
135 if (pch_colon)
136 *pch_colon = '\00';
137
138 snprintf(abspath,sizeof(abspath),"%s/%s",pch,binname);
139
140 if (debug>1)
141 fprintf(stderr,"Checking for binary '%s'\n", abspath);
142 if (access(abspath,X_OK) == 0) {
143 if (debug>1)
144 fprintf(stderr,"FindBinary: found binary '%s'\n", abspath);
145 return(abspath);
146 }
147
148 if (pch_colon)
149 pch = pch_colon+1;
150 else
151 pch = NULL;
152 }
153
154 if (debug)
155 fprintf(stderr,"FindBinary: couldn't find binary '%s' in PATH\n",
156 binname);
157
158 return(NULL);
159 }
160
161
162
163 static struct comp_formats *
WhichFormat(char * filename)164 WhichFormat(
165 char *filename)
166 {
167 static struct comp_formats *pf_cache = NULL;
168 static char *pf_file_cache = NULL;
169 int len;
170 int lens;
171 int i;
172
173 /* check the "cache" :-) */
174 if (pf_file_cache && (strcmp(filename,pf_file_cache) == 0)) {
175 return(pf_cache);
176 }
177
178 len = strlen(filename);
179
180 for (i=0; i < NUM_COMP_FORMATS; ++i) {
181 struct comp_formats *pf = &supported_comp_formats[i];
182
183 if (debug>1)
184 fprintf(stderr,"Checking for suffix match '%s' against '%s' (%s)\n",
185 filename,pf->comp_suffix,pf->comp_bin);
186 /* check for suffix match */
187 lens = strlen(pf->comp_suffix);
188 if (strcmp(filename+len-lens, pf->comp_suffix) == 0) {
189 if (debug>1)
190 fprintf(stderr,"Suffix match! '%s' against '%s'\n",
191 filename,pf->comp_suffix);
192 /* stick it in the cache */
193 pf_file_cache = strdup(filename);
194 pf_cache = pf;
195 is_compressed = TRUE;
196
197 /* and tell the world */
198 return(pf);
199 }
200 }
201
202 pf_file_cache = strdup(filename);
203 pf_cache = NULL;
204 is_compressed = FALSE;
205
206 if (debug)
207 fprintf(stderr,"WhichFormat: failed to find compression format for file '%s'\n",
208 filename);
209
210 return(NULL);
211 }
212
213
214
215 static FILE *
CompReopenFile(char * filename)216 CompReopenFile(
217 char *filename)
218 {
219 char buf[COMP_HDR_SIZE];
220 struct comp_formats *pf = WhichFormat(filename);
221 int len;
222 int fd;
223 long pos;
224
225 if (debug>1)
226 fprintf(stderr,"CompReopenFile('%s') called\n", filename);
227
228 /* we need to switch from the header file to a pipe connected */
229 /* to a process. Find out how far we've read from the file */
230 /* so far... */
231 pos = ftell(stdin);
232 if (debug>1)
233 fprintf(stderr,"CompReopenFile: current file position is %ld\n", pos);
234
235 /* open a pipe to the original (compressed) file */
236 fd = CompOpenPipe(filename,pf);
237 if (fd == -1)
238 return(NULL);
239
240 /* erase the file buffer and reposition to the front */
241 #ifdef HAVE_FPURGE
242 /* needed for NetBSD and FreeBSD (at least) */
243 fpurge(stdin); /* discard input buffer */
244 #else /* HAVE_FPURGE */
245 fflush(stdin); /* discard input buffer */
246 #endif /* HAVE_FPURGE */
247 rewind(stdin);
248
249 /* yank the FD out from under stdin and point to the pipe */
250 dup2(fd,0);
251
252 /* skip forward in the stream to the same place that we were in */
253 /* for the header file */
254 len = fread(buf,1,pos,stdin);
255 if ((len == 0) && ferror(stdin)) {
256 perror("read forward in stdin");
257 exit(-1);
258 }
259
260 /* OK, I guess we're all set... */
261 return(stdin);
262 }
263
264
265
266
267 static FILE *
CompSaveHeader(char * filename,struct comp_formats * pf)268 CompSaveHeader(
269 char *filename,
270 struct comp_formats *pf)
271 {
272 FILE *f_stream;
273 FILE *f_file;
274 char buf[COMP_HDR_SIZE];
275 int len;
276 int fd;
277
278 fd = CompOpenPipe(filename,pf);
279 if (fd == -1)
280 return(NULL);
281
282 #ifdef HAVE_MKSTEMP
283 {
284 /* From Mallman, supposed to be "safer" */
285 int fd;
286 extern int mkstemp(char *template);
287
288 /* grab a writable string to keep picky compilers happy */
289 tempfile = strdup("/tmp/trace_hdrXXXXXXXX");
290
291 /* create a temporary file name and open it */
292 if ((fd = mkstemp(tempfile)) == -1) {
293 perror("template");
294 exit(-1);
295 }
296
297 /* convert to a stream */
298 f_file = fdopen(fd,"w");
299 }
300 #else /* HAVE_MKSTEMP */
301 /* get a name for a temporary file to store the header in */
302 tempfile = tempnam("/tmp/","trace_hdr");
303
304 /* open the file */
305 if ((f_file = fopen(tempfile,"w+")) == NULL) {
306 perror(tempfile);
307 exit(-1);
308 }
309
310 #endif /* HAVE_MKSTEMP */
311
312
313 /* connect a stdio stream to the pipe */
314 if ((f_stream = fdopen(fd,"r")) == NULL) {
315 perror("open pipe stream for header");
316 exit(-1);
317 }
318
319 /* just grab the first X bytes and stuff into a temp file */
320 len = fread(buf,1,COMP_HDR_SIZE,f_stream);
321 if ((len == 0) && ferror(f_stream)) {
322 perror("read pipe stream for header");
323 exit(-1);
324 }
325
326 if (len == 0) {
327 /* EOF, failure */
328 return(NULL);
329 }
330
331 header_length = len;
332 if (debug>1)
333 fprintf(stderr,"Saved %d bytes from stream into temp header file '%s'\n",
334 len, tempfile);
335
336 /* save the header into a temp file */
337 len = fwrite(buf,1,len,f_file);
338 if ((len == 0) && ferror(f_file)) {
339 perror("write file stream for header");
340 exit(-1);
341 }
342
343 if (debug>1)
344 fprintf(stderr,"Saved the file header into temp file '%s'\n",
345 tempfile);
346
347
348 /* OK, we have the header, close the file */
349 fclose(f_file);
350
351 /* if it's stdin, make a copy for later */
352 if (FileIsStdin(filename)) {
353 f_orig_stdin = f_stream; /* remember where it is */
354 } else {
355 fclose(f_stream);
356 }
357
358 /* re-open the file as stdin */
359 if ((freopen(tempfile,"r",stdin)) == NULL) {
360 perror("tempfile");
361 exit(-1);
362 }
363
364 return(stdin);
365 }
366
367
368
369 static int
CompOpenPipe(char * filename,struct comp_formats * pf)370 CompOpenPipe(
371 char *filename,
372 struct comp_formats *pf)
373 {
374 int fdpipe[2];
375 char *abspath;
376 int i;
377 char *args[COMP_MAX_ARGS];
378
379 if (debug>1)
380 fprintf(stderr,"CompOpenPipe('%s') called\n", filename);
381
382 /* short hand if it's just reading from standard input */
383 if (FileIsStdin(filename)) {
384 return(dup(0)); /* 0: standard input */
385 }
386
387 abspath = FindBinary(pf->comp_bin);
388 if (!abspath) {
389 fprintf(stderr,
390 "Compression: failed to find binary for '%s' needed to uncompress file\n",
391 pf->comp_bin);
392 fprintf(stderr,
393 "According to my configuration, I need '%s' to decode files of type\n",
394 pf->comp_bin);
395 fprintf(stderr, "%s\n", pf->comp_descr);
396 exit(-1);
397 }
398
399 /* save the path for later... */
400 pf->comp_bin = strdup(abspath);
401
402 /* filter args */
403 for (i=0; i < COMP_MAX_ARGS; ++i) {
404 args[i] = pf->comp_args[i];
405 if (!args[i])
406 break;
407 if (strcmp(pf->comp_args[i],"%s") == 0)
408 args[i] = filename;
409 }
410
411
412 if (Mfpipe(fdpipe) == -1) {
413 perror("pipe");
414 exit(-1);
415 }
416
417 #ifdef __VMS
418 child_pid = vfork();
419 #else
420 child_pid = fork();
421 #endif
422 if (child_pid == -1) {
423 perror("fork");
424 exit(-1);
425 }
426 if (child_pid == 0) {
427 /* child */
428 dup2(fdpipe[1],1); /* redirect child's stdout to pipe */
429
430 /* close all other FDs - lazy, but close enough for our purposes :-) */
431 for (i=3; i < 100; ++i) close(i);
432
433 if (debug>1) {
434 fprintf(stderr,"Execing %s", abspath);
435 for (i=1; args[i]; ++i)
436 fprintf(stderr," %s", args[i]);
437 fprintf(stderr,"\n");
438 }
439
440
441 execv(abspath,args);
442 fprintf(stderr,"Exec of '%s' failed\n", abspath);
443 perror(abspath);
444 exit(-1);
445 }
446
447 close(fdpipe[1]);
448 return(fdpipe[0]);
449 }
450
451
452
453
454
455 FILE *
CompOpenHeader(char * filename)456 CompOpenHeader(
457 char *filename)
458 {
459 FILE *f;
460 struct comp_formats *pf;
461
462 /* short hand if it's just reading from standard input */
463 if (FileIsStdin(filename)) {
464 is_compressed = TRUE; /* pretend that it's compressed */
465 return(CompSaveHeader(filename,NULL));
466 }
467
468 /* see if it's a supported compression file */
469 pf = WhichFormat(filename);
470
471 #ifdef __WIN32
472 if(pf != NULL) {
473 fprintf(stderr, "\nError: windows version of tcptrace does not support\nreading compressed dump files. Uncompress the file\nmanually and try again. Sorry!\n");
474 return((FILE *)-1);
475 }
476 return(NULL);
477 #endif /* __WIN32 */
478
479 /* if no compression found, just open the file */
480 if (pf == NULL) {
481 if (freopen(filename,"r",stdin) == NULL) {
482 perror(filename);
483 return(NULL);
484 }
485 return(stdin);
486 }
487
488 /* open the file through compression */
489 if (debug>1)
490 printf("Decompressing file of type '%s' using program '%s'\n",
491 pf->comp_descr, pf->comp_bin);
492 else if (debug)
493 printf("Decompressing file using '%s'\n", pf->comp_bin);
494
495 f = CompSaveHeader(filename,pf);
496
497 if (!f) {
498 fprintf(stderr,"Decompression failed for file '%s'\n", filename);
499 exit(-1);
500 }
501
502 return(f);
503 }
504
505
506 FILE *
CompOpenFile(char * filename)507 CompOpenFile(
508 char *filename)
509 {
510 if (debug>1)
511 fprintf(stderr,"CompOpenFile('%s') called\n", filename);
512
513 /* if it isn't compressed, just leave it at stdin */
514 if (!is_compressed)
515 return(stdin);
516
517 /* if the header we already saved is the whole file, it must be */
518 /* short, so just read from the file */
519 if (header_length < COMP_HDR_SIZE) {
520 if (debug>1)
521 fprintf(stderr,"CompOpenFile: still using header file, short file...\n");
522 return(stdin);
523 }
524
525 /* if we're just reading from standard input, we'll need some help because */
526 /* part of the input is in a file and the rest is still stuck in a pipe */
527 if (FileIsStdin(filename)) {
528 posn=ftell(stdin);
529 if (posn < 0) {
530 perror("CompOpenFile : ftell failed");
531 exit(-1);
532 }
533 return(PipeHelper());
534 }
535
536 /* otherwise, there's more than we saved, we need to re-open the pipe */
537 /* and re-attach it to stdin */
538 return(CompReopenFile(filename));
539 }
540
541
542 /* return a FILE * that fill come from a helper process */
543 FILE *
PipeHelper(void)544 PipeHelper(void)
545 {
546 int fdpipe[2];
547 FILE *f_return;
548
549 /* On coming in, here's what's in the FDs: */
550 /* stdin: has the header file open */
551 /* f_stdin_file: holds the rest of the stream */
552
553 if (Mfpipe(fdpipe) == -1) {
554 perror("pipe");
555 exit(-1);
556 }
557 /* remember: fdpipe[0] is for reading, fdpipe[1] is for writing */
558
559 #ifdef __VMS
560 child_pid = vfork();
561 #else
562 child_pid = fork();
563 #endif
564 if (child_pid == -1) {
565 perror("fork");
566 exit(-1);
567 }
568 if (child_pid == 0) {
569 /* be the helper process */
570 FILE *f_pipe;
571
572 /* attach a stream to the pipe connection */
573 f_pipe = fdopen(fdpipe[1],"w");
574 if (f_pipe == NULL) {
575 perror("fdopen on pipe for writing");
576 exit(-1);
577 }
578
579 /* connect the header file and stream to the pipe */
580 PipeFitting(f_pipe, stdin, f_orig_stdin);
581
582 /* OK, both empty, we're done */
583 if (debug>1)
584 fprintf(stderr,
585 "PipeHelper(%d): all done, exiting\n", (int)getpid());
586
587 exit(0);
588 }
589
590 /* I'm still the parent */
591 if (debug>1)
592 fprintf(stderr,
593 "PipeHelper: forked off child %d to deal with stdin\n",
594 child_pid);
595
596 /* clean up the fd's */
597 close(fdpipe[1]);
598 // Now, we shall purge our old STDIN stream buffer, and point it to the
599 // read end of the pipe, fdpipe[0]
600
601 #ifdef HAVE_FPURGE
602 fpurge(stdin); // needed for NetBSD/FreeBSD
603 #else
604 fflush(stdin);
605 #endif
606 clearerr(stdin);
607
608 if (dup2(fdpipe[0],0)==-1) {
609 perror("PipeHelper : dup2 failed in parent");
610 exit(-1);
611 }
612
613 /* make a stream attached to the PIPE and return it */
614 f_return = fdopen(fdpipe[0],"r");
615 if (f_return == NULL) {
616 perror("PipeHelper : fdopen on pipe for reading");
617 exit(-1);
618 }
619 return(f_return);
620 }
621
622
623 static void
PipeFitting(FILE * f_pipe,FILE * f_header,FILE * f_orig_stdin)624 PipeFitting(
625 FILE *f_pipe,
626 FILE *f_header,
627 FILE *f_orig_stdin)
628 {
629 char buf[COMP_HDR_SIZE]; /* just a big buffer */
630 int len;
631
632 // Fix the file synchronization problems and undefined behavior exhibited
633 // by fread() in managing its buffers, when stdin is opened by both the
634 // parent and child processes.
635 // In the child process (where we are currently executing), close and
636 // re-open the temporary file currently opened as stdin, in which the
637 // first COMP_HDR_SIZE bytes of data were stored. The current file pointer
638 // position in the file was stored in the global variable posn.
639
640 if (fclose(f_header)<0)
641 perror("PipeFitting : fclose failed");
642
643 if ((f_header=fopen(tempfile,"r"))==NULL) {
644 perror("PipeFitting : fopen of tempfile failed");
645 exit(-1);
646 }
647
648 if (fread(buf,1,posn,f_header)!=posn) {
649 perror("PipeFitting : fread failed");
650 exit(-1);
651 }
652
653 /* read from f_header (the file) until empty */
654 while (1) {
655 /* read some more data */
656 len = fread(buf,1,sizeof(buf),f_header);
657 if (len == 0)
658 break;
659 if (len < 0) {
660 perror("fread from f_header");
661 exit(0);
662 }
663
664 if (debug>1)
665 fprintf(stderr,
666 "PipeFitting: read %d bytes from header file\n", len);
667
668 /* send those bytes to the pipe */
669 if (fwrite(buf,1,len,f_pipe) != len) {
670 perror("fwrite on pipe");
671 exit(-1);
672 }
673 }
674
675 // We are done with the temporary file. Time to close and unlink it.
676 if (fclose(f_header)<0)
677 perror("PipeFitting : fclose failed");
678
679 if (unlink(tempfile)<0)
680 perror("PipeFitting : unlink of tempfile failed");
681
682 if (debug>1)
683 fprintf(stderr,
684 "PipeFitting: header file empty, switching to old stdin\n");
685
686 /* OK, the file is empty, switch back to the stdin stream */
687 while (1) {
688 /* read some more data */
689 len = fread(buf,1,sizeof(buf),f_orig_stdin);
690 if (len == 0)
691 break;
692 if (len < 0) {
693 perror("fread from f_orig_stdin");
694 exit(0);
695 }
696
697 if (debug>1)
698 fprintf(stderr,
699 "PipeFitting: read %d bytes from f_orig_stdin\n", len);
700
701 /* send those bytes to the pipe */
702 if (fwrite(buf,1,len,f_pipe) != len) {
703 perror("fwrite on pipe");
704 exit(-1);
705 }
706 }
707 }
708
709
710
711
712 void
CompCloseFile(char * filename)713 CompCloseFile(
714 char *filename)
715 {
716 /* Hmmm... this was commented out, I wonder why? */
717 /* fclose(stdin); */
718
719 /* if we have a child, make sure it's dead */
720 if (child_pid != -1) {
721 kill(child_pid,SIGTERM);
722 child_pid = -1;
723 }
724
725 /* in case we have children child still in the background */
726 while (wait(0) != -1)
727 ; /* nothing */
728
729 /* zero out some globals */
730 header_length = -1;
731 }
732
733
734 int
CompIsCompressed(void)735 CompIsCompressed(void)
736 {
737 return(is_compressed);
738 }
739
740
741 void
CompFormats(void)742 CompFormats(void)
743 {
744 int i;
745
746 fprintf(stderr,"Supported Compression Formats:\n");
747 fprintf(stderr,"\tSuffix Description Uncompress Command\n");
748 fprintf(stderr,"\t------ -------------------- --------------------------\n");
749
750 for (i=0; i < NUM_COMP_FORMATS; ++i) {
751 int arg;
752 struct comp_formats *pf = &supported_comp_formats[i];
753
754 fprintf(stderr,"\t%6s %-20s %s",
755 pf->comp_suffix,
756 pf->comp_descr,
757 pf->comp_bin);
758 for (arg=1; pf->comp_args[arg]; ++arg)
759 fprintf(stderr," %s", pf->comp_args[arg]);
760 fprintf(stderr,"\n");
761 }
762 }
763
764
765 /* does the file name "filename" refer to stdin rather than a real file? */
766 /* (in case I need to extend this definition someday) */
767 Bool
FileIsStdin(char * filename)768 FileIsStdin(
769 char *filename)
770 {
771 if (strcmp(filename,"stdin") == 0)
772 return(1);
773 if (strcmp(filename,"stdin.gz") == 0)
774 return(1);
775 return(0);
776 }
777