/*
     extract - A network log processor
     Copyright (C) 1993 Douglas Lee Schales, David K. Hess, David R. Safford

     Please see the file `COPYING' for the complete copyright notice.

extract.c - 03/20/93

*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netdb.h>
#include <errno.h>
#include <malloc.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>


#include "extract.h"
#include "chario.h"
#include "parser.h"
#include "interp.h"
#include "stdunix.h"
#include "version.h"

extern char *inet_ntoa(struct in_addr);
extern int getopt(int, char **, char *);
extern char *optarg;
extern int optind;

extern struct parsetree *parse(void);
extern void extract(int, struct parsetree *);
extern char *getportname(unsigned short);
extern void flushbuf(void);
extern void writebin(struct tcpsynout *);
extern void writeascii(struct tcpsynout *);
extern void writebuf(struct tcpsynout *);
extern void setlogmode(int);
extern void setlogfile(FILE *);
extern char *gettimestr(struct timeval);
extern char *geticmpportname(unsigned short);
static void helpscreen(char *);

#define LOGBINARY 0
#define LOGASCII 1

int resolve = 1;
#define TCPDATA 0
#define UDPDATA 1
#define ICMPDATA 2
int protoflag = TCPDATA;

int unbuffered = 0;

int
main(int argc, char **argv)
{
     struct parsetree *parsetree;
     int logmode = LOGASCII;
     FILE *outfile = stdout;
     FILE *dumpfile = stdin;
     int file = 0, expr = 0, errflag = 0;
     int status;
     char *membufname;
     int c;

     while((c=getopt(argc, argv, "vd:e:f:E:F:o:utiabnhU")) != -1){
	  switch(c){
	  case 'v':
	       printf("extract, version %s\n", toolversion);
	       exit(0);
	       break;
	  case 'a':
	       logmode = LOGASCII;
	       break;
	  case 'b':
	       logmode = LOGBINARY;
	       break;
	  case 'n':
	       resolve = 0;
	       break;
	  case 't':
	       protoflag = TCPDATA;
	       break;
	  case 'u':
	       protoflag = UDPDATA;
	       break;
	  case 'i':
	       protoflag = ICMPDATA;
	       break;
	  case 'f':
	       if((status = addfile(optarg)) != NOERR){
		    errflag++;
		    if(status == FILEERR){
			 perror(optarg);
			 exit(1);
		    }
		    else {
			 fprintf(stderr, "%s: unexpected error\n", optarg);
			 exit(1);
		    }
	       }
	       file++;
	       break;
	  case 'F':
	       if((status = includefile(optarg)) != NOERR){
		    errflag++;
		    if(status == FILEERR){
			 perror(optarg);
			 exit(1);
		    }
		    else {
			 fprintf(stderr, "%s: unexpected error\n", optarg);
			 exit(1);
		    }
	       }
	       file++;
	       break;
	  case 'e':
	       membufname = (char *)malloc(20);
	       sprintf(membufname, "argv[%d]", optind-1);
	       if((status = addmembuf(optarg, membufname)) != NOERR){
		    fprintf(stderr, "-e: unexpected error\n");
		    errflag++;
	       }
	       expr++;
	       break;
	  case 'E':
	       membufname = (char *)malloc(20);
	       sprintf(membufname, "argv[%d]", optind-1);
	       if((status = includemembuf(optarg, membufname)) != NOERR){
		    fprintf(stderr, "-E: unexpected error\n");
		    errflag++;
	       }
	       expr++;
	       break;
	  case 'd':
	       if(!(dumpfile = fopen(optarg, "r"))){
		    perror(optarg);
		    errflag++;
	       }
	       break;
	  case 'o':
	       if(!errflag && !(outfile = fopen(optarg, "w"))){
		    perror(optarg);
		    errflag++;
	       }
	       break;
	  case 'h':
	       helpscreen(argv[0]);
	       exit(0);
	       break;
	  case 'U':
	       unbuffered = 1;
	       break;
	  default:
	       errflag++;
	       break;
	  }
     }
     if(errflag)
	  exit(1);

     if(!file && !expr)
	  addmembuf("{print}", "");

     setlogfile(outfile);
     setlogmode(logmode);
     if((parsetree = parse()))
	  extract(fileno(dumpfile), parsetree);
     else
	  exit(1);
     return 0;
}

void
helpscreen(char *name)
{
     fprintf(stdout,"\
%s: usage: %s [options]\n\
	options: -a		ASCII output (the default)\n\
		 -b		Binary output\n\
		 -n		Use IP addresses for ASCII output\n\
                 -u             Input dump file is UDP records\n\
                 -t             Input dump file is TCP records\n\
                 -i             Input dump file is ICMP records\n\
                 -U             Don't do buffered I/O for dump files\n\
		 -h		Print this help screen\n\
		 -e script	Specify script inline\n\
		 -E script	Specify script inline\n\
		 -f file	Specify script filename\n\
		 -F file	Specify script filename\n\
		 -d dumpfile	Specify dump file to process (def: stdin)\n\
		 -o outfile	Specify output file (def: stdout)\n\
\n\
%s processes a binary dump file created by 'tcpsynlogger',\n\
selecting records to output.  The syntax of the script file is similar\n\
to awk(1).  The script can be specified on the command line or can be\n\
stored in a file.  All scripts specified by '-e', '-E', '-f', '-F' are\n\
in effect, processed as a single file.  Scripts specified by '-e' and\n\
'-f' are appended, whereas scripts specified by '-E' and '-F' are pre-\n\
pended.  The use of '-E' or '-F' along with a '#!%s -f' script\n\
executable allows insertion of additional script lines from the command\n\
line.\n",
	     name, name, name, name);
}

int
nread(int fd, char *buf, int numbytes)
{
     int count = numbytes;
     int n;

     while(count){
	  if((n = read(fd, buf, count)) > 0){
	       buf += n;
	       count -= n;
	  }
	  else
	       break;
     }
     return numbytes-count;
}

void
extract(int fd, struct parsetree *pt)
{
     struct tcpsynout buf[1000];
     int i, n;
     int bsize = sizeof(buf);

     if(unbuffered)
	  bsize=sizeof(struct tcpsynout);

     while((n=nread(fd, (char *)&buf, bsize)) > 0){
	  for(i=0;i<(n/sizeof(struct tcpsynout));i++)
	       interp(&buf[i], pt);
     }
     flushbuf();
}

int outlogmode = LOGBINARY;
FILE *logfile = stdout;

void
setlogfile(FILE *f)
{
     logfile = f;
}

void
setlogmode(int logmode)
{
     outlogmode = logmode;
}

#define OUTBUFSIZE 1000
struct tcpsynout outbuffer[OUTBUFSIZE];
static int outptr = 0;

void
writebuf(struct tcpsynout *outbuf)
{
     switch(outlogmode){
     case LOGBINARY:
	  writebin(outbuf);
	  break;
     case LOGASCII:
	  writeascii(outbuf);
	  break;
     default:
	  fprintf(stderr, "Internal error... unknown output mode.\n");
	  exit(1);
	  break;
     }
}

void
writebin(struct tcpsynout *outbuf)
{
     if(outptr == OUTBUFSIZE)
	  flushbuf();
     memcpy(&outbuffer[outptr++], outbuf, sizeof(struct tcpsynout));
     if(unbuffered)
	  flushbuf();
}

void
flushbuf(void)
{
     if(outptr)
	  write(fileno(logfile), (char *)outbuffer,
		sizeof(struct tcpsynout)*outptr);
     outptr = 0;
}

void
writeascii(struct tcpsynout *outbuf)
{
     struct in_addr haddr;
     extern char *cgethostbyaddr(struct in_addr);

     fputs(gettimestr(outbuf->tp), logfile);
     fprintf(logfile, " %8X ", outbuf->tcpseq);
     memcpy(&haddr.s_addr, &outbuf->ipsrcaddr, 4);
     fprintf(logfile, "%-21s", resolve ? cgethostbyaddr(haddr) : inet_ntoa(haddr));
     if(protoflag != ICMPDATA){
	  fputc(' ', logfile);
	  fputs(getportname(outbuf->tcpsrcport), logfile);
     }
     fputs(" -> ", logfile);
     memcpy(&haddr.s_addr, &outbuf->ipdstaddr, 4);
     fprintf(logfile, "%-21s", resolve ? cgethostbyaddr(haddr) : inet_ntoa(haddr));
     fputc(' ', logfile);
     fputs(getportname(outbuf->tcpdstport), logfile);;
     fputc('\n', logfile);
     fflush(logfile);
}


char *
getportname(unsigned short p)
{
     struct servent *se;
     static char result[80];

     if(protoflag == ICMPDATA)
	  return geticmpportname(p);

     if(resolve && (se = getservbyport((unsigned int)ntohs(p), 
			    protoflag == TCPDATA ? "tcp" : "udp")))
	  strcpy(result, se->s_name);
     else
	  sprintf(result, "%u", (unsigned int)ntohs(p));
     
     return result;
}

static char *icmpmsgs[] = {
     "echorep",  /* 0 */
     "icmp1",  /* 1 */
     "icmp2",  /* 2 */
     "unreachable",  /* 3 */
     "srcquench", /* 4 */
     "redirect",  /* 5 */
     "icmp6", /* 6 */
     "icmp7", /* 7 */
     "echoreq",  /* 8 */
     "icmp9",
     "icmp10",
     "timex",
     "badhdr",
     "tstampreq",
     "tstamprep",
     "inforeq",
     "inforep",
     "maskreq",
     "maskrep",
     0
     };

static char *icmpunreach[] = {
     "netunreach",
     "hostunreach",
     "protounreach",
     "portunreach",
     "fraglost",
     "badsrcroute",
     0
     };

static char *icmpredir[] = {
     "netredir",
     "hostredir",
     "tosnetredir",
     "toshostredir",
     0
     };


char *
geticmpportname(unsigned short p)
{
     static char result[80];

     int type, subtype;

     type = (p & 0xff00) >> 8;
     subtype = p&0xff;

     if(resolve && type <= ICMP_MAXTYPE){
	  switch(type){
	  case ICMP_UNREACH:
	       if(subtype < 6)
		    strcpy(result, icmpunreach[subtype]);
	       else
		    sprintf(result, "unreach_%d", subtype);
	       break;
	  case ICMP_REDIRECT:
	       if(subtype < 4)
		    strcpy(result, icmpredir[subtype]);
	       else
		    sprintf(result, "redirect_%d", subtype);
	       break;
	  default:
	       strcpy(result, icmpmsgs[type]);
	       break;
	  }
     }
     else
	  sprintf(result, "%u.%u", type, subtype);
     
     return result;
}
