/*
 *****************************************************************************
 *
 *        Copyright 1989, Xylogics, Inc.  ALL RIGHTS RESERVED.
 *
 * ALL RIGHTS RESERVED. Licensed Material - Property of Xylogics, Inc.
 * This software is made available solely pursuant to the terms of a
 * software license agreement which governs its use.
 * Unauthorized duplication, distribution or sale is strictly prohibited.
 *
 * Module Description::
 *
 * 	Secure Remote Procedure Call (SRPC) library
 *
 * Original Author: Dave Harris		Created on: July 14, 1987
 *
 * Module Reviewers:
 *
 *	%$(reviewers)$%
 *
 * Revision Control Information:
 *
 * $Header: /annex/mckinley/src/libannex/RCS/srpc.c,v 1.17.5.2 1995/08/30 17:01:53 slu Exp $
 *
 * This file created by RCS from:
 *
 * $Source: /annex/mckinley/src/libannex/RCS/srpc.c,v $
 *
 * Revision History:
 *
 * $Log: srpc.c,v $
 *
 * Revision 1.17.5.2  1995/08/30  17:01:53  slu
 * Cleanup debug variable slubuf, and enhanced debug output.
 *
 * Revision 1.17.5.1  1995/08/16  12:44:38  slu
 * Support NT.
 *
 * Revision 1.17  1994/11/16  09:26:02  parker
 * Changes for port to Windows NT
 *
 * Revision 1.16  1993/12/30  13:14:21  carlson
 * SPR 2084 -- made compatible with 64 bit machines.
 *
 * Revision 1.15  1992/11/23  14:19:13  carlson
 * Annex code up to and including R7.0 can return a zero-length message
 * in response to a srpc call from na.  We have to accept these.
 *
 * Revision 1.14  92/11/13  14:37:30  carlson
 * SPR 1080 -- srpc_callresp now checks the sequence numbers on the
 * data received.
 *
 * Revision 1.13  92/03/10  16:13:04  carlson
 * Added a few simple notes where the unfixable problems are.
 *
 * Revision 1.12  91/08/01  18:06:44  emond
 * Made changes to force a signed char on certain machines which default
 * to unsighed chars, per Griff.
 *
 * Revision 1.11  91/04/08  14:28:53  emond
 * ifndef'ed include of socket.h since we can optionally work with TLI instead.
 *
 * Revision 1.10  89/10/16  17:29:38  loverso
 * Add workaround for broken Convergent compiler
 *
 * Revision 1.9  89/04/13  16:19:53  loverso
 * further alignment fixes
 *
 * Revision 1.8  89/04/05  12:40:24  loverso
 * Changed copyright notice
 *
 * Revision 1.7  88/11/29  14:52:35  harris
 * Random_seed argument is treated as a long, so pass it as one.  Sun 4/Gould.
 *
 * Revision 1.6  88/05/31  17:12:29  parker
 * fixes to get XENIX/SLIP to build again
 *
 * Revision 1.5  88/05/25  09:15:58  parker
 * include file changes
 *
 * Revision 1.4  88/05/19  18:01:19  harris
 * Changed encryption algortihm and SRPC protocol to be base on 16 byte
 * keys and translation tables generated from various keys.
 *
 * Revision 1.3  88/05/10  17:21:23  harris
 * SYS_V uses rand() and srand() instead of random() and srandom().
 *
 * Revision 1.2  88/05/05  16:52:45  harris
 * Enhanced encryption algorithm.  This allows wrong password detection.
 *
 * Revision 1.1  88/05/04  17:39:55  harris
 * Initial revision
 *
 * Revision 1.2  88/04/15  12:10:04  mattes
 * SL/IP integration
 *
 * Revision 1.1  87/07/28  11:53:43  parker
 * merged in security
 *
 *
 * This file is currently under revision by:
 *
 * $Locker:  $
 *
 *****************************************************************************
 */

#define RCSDATE $Date: 1995/08/30 17:01:53 $
#define RCSREV	$Revision: 1.17.5.2 $
#define RCSID   "$Header: /annex/mckinley/src/libannex/RCS/srpc.c,v 1.17.5.2 1995/08/30 17:01:53 slu Exp $"
#ifndef lint
static char rcsid[] = RCSID;
#endif

/*
 *	Include Files
 */
#include "../inc/config.h"

#include "../inc/port/port.h"
#include <sys/types.h>

#ifndef _WIN32
#include <netinet/in.h>
#include <sys/uio.h>
#include <strings.h>
extern	time_t	time();
#else
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <process.h>
#include "../inc/port/xuio.h"
#endif

#include "srpc.h"
#include "../inc/courier/courier.h"

extern	int	debug;

#ifndef _WIN32
extern	char	*malloc();
extern	long	mrand48();
#endif

#ifdef NEED_SIGNED_CHARS
#define signchar(x) (x & 0x80?(0xffffff00 | x):x)
#else
#define signchar(x) (x)
#endif


/* Routine Declarations */
UINT32 random_long();
unsigned char random_byte();
void random_seed();
void random_key();
void generate_table();
void erpc_call ();
int erpc_callresp ();
void erpc_reject();
void erpc_return ();
void erpc_abort ();
int srpc_create();
int srpc_answer();
int srpc_decode();
int srpc_cmp();
int srpc_return();
int srpc_call();
int srpc_callresp();
int rejected();
int aborted();
KEYDATA *make_table();
void cipher();
int dump();
int printkey();

/*
 *	Define encryption tables to be used so we don't need to allocate
 *	memory in cases where new encryption tables are required.
 */

KEYDATA		rtable;		/*  place to hold rcv side crypt tables  */
KEYDATA		xtable;		/*  place to hold xmt side crypt tables  */


srpc_create(Srpc, s, to, pid, rpnum, rpver, rproc, key)

SRPC			*Srpc;	/*  pointer to srpc_state structure  */
int			s;	/*  socket file descriptor  */
struct sockaddr_in	*to;	/*  destination information  */
UINT32			pid,	/*  supposedly unique pep id  */
			rpnum;	/*  remote program number  */
unsigned short		rpver,	/*  remote program version #  */
			rproc;	/*  remote procedure number  */
KEYDATA			*key;	/*  ptr to encryption info  */
{
	int		retcd;	/*  return from srpc_callresp  */
	int		size;	/*  size of returned message  */
	int		i;	/*  loop  */
	SRPC_OPEN	open;	/*  SRPC open message parameters  */
	SRPC_RETN	retn;	/*  SRPC open returned parameters  */

	/*  Use key tables passed by client  */

	Srpc->xmt_key = key;

	/*  Randomly select new key - place in open message  */

	if(key && key->password[0])
	{
	    random_seed(Srpc->xmt_key->password);
	    Srpc->rcv_key = make_table((char *)0, &rtable);
	    (void)bcopy(Srpc->rcv_key->password, open.open_key, KEYSZ);
	}
	else
	{
	    time_t clock;

            clock = time((time_t *)0);
	    random_seed(ctime(&clock));
	    Srpc->rcv_key = (KEYDATA *)0;
	    for(i = 0; i < KEYSZ; i++)
		open.open_key[i] = '\0';
	}

	if(debug)
	{
	    printf("Host sent call with new key.\n");
	    printkey(open.open_key);
	    printf("Sequence was %08lX",Srpc->rcv_seq);
	}

	/*  Randomly select a SRPC handle and sequence number  */

	Srpc->srpc_id = random_long();
	Srpc->xmt_seq = Srpc->rcv_seq = random_long();	/* same for now */

	if (debug)
	    printf(", sequence changed to %08lX.\n",Srpc->rcv_seq);

	/*  Now we make procedure call, encrypted with shared key  */

	retcd =
	srpc_callresp(Srpc, s, to, pid, rpnum, rpver, rproc,
		      (char *)&open, sizeof(SRPC_OPEN),
		      SRPC_DELAY, SRPC_TIMEOUT,
		      (char *)&retn, sizeof(SRPC_RETN), &size);

	if(retcd)
	    return(retcd);

	if(debug)
	{
	    printf("Host received answer with final key\n");
	    printkey(retn.retn_key);
	}

	/*
	 *  The response has been decrypted using the key selected
	 *  above.  But now, we switch to the final key, returned to
	 *  us by the server, in the open return message.  Or null.
	 */

	if(key && key->password[0])
	    Srpc->xmt_key = make_table(retn.retn_key, &xtable);
	else
	    Srpc->xmt_key = (KEYDATA *)0;

	Srpc->rcv_key = Srpc->xmt_key;		/* Got it? */

	return S_SUCCESS;
}


srpc_answer(Srpc, s, to, pid, key, message, length)

SRPC			*Srpc;		/* pointer to SRPC state structure */
int			s;		/* file descriptor for socket */
struct sockaddr_in	*to;		/* destination address */
UINT32			pid;		/* PID to return to remote caller */
KEYDATA			*key;		/* encryption key */
char			*message;	/* message containing open call */
int			length;		/* length of "message" */
{
	int 		ret;		/* return code from srpc_return */
	int		i;		/* loop */
	SRPC_OPEN	data;		/* decrypted open message args */
	SRPC_RETN	response;	/* response (return args) */

	/*  point to encryption tables, presumably setup by client  */

	Srpc->rcv_key = key;

	/*  reject any message too short to have a SRPC header  */

	if(length < sizeof(SHDR))
	{
		erpc_reject(s, (struct sockaddr_in *)(0),
			    pid, CMJ_INVARG, 0, 0);
		if(debug)
		  printf("Message rejected: SRPC OPEN is too short!\n");

		return S_FRAGMENT;
	}
	else
	{
		srpc_decode(Srpc, message, (char *)&data, sizeof(SRPC_OPEN));

	/*
	 *	For backwards compatibilty - if message is shorter than
	 *	the current SRPC_OPEN message definition, we probably
	 *	have an open from an Annex running release R3.0, with
	 *	shorter key - assume that the key is zero.
	 */

		if(length < sizeof(SRPC_OPEN))
		    for(i = 0; i < KEYSZ; i++)
			data.open_key[i] = '\0';

		Srpc->srpc_id = ntohl(data.open_hdr.srpc_id);
		Srpc->rcv_seq = ntohl(data.open_hdr.sequence);
		Srpc->xmt_seq = ~Srpc->rcv_seq;

	/*
	 *	Create new encryption tables base on key just received
	 *	and newly selected key at random.  Or disable encryption.
	 */
		if(key && key->password[0])
		{
			random_seed(Srpc->rcv_key->password);
			Srpc->xmt_key = make_table(data.open_key, &xtable);
			Srpc->rcv_key = make_table((char *)0, &rtable);
			(void)bcopy(Srpc->rcv_key->password,
				    response.retn_key, KEYSZ);
		}
		else
		{
			Srpc->rcv_key = (KEYDATA *)0;
			for(i = 0; i < KEYSZ; i++)
			    response.retn_key[i] = '\0';
		}

	/*
	 *	Return the answer containing final key, encrypted with
	 *	the key just received in the open message, if any
	 */

		ret = srpc_return(
			Srpc, s, to, pid,
			(char *)&response, sizeof(SRPC_RETN));

		if(ret)
			return ret;

/*
 * This SHOULD be done, but the srpc protocol doesn't match its own
 * specification.  Someday this should be rewritten.
 */
		/* Srpc->xmt_seq++; */

	/*
	 *	Now synchronize rcv and xmt side encryption
	 */

		Srpc->xmt_key = Srpc->rcv_key;
	}

	return S_SUCCESS;
}

srpc_decode(Srpc, message, arg, nbytes)

SRPC		*Srpc;		/*  pointer to srpc_state structure  */
char		*message,	/*  message to be interpreted        */
		*arg;		/*  decrypted message and srpc head  */
int		nbytes;		/*  number of bytes in message       */
{
	if (Srpc->rcv_key && Srpc->rcv_key->password[0]) {
	    if (debug)
		printf("srpc_decode:  deciphering.\n");
	    cipher(message, arg, nbytes, Srpc->rcv_key);
	} else {
	    if (debug)
		printf("srpc_decode:  copying. message=%s, arg=%s, nbytes=%d\n", message, arg, nbytes);
	    bcopy(message, arg, nbytes);
	}
	return 0;
}

void srpc_encode(Srpc, message, arg, nbytes)

SRPC		*Srpc;		/*  pointer to srpc_state structure  */
char		*message,	/*  message to be interpreted        */
		*arg;		/*  decrypted message and srpc head  */

int		nbytes;		/*  number of bytes in message       */
{
	if(Srpc->xmt_key && Srpc->xmt_key->password[0])
	  cipher(message, arg, nbytes, Srpc->xmt_key);
	else
	  bcopy(message, arg, nbytes);

	return;
}

srpc_cmp(Srpc, Shdr)			/*  validate SRPC header  */

SRPC	*Srpc;			/*  pointer to srpc_state structure  */
SHDR	*Shdr;			/*  pointer to recvd message header  */
{
	return (Srpc->srpc_id == ntohl(Shdr->srpc_id));
}

srpc_return(Srpc, s, to, pid, rdata, rlength)

SRPC			*Srpc;	/*  pointer to srpc_state structure  */
int			s;	/*  socket file descriptor  */
struct sockaddr_in 	*to;	/*  destination information  */
UINT32			pid;	/*  supposedly unique pep id  */
int			rlength;/*  length of return message  */
char			*rdata; /*  return message data area  */
{
	struct iovec	iov[2];
	int		iovlen = 1;	/*  erpc_return adds 1  */
	SHDR		*header;
	char		xdata[SRPC_MAX];

	header = (SHDR *)rdata;
	header->srpc_id = htonl(Srpc->srpc_id);
	header->sequence = htonl(Srpc->rcv_seq);

	srpc_encode(Srpc, rdata, xdata, rlength);

	iov[1].iov_base = xdata;
	iov[1].iov_len  = rlength;


	erpc_return(s, to, pid, iovlen, iov);

	return S_SUCCESS;
}


srpc_call(Srpc, s, to, pid, rpnum, rpver, rproc, cdata, clength)

SRPC			*Srpc;	/*  pointer to srpc_state structure  */
int			s,	/*  socket file descriptor  */
			clength;/*  length of call message  */
struct sockaddr_in	*to;	/*  destination information  */
UINT32			pid,	/*  supposedly unique pep id  */
			rpnum;	/*  remote program number  */
unsigned short		rpver,	/*  remote program version #  */
			rproc;	/*  remote procedure number  */
char			*cdata;	/*  call message data area  */
{
	struct iovec	iov[2];
	int		iovlen = 1;
	SHDR		*header;
	char		xdata[SRPC_MAX];

	header = (SHDR *)cdata;
	header->srpc_id = htonl(Srpc->srpc_id);
	header->sequence = htonl(Srpc->xmt_seq++);

	srpc_encode(Srpc, cdata, xdata, clength);

	iov[1].iov_base = xdata;
	iov[1].iov_len = clength;

	erpc_call(s, to, pid, rpnum, rpver, rproc, iovlen, iov);

	return S_SUCCESS;
}


srpc_callresp(Srpc, s, to, pid, rpnum, rpver, rproc,
	      cdata, clength, delay, timeout,
	      rdata, rlength, status)

SRPC			*Srpc;	/*  pointer to srpc_state structure  */
int			s,	/*  socket file descriptor  */
			delay,	/*  expected turnaround time  */
			timeout,/*  maximum time for response  */
			clength,/*  length of call message  */
			rlength,/*  length of return message  */
			*status;/*  actual length, or abort/reject type  */
struct sockaddr_in	*to;	/*  destination information  */
UINT32			pid,	/*  supposedly unique pep id  */
			rpnum;	/*  remote program number  */
unsigned short		rpver,	/*  remote program version #  */
			rproc;	/*  remote procedure number  */
char			*cdata,	/*  call message data area  */
			*rdata;	/*  return message data area  */
{
	struct iovec	iov[2], riov[3];

	int		erpc_retcd,	/* return from erpc_callresp */
			rcode,		/* return code reject? abort? */
			alength,	/* actual length of return data */
			skipsend;
	SHDR		*header;
	CMRETURN	cmr;
	char		xdata[SRPC_MAX],
			zdata[SRPC_MAX];

	header = (SHDR *)cdata;
	header->srpc_id = htonl(Srpc->srpc_id);
	header->sequence = htonl(Srpc->xmt_seq++);

	if(debug)
		dump("call", cdata, clength);

	srpc_encode(Srpc, cdata, xdata, clength);

	skipsend = 0;

try_again:
	iov[1].iov_base = xdata;
	iov[1].iov_len = clength;
	riov[1].iov_base = (char *)&cmr;
	riov[1].iov_len = sizeof(CMRETURN);
	riov[2].iov_base = zdata;
	riov[2].iov_len = rlength;

	erpc_retcd =
	erpc_callresp(s, to, pid, rpnum, rpver, rproc,
		      1, iov, delay, timeout, 2, riov,
		      skipsend);

	skipsend = 1;

	if(erpc_retcd == -1)
	{
		*status = 0;
		return S_TIMEDOUT;
	}
	alength = erpc_retcd;		/* get nbytes returned by erpc */
	alength -= sizeof(CMRETURN);	/* subtract courier header size */

	if(alength < 0)
	{
		*status = 0;
		return S_FRAGMENT;
	}

	/*  Note that status of -1 for reject or abort indicates fragment  */

	if(rcode = rejected(riov, alength))
	{
		*status = rcode;
		return S_REJECTED;
	}

	if(rcode = aborted(riov, alength))
	{
		*status = rcode;
		return S_ABORTED;
	}
	*status = alength;
	srpc_decode(Srpc, zdata, rdata, alength);

	if (debug)
		dump("return", rdata, alength);

/* void data returned -- assume everything's ok. */
	if (alength < sizeof(SHDR))
		return S_SUCCESS;

	header = (SHDR *)rdata;
	if (ntohl(header->srpc_id) != Srpc->srpc_id ||
	    ntohl(header->sequence) != Srpc->xmt_seq) {
		if (debug)
			printf("srpc_callresp:  got ID/seq %X/%X, expected %X/%X.\n",ntohl(header->srpc_id),ntohl(header->sequence),Srpc->srpc_id,Srpc->xmt_seq);
		goto try_again;
	}

	return S_SUCCESS;
}


/*
 *	The routines rejected() and aborted() make a few assumptions about
 *	courier.  A Courier return message header is equal to a Courier
 *	message header, and two bytes shorter than an abort or reject.  The
 *	two bytes are used to store a reject or abort detail code, net-wise.
 */

int
rejected(iov, length)		/*  see if msg is a valid reject  */

struct	iovec	iov[];		/*  iovec - pep hdr, courier hdr, detail  */
int		length;		/*  calculated length of detail segment  */
{
	COUR_MSG	*Courier;

	Courier = (COUR_MSG *)iov[1].iov_base;

	switch (ntohs(Courier->cm_type))
	{
	    case C_RETURN:
	    case C_ABORT:

		return 0;
		break;

	    case C_REJECT:

		if(length < sizeof (unsigned short))
		  return -1;
		else
		  return (int)ntohs(*(unsigned short *)iov[2].iov_base);
		break;
	}
	return -1;
}

int
aborted(iov, length)		/*  see if msg is a valid abort  */

struct	iovec	iov[];		/*  iovec - pep hdr, courier hdr, detail  */
int		length;		/*  calculated length of detail segment  */
{
	COUR_MSG	*Courier;

	Courier = (COUR_MSG *)iov[1].iov_base;

	switch (ntohs(Courier->cm_type))
	{
	    case C_RETURN:
	    case C_REJECT:

		return 0;
		break;

	    case C_ABORT:

		if(length < sizeof (unsigned short))
		  return -1;
		else
		  return (int)ntohs(*(unsigned short *)iov[2].iov_base);
		break;
	}
	return -1;
}

void random_seed(seed)

char	*seed;
{
	int	value = 0;
    int the_time;
    int result;
    
	/*
	 *  Hash 32-bits from a character string to seed srand48()
	 */

	while(*seed)
	{
		value *= 29;
		value += (*seed) % 31;
		seed++;
	}

    the_time = (int)time((time_t *)0);
    result = (int)(value ^ (the_time * getpid()));
	srand48(result);

	return;
}

UINT32
random_long()
{
	return (UINT32)mrand48();
}

unsigned char
random_byte()
{
        UINT32  i;

        i = mrand48();
        i = i >> 24;
        i &= 0xff;
        return((unsigned char) i );		/* this value has only 8 bits of precision */
}

KEYDATA *
make_table(key, table)

char	*key;
KEYDATA	*table;
{
	if(!table)
	{
	    table = (KEYDATA *)malloc(sizeof(KEYDATA));

	    if(!table)
		goto nomem;
	}

	if(!key)
	    random_key(table->password);
	else
	    (void)bcopy(key, table->password, KEYSZ);

	generate_table(table);

nomem:
	return table;
}

void random_key(password)

char	*password;
{
	int		i;
	UINT32		key;

	for(i = 0; i < KEYSZ; i += sizeof(UINT32))
	{
	    key = random_long();
	    (void)bcopy((char *)&key, &password[i], sizeof(UINT32));
	}

	for(i = 0; i < KEYSZ - 1; i++)
	    if(!password[i])
		password[i] = (char)0xff;

	password[KEYSZ - 1] = '\0';

	return;
}

void generate_table(table)

KEYDATA	*table;
{
	int xc, i, k, temp;
	int slen;
	unsigned psuedo;
	INT32 seed, ptmp;
	char *buf = table->password;
	char *taba = table->table_a;
	char *tabb = table->table_b;
	char *tabc = table->table_c;

	slen = strlen(buf);
	if(!slen)
	  goto skipit;

	seed = 997;

	for (i = 0; i < slen; i++)
 		seed = seed * signchar(buf[i]) + i;

	for(i = 0; i < TABSZ; i++)
	{
		taba[i] = i;
		tabc[i] = '\0';
	}

	for(i = 0; i < TABSZ; i++)
	{
 		seed = 5 * seed + signchar(buf[i % slen]);  /*buf was signed */
		/*
		 * Use temp variable
		 * (workaround for compiler bug on Convergent)
		 */
		ptmp = seed % 65521;
		psuedo = ptmp;
		k = TABSZ - 1 - i;
		xc = (psuedo & MASK) % (k + 1);
		psuedo >>= 8;
		temp = taba[k];
		taba[k] = taba[xc];
		taba[xc] = temp;
		if(tabc[k] != 0) continue;
		xc = (psuedo & MASK) % k;
		while(tabc[xc] != 0) xc = (xc + 1) % k;
		tabc[k] = xc;
		tabc[xc] = k;
	}

	for(i = 0; i < TABSZ; i++)
		tabb[taba[i] & MASK] = i;
skipit:
	return;
}

void cipher(src, dst, len, table)
char	*src;		/* pointer to message to be encrypted */
char	*dst;		/* pointer to place to put result */
int	len;		/* length of message */
KEYDATA	*table;		/* translation tables */
{
	int	i, xa, xb, y;
	char	*taba = table->table_a;
	char	*tabb = table->table_b;
	char	*tabc = table->table_c;

	xa = xb = 0;

	for(i = 0; i < len; i++, src++, dst++)
	{
 		y = (signchar(taba[(signchar(*src)+xa)&MASK])+xb)&MASK;
 		*dst = signchar(tabb[(signchar(tabc[y])-xb)&MASK])-xa;
		xa++;
		xa &= MASK;
		if(!xa)
		{
			xb++;
			xb &= MASK;
		}
	}
}

dump(mesg, address, length)

char		*mesg;
char		*address;
int		length;
{
	unsigned char *data = (unsigned char *)address;
	int i;

	printf("dump %s: \n< ", mesg);

	for(i = 0; i < length; i++)
	    printf("%2.2x ", data[i]);

	printf(">\n");
	return 0;
}

printkey(string)
char	*string;
{
	dump("Key in hex",string,KEYSZ);
	return 0;
}
