/*
 * SSL server high-level interface.  Applications using this interface
 * supply callback routines that this layer will call to perform raw
 * input and output to the client connection.  The get/put callbacks
 * match the semantics of tserver_tcp's interface.
 *
 * This module (ssl_server_dnet.c) exports the callbacks to a DECnet task,
 * multiplexing 2 bi-directional data streams over a single logical link.
 *
 * Logical names:
 *    WWWSSL_OBJECT	Translates to DECnet task specification of SSL
 *			engine, default is '0::"0=WWWSSL"'.
 *
 * Author:	David Jones
 * Date:	23-JUL-1996
 * Revised:	28-NOV-2001	Support parameter array in tssl_initialize()
 * Revised:	9_DEC-2001	Add tssl_get_client_cert() function.
 */
#include "pthread_1c_np.h"	/* portability wrapper for <pthread.h> */
#include "tutil.h"		/* thread-safe string functions */
#include "decnet_access.h"	/* DECnet task-to-task */

#include <stdio.h>
#include <stdlib.h>
#include <ssdef.h>
/*
 * Basic message structure for communicating with DECnet task.
 *   Server sends 'A',G/P,length[,data], then reads and processes responsess 
 *   until a 'A','C' recieved.  Intermediate to the confirm, the remote
 *   may request operations on the 'R' channel (remote client) or 'G' channel
 *   (special global data).
 *   
 */
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)
#define MAX_HEADROOM 36
/*
 * define primary context structure returned by init_context.
 */
struct ssl_control {
    struct ssl_control *next;
    int ssl_version;		/* sll protocol in use (2, 3, etc) */
    /*
     * Communication stream.
     */
    void *tcp_ctx;		/* Context for TCP transport layer */
    int (*get)();		/* Read from client */
    int (*put)();		/* Write to client */
    int (*error)();		/* Report error */
    int max_io;			/* Largest I/O to perform to network */
    /*
     * RPC parameters.
     */
    void *link;			/* Logical link to remote task */
    int out_pos;
    int msg_filled;		/* read data portion */
    struct rpc_msg msg;		/* Data read from channel. */
    struct rpc_msg outmsg;
};
typedef struct ssl_control *ssl_context;
#define SSL_CTX 1		/* Inhibits definition of ssl_context */
#include "ssl_server.h"		/* verify prototypes */

static ssl_context free_ctx;
static pthread_mutex_t context_db;   /* Mutex to guard access to global data */
static char ssl_taskname[256];
#define SSL_TASK_LOGICAL "OBJECT"
#define SSL_DEFAULT_TASK "0::\"0=WWWSSL\""
static int flush_outmsg ( ssl_context ctx );	/* forward reference */

/*
 * Scan list of keyword=value pairs in penv for first one whose keyword
 * matches var and return value if found.  If not found, return
 * getenv ( "WWWSSL_'var'" );
 */
static char *getpenv ( const char *var, char **penv )
{
    char *result, logical[256];
    int i, j, klen;
    /*
     * Scan the penv.
     */
    klen = tu_strlen ( var );
    for ( i = 0; penv[i]; i++ ) {
	if ( tu_strncmp ( var, penv[i], klen ) == 0 ) {
	    /* Possibile match, next character must be '=' */
	    if ( penv[i][klen] == '=' ) return &penv[i][klen+1];
	}
    }
    /*
     * Fallback to standard environment variable, construct name.
     */
    tu_strcpy ( logical, "WWWSSL_" );
    tu_strnzcpy ( &logical[7], var, sizeof(logical)-12 );
    result = getenv ( logical );
    return result;
}
/***************************************************************************/
/* Function tssl_initalize() should be called once per image invocation.
 * It handles any global configuration needed by the ssl_server module.
 */
int tssl_initialize(int (*error) ( int, char *), char **penv )
{
    char *task_name;
    int status;
    /*
     * Initialize ssl_context lookaside list and mutex that guards it.
     */
    free_ctx = (ssl_context) 0;
    INITIALIZE_MUTEX ( &context_db );
    /*
     * Initialize DECnet, don't do search lists right now.
     */
    status = dnet_initialize();
    if ( (status&1) == 0 ) {
	(*error) ( status, "SSL-DNET initialization failure" );
	return status;
    }
    task_name = getpenv ( SSL_TASK_LOGICAL, penv );
    if ( !task_name ) task_name = SSL_DEFAULT_TASK;
    pthread_mutex_lock ( &context_db );
    tu_strnzcpy ( ssl_taskname, task_name, sizeof(ssl_taskname) - 1 );
    pthread_mutex_unlock ( &context_db );

    (*error) ( 1, "SSL-DNET initialized" );
    return SS$_NORMAL;
}

/***************************************************************************/
/* Initialize context for new client connect.  Connect to SSL object at
 * this point.
 */
int tssl_init_server_context ( ssl_context *ctx,	/* New context */
    int (*get) ( void *, char *, int, int * ),
    int (*put) ( void *, char *, int ),
    int (*error) ( int code, char *text ),
    void *io_ctx,		/* I/O context for get() and put() calls */
    int max_io_size )		/* Max I/O length per get or put call */
{
    void *new_link;
    int status;
    char dnet_error[300];
    /*
     * Try to connect to SSL engine. Doing it now means we don't have to
     * free any allocated structures if it fails.
     */
    status = dnet_connect ( ssl_taskname, &new_link );
    if ( (status&1) == 0 ) {
	int length;
	tu_strcpy ( dnet_error, "connect failure: " );
	length = tu_strlen ( dnet_error );
	dnet_format_error ( status, &dnet_error[length], 
		sizeof(dnet_error)-length );
	(*error) ( status, dnet_error );
	return status;
    }
    /*
     * Allocate a structure, either removing from free_ctx lookaside list
     * or mallocing new.
     */
    pthread_mutex_lock ( &context_db );
    if ( free_ctx ) {
	*ctx = free_ctx;
	free_ctx = (*ctx)->next;
	pthread_mutex_unlock ( &context_db );
    } else {
	pthread_mutex_unlock ( &context_db );
	LOCK_C_RTL
	*ctx = (ssl_context) malloc ( sizeof(struct ssl_control) );
	UNLOCK_C_RTL
	if ( !*ctx ) {
	    (*error) ( SS$_INSFMEM, "Failure to allocate ssl_context" );
	    dnet_disconnect ( new_link );
	    return SS$_INSFMEM;
	}
    }
    (*ctx)->next = (ssl_context) 0;
    /*
     * Initialize TCP callback interface.
     */
    (*ctx)->ssl_version = 0;
    (*ctx)->tcp_ctx = io_ctx;
    (*ctx)->get = get;
    (*ctx)->put = put;
    (*ctx)->error = error;
    (*ctx)->max_io = max_io_size;
    /*
     * Initialize for RPC loop.
     */
    (*ctx)->link = new_link;
    (*ctx)->out_pos = 0;
    (*ctx)->outmsg.channel = 'A';
    (*ctx)->outmsg.function = 'P';

    return status;
}
/*****************************************************************************/
/* RCP dispatcher.  Read messages from remote task and perform requested
 * actions until connection close or application data operation.
 *
 */
static int confirm_app_data ( ssl_context ctx )
{
    int status, size, length, segment, i, written;

    for ( status = 1; (status&1); ) {
        status = dnet_read ( ctx->link, (char *) &ctx->msg, 
			sizeof(ctx->msg), &ctx->msg_filled );
	if ( ctx->msg_filled < 4 ) { status = 20; break; }

	/*
	 * response to data in header.
	 */
	if ( ctx->msg.channel == 'A' ) {
	    /*
	     * Only operations we should be getting on the A channel
	     * is a confirm (function=='C');
	     */
	    if ( ctx->msg.function == 'C' ) break;
	    else {
		/* Error */
		status = 20;
	    }
	} else if ( ctx->msg.channel == 'R' ) {
	    /*
	     * relay the request to the remote client.
	     */
	    switch ( ctx->msg.function ) {
		case 'G':		/* get */
		    /*
		     * Read data from remote IP connection.
		     */
		    ctx->msg.function = 'C';
		    size = sizeof(ctx->msg.data);
		    if ( size > ctx->max_io ) size = ctx->max_io;
		    status = (*ctx->get) ( ctx->tcp_ctx, ctx->msg.data, 
			size, &length );
		    if ( (status&1) == 0 ) {
			ctx->msg.function = 'X';
			length = 0;
		    }
		    /*
		     * Send confirm message with the data we read.
		     */
		    ctx->msg.length = length;
		    status = dnet_write ( ctx->link, (char *) &ctx->msg,
			RPC_HDR_SIZE + length, &written );
		    break;


		case 'P':		/* put */
		    /*
		     * Read data from decnet side and write to TCP side.
		     */
		    ctx->msg.function = 'C';
		    segment = ctx->msg_filled - RPC_HDR_SIZE;
		    if ( segment > 0 ) {
			/* Send data included with message */
			status = (*ctx->put) ( ctx->tcp_ctx, 
				ctx->msg.data,	segment );
			if ( (status&1) == 0 ) break;
			
		    }
		    /*
		     * send confirm.
		     */
		    ctx->msg.length = 0;
		    status = dnet_write ( ctx->link, (char *) &ctx->msg,
			RPC_HDR_SIZE, &written );
		    break;
		case 'X':		/* Close remote (ignored) */
		    break;
		default:
		    break;
	    }
	} else if ( ctx->msg.channel == 'G' ) {
	    /*
	     * Special channel for allowing remote to make general
	     * queries of server.
	     */
	} else {
	    /*  Invalid channel identifier. */
	    status = 20;
	}
    }
    if ( (status&1) == 0 ) {
	ctx->msg_filled = 4;
	ctx->msg.channel = 'A';
	ctx->msg.function = 'X';
        ctx->msg.length = status;
    }
    return status;
}
/*****************************************************************************/
/*  The tssl_put_app_data function encrypts the application data and sends
 *  it to the client.  The application data may be buffered prior to
 *  encryption.
 */
int tssl_put_app_data ( ssl_context ctx,  unsigned char *data, int length )
{
    int status, i, j;
    /*
     * Buffer output data.  MAX_HEADROOM prevents loss of efficiency that
     * would occur from the encrypted data being fragmented into multiple
     * puts to the 'R' channel.
     */
    j = ctx->out_pos;
    for ( i = 0; i < length; i++ ) {
	if ( j >= sizeof ( ctx->outmsg.data ) - MAX_HEADROOM ) {
	    ctx->out_pos = j;
	    status = flush_outmsg ( ctx );
	    if ( (status&1) == 0 ) return status;
	    j = 0;
	}
	ctx->outmsg.data[j++] = data[i];
    }
    ctx->out_pos = j;

    return SS$_NORMAL;
}

/*****************************************************************************/
/* Tssl_get_app_data flushes any pending output data and reads sll records
 * until more application data arrives.  The function returns a pointer to
 * the decrypted application data.
 */
int tssl_get_app_data ( ssl_context ctx, int *length, unsigned char **data )
{
    int status, io_length, written;
    /*
     * Flush any pending outbound data we've buffered.
     */
    if ( ctx->out_pos > 0 ) {
	status = flush_outmsg ( ctx );
	if ( (status&1) == 0 ) return status;
    }
    /*
     * Request more application data from the SSL engine.
     */
    *data = (unsigned char *) ctx->msg.data;
    io_length = ctx->max_io;
    if ( io_length > sizeof(ctx->msg.data) ) io_length = sizeof(ctx->msg.data);
    ctx->msg.channel = 'A';
    ctx->msg.function = 'G';
    ctx->msg.length = io_length;
    status = dnet_write ( ctx->link, (char *) &ctx->msg, RPC_HDR_SIZE, 
		&written );
    if ( (status&1) == 1 ) {
	/*
	 * Wait for SSL to confirm request, confirm message will include
	 * the more data.
	 */
	status = confirm_app_data ( ctx );
	*length = ctx->msg.length;
    }

    return status;
}

/*****************************************************************************/
/* Tssl_get_client_cert flushes any pending output data and reads a 'G' channel
 * message from the SSL engine, returning a message to the data buffer.
 * The data returned is an encoded form of the client certificate and
 * related information.
 */
int tssl_get_client_cert ( ssl_context ctx, int *length, unsigned char **data )
{
    int status, io_length, written;
    /*
     * Flush any pending outbound data we've buffered.
     */
    if ( ctx->out_pos > 0 ) {
	status = flush_outmsg ( ctx );
	if ( (status&1) == 0 ) return status;
    }
    /*
     * Request data from special 'G' channel.
     */
    *data = (unsigned char *) ctx->msg.data;
    io_length = ctx->max_io;
    if ( io_length > sizeof(ctx->msg.data) ) io_length = sizeof(ctx->msg.data);
    ctx->msg.channel = 'G';
    ctx->msg.function = 'G';
    ctx->msg.length = io_length;
    status = mst_write ( ctx->link, (char *) &ctx->msg, RPC_HDR_SIZE, 
		&written );
    if ( (status&1) == 1 ) {
	/*
	 * Wait for SSL to confirm request, confirm message will include
	 * the more data.
	 */
	status = confirm_app_data ( ctx );
	*length = ctx->msg.length;
    }

    return status;
}

/*****************************************************************************/
/* Cleanup context (session IDs for context may still be cached).  If flags
 * bit 0 is set, rundown will flush pending output data before performing
 * rundown.
 */
int tssl_rundown_context ( ssl_context ctx, int flags )
{
    int status;
    /*
     * Flush remaining outbound data if requested.
     */
    if ( (flags&1) == 1 && ctx->out_pos > 0 ) status = flush_outmsg ( ctx );
    else status = SS$_NORMAL;
    /*
     * Terminate decnet.
     */
    if ( ctx->link ) {
	status = dnet_disconnect ( ctx->link );
    }
    /*
     * Put structure back on free list.
     */
    pthread_mutex_lock ( &context_db );
    ctx->next = free_ctx;
    free_ctx = ctx;
    pthread_mutex_unlock ( &context_db );
    return status;
}

/*****************************************************************************/
/* Flush pending outbound application data to the SSL engine.
 */
static int flush_outmsg ( ssl_context ctx )
{
    int status, segment, i;
    /*
     * Write data to decnet task and await confirm.
     */
    status = SS$_NORMAL;
    (*ctx->error) ( 3, "Flushing output data" );
    ctx->outmsg.channel = 'A';
    ctx->outmsg.function = 'P';
    ctx->outmsg.length = ctx->out_pos;

    status = dnet_write ( ctx->link, (char *) &ctx->outmsg, 
	ctx->outmsg.length + RPC_HDR_SIZE, &segment );

    if ( (status&1) == 1 ) status = confirm_app_data ( ctx );
    if ( (status&1) == 0 ) {
	(*ctx->error) ( status, "failure in outbound flush" );
    }
    ctx->out_pos = 0;
    return status;
}
