/*
 * $Id: ngrep.c,v 1.37 2000/02/01 12:28:02 jpr5 Exp $
 *
 */

#if defined(BSD) || defined(SOLARIS)
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <net/if.h>
#endif

#if defined(OSF1)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <net/route.h>
#include <sys/mbuf.h>
#endif

#if defined(LINUX)
#include <getopt.h>
#endif


#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>

#include <pcap.h>
#include <net/bpf.h>

#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include "regex.h"
#include "ngrep.h"


static char rcsver[] = "$Revision: 1.37 $";

int snaplen = 65535, promisc = 1, to = 1000;
int show_empty = 0, show_hex = 0, quiet = 0;
int match_after = 0, keep_matching = 0, invert_match = 0;
int matches = 0, max_matches = 0;

char pc_err[PCAP_ERRBUF_SIZE], *re_err;

int re_match_word = 0, re_ignore_case = 0;
char *regex = NULL, *filter = NULL;
struct re_pattern_buffer pattern;
int (*match_func)();

struct bpf_program pcapfilter;
struct in_addr net, mask;
char *dev = NULL;
int link_offset;
pcap_t *pd;


int main(int argc, char **argv) {
  int c;

  signal(SIGINT,  clean_exit);
  signal(SIGQUIT, clean_exit);
  signal(SIGABRT, clean_exit);
  signal(SIGPIPE, clean_exit);

  while ((c = getopt(argc, argv, "hViwqevxln:d:A:")) != EOF) {
    switch (c) {
    case 'A': 
      match_after = atoi(optarg) + 1;
      break;
    case 'd': 
      dev = optarg;
      break;
    case 'n':
      max_matches = atoi(optarg);
      break;
    case 'l':
      setvbuf(stdout, NULL, _IOLBF, 0);
      break;
    case 'x':
      show_hex++;
      break;
    case 'v':
      invert_match++;
      break;
    case 'e':
      show_empty++;
      break;
    case 'q':
      quiet++;
      break;
    case 'w':
      re_match_word++;
      break;
    case 'i':
      re_ignore_case++;
      break;
    case 'V':
      version();
    case 'h':
      usage(0);
    default:
      usage(-1);
    }
  }

  if (argv[optind]) 
    regex = argv[optind++];


  if (!dev) 
    if (!(dev = pcap_lookupdev(pc_err))) {
      perror(pc_err);
      exit(-1);
    }


  if ((pd = pcap_open_live(dev, snaplen, promisc, to, pc_err)) == NULL) {
    perror(pc_err);
    exit(-1);
  }

  if (pcap_lookupnet(dev, &net.s_addr, &mask.s_addr, pc_err) == -1) {
    perror(pc_err);
    memset(&net, 0, sizeof(net));
    memset(&mask, 0, sizeof(mask));
  } 

  if (!quiet) {
    printf("interface: %s", dev);
    if (net.s_addr && mask.s_addr) {
      printf(" (%s/", inet_ntoa(net));
      printf("%s)", inet_ntoa(mask)); 
    }
    printf("\n");
  }


  if (argv[optind]) {
    filter = get_filter(&argv[optind]); 

    if (pcap_compile(pd, &pcapfilter, filter, 0, mask.s_addr)) {
      free(filter); 
      filter = get_filter(&argv[optind-1]); 

      PCAP_RESTART();
      if (pcap_compile(pd, &pcapfilter, filter, 0, mask.s_addr)) {
	pcap_perror(pd, "pcap compile");
	exit(-1);
      } else regex = NULL;
    }

    if (!quiet) printf("filter: %s\n", filter); 
    
    if (pcap_setfilter(pd, &pcapfilter)) {
      pcap_perror(pd, "pcap set");
      exit(-1);
    }
  }


  if (regex) {
    match_func = &re_match_func;
    re_syntax_options = RE_SYNTAX_EGREP;

    if (re_ignore_case) {
      char *s;
      int i;

      pattern.translate = (char*)malloc(256);
      s = pattern.translate;
      for (i = 0; i < 256; i++) 
	s[i] = i;
      for (i = 'A'; i <= 'Z'; i++) 
	s[i] = i + 32;

      s = regex;
      while (*s) 
	*s++ = tolower(*s);
    } else pattern.translate = NULL;

    if (re_match_word) {
      char *word_regex = malloc(strlen(regex) * 3 + strlen(WORD_REGEX));
      sprintf(word_regex, WORD_REGEX, regex, regex, regex);
      regex = word_regex;
    }

    (const char *)re_err = re_compile_pattern(regex, strlen(regex), &pattern);
    if (re_err) {
      fprintf(stderr, "regex compile: %s\n", re_err);
      exit(-1);
    }

    pattern.fastmap = (char*)malloc(256);
    if (re_compile_fastmap(&pattern)) {
      perror("fastmap compile failed");
      exit(-1);
    }

    if (!quiet && regex && strlen(regex)) 
      printf("%smatch: %s\n", invert_match?"don't ":"", regex);
  } else match_func = &blank_match_func;


  if (re_match_word) free(regex);
  if (filter) free(filter);


  switch(pcap_datalink(pd)) {
  case DLT_EN10MB:
  case DLT_IEEE802:
    link_offset = ETHHDR_SIZE;
    break;

  case DLT_FDDI:
    link_offset = FDDIHDR_SIZE;
    break;

  case DLT_SLIP: 
    link_offset = SLIPHDR_SIZE;
    break;
    
  case DLT_PPP:
    link_offset = PPPHDR_SIZE;
    break;

  case DLT_RAW: 
    link_offset = RAWHDR_SIZE;
    break;

  case DLT_NULL:
    link_offset = LOOPHDR_SIZE;
    break;

  default:
    fprintf(stderr, "fatal: unsupported interface type\n");
    exit(-1);
  }
  

  while (pcap_loop(pd, 0, (pcap_handler)process, 0));
}	


void process(u_char *data1, struct pcap_pkthdr* h, u_char *p) {
  struct ip* ip_packet = (struct ip *)(p + link_offset);

  unsigned ip_hl = ip_packet->ip_hl*4;
  unsigned ip_off = ntohs(ip_packet->ip_off);
  unsigned fragmented = ip_off & (IP_MF | IP_OFFMASK);
  unsigned frag_offset = fragmented?(ip_off & IP_OFFMASK) * 8:0;

  char *data;
  int len;

  switch (ip_packet->ip_p) {
  case IPPROTO_TCP: {
    struct tcphdr* tcp = (struct tcphdr *)(((char *)ip_packet) + ip_hl);
    unsigned tcphdr_offset = fragmented?0:(tcp->th_off * 4);

    if (!quiet) {
      printf("#");
      fflush(stdout);
    }

    data = ((char*)tcp) + tcphdr_offset;
    len = ntohs(ip_packet->ip_len) - ip_hl - tcphdr_offset;

    if (((len || show_empty) && (((int)(*match_func)(data, len)) != invert_match))
	|| keep_matching) { 
      printf("\nT ");

      if (tcphdr_offset || !frag_offset) {
	printf("%s:%d -", inet_ntoa(ip_packet->ip_src), ntohs(tcp->th_sport));
	printf("> %s:%d", inet_ntoa(ip_packet->ip_dst), ntohs(tcp->th_dport));
	printf(" [%s%s%s%s%s%s]",
	       (tcp->th_flags & TH_ACK)?"A":"",
	       (tcp->th_flags & TH_SYN)?"S":"",
	       (tcp->th_flags & TH_RST)?"R":"",
	       (tcp->th_flags & TH_FIN)?"F":"",
	       (tcp->th_flags & TH_URG)?"U":"",
	       (tcp->th_flags & TH_PUSH)?"P":"");
      } else {
	printf("%s -", inet_ntoa(ip_packet->ip_src));
	printf("> %s", inet_ntoa(ip_packet->ip_dst));
      }

      if (fragmented) {
	printf(" %s%d@%d:%d\n", frag_offset?"+":"", ntohs(ip_packet->ip_id),
                                frag_offset, len); 
      } else printf("\n");
      
      dump(data,len);
    }
  }
  break;

  case IPPROTO_UDP: {
    struct udphdr* udp = (struct udphdr *)(((char *)ip_packet) + ip_hl);
    unsigned udphdr_offset = (fragmented)?0:sizeof(struct udphdr); 

    if (!quiet) {
      printf("#"); 
      fflush(stdout);
    }

    data = ((char*)udp) + udphdr_offset;
    len = ntohs(ip_packet->ip_len) - ip_hl - udphdr_offset;

    if (((len || show_empty) && (((int)(*match_func)(data, len)) != invert_match))
	|| keep_matching) { 
      printf("\nU ");

      if (udphdr_offset || !frag_offset) {
#ifdef HAVE_DUMB_UDPHDR
	printf("%s:%d -", inet_ntoa(ip_packet->ip_src), ntohs(udp->source));
	printf("> %s:%d", inet_ntoa(ip_packet->ip_dst), ntohs(udp->dest));
#else
	printf("%s:%d -", inet_ntoa(ip_packet->ip_src), ntohs(udp->uh_sport));
	printf("> %s:%d", inet_ntoa(ip_packet->ip_dst), ntohs(udp->uh_dport));
#endif
      } else {
	printf("%s -", inet_ntoa(ip_packet->ip_src));
	printf("> %s", inet_ntoa(ip_packet->ip_dst));
      }

      if (fragmented) {
	printf(" %s%d@%d:%d\n", frag_offset?"+":"", ntohs(ip_packet->ip_id),
                                frag_offset, len); 
      } else printf("\n");
      
      dump(data,len);
    }
  }
  break;

  case IPPROTO_ICMP: {
    struct icmp* ic = (struct icmp *)(((char *)ip_packet) + ip_hl);
    unsigned icmphdr_offset = fragmented?0:4;

    if (!quiet) {
      printf("#"); 
      fflush(stdout);
    }

    data = ((char*)ic) + icmphdr_offset;
    len = ntohs(ip_packet->ip_len) - ip_hl - icmphdr_offset;

    if (((len || show_empty) && (((int)(*match_func)(data, len)) != invert_match))
	|| keep_matching) { 
      printf("\nI ");

      printf("%s -", inet_ntoa(ip_packet->ip_src));
      printf("> %s", inet_ntoa(ip_packet->ip_dst));

      if (icmphdr_offset || !frag_offset) 
	printf(" %d:%d", ic->icmp_type, ic->icmp_code);

      if (fragmented) {
	printf(" %s%d@%d:%d\n", frag_offset?"+":"", ntohs(ip_packet->ip_id),
                                frag_offset, len); 
      } else printf("\n");
      dump(data,len);
    }
  }
  break;
  
  }

  if (match_after && keep_matching)
    keep_matching--;
}


void dump(char *data, int len) {  
  if (len > 0) {
    unsigned width = show_hex?16:70;
    char *str = data;
    int j, i = 0;

    while (i < len) {
      printf("  ");

      if (show_hex) 
	for (j = 0; j < width; j++) {
	  if (i+j < len) 
	    printf("%02x ", (unsigned char)str[j]);
	  else printf("   ");

	  if((j+1) % (width/2) == 0)
	    printf("   ");
	}

      for (j = 0; j < width; j++) 
	if (i+j < len) 
	  printf("%c", isprint(str[j])?str[j]:'.');
	else printf(" ");
      
      str += width;
      i += j;

      printf("\n");
    }
  }
}


int re_match_func(char *data, int len) {
  switch (re_search(&pattern, data, len, 0, len, 0)) {
    case -2: 
      perror("she's dead, jim\n");
      exit(-1);
    case -1:
      return 0;
  }
  
  if (max_matches && ++matches > max_matches)
    clean_exit(0);

  if (match_after && keep_matching != match_after)
    keep_matching = match_after;

  return 1;
}

int blank_match_func(char *data, int len) {
  if (max_matches && ++matches > max_matches)
    clean_exit(0);

  return 1;
}


char *get_filter(char **argv) {
  char **arg = argv, *theirs, *mine;
  char *from, *to;
  int len = 0;

  if (!*arg)
    return NULL;

  while (*arg) 
    len += strlen(*arg++) + 1;

  if (!(theirs = (char*)malloc(len + 1)) || 
      !(mine = (char*)malloc(len + sizeof(IP_ONLY))))
    return NULL;

  memset(theirs, 0, len + 1);
  memset(mine, 0, len + sizeof(IP_ONLY));

  arg = argv;
  to = theirs;

  while ((from = *arg++)) {
    while ((*to++ = *from++));
    *(to-1) = ' ';
  }

  sprintf(mine,IP_ONLY,theirs);

  free(theirs);
  return mine;
}


void clean_exit(int sig) {
  if (pattern.translate) free(pattern.translate);
  if (pattern.fastmap) free(pattern.fastmap);

  printf("exit\n");
  exit(0);
}


void usage(int e) {
  printf("usage: ngrep <-hViwqevxl> <-n num> <-d dev> <-A num> <regex> <pcap filter logic>\n");
  exit(e);
}


void version(void) {
  printf("ngrep: %s\n", rcsver);
  exit(0);
}
