/* $Id: extprog.c,v 1.11 2003/03/25 00:21:46 d3august Exp $
*/
/*  xtraceroute - graphically show traceroute information.
 *  Copyright (C) 1996-1998  Björn Augustsson 
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/


#include "xt.h"
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

traceroute_state_t traceroute_state;

extern GtkWidget *clist;    // from main.c

/**
 * get_from_extDNS collects data from the external program into a per-site
 * buffer. When the pipe runs out of data, it closes it.
 *
 * It is used as a callback.
 *
 * The fd argument is the same thing as site->extpipe[0].
 */

gint 
get_from_extDNS(site* site, int fd, GdkInputCondition cond)
{
  int count;
  char c;

  if(!(cond & GDK_INPUT_READ))
    {
      DPRINTF("Huh? Not read?\n");
      return FALSE;
    }
  
  count = read(fd, &c, 1);   //FIXME future performance opt, read more than a byte at a time.
  
  /* This can happen, it's a nonblocking pipe */
  if (count == -1)
    { 
      if(errno != EAGAIN)
	perror("read in get_from_extdns"); 

      if(site->extpipe_data[strlen(site->extpipe_data)-1] == '\n' )
	{
          /* Trigger the test to abort if we're resolving localhost. 
             Add that to here? */
          site->extpipe_active = FALSE;
	  return FALSE;
	}
      else
	return TRUE; 
    }
 
  if(count == 0)
    {
      DPRINTF("wow");
      return TRUE;
    }
 
  if(c == 0)
    {
      DPRINTF("wow2");
      return TRUE;
     }
 
  site->extpipe_data[site->extpipe_data_counter++] = c;
  
  if(c == '\n')
    {
      int i = 0;
      site->extpipe_data[site->extpipe_data_counter] = '\0';
      
      /* Parse the data. If the first word is "Nothing", abort now. */

      if(!strncmp(site->extpipe_data, "Nothing", strlen("Nothing")))
	goto escape;
      else if(!strncmp(site->extpipe_data, "Strange host version", 
		       strlen("Strange host version")))
        {
	  printf("Strange version of the \"host\" program found!\n"
		 "I can't use this, see the man page for details.\n");
	  exit(EXIT_FAILURE);
        }

      /*  Otherwise skip it. */
      while(!is_whitespace(site->extpipe_data[i]))
	i++;
      /* Now skip all whitespace after that. */
      while(is_whitespace(site->extpipe_data[i]))
	i++;
      
      if(!strncmp(&site->extpipe_data[i], "LOC", 3))
	{
	  float tmplat, tmplon;
	  i += 3;
	  
	  /* Actually do something with the data. */
	  
	  tmplat = locStrToNum(&site->extpipe_data[i], LATITUDE);
	  if(tmplat < -900)
	    {
	      /* It was an illegal LOC string! */
	      goto escape;
	    }
	  site->lat = tmplat;

	  while(toupper(site->extpipe_data[i]) != 'N' && 
		toupper(site->extpipe_data[i]) != 'S')
	    {
	      i++;
	    }
	  i++;
	  
	  tmplon = locStrToNum(&site->extpipe_data[i], LONGITUDE);
	  if(tmplon < -900)
	    {
	      /* It was an illegal LOC string! */
	      goto escape;
	    }
	  site->lon = tmplon;

	  site->accuracy = ACC_RFC_1876;
	  DPRINTF("Whee! LOC data for: %s\n", site->name);
	  strcpy(site->info, "No further info.");
	  
escape:
	  gdk_input_remove(site->extpipe_tag); 
	  close(fd);
	  site->extpipe[0]           = -1;
	  site->extpipe_tag          = 0;
	  site->extpipe_data_counter = 0;
	  site->extpipe_active       = FALSE;
	  
	  if( !(cond & SYNCH_RESOLV) )
	    {
	      arrangeUnknownSites();
	      makeearth();
	    }
	  return FALSE;
	}
      else if(i==1)
	{
	  /* Not supposed to happen. It's probably a bug if it does. */
	  DPRINTF("Hmmm. An empty line...\n");
          DPRINTF("\tData:\"%s\"\n", site->extpipe_data);
	}
      else   /* Strange data */
	{
	  printf("Didn't get expected in the string from host helper!\n");
	  printf("This is what I got: \"%s\"\n", site->extpipe_data);
	}
      site->extpipe_data_counter = 0;
    }
  return TRUE;
} 


/**
 * callExternalDNS() calls an external program (at this point "host") and
 * sets up a per-site pipe to its' output.
 */

void callExternalDNS(site* site)
{
  if(pipe(site->extpipe) == -1)
    {
      perror("pipe"); 
      exit(EXIT_FAILURE);
    }

  if(fcntl(site->extpipe[0], F_SETFL, O_NONBLOCK) != 0)
    {
      perror("O_NONBLOCKifying pipe");
      exit(EXIT_FAILURE);
    }

  /* Make a distinction here host/localhost here? */

  spinner_ref("host");

  site->extpipe_pid = fork();

  switch (site->extpipe_pid)
    {
    case -1:
      /* Fork failed! */
      perror("fork");
      exit(EXIT_FAILURE);
      break;
    case 0:      
      /* We're going to be the exernal program! */
      
      close(site->extpipe[0]);    /* close read end of pipe               */
      //      close(STDERR_FILENO);       /* close stderr. I'm not interested.    */
      dup2(site->extpipe[1], STDOUT_FILENO); /* make 1 same as write-to end of pipe  */
      dup2(site->extpipe[1], STDERR_FILENO); /* make 2 same as write-to end of pipe  */
      close(site->extpipe[1]);    /* close excess fildes                  */

      execlp(DATADIR "/xtraceroute-resolve-location.sh", 
             "xtraceroute-resolve-location.sh", site->name, NULL); 
      perror("exec");       /* still around?  exec failed           */
      _exit(EXIT_FAILURE);  /* no flush                             */ 
      break;
    default:
      /* We're the parent */
      site->extpipe_active = TRUE;
      site->extpipe_tag = gdk_input_add(site->extpipe[0], GDK_INPUT_READ,
					(GdkInputFunction)get_from_extDNS, site);
    }
}


/*********************************************************************/


/**
 * Only called from get_from_traceroute() when it gets a whole line.
 */

static void 
parse_row_from_traceroute(char *input)
{
  int i;
  int no;
  int last;
  char *clistrow[3];

  //  DPRINTF("O");

  /* This is a SGI compatibility thing; On SGI "traceroute" prints
     the first line "traceroute to test.com (0.0.0.0), 40 hops max, 
     40 byte packets" to stdout instad of stderr (which most other
     machines does). FWIW, I actually think SGI's behavior is saner 
     (stderr is for errors!), but... */
  /* On the other hand, SGI-traceroute prints errors as well to 
     stdout... Oh well... */
  
  if(!strncasecmp("traceroute",input, 10))
    {
      DPRINTF("Got traceroute header!\n");

      if(isin("unknown host", input))
	{
	  char tmp[200];
	  DPRINTF("unknown host!\n");
	  gdk_input_remove(traceroute_state.tag);
	  close(traceroute_state.fd[0]);
	  traceroute_state.fd[0] = -1;
	  strcpy(tmp, _("xtraceroute: unknown host "));
	  strcat(tmp, user_settings->current_target);
	  tell_user(tmp);
	  traceroute_state.scanning = 0;
	}
      return;
    }
  else if(strlen(input)< 10)
    {
      /* All we got was a couple of spaces, from a "XXX * * *"-row, that's
	 had the stars stripped off */

      sscanf(input,"%d",&no);
      no--;
      strcpy(sites[no].name, "No response");
      strcpy(sites[no].ip  , "No response");
      strcpy(sites[no].info, "There were no response from this machine.");
      sites[no].lat = sites[no-1].lat; // FIXME:
      sites[no].lon = sites[no-1].lon; //potential trouble here if no == 0.
      sites[no].time = 0;
      sites[no].accuracy = ACC_NONE;
      sites[no].draw = 1;
    }
  else /* All is well, do the normal stuff. */
    {
      i = sscanf(input,"%d",&no);
      
      /* Skip past the first number. */
      while(!is_whitespace(input[i]))
	i++;
      no--;
      sscanf(input+i,"%s %*c%s %d",sites[no].name,sites[no].ip,&sites[no].time);
      
      /* Get rid of the trailing ')' from the IP... */
      sites[no].ip[ strlen(sites[no].ip) - 1 ] = '\0';

      resolve_by_id(no);
      makeearth();       /* FIXME maybe this should'nt be here. */
    }

  clistrow[0] = (char *)malloc(10);
  clistrow[1] = (char *)malloc(sizeof(sites[no].name));
  clistrow[2] = (char *)malloc(sizeof(sites[no].ip));
  
  sprintf(clistrow[0],"%d",no);
  sprintf(clistrow[1],"%s",sites[no].name);
  sprintf(clistrow[2],"%s",sites[no].ip);
  
  /* Add an entry to the list */
  last = gtk_clist_append(GTK_CLIST(clist), clistrow);
  
  /* Free the clistrow here */
  free(clistrow[0]);
  free(clistrow[1]);
  free(clistrow[2]);
  /* The above SHOULD be done! But there's an error here somewhere.
     Doing that causes malloc in glibc-linux to dump sometimes. 
     Investigating... This just has to be a glibc bug... Efence finds
     nothing... (This comment kept in case it shows up again.) */
  
  /* Scroll the clist so the new entry is visible */
  gtk_clist_moveto(GTK_CLIST(clist), last, 1, 1.0, 0.5);
  
  if(no >= MAX_SITES-1)
    {
      /*
	printf("Too many hops! Change the MAX_SITES value in xt.h,\n");
	printf("recompile, and mail August and tell him about it!\n");
      */
      close(traceroute_state.fd[0]);
      traceroute_state.fd[0]    = -1;
      traceroute_state.scanning = 0;
      gdk_input_remove(traceroute_state.tag);
      spinner_unref("traceroute");
      makeearth();
    }
  return;
}

/**
 * get_from_traceroute collects data from the external traceroute 
 * program into a buffer. When it has a whole line, it calls
 * parse_row_from_traceroute().
 *
 * When the pipe runs out of data, it gets closed.
 *
 * It is used as a callback.
 */

gint 
get_from_traceroute(char* nope, int fd, GdkInputCondition cond)
{
  //    DPRINTF(".");

  if(traceroute_state.scanning == 0)
    {
      /* FIX for API brokenness. This callback should unregister if it 
	 returns false, as all other callbacks (in gtk at least) do. */
      gdk_input_remove(traceroute_state.tag);      

      return FALSE;
    }
  if(cond & GDK_INPUT_READ)
    {
      int count;
      char c;

      count = read(fd, &c, 1);   //FIXME future performance opt, read more than a byte at a time.
      
      if(c == -1)  printf("Hmmm.. -1 from read.\n");

      if(c == 0)  printf("Hmmm.. This shouldn't happen.\n");

      if(count == 0)          /* Other end of pipe closed. */
	{
	  close(traceroute_state.fd[0]); // Hmmm... what if this is stdin?
	  traceroute_state.fd[0] = -1;
	  traceroute_state.scanning = 0;
	  makeearth();
	  spinner_unref("traceroute");
	  gdk_input_remove(traceroute_state.tag); /* Fix for bad API. See above. */
	  return FALSE;
	}

      if(c == '*')
	return TRUE;
      
      traceroute_state.row_so_far[traceroute_state.buffer_counter++] = c;
      
      if(c == '\n')
	{
	  traceroute_state.row_so_far[traceroute_state.buffer_counter] = '\0';
	  parse_row_from_traceroute(traceroute_state.row_so_far);
	  traceroute_state.buffer_counter = 0;
	}
    }
  return TRUE;
} 


/** 
 * Calls traceroute if the argument is 0, else reads debug input from stdin. 
 */

void 
calltrace(void)
{
  /*   I don't know if there are several versions of traceroute out there,
       but hopefully they can be configured to deliver what I want with this.
       Otherwise, this function must be hacked some more.  */
  
  char tracepgm[]   = TRACEPGM;
  
  DPRINTF("calltrace\n");

  if(!strncmp(user_settings->current_target, "-", strlen("-")))
    {
      traceroute_state.fd[0] = STDIN_FILENO;
      traceroute_state.scanning = 1;
      spinner_ref("traceroute");
      traceroute_state.tag = gdk_input_add(traceroute_state.fd[0], GDK_INPUT_READ,
					   (GdkInputFunction)get_from_traceroute, NULL);
      return;
    }
  
  if (pipe(traceroute_state.fd) == -1)
    {
      perror("traceroute pipe"); 
      exit(EXIT_FAILURE);
    }
  
  traceroute_state.pid = fork();
  
  switch (traceroute_state.pid)
    {
    case -1:
      /* Fork failed! */
      perror("fork");
      exit(EXIT_FAILURE);
      break;
    case 0:      
      /* We're going to be the exernal program! */
      
      close(traceroute_state.fd[0]);    /* close read end of pipe               */
      dup2(traceroute_state.fd[1], STDOUT_FILENO);   /* make 1 same as write-to end of pipe  */
      dup2(traceroute_state.fd[1], STDERR_FILENO);   /* make 2 same as write-to end of pipe  */
      close(traceroute_state.fd[1]);    /* close excess fildes                  */

      /* FIXME I'd _like_ to use the MAX_SITES define instead of "40"
	 here, but that doesn't work since it's in a string. 
	 Suggestions? */ 
      execlp(tracepgm, "traceroute", 
	     "-q", "2",  /* Two queries (default is 3.) */
	     "-w", "20", /* Time before timing out (default is 5) */ 
	     "-m", "40", /* Max hops, default is 30, we need more. */
	     user_settings->current_target, NULL);
      perror("exec");       /* still around?  exec failed           */
      _exit(EXIT_FAILURE);  /* no flush                             */ 
      break;
    default:
      /* We're the parent */
      traceroute_state.scanning = 1;
      spinner_ref("traceroute");
      traceroute_state.tag = gdk_input_add(traceroute_state.fd[0], GDK_INPUT_READ,
					   (GdkInputFunction)get_from_traceroute, NULL);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1