/** Code by Chris Palmer                    last modified 12/15/1998  **/

/** Sniffer.c is a small network sniffing device designed primarily to **/
/** show who is using the most bandwidth on a TCP network.  It **/
/** is set up by having someone with root access type: **/
/** tcpdump -qt | [path]Sniffer **/
/** where [path] is the applicable path to the Sniffer utility. **/
/** This program is designed to be run for short periods of time **/
/** (such as a few minutes), it is not optimized for too keep a **/
/** permanent tally of the top users, although in theory it should **/
/** be able to do so.  **/
/** Sniffer.c works by taking the output of tcpdump and parsing it **/
/** (in get_and_...) to get the sender and receiver of each packet. **/
/** These are sent to addsender, which uses hashme to find a place **/
/** to insert the sender in an array of ITERATIONS rows.  addreceiver **/
/** is then called to place the receiver in a linked list hanging **/
/** off of the main array.  Every ADDCOUNT additions triggers a flush **/
/** of this array, which decrements the .TimeToLive of each entry. **/
/** When an address hasn't sent in a while, .TimeToLive reaches 0 and **/
/** the .count is decremented until when .count == 0 that element is **/
/** removed.  Every FLUSHCOUNT * ADDCOUNT additions triggers **/
/** updatefile to re-write SENDFILENAME (an .html file) with the **/
/** most recent data.  When a .count exceeds 30,000 the entire **/
/** array is wiped clean as a safety and accuracy feature.  Unless **/
/** your network is pretty busy or you leave Sniffer.c running a for **/
/** a long time, this should not be implemented. **/

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

#define SENDFILENAME output.html /** The file to write the output to **/
#define ITERATIONS 1049 /** How big should our array be?            **/
			/** Note that this should be a prime number **/
			/** for the functions hashme to work well   **/
#define ADDCOUNT 200        /** How often should we flush the array?  **/
#define FLUSHCOUNT 5    /** How often should we update the .html    **/
                        /** file?  (FLUSCHCOUNT * ADDCOUNT)         **/
#define NUMBERTODISPLAY 20  /** The number of .count values to        **/
                            /** display addresses for.                **/

typedef struct recv_tag     /**  This is the linked list **/
{                           /**  that hangs off of the   **/
  struct recv_tag *prev;    /**  "big" array and holds   **/
  int count;                /**  receiver's addresses    **/
  char the_address[253];    /**  and their frequency.    **/
  struct recv_tag *next;    /**  It is doubly linked.    **/
} recv_list;

typedef struct send_array_tag  /**  This is the "big" array    **/
{                              /**  composed of a counter and  **/
  int count;                   /**  an array for the address   **/
  char the_address[253];       /**  and a pointer to header    **/
  recv_list *header;           /**  of the receive list plus   **/
  int TimeToLive;              /**  the time until it's killed **/
} send_array;



/** clear the main array **/
void clear_array (send_array arr[]);

/** the main loop, it gets the addresses and hands them to addsender **/
void get_and_give_addresses(send_array arr[]);

/** add the senders address to the main array **/
void addsender(char send[], char recv[], send_array arr[]);

/** add the reciever's address as a linked list off of the main array **/
void addreceiver(recv_list *, char addres[]);

/** hashme generates a hashcode for the input **/
int hashme (char *);

/** updates TimeToLive and kills those whose time are up **/
void clean_up_array(send_array arr[]);

/** creates the .html file that can be viewed see the **/
/** top x senders.  Just re-creates the entire file.  **/
void update_file(send_array arr[]);

/** adds the list of receivers as a pull-down menu **/
void view_receiver(FILE *,recv_list *);

/** clears the array and frees the memory when the .count gets too big **/
void RestartArray(send_array arr[]);

void main() 
{
  send_array sendarray[ITERATIONS];

  clear_array(sendarray);             /** first we clear the array         **/
  get_and_give_addresses(sendarray);  /** then we run the main procedure   **/
  /*  update_file(sendarray); */

  return;
} /** end main **/



void clear_array (send_array clear_me[])
     /**  this will loop through each row in the array (represented   **/
     /**  by tmpcount) and set all the elements to 0.  **/
{
  int tmpcount;
  
  for (tmpcount=0;tmpcount<ITERATIONS;tmpcount++)
    {
      clear_me[tmpcount].count = 0;
      clear_me[tmpcount].the_address[0] = ' ';
      clear_me[tmpcount].TimeToLive = 0;
    }
  return;
} /** end of procedure clear_array **/



void get_and_give_addresses(send_array *sendarr)
     /** This loop repeats until the end of the file, which **/
     /** is potentially forever, which is fine.  It grabs **/
     /** the sender and receiver addresses (send[] & **/
     /** recv[]) and puts them in the array.  It also **/
     /** flushes the array every FLUSHCOUNT adds. **/
{
  int tmpcount, total_lines, countLoop, countFlush;
  char tmpchar, send[255], recv[255];
    

  /**  Main while loop of get_addresses  **/
  tmpchar = 'x';
  total_lines=0;
  countLoop=0;
  countFlush=0;
  while (tmpchar != EOF)
    {
      tmpcount = -1;

      /** The following loop gets the sender's address **/
      while (((tmpchar=getchar()) != ' ') && (tmpcount < 253))
	{
	  if (tmpchar == EOF) break;
	  tmpcount++;
	  send[tmpcount] = tmpchar;
	}

      /** gets rid of the port number, if it's there **/
      if ((send[0] < '0') || (send[0] > '9'))
	{
	  while ((send[tmpcount] == '1') || (send[tmpcount] == '2') || (send[tmpcount] == '3') || (send[tmpcount] == '4') || (send[tmpcount]  == '5') || (send[tmpcount] == '6') || (send[tmpcount] == '7') || (send[tmpcount] == '8') || (send[tmpcount] == '9') || (send[tmpcount] == '0') || (send[tmpcount] == '.'))
	    {
	      send[tmpcount] = '-';
	      tmpcount--;
	    }
	}

      /** Put a null at the end of the address **/
      if (tmpchar == EOF) break;
      tmpcount++;
      send[tmpcount]='\0';

      /** Put an asterix if the URL is > 253 characters **/
      if (tmpcount > 253)
	send[tmpcount]='*';
      
      /** Move to the next (receiver's) address and reset tmpcount **/
      while (((tmpchar=getchar()) == ' ') || (tmpchar == '>'));
      tmpcount = 0;
      recv[tmpcount] = tmpchar;

      /** The following loop gets the receiver's address **/
      while (((tmpchar=getchar()) != ':') && (tmpcount < 253))
	{
	  tmpcount++;
	  recv[tmpcount] = tmpchar;
	}

      /** Kill the port numbers, if they exist **/
      if ((recv[0] < '0') || (recv[0] > '9')) {
	while ((recv[tmpcount] == '1') || (recv[tmpcount] == '2') || (recv[tmpcount] == '3') || (recv[tmpcount] == '4') || (recv[tmpcount] == '5') || (recv[tmpcount] == '6') || (recv[tmpcount] == '7') || (recv[tmpcount] == '8') || (recv[tmpcount] == '9') || (recv[tmpcount] == '0') || (recv[tmpcount] == '.'))
	  {
	    recv[tmpcount] = '-';
	    tmpcount--;
	  }
      }
      /** Put a null at the end of the address **/
      tmpcount++;
      recv[tmpcount]='\0';
      /** Put an asterix if the URL is > 253 characters **/
      if (tmpcount > 253)
	recv[tmpcount]='*';
      
      /** Move to the next line **/
      while ((tmpchar=getchar()) != '\n' && (tmpchar != EOF));
	
      addsender(send, recv, sendarr);   /** insert the address! **/
      countLoop++;
      if (countLoop >= ADDCOUNT) 
	/** clean the array and increment countFlush **/
	{
	  clean_up_array(sendarr);
	  countLoop = 0;
	  countFlush++;
	} /** end "if (countLoop>=ADDCOUNT)" **/
      if (countFlush >= FLUSHCOUNT)
	/** open the .html file, write to it and close it **/
	{
	  update_file(sendarr);
	  countFlush = 0;
	} /** end "if (countFlush==FLUSHCOUNT)" **/
      total_lines++;
    }  /** end of main WHILE loop **/

  return;
}  /** end of get_and_give_addresses **/



void addsender(char inpt[], char listin[], send_array bigguy[])
     /** This adds a sender to the main array.  It calls hashme **/
     /** to generate a hash code, and if the space is empty **/
     /** it inserts the address and adds the receiver.  If it's **/
     /** not empty, it checks for a match, and then trys to **/
     /** hash the doubled value of its address, and then just **/
     /** starts looping around the array until it finds an **/
     /** empty spot or a match.  It also checks for infinite **/
     /** loops via WeLooped, and flushes if there's no space. **/
{
  int hashcode, foundspot, tmpcount, infinite_loop, WeLooped, TimeToClear;
	
  foundspot = 0;
  WeLooped = 0;
  TimeToClear = 0;
  infinite_loop= (ITERATIONS + 1);  /** used in hashing later **/
  hashcode = hashme(inpt);

  while (foundspot == 0)
    {
      if (isspace(bigguy[hashcode].the_address[0]))
	{  /** if the entry is blank, insert the address **/
	  for(tmpcount=0;tmpcount<255;tmpcount++)
	    bigguy[hashcode].the_address[tmpcount] = inpt[tmpcount];
	  bigguy[hashcode].count = 1;
	  bigguy[hashcode].TimeToLive = 1;
	  if ((bigguy[hashcode].header=((recv_list *) malloc(sizeof(recv_list))))!='\0')
	    {  /** if the malloc worked, create the header **/
	      bigguy[hashcode].header->count=1;
	      bigguy[hashcode].header->next='\0';
	      bigguy[hashcode].header->prev='\0';
	      for(tmpcount=0;tmpcount<255;tmpcount++)
		bigguy[hashcode].header->the_address[tmpcount]=listin[tmpcount];
	    }
	  else
	    {  /** if the malloc doesn't work, we're outta here! **/
	      printf ("OUT OF MEMORY!!!!!\n\n");
	      printf ("NOW EXITING!!\n\n");
	      exit(1);
	    }
	  
	  foundspot = 1;
	}
      else  /** if the space isn't empty  **/
	{
	  if (*bigguy[hashcode].the_address == *inpt)
	    { /** if the address has already been entered **/
	      bigguy[hashcode].count++;
	      if (bigguy[hashcode].count > 30000) RestartArray(bigguy);
	      bigguy[hashcode].TimeToLive = 1;
	      addreceiver(bigguy[hashcode].header,listin);
	      foundspot = 1;
	    }
	  else
	    { /** doh! collision! **/
	      if (infinite_loop==(ITERATIONS + 1))
		{
		  infinite_loop=hashcode;
		  hashcode = ((hashcode + strlen(inpt)) % ITERATIONS);
		}
	      else
		{
		  if (infinite_loop != hashcode)
		    {
		      if (infinite_loop == (ITERATIONS + 2))
			{
			  hashcode++;
			  if (hashcode >= ITERATIONS)
			    {
			      hashcode=0;
			      infinite_loop = hashme(inpt);
			      WeLooped=1;
			    }
			}
		      else
			{
			  hashcode = ((hashcode + strlen(inpt)) % ITERATIONS);
			}
		    }
		  else
		    {
		      hashcode = hashcode++;
		      if (hashcode >= ITERATIONS) hashcode = 0;
		      infinite_loop=(ITERATIONS + 2);
		      if (WeLooped == 1)
			/** If we've looped around the array and there's **/
			/** no open spots, flush it and try again **/
			{
			  clean_up_array(bigguy);
			  WeLooped = 0;
			}
		    }
		}
	    }
	}  /** end of collision scenerio **/
    } /** end of while (foundspot == 0) **/
  
  return;
} /** end of procedure addsender **/



void addreceiver(recv_list *tmphead, char addres[])
     /** This will add a receiver.  If it's the first one, a node **/
     /** is hung off of .header, otherwise it either increments **/
     /** the counter for the matching address or adds a new node **/
     /** at the end.  Note that the receivers are not kept in **/
     /** order, except as they come in. **/
{
  int tmpcount, testdone;
  recv_list *tmpnode, *tmpnode2;

  testdone=0;
  tmpnode=tmphead;
  if (*tmphead->the_address==*addres)
    {
      tmpnode->count++;
      if (tmpnode->count > 31000) tmpnode->count = 0;
      testdone=1;

    }
  else  /** if the header doesn't equal the new address **/
    {
      while (tmpnode->next!='\0')
	{
	  tmpnode=tmpnode->next;
	  if (*tmpnode->the_address==*addres)
	    {
	      if (tmpnode->count > 31000) tmpnode->count = 0;
	      tmpnode->count++;
	      testdone=1;
	    }
	}
      if (testdone==0)
	{
	  if ((tmpnode2 = ((recv_list *) malloc(sizeof(recv_list))))!='\0')
	    {
	      tmpnode->next = tmpnode2;
	      tmpnode2->prev = tmpnode;
	      tmpnode2->next = '\0';
	      tmpnode2->count = 1;
	      for (tmpcount=0;tmpcount<253;tmpcount++)
		tmpnode2->the_address[tmpcount] = addres[tmpcount];
	    }
	  else
	    { /** shit, out of memory **/
	      printf ("error:  out of memory\n");
	      printf ("now exiting\n\n");
	      exit(1);
	    }
	}  /** end of if adding a new node **/
    }  /** end of if the header != new address **/

  return;
} /** end of addreceiver procedure **/



int hashme(char *tohash)
     /** this takes in an array and generates a hash code by MOD'ing **/
     /** the (hopefully prime) ITERATIONS to the value of the chars  **/
{
  int tmpcount, hashvalue;
  
  tmpcount = 0;
  hashvalue = 0;
  while (tohash[tmpcount] != '\n')
    {
      hashvalue = hashvalue + atoi(&tohash[tmpcount]);
      tmpcount++;
    }
  hashvalue = hashvalue % ITERATIONS;
  return(hashvalue);
} /** end of hashcode function **/



void clean_up_array(send_array main_array[])
     /**  This procedure cycles through each element in the main array **/
     /**  via counter and decrements .TimeToLive  If .TimeToLive==-1, **/
     /**  it doesn't touch it [already empty], but if it's 0 then **/
     /**  it decrements .count, and if that's 0 then it clears the **/
     /**  entire element.  This will get rid of old, inactive elements. **/
{
  int counter;
  recv_list *tmpnode, *tmpnode2;

  for (counter = 0; counter <= ITERATIONS; counter++)
    {
      if (main_array[counter].count > -1)
	{ /** if the element isn't already empty **/
	  if (main_array[counter].TimeToLive <= 0)
	    { /** it's time to start reducing .count or clear it all **/
	      main_array[counter].count--;
	      if (main_array[counter].count == 0)
		{ /** it's time to clear this one **/
		  main_array[counter].the_address[0] = ' ';
		  tmpnode=main_array[counter].header;
		  while (tmpnode->next!='\0') tmpnode = tmpnode->next;
		  /** we're at the end of the list, climb back up & kill **/
		  while (tmpnode->prev!='\0')
		    {
		      tmpnode2 = tmpnode->prev;
		      free(tmpnode);
		      tmpnode = tmpnode2;
		    }
		  tmpnode->next='\0';
		} /** end of "if (main_array[counter].count == 0)" **/
	    } /** end of "if (main_array[counter].TimeToLive <= 0)" **/
	  else main_array[counter].TimeToLive--;
	} /** end of "if (main_array[counter].count > -1)" **/
    } /** end of "for (counter=0;counter<=ITERATIONS;counter++)" **/

  return;
} /** end of clean_up_array procedure **/




void update_file(send_array main_array[])
     /** Find the biggest value of .count, and then show **/
     /** it and the 19 next-biggest values.  The highest value **/
     /** of the counter so far encountered is held in tmpval, **/
     /** while tmpadd holds the place of that value.  It then **/
     /** loops the the main array NUMBERTODISPLAY times, each **/
     /** time displaying those value(s) equal to tmpval and **/
     /** then decrementing tmpval. **/
{
  int tmpcount, tmpcount2, tmpval, tmpadd, count_total;
  FILE *output;

  tmpval=0;
  count_total=0;

  /**  Try to open the file.  Die if it doesn't work.  **/
  if((output = fopen("SENDFILENAME", "w")) == NULL)
    {
      printf("Can't open file!!!\n");
      exit(1);
    }

  /** Ok, here's the top of the page:  **/
  fprintf (output, "Content-type: text/html\n\n");
  fprintf (output, "<HTML>\n");
  fprintf (output,"<HEAD><TITLE>Sniffer.c Output</TITLE></HEAD>\n");
  fprintf (output,"<BODY BGCOLOR=white>\n");
  fprintf (output,"<CENTER><H2>Sniffer.c Output</H2></CENTER>");
  fprintf (output,"<BR>\n");
  fprintf (output,"This page was generated by sniffer.c<BR>\n");
  fprintf (output,"<BR> Note that the <B>Count</B> is how many times the address has activated recently, and the receivers are all that have been sent to for as long as that sender has been on the active list.  Therefore the receiver list may be higher than the <B>Count</B> <BR>\n");

  /** The top of the table **/
  fprintf (output,"<FORM>\n");
  fprintf (output,"<CENTER>\n");
  fprintf (output,"<TABLE BORDER=1 BGCOLOR=Silver BORDERCOLOR=Black CELLPADDING=6 CELLSPACING=2>");
  fprintf (output,"<TR ALIGN=CENTER> <TD COLSPAN=3><B>List of Top Senders' 
Addresses and Their Frequency</B></TD></TR>\n");
  fprintf (output,"<TR ALIGN=LEFT> <TD><B>Addresses</B></TD><TD><B>Count</B></TD><TD><B>Receivers</B></TD></TR>\n");

  for(tmpcount=0;(tmpcount <= ITERATIONS);tmpcount++)
    {
      if (main_array[tmpcount].count > tmpval)   /** if the count is higher than the tmp **/
	{
	  tmpval=main_array[tmpcount].count;   /** tmp=count and it remembers where **/
	  tmpadd=tmpcount;                   /** that address was.                **/
	}
    }

  tmpcount = NUMBERTODISPLAY;
  while(tmpcount > 0)
    {  /** Cycle through the big array until NUMBERTODISPAY elements are displayed  **/
      for(tmpcount2=0;tmpcount2<ITERATIONS;tmpcount2++) /** the respective add's **/
	{
	  if ((main_array[tmpcount2].count==tmpval)&&(main_array[tmpcount2].count > -1))
	    {
	      fprintf (output,"<TR ALIGN=LEFT><TD>%s</TD><TD>%d</%D>",main_array[tmpcount2].the_address,main_array[tmpcount2].count);
	      if (main_array[tmpcount2].count > 0)
		{
		  view_receiver(output,main_array[tmpcount2].header);
		} else {
		  fprintf (output,"</TR>");
		}
	      count_total+=tmpval;
	      tmpcount--;
	    }
	}  /** end of cycling through the array looking for matches to tmpcount **/
      tmpval--;
    }  /** end of cycling through the array tmpval times **/
	
  fprintf (output,"<TR><TD><B>The total number of addresses:</B></TD><TD>%d</TD><TD><CENTER>---</CENTER></TD>\n",count_total);
  fprintf (output,"</TABLE></CENTER>\n");

  /** end of the file **/
  fprintf (output,"<BR><BR>\n");
  fprintf (output,"<HR WIDTH=\"85%%\">\n");
  fprintf (output,"<CENTER><A HREF=\"../index.html\">title</A><BR>\n");
  fprintf (output,"Copyright 1998, <A HREF=\"mailto:palmech@earlham.edu\">palmech@earlham.edu</A><BR>\n");
  fprintf (output,"</BODY>\n");
  fprintf (output,"</HTML>\n\n");

  fclose(output);

  return;
} /** end of procedure update_file **/



void view_receiver(FILE *outfile,recv_list *toplist)
     /** displays a pull-down menu with the recievers **/
     /** displayed in sorted order by .count, the highest **/
     /** of which is stored in tmpval **/
{
  int tmpcount, tmpval, tot, mybool;
  char tmpaddr[256];
  recv_list *tmpnode;

  tmpval=0;
  tot=1;
  mybool=0;
  tmpnode=toplist;

  while(mybool==0)
    /** cycles through to find the biggest .count **/
    {
      if (tmpnode->count > tmpval)
	{
	  tmpval=tmpnode->count;
	  for (tmpcount=0;tmpcount<253;tmpcount++)
	    {
	      tmpaddr[tmpcount]=toplist->the_address[tmpcount];
	    }
	}
      if (tmpnode->next=='\0')
	{
	  mybool=1;
	}
      else
	{
	  tmpnode=tmpnode->next;
	}
    }
  
  /* */  
  fprintf (outfile,"<TD><P><SELECT NAME=\"recv list\">");

  if (toplist->next!='\0')
    {
      /*      fprintf (outfile,"<TD><P><SELECT NAME=\"recv list\">"); */
      for (tmpval;tmpval>0;tmpval--)
	{
	  tmpnode=toplist;
	  mybool=0;
	  while (mybool==0)
	    {
	      if (tmpnode->count == tmpval)
		{
		  fprintf (outfile,"<OPTION = \"%d\">%d %s</OPTION>", tot, tmpval, tmpnode->the_address);
		  tot++;
		}
	      if (tmpnode->next=='\0')
		{
		  mybool=1;
		}
	      else
		{
		  tmpnode=(tmpnode->next);
		}
	    }  /** End of sorting through each node **/
	}  /** End of counting down from the highest count **/

      /*     fprintf (outfile,"</SELECT></P></TD></TR>\n"); */

    }
  else {
fprintf (outfile,"<OPTION = \"%d\">1 %s</OPTION>\n",tmpval, tmpnode->the_address);
  }
  /* */
fprintf (outfile,"</SELECT></P></TD></TR>\n");
  return;
}  /** end view_receiver **/

void RestartArray (send_array clearMe[])
     /** When the numbers start to get really large, the **/
     /** entire main array is cleared.  Sort of a safeguard **/
     /** especially since Sniffer.c isn't really designed **/
     /** to keep infinite tallies **/
{
  int counter;
  recv_list *tmpnode, *tmpnode2;

  for (counter = 0; counter <= ITERATIONS; counter++)
    {
      clearMe[counter].the_address[0] = ' ';
      tmpnode = clearMe[counter].header;
      while (tmpnode->next!='\0') tmpnode = tmpnode->next;
      /** we're at the end of the list, climb back up & kill **/
      while (tmpnode->prev!='\0')
	{
	  tmpnode2 = tmpnode->prev;
	  free(tmpnode);
	  tmpnode = tmpnode2;
	}
      tmpnode->next='\0';
      clearMe[counter].count = 0;
      clearMe[counter].TimeToLive = 0;
    } /** end of "for (counter=0;counter<=ITERATIONS;counter++)" **/
  
  return;
} /** end of procedure RestartArray **/









