/*   OPENSSL/SSLEAY BUFFER I/O object  for use with multithreaded routines. */
/*
 *  This module provides a BIO_METHOD class for use with multi-threaded
 *  SSL applications on OpenVMS.  It also provides a set of replacement
 *  routines for the internal locking calls used by the SSL library
 *  the use POSIX threads to perform the locking.
 *
 *  The BIO_s_mst method distributes the I/O functions to a remote
 *  process via a message-based protocol the multi-plexes the 2 data
 *  channels over a single I/O link.
 *
 *  Global functions:
 *    BIO_METHOD *BIO_s_mst()	Buffered I/O class for use in an SSL_CTX.
 *				Set the FD to an opaque longword that is
 *				the handle for the externally called read/
 *				write functions (see below).
 *
 *    int pt_initialize()	Initializes pthread-based lock structures
 *				and sets up callbacks in  SSL library.
 *				Returns 1 on success.
 *
 *  External references (provided by app):
 *     int mst_write ( 		! Returns odd for success.
 *	    void *handle, 	! 'FD'
 *	    char *buffer, 	! buffer to write.
 *          int bufsize,	! Size to write.
 *          int *written )	! bytes actuall written.
 *	int mst_read (
 *	    void *handle, 	! 'FD'
 *	    char *buffer, 	! buffer to read
 *          int bufsize,	! Max number to read.
 *          int *length )	! bytes read.
 *
 *
 *  Author: David Jones
 *  Revised: 25-SEP-1997	update for 0.8.1 (BIO_CTRL_SET->BIO_C_SET_FD).
 *  Revised: 26-SEP-1997	Remove DNET_* calls, use MST_* only.
 *  Revised: 22-APR-1999	Move expected location to 'base_code' directory.
 *  Revised: 12-JAN-2001	Add entropy to random number generator on init.
 *  Revised: 14-DEC-2001		Add dynamic lock callbacks.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <jpidef.h>			/* VMS getjpi codes */
#include <ssdef.h>			/* VMS sys. service return codes */
#include <descrip.h>			/* VMS descriptors */
/*
 * Include header files from http server source tree.
 */
#include "message_service.h"
#include "pthread_1c_np.h"		/* pthreads portability wrapper */
int tlog_putlog();				/* error log routine */
/*
 * Include headers for SSL library calls.  We must assume following
 * compilers qualifiers are used:
 *     /STANDARD=ANSI89
 *     /INCLUDE=SSLINCLUDE:
 *     /WARNING=DISABLE=DOLLARID
 */
#include "buffer.h"
#include "bio.h"
#include "crypto.h"
#include "rand.h"

/*************************************************************************/
struct rpc_msg {		/* Should have member alignment inhibited */
   char channel;		/* 'A'-app data. 'R'-remote client 'G'-global */
   char function;		/* 'G'-get, 'P'-put, 'C'-confirm, 'X'-close */
   unsigned short int length;	/* Amount of data returned or max to return */
   char data[4092];		/* variable data */
};
#define RPC_HDR_SIZE (sizeof(struct rpc_msg) - 4092)

struct rpc_ctx {
    int filled, pos;		/* Stream position in data buffer */
    void *cnx;			/* handle for network connection */
    struct rpc_msg msg;		/* I/O buffer */
};

static int rtcp_write(BIO *h,char *buf,int num);
static int rtcp_read(BIO *h,char *buf,int size);
static int rtcp_puts(BIO *h,char *str);
static int rtcp_gets(BIO *h,char *str,int size);
static long rtcp_ctrl(BIO *h,int cmd,long arg1,char *arg2);
static int rtcp_new(BIO *h);
static int rtcp_free(BIO *data);

static BIO_METHOD rtcp_method=
	{
	BIO_TYPE_FD,"RTCP",
	rtcp_write,
	rtcp_read,
	rtcp_puts,
	rtcp_gets,
	rtcp_ctrl,
	rtcp_new,
	rtcp_free,
	};

BIO_METHOD *BIO_s_mst()
	{
	return(&rtcp_method);
	}
/*
 * Declare storage for locks and string tables to convert arguments to text.
 */
struct pt_rw_lock {		/* basic structure for read/write lock */
    pthread_mutex_t mutex;	/* Mutex for accessing structure elements */
    pthread_cond_t update_done;
    int reader_count;
    int update_pending;
};

static struct pt_rw_lock lock_table[CRYPTO_NUM_LOCKS];

static char *lock_modes[4] = { "null", "lock", "unlock", "Lock+unlock" };
static char *access_modes[4] = { "none", "read", "write", "read+write" };
static char* lock_names[CRYPTO_NUM_LOCKS] =	/* For debugging display */
	{
	"<<ERROR>>",	"err",	"err_hash",	"x509",
	"x509_info",	"x509_pkey",	"x509_crl",	"x509_req",
	"dsa",	"rsa",	"evp_pkey",	"certificate_ctx",
	"ssl_ctx",	"ssl_cert",	"ssl_session",	"ssl",
	"Random", "malloc", "bio", "bio_gethostbyname"
	};

/***********************************************************************/
/* Routines for returning thread ID, which is zero until set by a
 * higher layer.  Save in thread specific storage.
 */
static pthread_key_t identity;
static void identity_rundown ( void *ctx )
{
}

unsigned long pt_thread_id()
{
    unsigned long *result;
    GET_SPECIFIC ( identity, result );
    return result ? *result : 0;
}
pt_set_identity ( unsigned long *ndx )
{
    /* note that storage pointed to by ndx must remain for life of thread. */
    pthread_setspecific ( identity, ndx );
}
/***********************************************************************/
/* Dynamic lock functions.
 */
#define PT_DYNLOCK 1
#ifdef PT_DYNLOCK

struct CRYPTO_dynlock_value {
    struct pt_rw_lock lock;
    char name[28];
};

struct CRYPTO_dynlock_value *pt_create_dyn_lock ( char *file, int line )
{
    struct CRYPTO_dynlock_value *v;
#ifdef LOCK_DEBUG
    printf ( "Allocating dynamic lock, file: %s line %d...\n", file, line );
#endif
    v = malloc ( sizeof(struct CRYPTO_dynlock_value) );
    if ( v ) {
	sprintf ( v->name, "Dynamic %x", v );
	INITIALIZE_MUTEX ( &v->lock.mutex );
	INITIALIZE_CONDITION ( &v->lock.update_done );
	v->lock.reader_count = v->lock.update_pending = 0;
    }

    return v;
}
void pt_dyn_lock ( int mode, struct CRYPTO_dynlock_value *l, const char *file,
	int line )
{
    struct pt_rw_lock *lock;
#ifdef LOCK_DEBUG
    const char *fname;
    fname = strstr ( file, "]" );	/* trim directory path */
    if ( !fname ) fname = file; else fname++;
    printf("pt_dyn_lock request for %s: %s - %s %s/%d\n", l->name, 
	lock_modes[mode&3], access_modes[(mode/4)&3], fname, line );
#endif
    /*
     * Always need mutex locked before we can examine structure.
     */
    lock = &l->lock;
    pthread_mutex_lock ( &lock->mutex );
    /*
     * Interpret the mode argument.
     */
    if ( mode&CRYPTO_LOCK ) {
	/*
	 * In all cases, update_pending flag must be clear before we can
	 * proceed.
	 */
	while ( lock->update_pending )
	    pthread_cond_wait ( &lock->update_done, &lock->mutex );

	if ( mode&CRYPTO_READ ) lock->reader_count++;
	else /* if ( mode&CRYPTO_WRITE ) */ {
	    /*
	     * Assume not-read mode means write (safe way to allow caller to 
	     * get away  with a using mode CRYPTO_LOCK/CRYPT_UNLOCK only.
	     * Set write pending flag to block out new access to lock and wait
	     * for readers to clear.
	     */
	    lock->update_pending = 1;
	    while ( lock->reader_count > 0 )
		pthread_cond_wait ( &lock->update_done, &lock->mutex );
	    lock->reader_count = 1;	/* re-aquire read lock for ourselves */
	}
    } else if ( (mode&CRYPTO_UNLOCK) && (lock->reader_count > 0) ) {
	/*
	 * Signal potential waiters when we are last reader/writer to release
	 * lock.  If we are the writer (mode not CRYPTO_READ) clear write
	 * pending flag.
	 */
	--lock->reader_count;
	if ( (lock->reader_count == 0) && lock->update_pending ) {
	    if ( (mode&CRYPTO_READ) == 0 ) lock->update_pending = 0;
	    pthread_cond_broadcast ( &lock->update_done );
	}
    } else {
	/* oops */
	tlog_putlog ( 0,
	   "!AZ '!AZ', mode=!AZ access=!AZ, thread=!SL at line !SL in file !AZ!/",
		"BUGCHECK, invalid request for lock", l->name, 
		lock_modes[mode&3], access_modes[(mode/4)&3],
		pt_thread_id(), line, file );
    }
    pthread_mutex_unlock ( &lock->mutex );
    
}
void pt_destroy_dyn_lock ( struct CRYPTO_dynlock_value *l, const char *file,
	int line )
{
#ifdef LOCK_DEBUG
    printf ( "Destroying dynamic lock: %s\n", l->name );
#endif
   free ( l );
}
#endif
/***********************************************************************/
void pt_lock ( int mode, int id, char *file, int line )
{
    struct pt_rw_lock *lock;
#ifdef LOCK_DEBUG
    char *fname;
    fname = strstr ( file, "]" );	/* trim directory path */
    if ( !fname ) fname = file; else fname++;
    printf("pt_lock request for %s: %s - %s %s/%d\n", lock_names[id], 
	lock_modes[mode&3], access_modes[(mode/4)&3], fname, line );
#endif
    /*
     * Always need mutex locked before we can examine structure.
     */
    lock = &lock_table[id];
    pthread_mutex_lock ( &lock->mutex );
    /*
     * Interpret the mode argument.
     */
    if ( mode&CRYPTO_LOCK ) {
	/*
	 * In all cases, update_pending flag must be clear before we can
	 * proceed.
	 */
	while ( lock->update_pending )
	    pthread_cond_wait ( &lock->update_done, &lock->mutex );

	if ( mode&CRYPTO_READ ) lock->reader_count++;
	else /* if ( mode&CRYPTO_WRITE ) */ {
	    /*
	     * Assume not-read mode means write (safe way to allow caller to 
	     * get away  with a using mode CRYPTO_LOCK/CRYPT_UNLOCK only.
	     * Set write pending flag to block out new access to lock and wait
	     * for readers to clear.
	     */
	    lock->update_pending = 1;
	    while ( lock->reader_count > 0 )
		pthread_cond_wait ( &lock->update_done, &lock->mutex );
	    lock->reader_count = 1;	/* re-aquire read lock for ourselves */
	}
    } else if ( (mode&CRYPTO_UNLOCK) && (lock->reader_count > 0) ) {
	/*
	 * Signal potential waiters when we are last reader/writer to release
	 * lock.  If we are the writer (mode not CRYPTO_READ) clear write
	 * pending flag.
	 */
	--lock->reader_count;
	if ( (lock->reader_count == 0) && lock->update_pending ) {
	    if ( (mode&CRYPTO_READ) == 0 ) lock->update_pending = 0;
	    pthread_cond_broadcast ( &lock->update_done );
	}
    } else {
	/* oops */
	tlog_putlog ( 0,
	   "!AZ '!AZ', mode=!AZ access=!AZ, thread=!SL at line !SL in file !AZ!/",
		"BUGCHECK, invalid request for lock", lock_names[id], 
		lock_modes[mode&3], access_modes[(mode/4)&3],
		pt_thread_id(), line, file );
    }
    pthread_mutex_unlock ( &lock->mutex );
}

int pt_add_lock ( int *counter, int increment, int id, char *file, int line )
{
     /*
      * Operation is nothing more complicated than a mutex around an update.
      */
    int result;
#ifdef LOCK_DEBUG
    char *fname;
    fname = strstr ( file, "]" );
    if ( !fname ) fname = file; else fname++;
    printf("pt_add_lock request for %s: %d %s/%d\n", lock_names[id], increment, fname, line );
#endif
    pthread_mutex_lock ( &lock_table[id].mutex );
    result = *counter + increment;
    *counter = result;
    pthread_mutex_unlock ( &lock_table[id].mutex );
    return result;
}

static void load_random ( )
{
    long pid, iosb[2];
    int SYS$GETJPIW(), status, i, mbx_chan, status1, status2, SYS$GETTIM();
    static long lksb[2];
    struct { short length, code; long *buffer; int *retlen; } item[10];
    struct { long pid, bufio, cputim, dio, pageflts, wssize, lgn[2]; } info;
#define SET_ITEM(A,B,C) item[A].length=sizeof(long); item[A].code=(B);\
	item[A].buffer = &info.C; item[A].retlen = (int *) 0;
    /*
     * Set up for JPI scan.
     */
    SET_ITEM(0,JPI$_PID,pid)
    SET_ITEM(1,JPI$_BUFIO,bufio)
    SET_ITEM(2,JPI$_DIRIO,dio)
    SET_ITEM(3,JPI$_CPUTIM,cputim)
    SET_ITEM(4,JPI$_PAGEFLTS,pageflts)
    SET_ITEM(5,JPI$_WSSIZE,wssize)
    SET_ITEM(6, JPI$_LOGINTIM, lgn[0])
    item[6].length = sizeof(info.lgn);
    item[7].length = item[7].code = 0;	/* terminate list */
    /*
     * Scan process list and load random number generator with results.
     */
    status1 = 1; /* RAND_status(); */
    pid = -1;			/* search context */
    for ( i = 0; ; i++ ) {
	status = SYS$GETJPIW ( 8, &pid,  0, item, iosb, 0, 0 );
	if ( status == SS$_NOMOREPROC ) break;
	if ( status == SS$_NOPRIV ) continue;
	if ( status == SS$_SUSPENDED ) continue;
	if ( status == SS$_NORMAL ) {
	    RAND_seed ( (unsigned char *) &info, sizeof(info) );
	} else {
	    printf("unexpected error in JPI call: %d\n", status );
	    break;
	}
    }
    SYS$GETTIM ( iosb );
    RAND_seed ( (unsigned char *) iosb, sizeof(iosb) );
    status2 = RAND_status();
    if ( status2 == 0 ) {
	printf("/BSS_MST/ Low entropy in random number generator\n");
    }
}

int pt_initialize ()
{
    /*
     * Initialize for multithreaded access.
     */
    int status, i;
    /*
     * Make context for saving thread ID and build lock table.
     */
    status = CREATE_KEY ( &identity, identity_rundown );
    for ( i = 0; i < CRYPTO_NUM_LOCKS; i++ ) {
	INITIALIZE_MUTEX ( &lock_table[i].mutex );
	INITIALIZE_CONDITION ( &lock_table[i].update_done );
	lock_table[i].reader_count = lock_table[i].update_pending = 0;
    }
    /*
     * Replace cryptlib's lock function with our own callbacks.
     */
    CRYPTO_set_locking_callback ( pt_lock );
    CRYPTO_set_add_lock_callback ( pt_add_lock );
#ifdef PT_DYNLOCK
    CRYPTO_set_dynlock_create_callback ( pt_create_dyn_lock );
    CRYPTO_set_dynlock_lock_callback ( pt_create_dyn_lock );
    CRYPTO_set_dynlock_destroy_callback ( pt_create_dyn_lock );
#endif    
    CRYPTO_set_id_callback ( pt_thread_id );
    /*
     * Add extra randomness.
     */
    load_random ( );
    return 1;
}
/***************************************************************************/

static int rtcp_new(bi)
BIO *bi;
{
    struct rpc_ctx *ctx;
	bi->init=1;
	bi->num=0;
	bi->flags = 0;
	bi->ptr=malloc(sizeof(struct rpc_ctx));
	ctx = (struct rpc_ctx *) bi->ptr;
	ctx->filled = 0;
	ctx->pos = 0;
	ctx->cnx = (void *) 0;
	return(1);
}

static int rtcp_free(a)
BIO *a;
{
	if (a == NULL) return(0);
	if ( a->ptr ) free ( a->ptr );
	a->ptr = NULL;
	return(1);
}
	
static int rtcp_read(b,out,outl)
BIO *b;
char *out;
int outl;
{
    int status, length, written;
    struct rpc_ctx *ctx;
    /*
     * read data, return existing.
     */
    ctx = (struct rpc_ctx *) b->ptr;
    if ( ctx->pos < ctx->filled ) {
	length = ctx->filled - ctx->pos;
	if ( length > outl ) length = outl;
	memmove ( out, &ctx->msg.data[ctx->pos], length );
	ctx->pos += length;
	return length;
    }
    /*
     * Requst more data from R channel.
     */
    ctx->msg.channel = 'R';
    ctx->msg.function = 'G';
    ctx->msg.length = sizeof(ctx->msg.data);
    status = mst_write ( (mst_link_t) ctx->cnx, (char *) &ctx->msg, 
		RPC_HDR_SIZE, &written );
    if ( (status&1) == 0 ) return -1;
    /*
     * Read.
     */
    ctx->pos = ctx->filled = 0;
    status = mst_read ( (mst_link_t) ctx->cnx, (char *) &ctx->msg, 
		sizeof(ctx->msg), &length );
    if ( (status&1) == 0 ) length = -1;
    if ( ctx->msg.channel != 'R' || ctx->msg.function != 'C' ) {
	length = -1;
    }
    ctx->filled = length - RPC_HDR_SIZE;
    
    if ( ctx->pos < ctx->filled ) {
	length = ctx->filled - ctx->pos;
	if ( length > outl ) length = outl;
	memmove ( out, ctx->msg.data, length );
	ctx->pos += length;
	return length;
    }

    return length;
}

static int rtcp_write(b,in,inl)
BIO *b;
char *in;
int inl;
{
    int status, i, segment, length, written;
    struct rpc_ctx *ctx;
    /*
     * Output data, send in chunks no larger that sizeof(ctx->msg.data).
     */
    ctx = (struct rpc_ctx *) b->ptr;
    for ( i = 0; i < inl; i += segment ) {
	segment = inl - i;
	if ( segment > sizeof(ctx->msg.data) ) segment = sizeof(ctx->msg.data);
	ctx->msg.channel = 'R';
	ctx->msg.function = 'P';
	ctx->msg.length = segment;
	memmove ( ctx->msg.data, &in[i], segment );
	status = mst_write ( (mst_link_t) ctx->cnx, (char *) &ctx->msg, 
		segment + RPC_HDR_SIZE, &written );
	if ((status&1) == 0 ) { i = -1; break; }

	status = mst_read ( (mst_link_t) ctx->cnx, (char *) &ctx->msg, 
		sizeof(ctx->msg), &length );
	if ( ((status&1) == 0) || (length < RPC_HDR_SIZE) ) { i = -1; break; }
	if ( (ctx->msg.channel != 'R') || (ctx->msg.function != 'C') ) {
	   printf("unexpected response when confirming put %c %c\n",
		ctx->msg.channel, ctx->msg.function );

	}
    }
    return(i);
}

static long rtcp_ctrl(b,cmd,num,ptr)
BIO *b;
int cmd;
long num;
char *ptr;
	{
	long ret=1;
	struct rpc_ctx *ctx;

	switch (cmd)
		{
	case BIO_CTRL_RESET:
	case BIO_CTRL_EOF:
		ret = 1;
		break;
	case BIO_C_SET_FD:
		b->num = *((int *) ptr);
		ctx = (struct rpc_ctx *) b->ptr;
		ctx->cnx = (void *) num;
		ret = 1;
	 	break;
	case BIO_CTRL_SET_CLOSE:
	case BIO_CTRL_FLUSH:
		ret=1;
		break;
	case BIO_CTRL_GET_CLOSE:
	case BIO_CTRL_INFO:
	case BIO_CTRL_GET:
	case BIO_CTRL_PENDING:
/* these seem to have evaporated between SSLeay .063 and .066, leave them
   out and whatever replaces them may fall in here anyway since it maps
   to default  DRM 28-AUG-1997

	case BIO_CTRL_SHOULD_RETRY:
	case BIO_CTRL_RETRY_TYPE:
*/
	default:
		ret=0;
		break;
		}
	return(ret);
	}

static int rtcp_gets(bp,buf,size)
BIO *bp;
char *buf;
int size;
	{
	return(0);
	}

static int rtcp_puts(bp,str)
BIO *bp;
char *str;
{
    int length;
    if (str == NULL) return(0);
    length = strlen ( str );
    if ( length == 0 ) return (0);
    return rtcp_write ( bp,str, length );
}

