/*
 * Program:	VMS TCP/IP routines for Netlib.
 *
 * Author:	Yehavi Bourvine, The Hebrew University of Jerusalem
 *		Internet: Yehavi@VMS.huji.ac.il
 *
 * Date:	2 August 1994
 * Last Edited:	9 December 1996
 *
 * Updated:     9 December 1996 Andy Harper
 *		-  Modify for netlib version 2.0 interface
 *		-  Added a user callable 'gethostname' routine
 *		-  Added logging (not in production code though!)
 *		-  Protect logging code with an ifdef
 *		-  Added a server setup call
 *		-  Added the tcp_clienthost call
 *		-  Added hack for GNU C (include essential NETLIB declarations in-line)
 *		-  Updated logging to go via mm_dlog routine
 *		-  Logging now controlled through IMAPD_DEBUG logical
 *
 * Copyright 1994 by the University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both the
 * above copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.	This software is made available
 * "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include <stdio.h>
#include "mail.h"
#include "misc.h"
#include "tcp_vms.h"
#include "tcp.h"

#ifdef __GNUC__
/* GNU C cannot handle some of the non standard NETLIB structures
   (variant_struct and variant_union so we extract what we need and insert it
   here. This makes GNU C support potentially breakable on future versions of
   NETLIB but at least it works, so its a small price to pay
*/

				/* NETLIB constants */
#define NETLIB_K_AF_INET    	    	2


				/* NETLIB structures */
    struct SOCKADDRDEF {
    	unsigned short sockaddr_w_family;
    	unsigned char  sockaddr_x_data[14];
    };

    struct INADDRDEF {
    	unsigned long inaddr_l_addr;
    };

    struct SINDEF {
    	unsigned short sin_w_family;
    	unsigned short sin_w_port;
    	struct INADDRDEF sin_x_addr;
    	unsigned char sin_x_mbz[8];
    };

					/* NETLIB 1.0 socket calls */
int net_get_hostname();
					/* NETLIB 2.0 socket calls */

unsigned int netlib_server_setup(void **socket, void *sa, unsigned int *salen);
unsigned int netlib_socket(void **socket, ...);
unsigned int netlib_connect_by_name(void **socket, void *dsc,
		unsigned short *port, ...);
unsigned int netlib_read(void **socket, void *bufdsc, ...);
unsigned int netlib_write(void **socket, void *bufdsc, ...);
unsigned int netlib_getpeername(void **socket, void *sa, 
		unsigned int *sasize, unsigned int *salen, ...);
unsigned int netlib_address_to_name(void **socket, unsigned int *which, void *addr,
		unsigned int *addrsize, void *namdsc, ...);
unsigned int netlib_close(void **socket, ...);
unsigned int netlib_addrtostr(struct INADDRDEF *a, void *dsc, ...);
unsigned short netlib_hton_word(unsigned int *value);
#else
#include "netlib_dir:netlibdef.h"	/* Use standard NETLIB include */
#endif

#include <descrip.h>

#ifdef NETTRACE
  extern FILE *logfile;
  extern int   loglevel;
# define DBGPRINT(level,fmt,a,b,c,d) if (logfile && (loglevel > level)) {fprintf(logfile,fmt,a,b,c,d);}
#else
# define DBGPRINT(level,fmt,a,b,c,d) {}
#endif

int get_local_host_name(char *buffer, int size);

static TCPSTREAM *clientstream = NULL;	/* For the client calling us */


				/* TCP timeout handler routine */
static tcptimeout_t tcptimeout = NIL;
				/* TCP timeouts, in seconds */
static long tcptimeout_read = 0;
static long tcptimeout_write = 0;

/* TCP/IP manipulate parameters
 * Accepts: function code
 *	    function-dependent value
 * Returns: function-dependent return value
 */

void *tcp_parameters (long function,void *value)
{
  switch ((int) function) {
  case SET_TIMEOUT:
    tcptimeout = (tcptimeout_t) value;
    break;
  case GET_TIMEOUT:
    value = (void *) tcptimeout;
    break;
  case SET_READTIMEOUT:
    tcptimeout_read = (long) value;
    break;
  case GET_READTIMEOUT:
    value = (void *) tcptimeout_read;
    break;
  case SET_WRITETIMEOUT:
    tcptimeout_write = (long) value;
    break;
  case GET_WRITETIMEOUT:
    value = (void *) tcptimeout_write;
    break;
  default:
    value = NIL;		/* error case */
    break;
  }
  return value;
}

/* TCP/IP server setup
 * Accepts: port number on which we should be started
 * Returns: TCP/IP stream structure for the stdio channels created by master server
 */
TCPSTREAM *tcp_server_init(int port)
{
  char hostname[MAILTMPLEN] = "UNKNOWN";
  char tmp[MAILTMPLEN];
  unsigned int *sock = 0;
  int status;

				/* Define and initialize the socket structure*/
  struct SINDEF sockaddr = {NETLIB_K_AF_INET, netlib_hton_word((unsigned *)&port), 0, {0,0,0,0,0,0,0,0}};
  int sockaddr_s = sizeof(sockaddr);

  				/* hostname connected to */
  struct dsc$descriptor HostDesc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };


				/* Initialize the channels created by master server */
  DBGPRINT(12,"tcp_server_init: entering (port %d)\r\n",port,0,0,0);
  status = netlib_server_setup(&sock, &sockaddr, &sockaddr_s);
  DBGPRINT(12,"tcp_server_init: return from netlib_server_setup,status=0x%08x,sock=0x%08x\r\n",status,sock,0,0);
  if (!(status & 0x1))
    {
      sprintf (tmp,"Unable to set up server on port %d, status=0x%08x",port,status);
      mm_log (tmp,ERROR);
      return NIL;
    }


  clientstream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
				/* copy official host name */
  clientstream->host = cpystr (hostname);
				/* copy local host name */
  get_local_host_name(tmp, sizeof(tmp));
  clientstream->localhost = cpystr (tmp);
				/* init sockets */
  clientstream->tcpsi = clientstream->tcpso = (int)sock;
  clientstream->ictr = 0;		/* init input counter */
  DBGPRINT(12,"tcp_server_init: exiting, stream addr =0x%08x\r\n",clientstream,0,0,0);
  return clientstream;		/* return success */
}

 
/* TCP/IP open
 * Accepts: host name
 *	    contact service name
 *	    contact port number
 * Returns: TCP/IP stream if success else NIL
 */

TCPSTREAM *tcp_open (char *host,char *service,long port)
{
  TCPSTREAM *stream = NIL;
  unsigned long *sock;
  int status;
  char *s;
  char hostname[MAILTMPLEN];
  char tmp[MAILTMPLEN];
				/* hostname to connect to */
  struct dsc$descriptor HostDesc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };

				/* assign a local socket */
  if (!((status = netlib_socket (&sock)) & 0x1)) {
    sprintf (tmp,"Unable to assign to net, status=0x%08x",status);
    mm_log (tmp,ERROR);
    return NIL;
  }

				/* open connection */
  HostDesc.dsc$w_length = strlen (host);
  HostDesc.dsc$a_pointer = host;
  if (!((status = netlib_connect_by_name (&sock, &HostDesc, &port)) & 0x1)) {
    sprintf (tmp,"Can't connect to %.80s,%d: 0x%08x",host,port,status);
    mm_log (tmp,ERROR);
    return NIL;
  }

  strcpy(hostname,host);
				/* create TCP/IP stream */
  stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
				/* copy official host name */
  stream->host = cpystr (hostname);
				/* copy local host name */
  get_local_host_name(tmp, sizeof(tmp));
  stream->localhost = cpystr (tmp);
				/* init sockets */
  stream->tcpsi = stream->tcpso = (int) sock;
  stream->ictr = 0;		/* init input counter */
  return stream;		/* return success */
}

/* TCP/IP authenticated open
 * Accepts: host name
 *	    service name
 * Returns: TCP/IP stream if success else NIL
 */

TCPSTREAM *tcp_aopen (char *host,char *service)
{
  return NIL;
}

/* TCP/IP receive line
 * Accepts: TCP/IP stream
 * Returns: text line string or NIL if failure
 */

char *tcp_getline (TCPSTREAM *stream)
{
  int n,m;
  char *st,*ret,*stp;
  char c = '\0';
  char d;
				/* make sure have data */
  DBGPRINT(12,"tcp_getline: Entering on stream 0x%08x\r\n",stream,0,0,0);
  if (!tcp_getdata (stream))
    {
      DBGPRINT(12,"tcp_getline:  NIL return from tcp_getdata\r\n",0,0,0,0);
      return NIL;
    }
  st = stream->iptr;		/* save start of string */
  n = 0;			/* init string count */
  while (stream->ictr--) {	/* look for end of line */
    d = *stream->iptr++;	/* slurp another character */
    if ((c == '\015') && (d == '\012')) {
      ret = (char *) fs_get (n--);
      memcpy (ret,st,n);	/* copy into a free storage string */
      ret[n] = '\0';		/* tie off string with null */
      DBGPRINT(12,"tcp_getline: returning line size %d\r\n%S\r\n",strlen(ret),ret,0,0);
      return ret;
    }
    n++;			/* count another character searched */
    c = d;			/* remember previous character */
  }
				/* copy partial string from buffer */
  memcpy ((ret = stp = (char *) fs_get (n)),st,n);
				/* get more data from the net */
  DBGPRINT(12,"tcp_getline: testing tcp_getdata again\r\n",0,0,0,0);
  if (!tcp_getdata (stream))
    {
      DBGPRINT(12,"tcp_getline: NIL return from tcp_getdata\r\n",0,0,0,0);
      return NIL;
    }
				/* special case of newline broken by buffer */
  if ((c == '\015') && (*stream->iptr == '\012')) {
    stream->iptr++;		/* eat the line feed */
    stream->ictr--;
    ret[n - 1] = '\0';		/* tie off string with null */
  }
				/* else recurse to get remainder */
  else if (st = tcp_getline (stream)) {
    ret = (char *) fs_get (n + 1 + (m = strlen (st)));
    memcpy (ret,stp,n);		/* copy first part */
    memcpy (ret + n,st,m);	/* and second part */
    fs_give ((void **) &stp);	/* flush first part */
    fs_give ((void **) &st);	/* flush second part */
    ret[n + m] = '\0';		/* tie off string with null */
  }
  DBGPRINT(12,"tcp_getline: returning line size %d\r\n%s\r\n",strlen(ret),ret,0,0);
  return ret;
}

/* TCP/IP receive buffer
 * Accepts: TCP/IP stream
 *	    size in bytes
 *	    buffer to read into
 * Returns: T if success, NIL otherwise
 */

long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
{
  unsigned long n;
  char *bufptr = buffer;
  DBGPRINT(12,"tcp_getbuffer: Entering on stream 0x%08x, size %d\r\n",stream,size,0,0);
  while (size > 0) {		/* until request satisfied */
    if (!tcp_getdata (stream))
      {
        DBGPRINT(12,"tcp_getbuffer: NIL return from tcp_getdata\r\n",0,0,0,0);
        return NIL;
      }
    n = min (size,stream->ictr);/* number of bytes to transfer */
				/* do the copy */
    memcpy (bufptr,stream->iptr,n);
    bufptr += n;		/* update pointer */
    stream->iptr +=n;
    size -= n;			/* update # of bytes to do */
    stream->ictr -=n;
  }
  bufptr[0] = '\0';		/* tie off string */
  DBGPRINT(12,"tcp_getbuffer: returning buffer, size %d\r\n%s\r\n",strlen(buffer),buffer,0,0);
  return T;
}


/* TCP/IP receive data
 * Accepts: TCP/IP stream
 * Returns: T if success, NIL otherwise
 */

long tcp_getdata (TCPSTREAM *stream)
{
  char tmp[MAILTMPLEN];
  int i,status;
  /* Note: the doc says we need here dynamic descriptor, but we need static
   * one... */
  struct dsc$descriptor BufDesc = {BUFLEN,DSC$K_DTYPE_T,DSC$K_CLASS_S,
				     stream->ibuf};
  static short iosb[4];
  DBGPRINT(12,"tcp_getdata: entering on stream 0x%08x, socket=0x%08x\r\n",stream,stream->tcpsi,0,0);
  if (stream->tcpsi < 0) return NIL;
  while (stream->ictr < 1) {	/* if nothing in the buffer */
    if (!((status = netlib_read(&(stream->tcpsi), &BufDesc, 0,0,0,0, iosb)) & 0x1)) {
      DBGPRINT(12,"tcp_getdata: error from netlib_read, status=0x%08x\r\n",status,0,0,0);
      sprintf (tmp,"Error reading from TcpIp/NETLIB, status=0x%08x",status);
      mm_log (tmp,ERROR);
      return tcp_abort (stream);
    }
    if (iosb[1] > BUFLEN) i = BUFLEN;
    else i = iosb[1];
    if (i < 1)
      {
         DBGPRINT(12,"tcp_getdata: byte count < 1 (=%d), aborting stream\r\n",i,0,0,0);
         return tcp_abort (stream);
      }
    stream->ictr = i;		/* set new byte count */
    stream->iptr = stream->ibuf;/* point at TCP buffer */
  }
  DBGPRINT(12,"tcp_data: exiting successfully\r\n",0,0,0,0);
  return T;
}

/* TCP/IP send string as record
 * Accepts: TCP/IP stream
 *	    string pointer
 * Returns: T if success else NIL
 */

long tcp_soutr (TCPSTREAM *stream,char *string)
{
  return tcp_sout (stream,string,(unsigned long) strlen (string));
}


/* TCP/IP send string
 * Accepts: TCP/IP stream
 *	    string pointer
 *	    byte count
 * Returns: T if success else NIL
 */

long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
{
  int status;
  struct dsc$descriptor_s BufDesc = {size,DSC$K_DTYPE_T,DSC$K_CLASS_S,string};

  DBGPRINT(12,"tcp_sout: entering on stream 0x%08x (socket 0x%08x) with string size %d\r\n%s\r\n",stream,stream->tcpso,strlen(string),string);
  if ( !((status = netlib_write(&(stream->tcpso),&BufDesc)) & 0x1))
    {
      DBGPRINT(12,"netlib_write: netlib_write failed, status=0x%08x\r\n",status,0,0,0);
      return tcp_abort(stream);
    }
  DBGPRINT(12,"netlib_write: netlib_write successful. Exiting with T\r\n",0,0,0,0);
  return T;
}

/* TCP/IP close
 * Accepts: TCP/IP stream
 */

void tcp_close (TCPSTREAM *stream)
{
  tcp_abort (stream);		/* nuke the stream */
				/* flush host names */
  fs_give ((void **) &stream->host);
  fs_give ((void **) &stream->localhost);
  fs_give ((void **) &stream);	/* flush the stream */
}


/* TCP/IP abort stream
 * Accepts: TCP/IP stream
 * Returns: NIL always
 */

long tcp_abort (TCPSTREAM *stream)
{
  if (stream->tcpsi >= 0) {	/* no-op if no socket */
				/* nuke the socket */
    netlib_close (&(stream->tcpsi));
    stream->tcpsi = stream->tcpso = -1;
  }
  return NIL;
}


/* TCP/IP get host name
 * Accepts: TCP/IP stream
 * Returns: host name for this stream
 */

char *tcp_host (TCPSTREAM *stream)
{
  return stream->host;		/* return host name */
}


/* TCP/IP get local host name
 * Accepts: TCP/IP stream
 * Returns: local host name
 */

char *tcp_localhost (TCPSTREAM *stream)
{
  return stream->localhost;	/* return local host name */
}

/* TCP/IP get server host name
 * Accepts: pointer to destination
 * Returns: string pointer if got results, else NIL
 */

char *tcp_clienthost (char *dst)
{
  char buffer[256];
  struct SINDEF sockaddr;
  struct INADDRDEF ipaddr;
  struct dsc$descriptor_s hostname = {sizeof(buffer), DSC$K_DTYPE_T, DSC$K_CLASS_S, buffer};
  unsigned int sockaddr_l, sockaddr_s = sizeof(sockaddr), ipaddr_s = sizeof(ipaddr);
  unsigned short int retlen_w;
  int *tempsock, status;

  strcpy(dst,"UNKNOWN");

  if ((clientstream == NULL) || (clientstream->tcpsi == -1))
    {
      DBGPRINT(12,"tcp_clienthost: null client stream or aborted socket (stream 0x%08x)\r\n",clientstream,0,0,0);
      return dst;		/* Not connected as a server yet (or an aborted connection)*/
    }


/* Get the socket information */
  if (!((status = netlib_getpeername(&clientstream->tcpsi, &sockaddr, &sockaddr_s, &sockaddr_l)) & 0x1))
    {
       DBGPRINT(12, "tcp_clienthost: netlib_getpeername failed, status = 0x%08x\r\n", status, 0,0,0);
       return dst;
    }


 /* Allocate a temporary socket and do a name lookup on the address */
  if ( ((status = netlib_socket(&tempsock))&0x1) &&
       ((status = netlib_address_to_name(&tempsock, 0, &sockaddr.sin_x_addr, &ipaddr_s, &hostname, &retlen_w))&0x1) &&
       ((status = netlib_close(&tempsock))&0x1)   )
    {
      buffer[retlen_w] = '\0';
      DBGPRINT(12,"tcp_clienthost: returned '%s'\r\n", buffer,0,0,0);
      strcpy(dst,buffer);
      return dst;
    }

  DBGPRINT(12,"tcp_clienthost: netlib_address_to_name failed, status=0x%08x\r\n", status,0,0,0);


/* Name lookup failed, try converting the address */
  if ( !((status=netlib_addrtostr(&sockaddr.sin_x_addr, &hostname, &retlen_w)) & 0x1))
    {
      DBGPRINT(12,"tcp_clienthost: netlib_addrtostr failed, status = 0x%08d\r\n", status,0,0,0);      
      return dst;
    }

   buffer[retlen_w] = '\0';
   DBGPRINT(12,"tcp_clienthost: netlib_addrtostr returned '%s'\r\n", buffer,0,0,0);
   strcpy(dst,"["); strcat(dst,buffer); strcat(dst, "]");
   return dst;
}


/*
 | return the local hostname in Buffer whose size is Size. This is a
 | replacement to the Gethostname() which does not exist in Netlib.
 */
get_local_host_name(char *buffer, int size)
{
  int status;
  struct dsc$descriptor hostdesc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_D,NULL};


			/* Get the local host name into a dynamic string */
  if (!((status = net_get_hostname (&hostdesc)) & 0x1))
    {
      char tmp[MAILTMPLEN];
      sprintf (tmp,"Can't get local hostname, status=0x%08x",status);
      mm_log (tmp,ERROR);
      sprintf(buffer, "%.*s", (size-1)>7 ? 7 : size-1, "UNKNOWN");
      return 0;
    }

			/* Copy max of 'size' characters to output buffer */
  if ( hostdesc.dsc$w_length > (size-1) )
    sprintf(buffer, "%.*s", size-1, hostdesc.dsc$a_pointer);
  else
    sprintf(buffer, "%.*s", hostdesc.dsc$w_length, hostdesc.dsc$a_pointer);


			/* Free the dynamic string */
  str$free1_dx (&hostdesc);
  return 1;
}
