/* ------------------------------------------------------------------
 * tcpip routines
 */


#include "config.h"

/* The include files */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <net/if.h>
#include <gtk/gtk.h>
#include <stdio.h>
#include "ip_var.h"
#include "udp_var.h"

#include "cheops.h"
#include "tcpip-mod.h"
#if (__GLIBC__ < 2)
#include "libc5.h"
#endif

extern void tcpip_socket_cb(void *data, int fd, GdkInputCondition condition);

/*-- LINUX routilng TABLES */
#include <linux/sockios.h>	/* GLIBC don't have sockios.h? */
typedef struct
  {
    char ifname[17];
    struct in_addr addr;
  }
interfacerec;

typedef struct
  {
    struct in_addr addr;
    unsigned long naddr;	/* netmask */
    interfacerec *iface;
  }
routerec;

static short numinterfaces, numroutes;
static interfacerec *interfaces;
static routerec *routes;

#ifdef PCAP
#include <pcap.h>
pcap_t *PCapHdlr=NULL;
#endif

/* Standard Macro */
#ifndef MIN
#define MIN(x,y) (x<y) ? x : y;
#endif

static int sendsock, readsock;
unsigned short ipident;

/* This function will determine the checksum for a specific packet. Used by */
/*  nearly EVERYTHING on the internet */
unsigned short
inet_checksum (void *addr, int len)
{
  register int nleft = len;
  register u_short *w = addr;
  register int sum = 0;
  u_short answer = 0;

  /*
   *  Our algorithm is simple, using a 32 bit accumulator (sum),
   *  we add sequential 16 bit words to it, and at the end, fold
   *  back all the carry bits from the top 16 bits into the lower
   *  16 bits.
   */
  while (nleft > 1)
    {
      sum += *w++;
      nleft -= 2;
    }

  /* mop up an odd byte, if necessary */
  if (nleft == 1)
    {
      *(u_char *) (&answer) = *(u_char *) w;
      sum += answer;
    }

  /*
   * add back carry outs from top 16 bits to low 16 bits
   */
  sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
  sum += (sum >> 16);		/* add carry */
  answer = ~sum;		/* truncate to 16 bits */
  return (answer);
}

struct psuedohdr  {
  struct in_addr source_address;
  struct in_addr dest_address;
  unsigned char place_holder;
  unsigned char protocol;
  unsigned short length;
} psuedohdr;

static unsigned short tcp_checksum(char *packet,
                           int length,
                           struct in_addr source_address,
                           struct in_addr dest_address)
{
  char *psuedo_packet;
  unsigned short cksum;
  
  psuedohdr.protocol = IPPROTO_TCP;
  psuedohdr.length = htons(length);
  psuedohdr.place_holder = 0;

  psuedohdr.source_address = source_address;
  psuedohdr.dest_address = dest_address;
  
  if((psuedo_packet = malloc(sizeof(psuedohdr) + length)) == NULL)  {
    perror("malloc");
    exit(EXIT_FAILURE);
  }
  
  memcpy(psuedo_packet,&psuedohdr,sizeof(psuedohdr));
  memcpy((psuedo_packet + sizeof(psuedohdr)),
         packet,length);
  
  cksum = inet_checksum((unsigned short *)psuedo_packet,(length + sizeof(psuedohdr)));
  free(psuedo_packet);
  return cksum;
}

/* Sends a TCP packet */
void
sendtcp (spoofrec * spoof, unsigned short flags, short rep)
{
  struct tcphdr tcp;
  struct ip ip;
  static char pkt[8192];
  int i;

/*-- IP HDR --*/
  ip.ip_hl = 5;
  ip.ip_v = 4;
  ip.ip_tos = 0;
#ifdef NEEDS_HTONS_IP_LEN
  ip.ip_len = htons (40);
#else
  ip.ip_len = 40;
#endif 
  ip.ip_id = htons (31337 + spoof->sport);
  ip.ip_off = 0;
  ip.ip_ttl = 255;
  ip.ip_p = IPPROTO_TCP;
  ip.ip_src = spoof->from.sin_addr;
  ip.ip_dst = spoof->dest.sin_addr;
#ifdef HAVE_STRUCT_IP_CSUM
#define ip_sum ip_csum
#endif
  ip.ip_sum = 0;
  ip.ip_sum = inet_checksum ((void *) &ip, sizeof (ip));

/*-- TCP HDR --*/
  tcp.th_sport = htons (spoof->sport);
  tcp.th_dport = htons (spoof->dport);
  tcp.th_seq = htonl (spoof->seq);
  tcp.th_ack = 0;
#ifdef X2_OFF
  tcp.th_x2_off = 0x50;
#else
  tcp.th_x2 = 0;
  tcp.th_off = 5;
#endif /* X2_OFF */
  tcp.th_flags = flags;
  tcp.th_win = htons (0x1234);
  tcp.th_urp = 0;
  tcp.th_sum = 0;

/*-- TCP Checksum --*/
  tcp.th_sum = tcp_checksum ((char *) &tcp,
			     sizeof (struct tcphdr),
			     spoof->from.sin_addr,
			     spoof->dest.sin_addr);

  memcpy (pkt, (char *) &ip, sizeof (ip));
  memcpy (pkt + sizeof (ip), (void *) &tcp, sizeof (tcp));

  for (i = 0; i < rep; i++)
    if (sendto (sendsock, (void *) pkt, sizeof (ip) + sizeof (tcp), 0, (struct sockaddr *) &spoof->dest, sizeof (spoof->dest)) < 0)
      perror ("sending message");

}

/* Sends an ICMP packet */
void
sendicmp (int ttl, struct in_addr to, int rep)
{
  static unsigned char pkt[8192];
  static struct ip *ip = (struct ip *)pkt;
  struct ip tip;
  struct udpiphdr *ui;
  static struct icmp *icmp = (struct icmp *)&pkt[sizeof(struct ip)];
  static struct udphdr *udp = (struct udphdr *)&pkt[sizeof(struct ip)];
  int i;
  int len;
  struct sockaddr_in dest, src;
  
#if 0
  src.sin_addr.s_addr = 0;
#else
  src.sin_addr = getlocalip(to.s_addr);
#endif
  src.sin_family = AF_INET;
  src.sin_port = 0;
  dest.sin_addr = to;
  dest.sin_family = AF_INET;

/*-- IP HDR --*/
  ip->ip_hl = 5;
  ip->ip_v = 4;
  ip->ip_tos = 0;
  ip->ip_id = htons(getpid());
  ip->ip_off = 0;
  ip->ip_ttl = ttl;
  ip->ip_src = src.sin_addr;
  ip->ip_dst = to;
#ifdef HAVE_STRUCT_IP_CSUM
#define ip_sum ip_csum
#endif
  ip->ip_sum = 0;

  if (option_use_icmp) {
    len = ICMP_MINLEN;
    dest.sin_port = 0;
    ip->ip_p = IPPROTO_ICMP;
  	/*-- ICMP HDR --*/
  	icmp->icmp_type = ICMP_ECHO;
  	icmp->icmp_code = 0;
  	icmp->icmp_id = htons(getpid());
  	icmp->icmp_seq = htons(ttl);
  	/* This isn't as stupid as it looks */
  	icmp->icmp_cksum = 0;
  	icmp->icmp_cksum = inet_checksum (icmp, len);
  } else {
    /* UDP header */
	len = ICMP_MINLEN;	/* That's convenient that they are the same */
    dest.sin_port = htons(EMPTY_PORT + ttl);
    ip->ip_p = IPPROTO_UDP;
	udp->uh_ulen = htons(len);
	udp->uh_sport = htons(getpid());
	udp->uh_dport = htons(EMPTY_PORT + ttl);
	udp->uh_sum = 0;
  }
  ip->ip_len = htons(len + sizeof(struct ip));
  ip->ip_sum = inet_checksum ((void *) ip, sizeof (struct ip)+len);
  if (ip->ip_sum == 0)
  	ip->ip_sum=0xffff;
  if (!option_use_icmp) {
  		/* UDP checksum calculation is more difficult */
  	    	tip = *ip;
		ui = (struct udpiphdr *)ip;
		ui->ui_next = 0;
		ui->ui_prev = 0;
		ui->ui_x1 = 0;
		ui->ui_len = udp->uh_ulen;
		udp->uh_sum = 0;
		udp->uh_sum = inet_checksum (ui, len + sizeof(struct ip));
		if (udp->uh_sum == 0)
			udp->uh_sum = 0xffff;
		*ip = tip;	
  }
  for (i = 0; i < rep; i++)
    if (sendto (sendsock, (void *) pkt, sizeof(struct ip) + len, 0, (struct sockaddr *)&dest, sizeof (dest)) < 0)
      perror ("sending message");
}

/* Get's a TCP packet */
#define MAXSIZE	65535

unsigned int
gettcp (tcprec * dtcp)
{
  char buf[MAXSIZE], *p=buf;
  tcprec *tcp;
  iprec *ip;

  int numread;

#if 0
  /* recvfrom on a raw socket doesn't seem to work under 2.0 */
  if ((numread = recvfrom (readsock, buf, MAXSIZE, 0, &sin, &addrlen)) < 0) {
    perror("recvfrom");
    return (0);
  }
#else
  if ((numread = recv(readsock,buf,MAXSIZE, 0)) < 0) {
    perror("recvfrom");
    return (0);
  }
#endif

  /* Check to see if it's an IP packet */
  if ((p[0] >> 4) != 4) 
    return (0);

  /* Check to see if it's a TCP packet */
  if (p[9] != 6) 
    return (0);
  ip = (iprec *) &p[0];
  tcp = (tcprec *) & p[20];
  
  memcpy ((void *) dtcp, (void *) tcp, sizeof (tcprec));
  return ip-> sip.s_addr;
}

static int do_close_interfaces(GtkWidget *w, GtkWidget *parent)
{
	add_network(&main_window, "My Network");
	if (parent)
		gtk_widget_destroy(parent);
	return 0;
}

static int do_load_interfaces(GtkWidget *w, GtkWidget *parent)
{
	int i, res=0, total=0, oldres=0;
	struct in_addr ina;
	struct net_page *np;
	char netmask[80];
	char addr[80];
	char lastif[80];
	char expected[80];

	if (parent)
		gtk_widget_destroy(parent);

	for (i=0;i<numroutes;i++)
	  {
	    if (((routes[i].naddr & routes[i].iface->addr.s_addr) == routes[i].addr.s_addr)
	    	&& routes[i].naddr) {
	    	ina.s_addr = routes[i].naddr;
		strncpy(netmask, inet_ntoa(ina), sizeof(netmask));
		ina.s_addr = routes[i].iface->addr.s_addr & routes[i].naddr;
		strncpy(addr, inet_ntoa(ina), sizeof(addr));
		if (strcasecmp(lastif, routes[i].iface->ifname) && !strncmp(routes[i].iface->ifname, "eth", 3)) {
			strncpy(lastif, routes[i].iface->ifname, sizeof(lastif));
			total++;
		}
	    }
	  }
	strcpy(lastif, "");
	while(res < total) {
	 oldres = res;
	 snprintf(expected, sizeof(expected), "eth%d", res);
	 for (i=0;i<numroutes;i++)
	  {
	    if (((routes[i].naddr & routes[i].iface->addr.s_addr) == routes[i].addr.s_addr)
	    	&& routes[i].naddr) {
	    	ina.s_addr = routes[i].naddr;
		strncpy(netmask, inet_ntoa(ina), sizeof(netmask));
		ina.s_addr = routes[i].iface->addr.s_addr & routes[i].naddr;
		strncpy(addr, inet_ntoa(ina), sizeof(addr));
		if (strcasecmp(lastif, routes[i].iface->ifname) && !strcmp(routes[i].iface->ifname, expected)) {
#if 0
	    		printf("Device %s, network %s netmask %s\n", routes[i].iface->ifname, addr, netmask);
#endif
			if (total > 1)
				snprintf(lastif, sizeof(lastif), "Local Network (%s)", routes[i].iface->ifname);
			else
				strncpy(lastif, "Local Network", sizeof(lastif));
			np = add_network(&main_window, lastif);
			discover_network_a(np, addr, netmask, 1);
			strncpy(lastif, routes[i].iface->ifname, sizeof(lastif));
			res++;
		}
	    }
	  }
	  if (oldres == res)
	  	break;
	}
	if (!res)
		add_network(&main_window, "My Network");
	return res;
}

int load_interfaces()
{
	GtkWidget *dlg;
	GtkWidget *label;
	GtkWidget *yes;
	GtkWidget *no;
	
	dlg = gtk_dialog_new();
	gtk_window_set_title(GTK_WINDOW(&GTK_DIALOG(dlg)->window), "Cheops Auto-scan");
	label = gtk_label_new("Would you like me to automatically add your local networks?");
	yes = gtk_button_new_with_label("Yes");
	no = gtk_button_new_with_label("No");
	gtk_widget_show(label);
	gtk_widget_show(yes);
	gtk_widget_show(no);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), label, FALSE, FALSE, 5);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), yes, FALSE, FALSE, 5);
	gtk_signal_connect(GTK_OBJECT(yes), "clicked", GTK_SIGNAL_FUNC(do_load_interfaces), dlg);
	gtk_signal_connect(GTK_OBJECT(no), "clicked", GTK_SIGNAL_FUNC(do_close_interfaces), dlg);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), no, FALSE, FALSE, 5);
	gtk_widget_grab_focus(yes);
	gtk_widget_show(dlg);
	return 0;
}


struct in_addr
getlocalip (unsigned int dest)
{
  static struct in_addr ina;
  int i;

  for (i = 0; i < numroutes; i++)
    {
      if ((dest & routes[i].naddr) == (unsigned long) routes[i].addr.s_addr)
        {
          return (routes[i].iface->addr);
        }
    }

  ina.s_addr = 0;
  return ina;
}


/*-- --*/
void init_route_tables(void)
{
  int ifsock, i, i1, found;
  struct ifconf ifc;
  struct ifreq *ifr;
  char buf[1024], iface[16], *ptr;
  FILE *f;

  /* Create a channel to the NET kernel. */
  if ((ifsock = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
    {
      perror ("socket");
      exit (EXIT_FAILURE);
    }

  ifc.ifc_len = sizeof (buf);
  ifc.ifc_buf = buf;
  if (ioctl (ifsock, SIOCGIFCONF, &ifc) < 0)
    {
      perror ("opening interface socket");
      close (ifsock);
      exit (EXIT_FAILURE);
    }

  numinterfaces = (ifc.ifc_len / sizeof (struct ifreq));
  interfaces = (interfacerec *) malloc (numinterfaces * sizeof (interfacerec));

  ifr = ifc.ifc_req;
  for (i = 0; i < numinterfaces; i++, ifr++)
    {
      strcpy (interfaces[i].ifname, ifr->ifr_name);
      memcpy (&interfaces[i].addr, &((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr, sizeof (struct in_addr));
      if (ioctl (ifsock, SIOCGIFADDR, ifr) < 0)
        printf ("Couldn't get address for %s\n", ifr->ifr_name);
    }
  close (ifsock);

  if ((f = fopen ("/proc/net/route", "r")) == NULL)
    {
      perror ("opening /proc/net/route");
      exit (EXIT_FAILURE);
    }

  numroutes = 0;
  fgets (buf, sizeof (buf), f);         /* strip out description line */
  while (!feof (f))
    {
      fgets (buf, sizeof (buf), f);
      numroutes++;
    }
  numroutes--;

  routes = (routerec *) malloc (numroutes * sizeof (routerec));

  rewind (f);

  fgets (buf, sizeof (buf), f); 
  for (i = 0; i < numroutes; i++)
    {
      if (fgets (buf, sizeof (buf), f) == NULL)
        {
          /* Important, since an interface might have been removed since our counting,
             causing us to parse bogus data */
          fputs ("Error reading /proc/net/route: iface count mismatch\n", stderr);
          fclose (f);
          exit (EXIT_FAILURE);
        }
      if ( strlen (buf) == sizeof(buf)-1 )
        {
          /* skip long lines */
          fputs ("Long (corrupt) line encountered, skipping.\n", stderr);
          while ((fgets (buf, sizeof (buf), f)))
            if (buf [strlen (buf) - 1] == '\n')
              break;
          continue; /* continue with next regular line (or fail if EOF */
        }
      ptr = strtok (buf, "\t ");
      if (!ptr)
        continue;
      if (strlen (ptr) >= sizeof (iface))
        continue; /* would overflow if fed with bogus data in a chroot()ed environment */
      else
        strcpy (iface, ptr);
      ptr = strtok (NULL, "\t ");       /* hack avoiding fscanf */
      routes[i].addr.s_addr=(unsigned long)strtoul(ptr,NULL,16);
      for (i1 = 0; i1 < 6; i1++)
        {
          ptr = strtok (NULL, "\t ");   /* ignore Gateway Flags RefCnt Use Metric */
        }
      if (!ptr) {
        fputs ("Error parsing /proc/net/route\n", stderr);
        continue;
      }
      routes[i].naddr=(unsigned long)strtoul(ptr,NULL,16);   /* Netmask */

      found = 0;
      for (i1 = 0; i1 < numinterfaces; i1++)
        {
          if (strcmp (interfaces[i1].ifname, iface) == 0)
            {
              routes[i].iface = &interfaces[i1];
              found = 1;
            }

        }

      if (!found)
         {
          printf ("Couldn't find interface %s\n", iface);
          exit (EXIT_FAILURE);
        }
   }
  fclose (f);
}

void
init_tcpip (void)
{
  int on=1;
  int rsflags;

  init_route_tables();
  
  /*-- SEND RAW socket --*/
  if ((sendsock = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
    {
      perror ("opening raw send socket");
      exit (EXIT_FAILURE);
    }
  if (setsockopt (sendsock, IPPROTO_IP, IP_HDRINCL, (char *) &on, sizeof (on)) < 0)
    {
      perror ("setting option IP_HDRINCL");
      exit (EXIT_FAILURE);
    }

  /*-- READ RAW socket --*/
  if ((readsock = socket (AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0)
    {
      perror ("opening raw read socket");
      exit (EXIT_FAILURE);
    }
  if ((rsflags = fcntl (readsock, F_GETFL)) == -1)
    {
      perror ("fcntl(readsock,F_GETFL)");
      exit (EXIT_FAILURE);
    }

  if (fcntl (readsock, F_SETFL, rsflags | O_NONBLOCK) == -1)
    {
      perror ("fcntl(readsock,F_SETFL)");
      exit (EXIT_FAILURE);
   }
#ifdef RAW_NEEDS_BIND
  name.sin_family = AF_INET;
  name.sin_addr.s_addr = INADDR_ANY;
  name.sin_port = 10000;
  if (bind (readsock, (struct sockaddr *) &name, sizeof (name)))
    {
      perror ("binding read socket");
      exit (EXIT_FAILURE);
    }
#endif /* RAW_NEEDS_BIND */ 
  gdk_input_add(readsock, GDK_INPUT_READ, tcpip_socket_cb, NULL);
}

