/***************************************************************
 *
 * Proyecto:	QueSO ( Que Sistema Operativo ?? )      
 * Autor:       Jordi Murgo <savage@apostols.org>
 * Descripcion:	Determina el tipo de Sistema Operativo de una
 *              maquina concreta a partir del comportamiento 
 *              de su pila TCP/IP ante paquetes TCP 'raros'.
 *              Ver el codigo para mas informacion.
 * Licencia:	GNU GPL 
 *
 * Modified to fit network-explorer by Mark Spencer
 *
 ***************************************************************
 * Agradecimientos a ToXyN, b0fh y a los colegas del canal #hack
 *		   especialmente a syn por la traduccion del doc
 ***************************************************************
 * CVS: $Id: queso-mod.c,v 1.5 1998/11/30 14:24:11 markster Exp $
 ***************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

#include <gtk/gtk.h>
#include "tcpip-mod.h"
#include "cheops.h"



static int VECES = 1;
static int caught_idle = 0;
static int caught_timeout = 0;

int try_ports[] = { 80, 139, 23 };

static int queue_lock = 0;

static char CFGFILE[255] = QUESO_CONF;

void debugtcp (unsigned long, tcprec);
void usage (const char *);

spoofrec *squeue=NULL;

#define SILENT	0


/* Fake locking functions.  We'll fill them out later */
static int grab_queue_lock()
{
	if (queue_lock)
		return 0;
	else
		queue_lock = 1;
	return 1;
}

static void free_queue_lock()
{
	queue_lock = 0;
}

int detect_host(spoofrec *sr)
{
	/* -1 means failed to know the OS, try again */

	char *c;
        char *os;
	os = ident_os(sr->dest.sin_addr.s_addr, sr->dport, sr->r);
	if (!os)
		return 0;
	c = strchr(os, ':');
	if (c) {
		c[strlen(c)-1]='\0';
		*c='\0';
		c++;
	}  else	{
		if (sr->retries<MAX_RETRIES) 
			return -1;
		c="desktop.xpm";
	}
	if (c) {
		set_status("Processing replies...");
		add_unique(sr->dest.sin_addr.s_addr, c, os, (struct net_page *)sr->data);
	}
	return 0;

}

void tcpip_socket_cb(void *data, int fd, GdkInputCondition condition)
{
     struct in_addr ia;
     tcprec tcp;
     spoofrec *sr;
     unsigned short dport;
     unsigned short sport;
     unsigned short pos;
     fd_set fds;
     struct timeval tv;

do {
     if ((ia.s_addr = gettcp (&tcp))) {
        dport = ntohs(tcp.dport);
        sport = ntohs(tcp.sport);
	sr = squeue;
	while(sr) {
		if (sr->dest.sin_addr.s_addr == ia.s_addr) {
			if (sr->dport == sport) 
				break;
		}
		sr=sr->next;
	}
	if (sr) {
		pos = dport - sr->sport;
		if (pos <= MAXPKT) {
#if 0
			printf("Received reply '%d' from '%s'\n", pos, inet_ntoa(sr->dest.sin_addr));
#endif
	      		sr->r[pos].seq = tcp.seqnum ? 1 : 0;
	      		sr->r[pos].ack = tcp.acknum ? (ntohl(tcp.acknum)-sr->seq+ACK_HACK) : 0;
	      		if(sr->r[pos].ack > RANDOM_ACK)
			 	sr->r[pos].ack = RANDOM_ACK;
	      		sr->r[pos].win = ntohs (tcp.window);
	      		sr->r[pos].flag = tcp.flags;
	      		sr->r[pos].set = 1;
	      		sr->r[pos].urg = tcp.urgentptr ? 1 : 0;
#if 1
			/* Immediately expire */
			if (pos == MAXPKT) 
				sr->timeout=1;
#endif			
		}
#if 0		
		 else 
			printf("response from '%s' too high (%d)\n", inet_ntoa(sr->dest.sin_addr), pos);
#endif
	}
     }
     tv.tv_sec = 0;
     tv.tv_usec = 0;
     FD_ZERO(&fds);
     FD_SET(fd, &fds);
} while (select(fd + 1, &fds, NULL, NULL, &tv) > 0);
}

static int
xmit_check_os (spoofrec *in)
{
  spoofrec spoof;
  unsigned short s;
  unsigned long myseq;



  srand (time (NULL) & 0x0000ffff);
  in->sport = s = (rand () % 26000) + 4000;
  in->seq = spoof.seq = myseq = rand ();
  in->ack = spoof.ack = 0;
  in->timeout = time(NULL) + LIFE;
  spoof.from = in->from;
  spoof.dest = in->dest;
  spoof.dport = in->dport;


  /*-- PKT 0 --*/
  spoof.sport = s++;
  sendtcp (&spoof, SYN, VECES);
  usleep (Zzz);

  /*-- PKT 1 --*/
  spoof.sport = s++;
  sendtcp (&spoof, SYN | ACK, VECES);
  usleep (Zzz);

  /*-- PKT 2 --*/
  spoof.sport = s++;
  sendtcp (&spoof, FIN, VECES);
  usleep (Zzz);

  /*-- PKT 3 --*/
  spoof.sport = s++;
  sendtcp (&spoof, FIN | ACK, VECES);
  usleep (Zzz);
  
  /*-- PKT 4 --*/
  spoof.sport = s++;
  sendtcp (&spoof, SYN | FIN, VECES);
  usleep (Zzz);

  /*-- PKT 5 --*/
  spoof.sport = s++;
  sendtcp (&spoof, PSH, VECES);
  usleep (Zzz);

  /*-- PKT 6 --*/
  spoof.sport = s++;
  sendtcp (&spoof, SYN | XXX | YYY, VECES);
  usleep (Zzz);

  return 0;
}

char *ident_os(unsigned int dest_addr, short dport, OSRES *r)
{
  FILE *f;
  char line[1024];
  struct in_addr dest;
  dest.s_addr = dest_addr;
  /*---------- CHECK RESULT -----------*/
  if ((f = fopen (find_file(CFGFILE), "r")))
    {
      static char osname[256];		/* should be smaller than line[], 256 should suffice */
      unsigned short flag1 = 0, found = 0, linez = 0;
      unsigned short pn = 0, ps = 0, pa = 0, pw = 0, pf = 0, pu = 0;
      char *p;

      while (fgets (line, sizeof (osname) - 1, f))
	{
	  if (line[0] == '\n')
	    {
	      if (flag1 && found == linez)
		{
		  fclose (f);
		  return osname;
		  printf ("%s:%d\t%s", inet_ntoa (dest), dport, osname);
		  if (osname[1] == '-')
		    return NULL;	/* Not accurate response */
		  else
		    return NULL;
		}
	    }

	  if (line[0] == '*')
	    {
	      strcpy (osname, line);
	      flag1 = 1;
	      found = 0, linez = 0;
	      continue;
	    }

	  /*------ PARSE LINE ---*/
	  linez++;
	  p = strtok (line, " ");
	  if (p && isdigit (*p))
	    pn = atoi (p);
	  else
	    {
	      linez = 0;
	      flag1 = 0;
	      found = 0;
	      continue;
	    }
	  
	  /*-- seq --*/
	  p = strtok (NULL, " ");
	  if (p)
	    ps = atoi (p);

	  /*-- ack --*/
	  p = strtok (NULL, " ");
	  if (p) 
	    {
	      if( *p == 'R' )
		pa = RANDOM_ACK;
	      else if( *p == '+' )
		pa = atoi (p)+ACK_HACK; /*-- extended ACK field --*/
	      else 
		pa = atoi (p);
	    }
	  
	  /*-- win --*/
	  p = strtok (NULL, " ");
	  if (p)
	    pw = 0xffff & strtol (p, NULL, 16);

	  /*-- flags --*/
	  p = strtok (NULL, " \n");
	  if (p)
	    {
	      pf = 0;
	      if (strchr (p, 'S'))
		pf |= SYN;
	      if (strchr (p, 'R'))
		pf |= RST;
	      if (strchr (p, 'A'))
		pf |= ACK;
	      if (strchr (p, 'F'))
		pf |= FIN;
	      if (strchr (p, 'P'))
		pf |= PSH;
	      if (strchr (p, 'X'))
		pf |= XXX;
	      if (strchr (p, 'Y'))
		pf |= YYY;
	      if (strchr (p, 'U'))
		pu = 1;
	      else
		pu = 0;
	    }


	  if (!r[pn].set)
	    {
	      if (pf)
		{
		  found = 0;
		  flag1 = 0;
		  continue;
		}
	      found++;
	      continue;
	    }

	  if ( 
	      ( pa>=ACK_HACK?(pa==r[pn].ack):pa==(r[pn].ack>0) ) &&
	      ps == r[pn].seq &&
	      ((pw == r[pn].win)
	       || (pw == 1 && r[pn].win)
	       || (!pw && !r[pn].win)
	       ) &&
	      pf == r[pn].flag &&
	      pu == r[pn].urg )
	    {
	      found++;
	      continue;
	    }
	  else
	    {
	      found = 0;
	      flag1 = 0;
	      continue;
	    }
	}

      fseek (f, 0L, SEEK_END);
      fclose (f);
      
      return NULL;
    }

  fprintf (stderr, "Can't open RO %s \n", CFGFILE);
  return NULL;
}

int timeout_queue(void *data)
{
	spoofrec *sp, *last=NULL;
	time_t current;
	int n;
	
	if (!grab_queue_lock()) {
#if 0
  		printf("Whoa, locked, can't timeout...\n");
#endif
		/* Try again */
		return TRUE;
	}
	sp = squeue;
	while(sp) {
		current = time(NULL);
		if (current > sp->timeout) {
#if 0
			printf("expiring '%s'\n",inet_ntoa(sp->dest.sin_addr));
#endif
			if (!detect_host(sp)) {
				if (last) {
					last->next = sp->next;
					g_free(sp);
					sp = last;
				} else {
					squeue = sp->next;
					g_free(sp);
					sp = squeue;
				}
			} else {
				/* try again */
#if 0
				printf("retry on '%s'\n", inet_ntoa(sp->dest.sin_addr));
#endif
				sp->retries++;
				if (sp->dport != try_ports[sp->retries]) {
					/* On the last try, do port 23 */
  					for (n = 0; n <= MAXPKT; n++)
      						sp->r[n].set = 0;
					sp->dport = try_ports[sp->retries];
#if 0
					printf("trying alternate port %d\n", sp->dport);
#endif
				}
				xmit_check_os(sp);
				sp->timeout = current + LIFE;
			}
		}
		last = sp;
		if (sp)
			sp=sp->next;
	}
	if (squeue) {
		free_queue_lock();
		return TRUE;
	} else {
		free_queue_lock();
		caught_timeout =0;
		gdk_window_set_cursor(main_window.window->window, NULL);
		set_status("Discovery complete");
		return FALSE;
	}
}

int idle_run_queue(void *data)
{
  spoofrec *sp;
  if (!grab_queue_lock()) {
#if 0
  	printf("Whoa, locked, can't idle...\n");
#endif
	/* Try again */
	return TRUE;
  }
  sp = squeue;
  while(sp) {
  	if (sp->timeout == 0)
		break;
  	sp=sp->next;
  }
  if (!sp) {
  	caught_idle = 0;
	free_queue_lock();
	return FALSE;
  }
  if (sp->timeout == 0) {
  	xmit_check_os (sp);
	free_queue_lock();
	return TRUE;
  } else {
  	caught_idle = 0;
	free_queue_lock();
 	return FALSE;
  }
}

int examine_host_queue(unsigned int addr, unsigned short port, struct net_page *page)
{
  spoofrec *sp;
  int n;
  
  sp = g_new(spoofrec, 1);
  for (n = 0; n <= MAXPKT; n++)
    {
      sp->r[n].set = 0;
    }
  sp->dest.sin_family = AF_INET;
  sp->dest.sin_addr.s_addr = addr;
  sp->data = page;
  sp->dport = port;
  sp->from.sin_family = AF_INET;
  sp->from.sin_port = 0;
  sp->retries = 0;
  sp->from.sin_addr = getlocalip (sp->dest.sin_addr.s_addr);
    
  if (!sp->from.sin_addr.s_addr)
      {
	fprintf (stderr, "Unable to determine Local IP\n");
	exit (EXIT_FAILURE);
      }

  sp->timeout = 0;
  grab_queue_lock();
  sp->next = squeue;
  squeue = sp;
  free_queue_lock();
  if (!caught_idle) {
  	gtk_idle_add(idle_run_queue, NULL);  
	caught_idle =  1;
  }
  if (!caught_timeout) {
  	gtk_timeout_add(100, timeout_queue, NULL);
	caught_timeout = 1;
  }
  return 0;

}
