/* Rpc.c -- This file contains the code to do the rpc over the serial
 * line.  It will do actual to the transmissions over the port.  It
 * keeps an internal set of file descriptors for input and output (they
 * do not have to be the same).
 *
 * Created by:	Derek Atkins <warlord@MIT.EDU>
 *
 * $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/rpc.c,v $
 * $Author: warlord $
 *
 */

#include <warlord-copyright.h>

#if !defined(lint) && !defined(SABER)
static char rcsid_rpc_c[] = "$Id: rpc.c,v 1.9 93/12/19 05:46:12 warlord Exp $";
#endif

#include <stdio.h>
#include <ia5.h>
#include <parser.h>
#include <rpc.h>
#include <charon_prot.h>
#include <charon_err.h>

#if defined(ibm032)
char *fgets(), *fputs();
#endif

int charon_debug = 0;

/* default to stdin and stdout */
static FILE *InputFile = stdin, *OutputFile = stdout, *ErrorFile = stderr; 
static int (*Putc)() = NULL, (*Getc)() = NULL;
static int (*Puts)() = NULL;
static int (*FPuts)() = NULL, (*FGets)() = NULL;
static int crn_read_packet_data();
static int crn_read_line();
static int crn_put_line();

/* Requires:	file be a valid file, open for reading
 * Modifies:	InputFile
 * Effects:	Sets the input file to ``file''.
 */
void
crn_set_input_file(FILE *file)
{
  InputFile = file;
  return;
}

/* Requires:	file be a valid file, open for writing
 * Modifies:	OutputFile
 * Effects:	Sets the output file to ``file''.
 */
void
crn_set_output_file(FILE *file)
{
  OutputFile = file;
  return;
}

/* Requires:	fcn be a function that puts a single character to a
 *		file, of the form int fcn(char c)
 * Modifies:	Putc
 * Effects:	Sets the Putc function.
 */
void
crn_set_putc_fcn(int (*fcn)())
{
  Putc = fcn;
}

/* Requires:	fcn be a function that gets a single character from a
 *		file, of the form int fcn()
 * Modifies:	Getc
 * Effects:	Sets the Getc function.
 */
void
crn_set_getc_fcn(int (*fcn)())
{
  Getc = fcn;
}

/* Requires:	fcn be of form int fcn(char *s, int len)
 * Effects:	Sets the Puts function.  This is for kermit compatibility.
 */
void
crn_set_puts_fcn(int (*fcn)())
{
  Puts = fcn;
}

/* Requires:	fcn be a function that puts a line of n chars to the
 *		file, and includes a CR/LF at the end. fc is of the form 
 *		char *fcn(char *line, FILE *stream)
 * Modifies:	FPuts
 * Effects:	Sets the FPuts function.
 */
void
crn_set_fputs_fcn(int (*fcn)())
{
  FPuts = fcn;
}

/* Requires:	fcn be a function that gets a line from a file, ignoring
 *		training CR/LF's, of the form 
 *		char *fcn(char *data, int space, FILE *stream)
 * Modifies:	FGets
 * Effects:	Sets the FGets function.
 */
void
crn_set_fgets_fcn(int (*fcn)())
{
  FGets = fcn;
}

/* Effects:	Will take the KTEXT packet and send it across to the
 *		other side.  It will vary the type depending on whether
 *	        the srv is SERVER_MODE or CLIENT_MODE.
 *		Retuns CRN_OK on success, otherwise an error.
 */
int
crn_rpc_put_ktext(KTEXT pkt, int srv)
{
  return (crn_send_data(pkt->dat, pkt->length, 
			(srv == CLIENT_MODE) ? PKT_CKDC : PKT_SKDC));
}

/* Modifies:	pkt
 * Effects:	Will get a KTEXT packet from the other side.  It will
 *		set equal to a boolean of whether the types matched or
 *		not.
 *		Retuns CRN_OK on success, otherwise an error.
 */
int
crn_rpc_get_ktext(KTEXT pkt)
{
  u_char type;

  pkt->length = MAX_KTXT_LEN; /* set to max size for default */

  return(crn_read_data(pkt->dat, (u_int *)&(pkt->length), &type));
}

/* Effects:	Sends an AUTH_DAT packet to the other side.
 */
int
crn_rpc_sendauth(KTEXT auth)
{
  return(crn_send_data(auth->dat, auth->length, PKT_CAUTH));
}

/* Effects:	Send a MSG_DAT packet to the other side.
 */
int
crn_rpc_senddat(MSG_DAT *data)
{
  return(crn_send_data(data->app_data, data->app_length, PKT_CAUTH));
}

/* Requires:	Space to have been provided already.
 * Effects:	Reads in a MSG_DAT structure from the other side and puts
 *		it in the space provided.
 */
int
crn_rpc_getdat(MSG_DAT *data)
{
  int status;
  u_char type;

  data->app_length = MAX_KTXT_LEN; /* initialize to largest size. */

  if ((status = crn_read_data(data->app_data, &data->app_length, &type))
      != CRN_OK)
    return(status);

  if (type != PKT_CAUTH)
    return(CRN_BAD_PACKET);

  return(CRN_OK);
}

/* Effects:	Reads a packet and expects it to be a status packet, and
 * 		will return CRN_OK on good status, or an error.
 */
int
crn_get_status()
{
  char msg[BUFSIZ];
  u_char type;
  u_int len = sizeof(msg);
  int status;

  if ((status = crn_read_data((u_char *)msg, &len, &type)) != CRN_OK)
    return(status);

  if (type != PKT_STATUS)
    return(CRN_BAD_PACKET);

  return(crn_read_status(msg, len));
}

/* Effects:	Parse a data and length, and return CRN_OK on error, or
 *		CRN_ERROR and print an error message.
 */
int
crn_read_status(char *msg, u_int len)
{
  if (len == 0)
    return(CRN_OK);

  fprintf(stderr, "%s\n", msg);
  return(CRN_ERROR);
}

/* Effects:	Send an OK status to the other side.
 */
void
crn_send_success()
{
  crn_send_data(NULL, 0, PKT_STATUS);
}

/* Effects:	Send an error message across to the other side.
 */
void
crn_send_error(char *msg)
{
  crn_send_data((u_char *)msg, strlen(msg)+1, PKT_STATUS);
}

/* Requires:	crn_read_line blocks.
 * Modified:	data, datalen
 * Effects:	This will wait for an incoming packet, somehow, and then
 *		it will read in the packettype, packet length, and the
 *		packet itself.  It will allocate enough data for the packet
 *		and return the packet type to the caller.
 */
int
crn_wait_for_data(u_char **data, u_int *datalen, u_char *datatype)
{
  u_char type_byte;
  int llen;
  char type[5];
  char enc_length[9];
  u_char length[4];
  int retval;
  u_int count, encodedlen;

  /* First, read the packet type byte (should only be one character) */
  if ((retval = crn_read_line(type, sizeof(type))) != 1)
    return(CRN_BAD_PACKET);

  /* This is special, since I really do know the IA5 encoding scheme here */
  type[1] = type[2] = type[3] = '=';
  type[4] = '\0';

  /* Decode the type-byte, and get the llen */
  encodedlen = 5;
  if ((retval = ia5_decode_data(type, 4, &type_byte, &encodedlen)) != CRN_OK)
    return(retval);
  *datatype = PKT_TYPE(type_byte);
  llen = PKT_LLENGTH(type_byte);

  /* Read in the length field */
  count = crn_read_line(enc_length, sizeof(enc_length));

  /* And convert it back ... */
  BCLEAR(length);
  encodedlen = 4;
  if ((retval = ia5_decode_data(enc_length, count, length + (4 - llen), 
		      &encodedlen)) != CRN_OK) {
    return(retval);
  }

  /* ... to an integer */
  octet2int(length, (int *)datalen);

  if (charon_debug)
    fprintf(ErrorFile, "crn_len: %d\n", *datalen);

  /* Now allocate and clear the space for this */
  *data = (u_char *)malloc(*datalen + 1);
  ZERO(*data, *datalen);
  
  return(crn_read_packet_data(*data, *datalen));
}

/* Requires:	Valid Input and Output Files, valid 8-bit data and datalength,
 *		and a valid type
 * Modifies:	Output stream
 * Effects:	Will convert the data to a 6-bit, IA5 encoding, and send
 *		that to the output file.
 *		Returns 0 on success, or an error code, otherwise
 */
int
crn_send_data(u_char *data, u_int datalen, u_char datatype)
{
  u_char type_byte;
  int llen, retval;
  char type[5];
  char enc_length[9];
  u_char length[4];
  char *coded_data;
  u_int count, encodedlen;

  /* Encoded the length into an octet */
  int2octet(datalen, length);

  llen = 4;
  if (datalen < 0x010000)
    llen = 2;
  if (datalen < 0x100)
    llen = 1;

  type_byte = TYPE_BYTE(llen, datatype);

  /* Encode and send the type.. Only the top byte is significant. */
  encodedlen = 5;
  if ((retval = ia5_encode_data(&type_byte, 1, type, &encodedlen)) != CRN_OK)
    return(retval);
  crn_put_line(type, 1);

  /* Encoded and send the length */
  encodedlen = 9;
  if ((retval = ia5_encode_data(length + (4 - llen), llen, enc_length, 
				&encodedlen)) != CRN_OK)
    return(retval);
  crn_put_line(enc_length, encodedlen);

  if (charon_debug)
    fprintf(ErrorFile, "crn_len: %d\n", datalen);

  /* Encode the data */
  encodedlen = IA5_LENGTH(datalen);
  coded_data = (char *)malloc(encodedlen);
  if ((retval = ia5_encode_data(data, datalen, coded_data, &encodedlen)) 
      != CRN_OK)
    return(retval);

  if (charon_debug)
    fprintf(ErrorFile, "num_chars: %d\n", encodedlen);

  for (count = 0; count <= encodedlen; count += LINE_SIZE) {
    if (encodedlen - count < LINE_SIZE)
      crn_put_line(coded_data+count, encodedlen % LINE_SIZE);
    else
      crn_put_line(coded_data+count, LINE_SIZE);
  }

  free(coded_data);
  return(CRN_OK);
}

/* Requires:	data be pointing into valid data, long enough to hold all the
 *		data coming in the packet, datalen should start out with the
 *		amount of space allocated, and must be a valid pointer, as
 *		datatype must also be a valid non-null pointer.
 * Modifies:	*data, *datalen, and *datatype
 * Effects:	reads data in IA5 encoding, converts it to 8-bit binary,
 * 		and put it into the space of data, put the length of the
 *		data into datalen, and the packet type into datatype.
 *		Returns 0 on success, error code otherwise.
 */
int
crn_read_data(u_char *data, u_int *datalen, u_char *datatype)
{
  u_char type_byte;
  int llen, retval;
  char type[5];
  char enc_length[9];
  u_char length[4];
  u_int count, encodedlen;

  /* First, read the packet type byte (should only be one character) */
  if ((crn_read_line(type, sizeof(type))) != 1)
    return(CRN_BAD_PACKET);

  /* This is special, since I really do know the IA5 encoding scheme here */
  type[1] = type[2] = type[3] = '=';
  type[4] = '\0';

  /* Decode the type-byte, and get the llen */
  encodedlen = 4;
  type_byte = 0;
  if ((retval = ia5_decode_data(type, 4, &type_byte, &encodedlen)) != CRN_OK)
    return(retval);
  *datatype = PKT_TYPE(type_byte);
  llen = PKT_LLENGTH(type_byte);

  /* Read in the length field */
  count = crn_read_line(enc_length, sizeof(enc_length));

  /* And convert it back ... */
  BCLEAR(length);
  encodedlen = 4;
  if ((retval = ia5_decode_data(enc_length, count, length + (4 - llen), 
				&encodedlen)) != CRN_OK)
    return(retval);

  /* ... to an integer */
  octet2int(length, (int *)datalen);

  return(crn_read_packet_data(data, *datalen));
}

/* Requires:	space must be allocated already for data.  Datalen must be
 *		the length of the data.
 * Modified:	data
 * Effects:	Reads in the data in IA5 and decodes it to octet into
 *		data.
 */
static int
crn_read_packet_data(u_char *data, u_int datalen)
{
  char *coded_data;
  u_int len, count = 0, enc_len, encodedlen;

  if (datalen == 0) {
    *data = '\0';
    return(CRN_OK);
  }    

  enc_len = IA5_LENGTH(datalen);
  coded_data = (char *)malloc(enc_len--);

  while (count < enc_len) {
    len = crn_read_line(coded_data + count, enc_len - count);
    count += len;
  }

  encodedlen = datalen;
  return(ia5_decode_data(coded_data, enc_len, data, &encodedlen));
}

/* Requires:	Enough data is allocated.
 * Effects:	Read a line into data, without including CR or LF.  Returns
 *		number of characters read.  Ignore empty lines and try again.
 *		Strip off ending CR, LF, and spaces.
 */
static int
crn_read_line(char *data, int space)
{
  char buf[LINE_SIZE+3];
  char *line;
  char c;
  int n = 0, i = 0;

  /* No reason to read in zero data */
  if (space == 0)
    return(0);

  BCLEAR(buf);

  /* Read in the line */
  if (FGets != NULL) {
    line = FGets(buf, LINE_SIZE+3, InputFile);

  } else {
    if (Getc == NULL) {
      FGets = (int (*)())fgets;
      return(crn_read_line(data, space));
    }
    
    line = buf;
    do {
      c = Getc();
      if (c != '\r' && c != '\n' && c != '\0') {
	buf[n++] = c;
      }
    } while (c != '\n' && n < LINE_SIZE+3 && c != EOF);

    /* This is bad if this is true, but check to make sure anyways. */
    if (c == EOF)
      line = NULL;
  }

  /* Make sure that we got input. If not, return a zero length. */
  if (line == NULL)
    return(0);

  /* Strip off trailng stuff */
  for (n = strlen(buf); n >= 0; n--)
    if (buf[n] == '\r' || buf[n] == '\n' || buf[n] == ' ')
      buf[n] = '\0';

  /* Ignore blank lines */
  if ((n = strlen(buf)) == 0)
    return(crn_read_line(data, space));

  if (charon_debug)
    fprintf(ErrorFile, "crn_get: %s\n", buf);

  /* Copy in the known amount of space into the data provided */
  strncpy(data, buf, space);
  return(n);
}

/* Requires:	len be less than LINE_SIZE
 * Effects:	write len characters of data out, and add a CR and LF 
 */
static int
crn_put_line(char *data, u_int len)
{
  char c[LINE_SIZE+3];
  int n = 0;

  if (len == 0 || strlen(data) == 0)
    return(CRN_OK);

  BCLEAR(c);
  strncpy(c, data, len);
  strcat(c, "\r\n");

  if (charon_debug)
    fprintf(ErrorFile, "crn_put: %s", c);

  if (FPuts != NULL) {
    FPuts(c, OutputFile);
    return(CRN_OK);
  }

  if (Puts != NULL) {
    Puts(c, len + 2);
    return(CRN_OK);
  }

  if (Putc == NULL) {
    FPuts = (int(*)())fputs;
    return(crn_put_line(data, len));
  }

  for (n = 0; n < len + 2; n++) {
    Putc(c[n]);
  }

  return(CRN_OK);
}

/* Modifies:	ErrorFile, charon_debug
 * Effects:	Tries to open file for write and will set ErrorFile to
 *	that file if possible.  Also sets charon_debug if successful.
 */
void
crn_rpc_set_debug_file(char *file)
{
  ErrorFile = fopen(file, "w");
  if (ErrorFile != NULL) 
    charon_debug = 1;
}
