/*
     tcplogger - A TCP traffic logger
     Copyright (C) 1993 Douglas Lee Schales, David K. Hess, David R. Safford

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

icmp.c - 09/16/93

*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <sys/time.h>
#include <netdb.h>
#include <memory.h>

extern char *optarg;
extern int optind, opterr;

#include "defs.h"
#include "version.h"

struct ipaddr {
     unsigned int addr:32;
};

struct tcpsynout {
     struct timeval tp;
     unsigned long ipsrcaddr;
     unsigned long ipdstaddr;
     unsigned long tcpseq;
     unsigned short tcpsrcport;
     unsigned short tcpdstport;
};

static void etherhandler(char *, int, struct timeval);
static void icmp(struct ip *, struct timeval);
static void tcplogbin(struct tcpsynout *);
static void tcplogascii(struct tcpsynout *);
static char *gettcpportname(unsigned short);
extern char *gettimestr(struct timeval);

extern void pushfilter(int);

struct val_str_hndl ether_types[] = {
     0x0800, "DOD Internet Protocol", icmp,
     0, (char *)0, 0
     };

#define LOGBINARY 0
#define LOGASCII 1
static int logtype = LOGBINARY;
static FILE *logfile = stdout;

int resolve = 1;

extern char *getdefaultif(char *);

main(int argc, char **argv)
{
     char *eif = getdefaultif((char *)0);
     int fd;
     int c;
     int promisc = 1;
     
     while((c=getopt(argc, argv, "pvni:abf:")) != -1){
	  switch(c){
	  case 'p':
	       promisc = 0;
	       break;
	  case 'v':
	       printf("icmplogger, version %s\n", toolversion);
	       exit(0);
	       break;
	  case 'a':
	       logtype = LOGASCII;
	       break;
	  case 'b':
	       logtype = LOGBINARY;
	       break;
	  case 'n':
	       resolve = 0;
	       break;
	  case 'i':
	       eif = optarg;
	       break;
	  case 'f':
	       if(!(logfile = fopen(optarg, "a"))){
		    perror(optarg);
		    exit(1);
	       }
	       break;
	  default:
	       fprintf(stderr, "Unknown option: %c\n", c);
	       exit(1);
	  }
     }

     fd = opennit(eif, promisc, pushfilter);
     procpkts(fd, etherhandler);
}

void
etherhandler(char *pkt, int size, struct timeval tp)
{
     icmp((struct ip *)(pkt + 14), tp);
}


void
icmp(struct ip *ip, struct timeval tp)
{
     struct icmp *icmpbuf;
     struct tcpsynout outbuf;

     icmpbuf = (struct icmp *)(((char *)ip) + sizeof(struct ip));

     memset((char *)&outbuf.tcpseq, 0, sizeof(long));
     memcpy((char *)&outbuf.tp, (char *)&tp, sizeof(struct timeval));
     memcpy((char *)&outbuf.ipsrcaddr, (char *)&ip->ip_src, 4);
     memcpy((char *)&outbuf.ipdstaddr, (char *)&ip->ip_dst, 4);
     outbuf.tcpsrcport = 0;
     memcpy((char *)&outbuf.tcpdstport, (char *)&icmpbuf->icmp_type, 2);

     switch(logtype){
     case LOGBINARY:
	  tcplogbin(&outbuf);
	  break;
     case LOGASCII:
	  tcplogascii(&outbuf);
	  break;
     default:
	  fprintf(stderr, "Log type unknown: %d\n", logtype);
	  break;
     }
}

void
tcplogbin(struct tcpsynout *outbuf)
{
     write(fileno(logfile), (char *)outbuf, sizeof(struct tcpsynout));
}

void
tcplogascii(struct tcpsynout *outbuf)
{
     struct in_addr haddr;

     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));
     fputs(" -> ", logfile);
     memcpy(&haddr.s_addr, &outbuf->ipdstaddr, 4);
     fprintf(logfile, "%-21s", resolve ? cgethostbyaddr(haddr) : inet_ntoa(haddr));
     fputc(' ', logfile);
     fputs(geticmpportname(outbuf->tcpdstport), logfile);;
     fputc('\n', logfile);
     fflush(logfile);
}

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;
}
