/*			Generic Communication Code		HTTCP.c
**			==========================
**
**	This code is in common between client and server sides.
**
**	16 Jan 92  TBL	Fix strtol() undefined on CMU Mach.
**	25 Jun 92  JFG  Added DECNET option through TCP socket emulation.
*/

#define HTTCP_WHOI_VERSION  "HTTCP_whoi  version 1.4b 5 Jul 2005"
/*
	 5 Jul 05  WJS
	    include unistd.h because of warnings on gb6.  Include it
	  unconditionally because we're lazy and also don't know what's
	  going on on other OSes.  In any case, this line should be in
	  either tcp.h or HTUtils.h, but that would mean maintenance of
	  another altered HTxxx file.  See "lazy", above
	23 Apr 04  WJS
	    Change name of global version string so it won't show up
	  in "grep version" output.
	 3 Dec 03  WJS
	    Replace sys_errlist/sys_nerr w/strerror.  Old stuff
	  caused trouble w/linuxes		
	    Add version defn + non-executed code to try to get it
	  into binary image.  If we call HTTCP from jgofs 1.5 release
	  1.0, then there was the Cygwin emulator (say, 1.1), Bob's mod of
	  Cygwin for his linux box (say 1.2 = 1.1+), and Cyndy's mod for
	  her linux box (say 1.3 = 1.0+)
	13 Oct 98  WJS  Cygwin emulator.  Looks for CYGWIN defn
	    1) Wants some SVR4 code but not its include file
	    2) Cygwin 'select' call does not seem to wait until EISCONN
*/


#include "HTUtils.h"
#include "HTParse.h"
#include "HTAlert.h"
#include "HTAccess.h"
#include "tcp.h"		/* Defines SHORT_NAMES if necessary */
#include "unistd.h"		/* For gb6 (Solaris 2.9. See comments, top */
#ifdef SHORT_NAMES
#define HTInetStatus		HTInStat
#define HTInetString 		HTInStri
#define HTParseInet		HTPaInet
#endif

#ifdef __STDC__
#include <stdlib.h>
#endif

/* #define TRACE 1 */

#if defined(SVR4) && !defined(SCO)
#include <sys/filio.h>
#endif

/* Apparently needed for AIX 3.2. */
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif

/*	Module-Wide variables
*/

PRIVATE char *hostname=0;		/* The name of this host */
char httcp_whoi_vers[] = HTTCP_WHOI_VERSION;

/*	PUBLIC VARIABLES
*/

/* PUBLIC SockA HTHostAddress; */	/* The internet address of the host */
					/* Valid after call to HTHostName() */

/*	Encode INET status (as in sys/errno.h)			  inet_status()
**	------------------
**
** On entry,
**	where		gives a description of what caused the error
**	global errno	gives the error number in the unix way.
**
** On return,
**	returns		a negative status in the unix way.
*/
#ifndef errno
extern int errno;
#endif /* errno */

/*	Report Internet Error
**	---------------------
*/
#ifdef __STDC__
PUBLIC int HTInetStatus(char *where)
#else
PUBLIC int HTInetStatus(where)
    char    *where;
#endif
{
    char    *s;
    if ( (s = strerror(errno)) == NULL  ) s = "unknown error";
    CTRACE(tfp, "TCP: Error %d in `errno' after call to %s() failed.\n\t%s\n",
	    errno,  where,  s);

    return -1;
}


/*	Parse a cardinal value				       parse_cardinal()
**	----------------------
**
** On entry,
**	*pp	    points to first character to be interpreted, terminated by
**		    non 0:9 character.
**	*pstatus    points to status already valid
**	maxvalue    gives the largest allowable value.
**
** On exit,
**	*pp	    points to first unread character
**	*pstatus    points to status updated iff bad
*/

PUBLIC unsigned int HTCardinal ARGS3
	(int *,		pstatus,
	char **,	pp,
	unsigned int,	max_value)
{
    int   n;
    if ( (**pp<'0') || (**pp>'9')) {	    /* Null string is error */
	*pstatus = -3;  /* No number where one expeceted */
	return 0;
    }

    n=0;
    while ((**pp>='0') && (**pp<='9')) n = n*10 + *((*pp)++) - '0';

    if (n>max_value) {
	*pstatus = -4;  /* Cardinal outside range */
	return 0;
    }

    return n;
}


/*	Produce a string for an Internet address
**	----------------------------------------
**
** On exit,
**	returns	a pointer to a static string which must be copied if
**		it is to be kept.
*/

PUBLIC CONST char * HTInetString ARGS1(SockA*,sin)
{
    static char string[16];
    sprintf(string, "%d.%d.%d.%d",
	    (int)*((unsigned char *)(&sin->sin_addr)+0),
	    (int)*((unsigned char *)(&sin->sin_addr)+1),
	    (int)*((unsigned char *)(&sin->sin_addr)+2),
	    (int)*((unsigned char *)(&sin->sin_addr)+3));
    return string;
}


/*	Parse a network node address and port
**	-------------------------------------
**
** On entry,
**	str	points to a string with a node name or number,
**		with optional trailing colon and port number.
**	sin	points to the binary internet or decnet address field.
**
** On exit,
**	*sin	is filled in. If no port is specified in str, that
**		field is left unchanged in *sin.
*/
PUBLIC int HTParseInet ARGS2(SockA *,sin, CONST char *,str)
{
  char *port;
  char host[256];
  struct hostent  *phost;	/* Pointer to host - See netdb.h */
  int numeric_addr;
  char *tmp;
  
  static char *cached_host = NULL;
  static char *cached_phost_h_addr = NULL;
  static int cached_phost_h_length = 0;

  strcpy(host, str);		/* Take a copy we can mutilate */
  
  /* Parse port number if present */    
  if (port=strchr(host, ':')) 
    {
      *port++ = 0;		/* Chop off port */
      if (port[0]>='0' && port[0]<='9') 
        {
          sin->sin_port = htons(atol(port));
	}
    }
  
  /* Parse host number if present. */  
  numeric_addr = 1;
  for (tmp = host; *tmp; tmp++)
    {
      /* If there's a non-numeric... */
      if ((*tmp < '0' || *tmp > '9') && *tmp != '.')
        {
          numeric_addr = 0;
          goto found_non_numeric_or_done;
        }
    }
  
 found_non_numeric_or_done:
  if (numeric_addr) 
    {   /* Numeric node address: */
      sin->sin_addr.s_addr = inet_addr(host); /* See arpa/inet.h */
    } 
  else 
    {		    /* Alphanumeric node name: */
      if (cached_host && (strcmp (cached_host, host) == 0))
        {
#if 0
          fprintf (stderr, "=-= Matched '%s' and '%s', using cached_phost.\n",
                   cached_host, host);
#endif
          memcpy(&sin->sin_addr, cached_phost_h_addr, cached_phost_h_length);
        }
      else
        {
#if 0
          fprintf (stderr, "=+= Fetching on '%s'\n", host);
#endif
          phost = gethostbyname (host);
          if (!phost) 
            {
              if (TRACE) 
                fprintf
                  (stderr, 
                   "HTTPAccess: Can't find internet node name `%s'.\n",host);
              return -1;  /* Fail? */
            }

          /* Free previously cached strings. */
          if (cached_host)
            free (cached_host);
          if (cached_phost_h_addr)
            free (cached_phost_h_addr);

          /* Cache new stuff. */
          cached_host = strdup (host);
          cached_phost_h_addr = (char *)calloc (phost->h_length + 1, 1);
          memcpy (cached_phost_h_addr, phost->h_addr, phost->h_length);
#if 0
          cached_phost_h_addr = strdup (phost->h_addr);
#endif
          cached_phost_h_length = phost->h_length;

          memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
        }
    }
  
  if (TRACE) 
    fprintf(stderr,  
            "TCP: Parsed address as port %d, IP address %d.%d.%d.%d\n",
            (int)ntohs(sin->sin_port),
            (int)*((unsigned char *)(&sin->sin_addr)+0),
            (int)*((unsigned char *)(&sin->sin_addr)+1),
            (int)*((unsigned char *)(&sin->sin_addr)+2),
            (int)*((unsigned char *)(&sin->sin_addr)+3));
  
  return 0;	/* OK */
}


/*	Derive the name of the host on which we are
**	-------------------------------------------
**
*/
#ifdef __STDC__
PRIVATE void get_host_details(void)
#else
PRIVATE void get_host_details()
#endif

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64		/* Arbitrary limit */
#endif

{
    char name[MAXHOSTNAMELEN+1];	/* The name of this host */
#ifdef NEED_HOST_ADDRESS		/* no -- needs name server! */
    struct hostent * phost;		/* Pointer to host -- See netdb.h */
#endif
    int namelength = sizeof(name);
    
    if (hostname) return;		/* Already done */
    gethostname(name, namelength);	/* Without domain */
    CTRACE(tfp, "TCP: Local host name is %s\n", name);
    StrAllocCopy(hostname, name);

#ifdef NEED_HOST_ADDRESS		/* no -- needs name server! */
    phost=gethostbyname(name);		/* See netdb.h */
    if (!phost) {
	if (TRACE) fprintf(stderr, 
		"TCP: Can't find my own internet node address for `%s'!!\n",
		name);
	return;  /* Fail! */
    }
    StrAllocCopy(hostname, phost->h_name);
    memcpy(&HTHostAddress, &phost->h_addr, phost->h_length);
    if (TRACE) fprintf(stderr, "     Name server says that I am `%s' = %s\n",
	    hostname, HTInetString(&HTHostAddress));
#endif
}

#ifdef __STDC__
PUBLIC char * HTHostName(void)
#else
PUBLIC char * HTHostName()
#endif
{
    get_host_details();
    return hostname;
}

#ifdef __STDC__
PUBLIC int HTDoConnect (char *url, char *protocol, int default_port, int *s)
#else
PUBLIC int HTDoConnect (url, protocol, default_port, s)
char *url; char *protocol; int default_port; int *s;
#endif
{
  struct sockaddr_in soc_address;
  struct sockaddr_in *sin = &soc_address;
  int status;

  /* Set up defaults: */
  sin->sin_family = AF_INET;
  sin->sin_port = htons(default_port);
  
  /* Get node name and optional port number: */
  {
    char line[256];
    char *p1 = HTParse(url, "", PARSE_HOST);
    int status;

    sprintf (line, "Looking up %s.", p1);
    HTProgress (line);

    status = HTParseInet(sin, p1);
    if (status) 
      {
        sprintf (line, "Unable to locate remote host %s.", p1);
        HTProgress(line);
        free (p1);
        return HT_NO_DATA;
      }

    sprintf (line, "Making %s connection to %s.", protocol, p1);
    HTProgress (line);
    free (p1);
  }

    /* Now, let's get a socket set up from the server for the data: */      
  *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    /*
     * Make the socket non-blocking, so the connect can be canceled.
     * This means that when we issue the connect we should NOT
     * have to wait for the accept on the other end.
     */
  {
    int ret;
    int val = 1;
    char line[256];
    
    ret = ioctl(*s, FIONBIO, &val);
    if (ret == -1)
      {
        sprintf (line, "Could not make connection non-blocking.");
        HTProgress(line);
      }
  }
  HTClearActiveIcon();

  /*
   * Issue the connect.  Since the server can't do an instantaneous accept
   * and we are non-blocking, this will almost certainly return a negative
   * status.
   */
  status = connect(*s, (struct sockaddr*)&soc_address, sizeof(soc_address));

  /*
   * According to the Sun man page for connect:
   *     EINPROGRESS         The socket is non-blocking and the  con-
   *                         nection cannot be completed immediately.
   *                         It is possible to select(2) for  comple-
   *                         tion  by  selecting the socket for writ-
   *                         ing.
   * According to the Motorola SVR4 man page for connect:
   *     EAGAIN              The socket is non-blocking and the  con-
   *                         nection cannot be completed immediately.
   *                         It is possible to select for  completion
   *                         by  selecting  the  socket  for writing.
   *                         However, this is only  possible  if  the
   *                         socket  STREAMS  module  is  the topmost
   *                         module on  the  protocol  stack  with  a
   *                         write  service  procedure.  This will be
   *                         the normal case.
   */
#if defined(SVR4) || defined(CYGWIN)
  if ((status < 0) && ((errno == EINPROGRESS)||(errno == EAGAIN)))
#else
  if ((status < 0) && (errno == EINPROGRESS))
#endif /* SVR4 */
    {
#ifdef CYGWIN
      int loopct;
#endif
      int ret;
      int ok_errno;
      struct timeval timeout;
      timeout.tv_sec = 0;
      timeout.tv_usec = 100000;
 
#ifdef CYGWIN
      loopct = 0;
  /* Test suggested that MAXLOOP of 17 would work on my Dell */
#define MAXLOOP 1000
#endif

      ret = 0;
      while (ret <= 0) {
        fd_set writefds;
        int intr;
          
        FD_ZERO(&writefds);
        FD_SET(*s, &writefds);
#ifdef __hpux
        ret = select(FD_SETSIZE, NULL, (int *)&writefds, NULL, &timeout);
#else
        ret = select(FD_SETSIZE, NULL, &writefds, NULL, &timeout);
#endif
	  /*
	   * Again according to the Sun and Motorola man pagse for connect:
           *     EALREADY            The socket is non-blocking and a  previ-
           *                         ous  connection attempt has not yet been
           *                         completed.
           * Thus if the errno is NOT EALREADY we have a real error, and
	   * should break out here and return that error.
           * Otherwise if it is EALREADY keep on trying to complete the
	   * connection.
	   */
         if ( (ret < 0) && (errno != EALREADY) ) {
           status = ret;
           break;
         } else if (ret > 0) {
	      /*
	       * Extra check here for connection success, if we try to connect
	       * again, and get EISCONN, it means we have a successful
	       * connection.
	       */
              /* Under Cygwin, am getting EALREADY after following connect unless
               * running under debugger, in which case am getting EISCONN.
               * Seems to me that in this area of code, the "final" connect
               * should either give EISCONN or normal status; eg, we should poll
               * until that happens.  Maybe that's the idea of the timeout in
               * the select; however, that's not how this block is coded, and
               * under Cygwin anyway, select finishes normally but seems not to
               * wait.  I decided to code the poll.  WJS
               */
	    status = connect(*s, (struct sockaddr*)&soc_address,
                               sizeof(soc_address));
#ifdef CYGWIN
	    if ( (status < 0) && (errno == EALREADY) )
	      if (loopct++ <= MAXLOOP) ret = 0;	/*  If sanity check OK,	*/
						/*  Set up to try again	*/
#endif
            if ( (status < 0) && (errno == EISCONN) ) status = 0;
            if (ret != 0) break;
          } else {
	      /*
	       * The select says we aren't ready yet.
	       * Try to connect again to make sure.  If we don't get EALREADY
	       * or EISCONN, something has gone wrong.  Break out and report it.
	       * For some reason SVR4 returns EAGAIN here instead of EALREADY,
	       * even though the man page says it should be EALREADY.
	       */
            status = connect(*s, (struct sockaddr*)&soc_address,
                               sizeof(soc_address));
            ok_errno = (errno==EALREADY) || (errno==EISCONN);
#if defined(SVR4) || defined(CYGWIN)
	    ok_errno = ok_errno || (errno==EAGAIN);
#endif /* SVR4 */
            if ( (status < 0) && ! ok_errno) break;
          }

          intr = HTCheckActiveIcon(1);
          if (intr) {
            if (TRACE) fprintf (stderr, "*** INTERRUPTED in middle of connect.\n");
            status = HT_INTERRUPTED;
            errno = EINTR;
            break;
          }
	}
    }

  /*
   * Make the socket blocking again on good connect
   */
  if (status >= 0)
    {
      int ret;
      int val = 0;
      char line[256];
      
      ret = ioctl(*s, FIONBIO, &val);
      if (ret == -1)
	{
          sprintf (line, "Could not restore socket to blocking.");
          HTProgress(line);
	}
    }
  /*
   * Else the connect attempt failed or was interrupted.
   * so close up the socket.
   */
  else
    {
	close(*s);
    }

  return status;
}

/* This is so interruptible reads can be implemented cleanly. */
#ifdef __STDC__
int HTDoRead (int fildes, void *buf, unsigned nbyte)
#else
int HTDoRead (fildes, buf,  nbyte)
int fildes; void *buf; unsigned nbyte;
#endif
{
  int ready, ret, intr;
  fd_set readfds;
  struct timeval timeout;

  timeout.tv_sec = 0;
  timeout.tv_usec = 100000;

  ready = 0;
  while (!ready)
    {
        FD_ZERO(&readfds);
        FD_SET(fildes, &readfds);
#ifdef __hpux
        ret = select(FD_SETSIZE, (int *)&readfds, NULL, NULL, &timeout);
#else
        ret = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
#endif
        if (ret < 0)
          {
                return -1;
          }
        else if (ret > 0)
          {
                ready = 1;
          }
        else
          {
                intr = HTCheckActiveIcon(1);
                if (intr)
                  {
                        return HT_INTERRUPTED;
                  }
          }
    }

  return read (fildes, buf, nbyte);
}
