#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pcap.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>
#include <ctype.h>


void outputAllInterfaces() {
  /* pouziti pcap_findalldevs inspirovano na http://dog.tele.jp/winpcap/html/group__wpcapsamp1.html */
  bpf_u_int32 netp;
  bpf_u_int32 maskp;
  char *net, *mask;
  char errbuf[PCAP_ERRBUF_SIZE];
  pcap_if_t *alldevs, *onedev;
  struct in_addr addr;
  /* vyhledej vsechna mozna rozhrani, na kterych se muze zachytavat */
  if (pcap_findalldevs(&alldevs, errbuf) == -1) {
    fprintf(stderr, "Chyba pri hledani vsech pouzitelnych rozhrani.\n");
    exit(6);
  }
  for (onedev = alldevs; onedev != NULL; onedev = onedev->next) {
    if (pcap_lookupnet(onedev->name, &netp, &maskp, errbuf) == -1) {
      /* doslo k chybe, nicmene neni to kriticke, takze nezastavujeme */
      net = 0;
      mask = 0;
    }
    /* prevod masky a IP adresy na string */
    addr.s_addr = netp;
    net = inet_ntoa(addr);
    addr.s_addr = maskp;
    mask = inet_ntoa(addr);
    /* formatovany vypis rozhrani, adresy a masky */
    printf("%-8s  addr: %-15s  mask: %-15s\n", onedev->name, net, mask);
  }
  return;

}

void printMac(u_char mac[6]) {
  /* vypis MAC adresy */
  printf("%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

void printPacketContents(const u_char *packet, struct pcap_pkthdr hdr) {
  /* vypis obsahu paketu v hexa a znakove podobe, 16 znaku na radek */
  unsigned int i = 0;
  unsigned int j = 0;
  u_char c;
  u_char line[16];
  unsigned int numberIterations;
  numberIterations = hdr.len / 16;
  
  for (i = 0; i < numberIterations; i++) {
    /* vypis offsetu */
    printf("0x%04x: ", i * 16);
    /* vypis hexa podoby znaku */
    for (j = 0; j < 16; j++) {
      c = *(packet + i * 16 + j);
      if (isprint(c)) line[j] = c;
      else line[j] = '.';
      printf("%02x ", c);      
      if (j == 7) printf(" ");
    }
    printf("  ");
    /* vypis znakove podoby */
    for (j = 0; j < 16; j++) {
      printf("%c", line[j]);
    }
    printf("\n");
  }
  if (numberIterations * 16 != hdr.len) {
    /* vypis zbylych bajtu v pripade, ze delka ramce neni delitelna 16 */
    printf("0x%04x: ", numberIterations * 16);
    for (i = numberIterations * 16; i < hdr.len; i++) {
      c = *(packet + i);
      printf("%02x ", c);
      if (i % 16 == 7) printf(" ");
    }
    for (i = hdr.len; i < (numberIterations + 1)*16; i++) printf("   ");
    if (hdr.len <= (numberIterations * 16 + 7)) printf(" ");
    printf("  ");
    for (i = numberIterations * 16; i < hdr.len; i++) {
      c = *(packet + i);
      if (isprint(c)) printf("%c", c);
      else printf(".");
    }
    printf("\n");
  }
  printf("\n");
}


void capturePackets(char *iface, int port, int numberPackets, int tcpFilter, int udpFilter) {
  int capturedPackets = 0; /* pocet zachycenych paketu, ktere vyhovuji podmince */
  char errbuf[PCAP_ERRBUF_SIZE]; /* buffer na chybove hlasky pcap */
  pcap_t *descr;  /* odkaz na zarizeni, na kterem se zachytava */
  const u_char *packet;  /* odkaz na zachyceny paket */
  struct pcap_pkthdr hdr; /* hlavicka paketu */
  struct timeval ts; /* timestamp paketu */
  struct ether_header *eth; /* hlavicka ethernetoveho ramce */
  struct iphdr *iphead;  /* hlavicka ip protokolu */
  struct tcphdr* tcphead; /* hlavicka tcp protokolu */
  struct udphdr* udphead; /* hlavicka udp protokolu */
  int src_port, dst_port; /* promenne pro zdrojovy a cilovy port */
  char src_ip[20], dst_ip[20]; /* promenne pro zdrojovou a cilovou IP adresu */
  int hour, minute, sec, usec;

  if (port >= 0 && port <= 65535 && !tcpFilter && !udpFilter) {
    /* neni-li zadny filtr, a je urceno cislo portu, zobruj jenom TCP a UDP pakety */
    tcpFilter = udpFilter = 1;
  }  
  /* otevri rozhrani v promiskuitnim modu */
  descr = pcap_open_live(iface, BUFSIZ, 1, -1, errbuf);
  if (descr == NULL) {
    fprintf(stderr, "Nepodarilo se prepnout rozhrani %s do promiskuitniho modu.\n", iface);
    exit(8);
  }
  
  /* test, je-li rozhrani typu Ethernet (napr. any neni) */
  if (pcap_datalink(descr) != DLT_EN10MB) {
    fprintf(stderr, "Rozhrani %s neni ethernetove, nelze pokracovat.\n", iface);
    exit(9);
  }

  while (capturedPackets < numberPackets) {
    /* dokud nemame nachytano dost paketu */
    packet = pcap_next(descr, &hdr);
    if (packet == NULL) {
      fprintf(stderr, "Doslo k chybe pri zachytavani paketu.\n");
      printf(errbuf);
      exit(9);
    }
    /* vypocet casu prichodu paketu */
    ts = hdr.ts;
    ts.tv_sec %= 86400; /* zajima nas jen cas, ne datum */
    hour = ts.tv_sec / 3600;
    ts.tv_sec -= 3600 * hour;
    minute = ts.tv_sec / 60;
    ts.tv_sec -= 60 * minute;
    sec = ts.tv_sec;
    usec = ts.tv_usec;
    
    eth = (struct ether_header *)packet;
    if (ntohs(eth->ether_type) == ETHERTYPE_IP) {
      /* detekce protokolu IP */
      
      /* ziskani IP hlavicky */
      iphead = (struct iphdr *)(packet + sizeof(struct ether_header));
      if (iphead->protocol == IPPROTO_TCP) {
        /* protokol je TCP */
	if (udpFilter && (!tcpFilter || !udpFilter)) continue;
	/* ziskani retezce s IP adresou z hlavicky IP paketu */
        inet_ntop(AF_INET, &iphead->saddr, src_ip, sizeof(src_ip));
	inet_ntop(AF_INET, &iphead->daddr, dst_ip, sizeof(dst_ip));
	/* ziskani TCP hlavicky */
	tcphead = (struct tcphdr *)(packet + sizeof(struct ether_header) + iphead->ihl*4);
	/* ziskani portu z TCP hlavicky */
	src_port = ntohs(tcphead->source);
	dst_port = ntohs(tcphead->dest);
	/* kontrola portu (je-li nastaven parametr) */
	if ((port < 0) || ((port >= 0) && ((src_port == port) || (dst_port == port)))) {
	  /* vypis paketu */
          printf("%02d:%02d:%02d.%06d ", hour, minute, sec, usec);
          printf("IP ");
  	  printf("%s.%d > %s.%d\n", src_ip, src_port, dst_ip, dst_port);
	  printPacketContents(packet, hdr);
  	  capturedPackets++;
	}
      } else if (iphead->protocol == IPPROTO_UDP) {
	if (tcpFilter && (!udpFilter || !tcpFilter)) continue;	
        inet_ntop(AF_INET, &iphead->saddr, src_ip, sizeof(src_ip));
	inet_ntop(AF_INET, &iphead->daddr, dst_ip, sizeof(dst_ip));
	udphead = (struct udphdr *)(packet + sizeof(struct ether_header) + iphead->ihl*4);
	src_port = ntohs(udphead->source);
	dst_port = ntohs(udphead->dest);
	if ((port < 0) || ((port >= 0) && ((src_port == port) || (dst_port == port)))) {
          printf("%02d:%02d:%02d.%06d ", hour, minute, sec, usec);  
  	  printf("IP ");
	  printf("%s.%d > %s.%d\n", src_ip, src_port, dst_ip, dst_port);
	  printPacketContents(packet, hdr);
	  capturedPackets++;
	}
      } else {
        /* neni ani TCP ani UDP */
	if (tcpFilter || udpFilter) continue;
        printf("%02d:%02d:%02d.%06d ", hour, minute, sec, usec);
	printf("XX ");
	/* vypis MAC adresy z hlavicky ethernetoveho ramce */
	printMac(eth->ether_shost);
	printf(" > ");
	printMac(eth->ether_dhost);
	printf("\n");
        printPacketContents(packet, hdr);
	capturedPackets++;
      }
    } else {
      /* ether_type neni IP, vypis MAC adresy */
      if (tcpFilter || udpFilter) continue;
      printf("%02d:%02d:%02d.%06d ", hour, minute, sec, usec);
      printf("XX ");
      printMac(eth->ether_shost);
      printf(" > ");
      printMac(eth->ether_dhost);
      printf("\n");
      printPacketContents(packet, hdr);
      capturedPackets++;
    }
  }
  pcap_close(descr);
}

int main(int argc, char *argv[]) {
    int c;
    char *iface;
    int port;
    int i;
    
    int numberPackets = 1;	/* pocet zachytavanych paketu */
    int ifaceSet, portSet, numberPacketsSet; /* promenne indikujici nastaveni filtru */
    int filterTcp, filterUdp;
    ifaceSet = portSet = numberPacketsSet = filterTcp = filterUdp = 0;
    
    /* vyfiltrovani parametru "-tcp" a "-udp", se kterymi si getopt neporadi */
    for (i = 0; i < argc; i++) {
      if (!strcmp(argv[i], "-tcp")) {
        filterTcp = 1;
	argv[i] = "";
      } else if (!strcmp(argv[i], "-udp")) {
        filterUdp = 1;
	argv[i] = "";
      }
    }
    /* zpracovani parametru prikazove radky pomoci getopt() */
    while ((c = getopt(argc, argv, "i:p:n:")) != -1) {
	switch (c) {
	    case 'i':
	      if (ifaceSet) {
	        fprintf(stderr, "Spatny vstup - Jmeno rozhrani jiz jednou bylo nastaveno.\n");
		return 1;
	      }
	      iface = (char *)malloc(strlen(optarg));
	      strcpy(iface, optarg);
	      ifaceSet = 1;
	      break;
	    case 'p':
	      if (portSet) {
	        fprintf(stderr, "Spatny vstup - Cislo portu jiz jednou bylo zadano.\n");
		return 2;
	      }
	      port = atoi(optarg);
	      if (port < 0 || port > 65535) {
	        fprintf(stderr, "Spatny vstup - nesmyslne cislo portu (muze byt pouze v rozsahu 0-65535).\n");
		return 7;
	      }
	      portSet = 1;
	      break;
	    case 'n':
	      if (numberPacketsSet) {
	        fprintf(stderr, "Spatny vstup - Pocet zachytavanych paketu jiz byl jednou nastaven.\n");
		return 3;
	      }
	      numberPackets = atoi(optarg);
	      numberPacketsSet = 1;
	      break;
	    case '?':
	      fprintf(stderr, "Spatny vstup - neznamy parametr %c\n", optopt);
	      opterr = 0;	
	      return 4;
	      break;
	    case ':':
	      fprintf(stderr, "Spatny vstup - chybi hodnota k parametru %c\n", optopt);
	      opterr = 0;
	      return 5;
	      break;
	    default:
	      opterr = 0;
	      printf("Chyba: %c\n", optopt);
	      break;
	      
	}
    }

    if (!ifaceSet) {
	/* neni-li zadano rozhrani, vypise vsechny aktivni a skonci */
	outputAllInterfaces();
	return 0;
    }
    /* neni-li zadano cislo portu, nastavime jeho hodnotu na -1 */
    if (!portSet) port = -1;
    capturePackets(iface, port, numberPackets, filterTcp, filterUdp);
}

