/*                               -*- Mode: C -*- 
 * net_block.c -- 
 * Copyright (c) 1995 Shaun Savage.
 * All rights reserved.
 * Author          : Shaun Savage
 * Created On      : Fri Feb  3 12:17:11 1995
 * Last Modified By: Shaun Savage
 * Last Modified On: Sun May  7 17:22:07 1995
 * Update Count    : 45
 * Status          : Unknown, Use with caution!
 * PURPOSE
 * 	|>Description of modules purpose<|
 * TABLE OF CONTENTS
 * 
 */
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include "tcplib.h"
#include "ssl.h"

#define	MIN(a,b)	(((a)<(b))?(a):(b))

#define RecvSSL_DONE		0
#define	RecvSSL_INTERRUPTED	1
#define RecvSSL_OVERFLOW	2
#define RecvSSL_BADFORMAT	3

char pname[80];
/************************************************************************/
/* TCPRead takes a TCP stream descriptor and reads data from a stream	*/
/*  into its buffer.  It returns TRUE if the descriptor has been added	*/
/*  to, otherwise it returns FALSE if no data was read because of	*/
/*  blocking or timeouts or a network error.				*/
/************************************************************************/

bool TCPRead(StreamInfo *tcp)
{
  ssize_t rlen;
  if(tcp->InPtr<tcp->BufSize){	/* if room for data exists in buffer */
    rlen=read(tcp->desc,&(tcp->Buf[tcp->InPtr]),tcp->BufSize-tcp->InPtr);
    if(rlen==-1){
      return(FALSE);		/* indicate only a partial read */
    }else{
      tcp->InPtr+=rlen;		/* record how many were read */
    }
  }
  return(TRUE);
}

/************************************************************************/
/* TCPWrite takes a TCP stream descriptor and writes the buffered data	*/
/*  in it to the TCP stream.  It returns TRUE if the descriptor has	*/
/*  emptied, otherwise it returns FALSE if there is data that was not	*/
/*  written because of blocking or timeouts or a network error.		*/
/************************************************************************/

bool TCPWrite(StreamInfo *tcp)
{
  ssize_t wlen;
  if(tcp->InPtr>tcp->OutPtr){	/* if data exists to be sent */
    wlen=write(tcp->desc,&(tcp->Buf[tcp->OutPtr]),tcp->InPtr-tcp->OutPtr);
    if(wlen<(tcp->InPtr-tcp->OutPtr)){
      if(wlen==-1){
      }else{
	tcp->OutPtr+=wlen;	/* record how many were copied */
      }
      return(FALSE);		/* indicate only a partial write */
    }
    tcp->InPtr=0;		/* indicate nothing is waiting to send */
    tcp->OutPtr=0;		/* indicate nothing has been sent */
  }
  return(TRUE);
}

/************************************************************************/
/* TCPOpen takes a remote IP address and port, and sets up a		*/
/*  connection.  It returns the descriptor for further access.		*/
/************************************************************************/

int TCPOpen(u_long IPAddr, u_short Port)
{
  int sock;
  struct sockaddr_in server;

  if((sock=socket(PF_INET,SOCK_STREAM,0))<0)
    return(-1);
  server.sin_family=AF_INET;
  server.sin_port=htons(Port);
  server.sin_addr.s_addr=htonl(IPAddr);
  if(connect(sock,(struct sockaddr *)&server,sizeof(server))<0)
    return(-3);
  return(sock);
}

/************************************************************************/
/* TCPClose closes a TCP connection.					*/
/************************************************************************/

int TCPClose(int fd)
{
  return(close(fd));
}

/************************************************************************/
/* TCPListen sets up a socket to listen for incomming connections, and	*/
/*  waits for them.  When one arrives, it returns with a pointer to the	*/
/*  open socket.							*/
/************************************************************************/

int TCPListen(u_short Port)
{
  int lsock;
  struct sockaddr_in server;
  if((lsock=socket(PF_INET,SOCK_STREAM,0))<0)	/* create listen socket */
    return(-1);
  server.sin_family=AF_INET;
  server.sin_port=htons(Port);
  server.sin_addr.s_addr=htonl(INADDR_ANY);
  if(bind(lsock,(struct sockaddr *)&server,sizeof(server))<0) /* set port # */
    return(-2);
  if(listen(lsock,1)<0)				/* listen, buffer only 1 */
    return(-4);
  return(lsock);
}

int TCPAccept(int lsock)
{
  int csock;
  size_t alen;
  struct sockaddr_in client_info;
  client_info.sin_family=AF_INET;
  alen=sizeof(client_info);
  if((csock=accept(lsock,(struct sockaddr *)&client_info,&alen))<0)
    return(-1);
  return(csock);
}

/************************************************************************/
/* Block2TCP takes a block of arbitrary charactors, and sends them	*/
/*  in a TCP stream so that block boundries are retained.  The new flag	*/
/*  indicates that this is the first Block2TCP call for a block.  If	*/
/*  whole block is in this call the flag should also be set.  If	*/
/*  multiple calls are to be used to send the block, then this should	*/
/*  only be set on the first.  The flush flag indicates if this is the	*/
/*  last block for a while, in which case the end of block marker is	*/
/*  written and the static buffers are flushed.  If not flushing, the	*/
/*  end marker is not written, as the next start marker will take care	*/
/*  of delineation.  Number of bytes processed to stream buffers is	*/
/*  returned, which will equal dlen unless the networking blocked and	*/
/*  the stream was set up with noblock or an expired timeout.  If the	*/
/*  happens, the next call should be the remainder of the old block,	*/
/*  which will correctly finish its transmission.			*/
/*									*/
/************************************************************************/

size_t Block2TCP(void *block,size_t dlen,bool new,bool flush,StreamInfo *tcp)
{
  char *InsPtr;
  size_t edlen;
  size_t TransLen;
  edlen=dlen;
  if(tcp->cont){	/* if older EDI was not completed */
      /* Try to flush remainder of old data to the TCP stream */
    if(!TCPWrite(tcp))
      return(0);	/* if even flush fails, return 0 progress */
  }else if(new){	/* send start of block only if new and not continued */
    tcp->Buf[tcp->InPtr++]=TCPEDIESC;	/* Start of block sequence */
    tcp->Buf[tcp->InPtr++]=TCPEDISTX;
  }
  while(dlen>0){
      /* copy as much as possible */
    TransLen=MIN(dlen,(tcp->BufSize-8)-tcp->InPtr);
    InsPtr=memccpy(&(tcp->Buf[tcp->InPtr]),block,TCPEDIESC,TransLen);
    if(InsPtr!=NULL){
        /* get actual bytes copied */
      TransLen=InsPtr-&(tcp->Buf[tcp->InPtr]);
        /* FF has been copied, add 0x08 */
      tcp->Buf[(tcp->InPtr++)+TransLen]=TCPEDIDLE;
    }
    tcp->InPtr+=TransLen;		/* adjust dest index */
    ((char *)block)+=TransLen;		/* adjust src ptr */
    dlen-=TransLen;			/* adjust src len */
    if(tcp->InPtr>=(tcp->BufSize-8) && dlen>0){
        /* write a block of size InPtr to the TCP stream */
      if(!TCPWrite(tcp)){
	tcp->cont=TRUE;		/* next call will be continuation */
	return(edlen-dlen);	/* return how much was copied */
      }
    }
  }
  if(flush){
    tcp->Buf[tcp->InPtr++]=TCPEDIESC;	/* End of block sequence */
    tcp->Buf[tcp->InPtr++]=TCPEDIETX;
      /* write a block of size B2TInPtr to the TCP stream */
    TCPWrite(tcp);
  }
  tcp->cont=FALSE;		/* next call will be new block */
  return(edlen);		/* everything was copied down */
}

/************************************************************************/
/* TCP2Block gets a block of arbitrary charactors from a TCP stream so	*/
/*  that block boundries are retained.  If a lower level condition	*/
/*  prevents completion, it returns with the number received, and the	*/
/*  done flag FALSE.  The next call will continue where this left off.	*/
/*  If a complete block is received, it returns with the done flag	*/
/*  TRUE.  If the block is oversized, it returns with the done flag	*/
/*  TRUE, the length is 1 larger than max, (actual data size is bmax,	*/
/*  the size is just a flag), and will get the next block when called	*/
/*  again, the remainder of the oversize block is discarded.  If the	*/
/*  notrunc flag is TRUE, then an overlength block will result in the	*/
/*  length of bmax+1, the done flag FALSE, and the next call will get	*/
/*  however much more of the block it can.  Only when the whole block	*/
/*  has arrived will the done flag be TRUE.				*/
/*									*/
/************************************************************************/

size_t TCP2Block(void *block,size_t bmax,bool notrunc,bool *done,StreamInfo *tcp)
{
  char *InsPtr;
  size_t TransLen;
  bool force_read;
  size_t dlen=0;
  if(!tcp->cont){		/* if not a continuation, search for STX */
    while(TRUE){
      InsPtr=memchr(&(tcp->Buf[tcp->OutPtr]),TCPEDIESC,tcp->InPtr-tcp->OutPtr);
      if(InsPtr==NULL){		/* not found in whole buffer */
	tcp->InPtr=0;
	tcp->OutPtr=0;		/* flag buffer as empty */
      }else{
	tcp->OutPtr=(InsPtr-(tcp->Buf))+1;
	if(tcp->OutPtr<tcp->InPtr){
	  if(tcp->Buf[tcp->OutPtr]==TCPEDISTX){
	    tcp->OutPtr++;	/* bump past STX */
	    break;		/* go process block */
	  }
	  continue;		/* resume search, starting with non-STX char */
	}else{
	  tcp->Buf[0]=TCPEDIESC;/* setup fake buffer */
	  tcp->InPtr=1;
	  tcp->OutPtr=0;
	}
      }
      if(!TCPRead(tcp)){	/* try to read more */
	*done=FALSE;
	return(0);		/* no data was read */
      }
    }
  }
  while(dlen<bmax){
    force_read=FALSE;
    TransLen=MIN((bmax-dlen),tcp->InPtr-tcp->OutPtr); /* copy as much as possible */
    InsPtr=memccpy(((char *)block)+dlen,&(tcp->Buf[tcp->OutPtr]),TCPEDIESC,TransLen);
    if(InsPtr!=NULL){
      TransLen=InsPtr-(((char *)block)+dlen);	/* get actual bytes copied */
      tcp->OutPtr+=TransLen;			/* adjust src index */
      dlen+=TransLen;				/* adjust dest len */
      if(tcp->OutPtr<tcp->InPtr){		/* if next code is in buffer */
	switch(tcp->Buf[tcp->OutPtr++]){
	case TCPEDISTX:
	  tcp->OutPtr-=2;		/* rewind so ESC STX in buffer */
	case TCPEDIETX:
	  *done=TRUE;
	  tcp->cont=FALSE;		/* whole block has arrived */
	  dlen--;			/* remove ESC from dest */
	  return(dlen);
	case TCPEDIDLE:
	  break;			/* leave ESC in dest */
	default:
	  dlen--;			/* remove ESC from dest */
	}
      }else{
	tcp->Buf[0]=TCPEDIESC;			/* setup fake buffer */
	tcp->InPtr=1;
	tcp->OutPtr=0;
	dlen--;					/* remove ESC from output */
	force_read=TRUE;		/* force a read */
      }
    }else{
      tcp->OutPtr+=TransLen;		/* adjust src index */
      dlen+=TransLen;			/* adjust dest len */
    }
    if(tcp->InPtr<=tcp->OutPtr || force_read){
      if(!force_read){
	tcp->InPtr=0;
	tcp->OutPtr=0;		/* buffer is empty */
      }
      if(!TCPRead(tcp)){
	*done=FALSE;
	tcp->cont=TRUE;		/* next call will be continuation */
	return(dlen);		/* return how much was copied */
      }
    }
  }
  if(notrunc){
    *done=FALSE;
    tcp->cont=TRUE;		/* keep reading block */
  }else{
    *done=TRUE;
    tcp->cont=FALSE;		/* whole block has arrived */
  }
  return(dlen+1);		/* !! overflow !! */
}

/*
 * Go through a list of Internet addresses,
 * printing each one in dotted-decimal notation.
 */
void pr_inet(char **listptr, int length)
{
  struct in_addr	*ptr;

  while ( (ptr = (struct in_addr *) *listptr++) != NULL)
    printf("  Internet address: %s\n", inet_ntoa(*ptr));
}

/* 
 * Given a host and type string (e.g., Target or Host),  it prints out
 * information about host
 */
void host_info(register char *host, char *type_str)
     
{
  register struct hostent *hostptr;
  
  if ( (hostptr = gethostbyname(host)) == NULL) 
     {
     sslError(-10,"");
     return;
     }
  
  printf("%s: %s\n",type_str,host);
  printf("%s official name: %s\n",type_str, hostptr->h_name);
  switch (hostptr->h_addrtype) {
  case AF_INET:
    pr_inet(hostptr->h_addr_list, hostptr->h_length);
    break;
  default:
    sslError(-1,"");
    break;
  }
}



/* 
 * Get the address of the server host. The server host may
 * be multihomed; however, since we made the server to respond to any requests
 * addressed to it, regardless of the network, any address will do.
 */ 
INET_ADDR get_firsthost_addr(char *name)
{
  register struct hostent *hostptr;
  struct in_addr *ptr;
  
  if ( (hostptr = gethostbyname(name)) == NULL)
     return sslError(-9,"");
 

  switch (hostptr->h_addrtype) {
  case AF_INET:				/* We expect an internet address. */
    if (hostptr->h_addr_list != NULL) {
      /* Point to first address in list of addresses. */
      ptr =(struct in_addr *) *hostptr->h_addr_list;
      /* return the internet address of type INET_ADDR (u_long) */
      return (ptr->s_addr);
    }
    else 
       return sslError(-11,"");
  default:
     return sslError(-1,"");
    break;
  }
}

