/*
 * 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_null.c) just provides stub routines for the
 * interface to use as a starting point.
 */
#include "pthread_1c_np.h"
#include "tutil.h"

#include <stdio.h>
#include <stdlib.h>
#include <ssdef.h>
/*
 * 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 */

    int out_pos;
    unsigned char inbuf[32768];		/* Buffer for inbound (get) data */
    unsigned char outbuf[32768];	/* Buffer for outbound data */
};
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 flush_outbuf ( ssl_context ctx );	/* forward reference */

/***************************************************************************/
/* 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 )
{
    /*
     * Initialize ssl_context lookaside list and mutex that guards it.
     */
    free_ctx = (ssl_context) 0;
    INITIALIZE_MUTEX ( &context_db );

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

/***************************************************************************/
/* Initialize context for new client connect.  SSL negoitation takes place
 * 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 */
{
    /*
     * Allocate a structure.
     */
    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" );
	    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;
     (*ctx)->out_pos = 0;

    return SS$_NORMAL;		/* success */
}

/*****************************************************************************/
/*  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;
    /*
     * Save data in output buffer.
     */
    j = ctx->out_pos;
    for ( i = 0; i < length; i++ ) {
	if ( j >= sizeof ( ctx->outbuf ) ) {
	    ctx->out_pos = j;
	    status = flush_outbuf ( ctx );
	    if ( (status&1) == 0 ) return status;
	    j = 0;
	}
	ctx->outbuf[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;
    if ( ctx->out_pos > 0 ) {
	status = flush_outbuf ( ctx );
	if ( (status&1) == 0 ) return status;
    }
    status = (*ctx->get) ( ctx->tcp_ctx, ctx->inbuf, ctx->max_io, length );
    *data = ctx->inbuf;

    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_outbuf ( ctx );
    else status = SS$_NORMAL;
    /*
     * Put structure back on free list.
     */
    pthread_mutex_lock ( &context_db );
    ctx->next = free_ctx;
    free_ctx = ctx;
    pthread_mutex_unlock ( &context_db );
    return 0;
}

/*****************************************************************************/
/* Send output in max_io-sized chunks to client.
 */
int flush_outbuf ( ssl_context ctx )
{
    int status, segment, i;

    status = SS$_NORMAL;
    (*ctx->error) ( 1, "Flushing output data" );
    for ( i = 0; i < ctx->out_pos; i += segment ) {
	segment = ctx->out_pos - i;
	if ( segment > ctx->max_io ) segment = ctx->max_io;
	status = (*ctx->put) (ctx->tcp_ctx, (char *) &ctx->outbuf[i], segment);
	if ( (status&1) == 0 ) {
	    (*ctx->error) ( status, "failure writing to client connection" );
	    break;
	}
    }
    ctx->out_pos = 0;
    return status;
}
