/*  File: aceclientlib.c
 *  Author: Jean Thierry-Mieg (mieg@kaa.cnrs-mop.fr)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1992
 *-------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * Description:
 * I started from a sample code generated by rpcgen on Solaris
 * and a first version by Peter Kocab.
 * Does not require any ACEDB library code.
 * 
 * Exported functions:
	 openServer()
	 closeServer()
	 askServer()
	 askServerBinary()
 * HISTORY:
 * Last edited: Sep 10 19:48 1997 (rd)
 * Created: Wed Nov 25 20:02:45 1992 (mieg)
 *-------------------------------------------------------------------
 */

/* $Id: aceclientlib.c,v 1.1 2002/11/14 20:00:06 lstein Exp $ */

#include "mystdlib.h"
#define __malloc_h  
#include <errno.h>
#include <rpc/rpc.h>
#include "rpcace.h"
#include "aceclient.h"
#include "regular.h"

BOOL accessDebug = FALSE ;

#include <signal.h>		/* for alarm stuff */
#include <unistd.h>		/* for pause() */
#include <sys/time.h>		/* for setitimer() etc. */


static void wakeUp (int x) 
{ 
  static int sig = 0 ; 
  sig = x ;     
  signal (SIGALRM, wakeUp) ; /* reregister, otherwise you exit on SGI and LINUX */
}

static FILE *magicFileOpen (char *name)
{
  FILE *f ;

  f = fopen (name, "r") ;
  if (f) 
    { if (accessDebug) 
	printf ("//   found %s immediately\n", name) ;
      return f ;
    }

  /* test if directory readable by trying to open the file "." in
     the directory.  filcheck() and access() won't work in setuid()
     situations.
  */
  { char *dirName, *cp ;

    dirName = strnew (name, 0) ;
    for (cp = dirName ; *cp ; ++cp) ;
    while (cp > dirName && *cp != '/') --cp ;
    *++cp = '.' ;
    *++cp = 0 ;
    if (!(f = fopen(dirName, "r")))
      { if (accessDebug) 
	  printf ("//   directory %s not readable\n", dirName) ;
	return 0 ;
      }
    fclose (f) ;
  }

  { int i ;
    struct itimerval tval ;
    
    signal (SIGALRM, wakeUp) ;
    tval.it_interval.tv_sec = 0 ;
    tval.it_interval.tv_usec = 5000 ; /* 5ms reload */
    tval.it_value.tv_sec = 0 ;
    tval.it_value.tv_usec = 1000 ; /* 1ms initial */
    setitimer (ITIMER_REAL, &tval, 0) ;

    for (i = 0 ; i < 1000 ; ++i) /* 5 seconds */
      { pause () ;		/* wait until SIGALRM handled */
	f = fopen (name, "r") ;
	if (f) 
	  { if (accessDebug) 
	      printf ("//   found %s after %d msecs\n", name, 5*i+1) ;
	    tval.it_interval.tv_usec = tval.it_value.tv_usec = 0 ;
	    setitimer (ITIMER_REAL, &tval, 0) ;
	    return f ;
	  }
      }

    if (accessDebug)
      printf ("//   failed to find %s after %d msecs\n", name, 5*i+1) ;
    tval.it_interval.tv_usec = tval.it_value.tv_usec = 0 ;
    setitimer (ITIMER_REAL, &tval, 0) ;
  }

  return 0 ;
}

static int getMagic (int magic1, char *nm)
{ int magic = 0, magic2 = 0, magic3 = 0 ;
  FILE *f ;
  int level ;
  char *cp ;

  if (magic1 < 0) magic1 = -magic1 ; /* old system */
  if (!nm || !*nm) return 0 ;
  freeinit() ;
  level = freesettext(nm,0) ;
  if (!freecard(level))
    goto fin ;

  cp = freeword () ;
  if (!cp)
    { messerror ("Can't obtain write pass name from server") ;
      goto fin ;
    }

  if (accessDebug)
    printf ("// Write pass file: %s\n", cp) ;  
  if (strcmp(cp, "NON_WRITABLE"))
    { f = magicFileOpen (cp) ;
      if (f)
	{ if (fscanf(f, "%d", &magic3) != 1)
	    messerror ("failed to read file") ;
	  fclose(f) ;
	}
    }

  if ((cp = freeword ()) && 
      !magic3)		/* must be able to read if can write */
    { if (accessDebug)
	printf ("// Read pass file: %s\n", cp) ;  
      if (strcmp(cp, "PUBLIC") && strcmp(cp,"RESTRICTED"))
	{ f = magicFileOpen (cp) ;
	  if (!f)
	    { messout ("// Access to this database is restricted, sorry (can't open pass file)\n") ;
	      goto fin ;
	    }  
	  if (fscanf(f, "%d", &magic2) != 1)
	    messerror ("failed to read file") ;
	  fclose(f) ;
	}
    }

  magic = magic1 ;
  if (magic2)
    magic  = magic1 * magic2 % 73256171 ;
  if (magic3)
    magic = magic1 * magic3 % 43532334 ;

fin:
  freeclose(level) ;

#ifdef DEEP_DEBUG
    printf ("// magic1=%d, magic2=%d, magic3=%d, magic=%d\n", 
	     magic1, magic2, magic3, magic) ;
#endif

  return magic ;
}

/*************************************************************
Open RPC connection to server
INPUT
 char *host    hostname running server 
 int  timeOut  maximum peroid to wait for answer

OUTPUT
 return value:
 ace_handle *  pointer to structure containing open connection
               and client identification information
*/
ace_handle *openServer(char *host, u_long rpc_port, int timeOut)
{
  struct timeval tv;
  char *answer;
  int length,
      clientId = 0, n,
      magic1, magic3 = 0 ;
  ace_reponse *reponse = 0;
  ace_data question ;
  ace_handle *handle;
  CLIENT *clnt;

/* open rpc connection */
/* lao: */
  clnt = clnt_create (host, RPC_ACE, RPC_ACE_VERS, "tcp");

  if (!clnt) return((ace_handle *)NULL);

/* authenticate */
  question.clientId = 0;
  question.magic = 0;
  question.reponse.reponse_len = 0;
  question.reponse.reponse_val = "";
  question.question = "";
  question.aceError = 0;
  question.kBytes = 0;
  question.encore = 0;

#ifdef JUNK
  int first = 1 ;
  /* kludge: on first connection to a daemon
    the  conection is lost, so i try to connect twice,
    once with a short timeOut, then the real try

    at least on a dec alpha, the first connection keeps hanging and the
  inetd daemon keeps restrating the server for ever

    the advantage of this kludge is that the first client connection no 
    longer fails, and it is otherwise harmless since the restarting
    server happenned before i introduced this kludge
    */
  if (first)
    { first = 0 ;
      tv.tv_sec = 5 ;
      tv.tv_usec = 0;
      clnt_control(clnt, CLSET_TIMEOUT, (char *)&tv);

      reponse = ace_server_1(&question, clnt);
      if (!reponse) /* i ll try a second time */
	{ clnt_destroy(clnt); goto lao ; }
    }
#endif

  tv.tv_sec = timeOut;
  tv.tv_usec = 0;
  clnt_control(clnt, CLSET_TIMEOUT, (char *)&tv);
  
  if (!reponse) /* hopefully first connection worked */
    reponse = ace_server_1(&question, clnt);
  if (!reponse) return ((ace_handle *)NULL);
  
  clientId = reponse->ace_reponse_u.res_data.clientId;
  magic1 = reponse->ace_reponse_u.res_data.magic;
  if (!clientId) {
    xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse); 
    memset (reponse,0, sizeof(ace_reponse)) ;
    clnt_destroy(clnt);
    return 0 ;
  }
  if (reponse->ace_reponse_u.res_data.aceError) {
    xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
    memset (reponse,0, sizeof(ace_reponse)) ;
    clnt_destroy(clnt);
    return 0;
  }

  answer = reponse->ace_reponse_u.res_data.reponse.reponse_val;
  length = reponse->ace_reponse_u.res_data.reponse.reponse_len;
  if (answer && length) {
    magic3 = getMagic(magic1, answer) ;
    xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
    memset (reponse,0, sizeof(ace_reponse)) ;
/* confirm magic by reaccessing client */
    question.clientId = clientId ;
    question.magic = magic3 ;
    question.reponse.reponse_len = 0;
    question.reponse.reponse_val = "";
    question.question = "";
    question.aceError = 0;
    question.kBytes = 0;
    question.encore = 0;
    reponse = ace_server_1(&question, clnt);
    if (!reponse) {
      clnt_destroy(clnt);
      return 0 ;
    }
    n = reponse->ace_reponse_u.res_data.clientId;
  } 
  else
    n = clientId + 1 ; /* so we fail */
  if (reponse->ace_reponse_u.res_data.aceError) {
    xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
    memset (reponse,0, sizeof(ace_reponse)) ;
    clnt_destroy(clnt);
    return 0;
  }
  xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
  memset (reponse,0, sizeof(ace_reponse)) ;
  if (n != clientId) {
  /* authentication failed */
    clnt_destroy(clnt);
    return 0 ;
  }
/* create mem for handle */
  if ((handle = (ace_handle *)malloc(sizeof(ace_handle))) == NULL) {
     question.clientId = clientId ;
     question.magic = magic3 ;
     question.reponse.reponse_len = 0;
     question.reponse.reponse_val = "";
     question.question = "Quit";
     question.aceError = 0;
     question.kBytes = 0;
     question.encore = 0;
     reponse = ace_server_1(&question, clnt);
     xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
     memset (reponse,0, sizeof(ace_reponse)) ;
     clnt_destroy(clnt);
     return 0 ;
  }
  handle->clientId = clientId;
  handle->magic = magic3;
  handle->clnt = clnt;

  return handle ;
}

/*************************************************************
notify server of intent to close connection
close connection
free structures

INPUT 
 ace_handle *  pointer to structure containing open connection
               and client identification information
OUTPUT
none
*/

void closeServer(ace_handle *handle) {
ace_data question;
ace_reponse *reponse = 0;

  if (handle) {
    if ( (int *)handle && (CLIENT *)handle->clnt) { 
 /* JC not sure whether I should/need check (int *)handle */
      question.clientId = handle->clientId ;
      question.magic = handle->magic ;
      question.reponse.reponse_len = 0;
      question.reponse.reponse_val = "";
      question.question = "Quit";
      question.aceError = 0;
      question.kBytes = 0;
      question.encore = 0;
      reponse = ace_server_1(&question, handle->clnt);
      if (reponse)
         { xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
	 memset (reponse,0, sizeof(ace_reponse)) ;
	 }
      clnt_destroy((CLIENT *)handle->clnt);
    }
    free((char *)handle);
  }
}

/*************************************************************
transfer request to server, and wait for binary answer
INPUT
 char * request  string containing request
 unsigned char ** answer  ptr to char ptr, that has to be filled with answer
 ace_handle *    pointer to structure containing open connection
                 and client identification information
 int chunkSize desired size (in kBytes) of returned data-block
            This is only a hint. The server can return more.
            The server splits on ace boundaries
            a chunkSize of 0 indicates a request for unbuffered answers

OUTPUT
 unsigned char ** answer  ptr to char ptr. Pointing to allocated memory containing 
                 answer string. This memory will be filled with the 
                 unmodified data handled as binary bytes.
 return value:
 int      error condition
  ESUCCESS  (0)  no error.
  EIO       (5)  no response received from server.
  ENOMEM   (12)  no memory available to store answer.
  or a server generated error 

JC if the server can return both an encore and an aceError at the same time
I'm in trouble. I use only one int return value for both 
*/

int askServerBinary(ace_handle *handle, char *request, unsigned char **answerPtr, 
		    int *answerLength, int *encorep, int chunkSize) 
{
  ace_data question ;
  ace_reponse *reponse = 0 ;
  unsigned char *answer, *loop ;
  int aceError, length, i, encore = 0 ;

/* generate question structure */
  question.clientId = handle->clientId;
  question.magic = handle->magic;
  question.reponse.reponse_len = 0;
  question.reponse.reponse_val = "";
  question.kBytes = chunkSize;
  question.aceError = 0;
/* check if request contains a local command */
  if (!strncasecmp(request,"encore",6)) 
    {
      /* encore request */
      question.encore = WANT_ENCORE;
      question.question = ""; 
    } 
  else if (!strncasecmp(request,"noencore",8)) 
    {
      /* encore request */
      question.encore = DROP_ENCORE;
      question.question = ""; 
    } 
  else if (!strncasecmp(request,"quit",4)) 
    { /* ignore quit request. Must go through closeServer routine */
      *answerLength = 0;
      *answerPtr = NULL;
      return 0;
    } 
  else
    { question.encore = 0;
      question.question = request;
    }
  
  if (*encorep == 3)
    question.encore = -3 ;
  reponse = ace_server_1(&question, handle->clnt);

  /* validity checking of reponse */
  /* no data was received, return error */
  if (!reponse) 
    return EIO ; 
  
  /* store server returned error status. Give this to the client */
  /* JC answer could contain more info on error, so
     continue normal handling of the answer */
  aceError =  reponse->ace_reponse_u.res_data.aceError;
  
  /* no answer was received, return NULL answer 
     leave checking for NULL reponse to upper layer 
  if (reponse->ace_reponse_u.res_data.reponse.reponse_len == 0) {
    xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
    memset (reponse,0, sizeof(ace_reponse)) ;
    *answerLength = 0;
    *answerPtr = NULL;
    return aceError;
  }
  */
  
  /* answer received. allocate memory and fill with answer */
  length = reponse->ace_reponse_u.res_data.reponse.reponse_len;
  loop = (unsigned char *) reponse->ace_reponse_u.res_data.reponse.reponse_val;
  encore = reponse->ace_reponse_u.res_data.encore ;
  if ((answer = (unsigned char *)malloc(sizeof(unsigned char)*(length+1))) == NULL)
    {
      /* JC Need to tell the server we have a problem ?
	 I guess if the server gave an encore, we need to cancel it
	 */
      xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
      return(ENOMEM);
    }
  
  for (i=0;i<length;i++)
    answer[i] = loop[i];
  answer[i] = 0 ; /* zero terminate */
  xdr_free((xdrproc_t )xdr_ace_reponse, (char *)reponse);
  *answerPtr = answer;
  *answerLength = length;
  *encorep = encore ;
  return aceError ? aceError : - encore ; /* surcharge pour JD */
}

/***************************************************************
transfer request to server, and wait for binary answer. Convert answer 
to ASCII string

INPUT
 char * request  string containing request
 char ** answer  ptr to char ptr, that has to be filled with answer
 ace_handle *    pointer to structure containing open connection
                 and client identification information
 int chunkSize desired size (in kBytes) of returned data-block
            This is only a hint. The server can return more.
            The server splits on ace boundaries
            a chunkSize of 0 indicates a request for unbuffered answers

OUTPUT
 char ** answer  ptr to char ptr. Pointing to allocated memory containing 
                 answer string.
 return value:
 int      error condition
  ESUCCESS  (0)  no error.
  EIO       (5)  no response received from server.
  ENOMEM   (12)  no memory available to store answer.
  or a server generated error 
*/

int askServer(ace_handle *handle, char *request, char **answerPtr, int chunkSize) 
{ int length, i, encore ;
  int returnValue;
  unsigned char *binaryAnswer;
  char *answer;
  char *loop;
  
  returnValue = askServerBinary(handle, request, &binaryAnswer, &length, &encore, chunkSize) ;
  if (returnValue <= 0)
    { /* allocate memory for return string */
      /* if memory is more important than speed, we could run
	 through the string first and count the number of '\0''s 
	 and substract this from the memoryblock we allocate */
      if (!length )   /* empty string */
	{ *answerPtr = 0;
	  return returnValue;
	}
      if ((answer = (char *)malloc(length+1)) == NULL) 
	{ free(binaryAnswer);
	  return(ENOMEM);
	}
      /* initial step of the copy process */
      loop = (char *)binaryAnswer;
      strcpy(answer,loop);
      i = *loop ? strlen(loop): 0 ;
      loop += i;
      for (;(*loop == '\0')&&(i<length) ;loop++,i++);
      
      for (;i<length;) 
	{ strcat(answer,loop);
	  i += strlen(loop);
	  loop +=  strlen(loop);
	  for (;(*loop == '\0')&&(i<length) ;loop++,i++);
	}
      *(answer+i) = '\0'; /* for safety, make sure the string is terminated */
      free((char *)binaryAnswer);
      *answerPtr = answer;
    }
  return returnValue;
}

/************** end of file **************/